packages feed

phino 0.0.156 → 0.0.157

raw patch · 157 files changed

+6626/−7646 lines, 157 filesdep +QuickCheckPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

Dependencies added: QuickCheck

API changes (from Hackage documentation)

- Deps: EvJoin :: Int -> Text -> (Text, Text) -> Expression -> Evaluation
- Deps: EvKnown :: Int -> Int -> Bytes -> Evaluation
- Deps: EvSymbolize :: Int -> Text -> Expression -> Expression -> Evaluation
- Deps: EvTerminate :: Int -> Maybe (Either Int Bytes) -> Text -> Text -> Maybe Expression -> Evaluation
- Deps: EvUnjoined :: Int -> Text -> Expression -> Expression -> Evaluation
- Deps: EvUnreminted :: Int -> Text -> Expression -> Evaluation
- Deps: EvUpcast :: Int -> Int -> Expression -> [(Int, [Attribute])] -> Evaluation
- Lambdas: Paired :: Meta -> Meta -> Meta -> Line
- Lambdas: Unfolded :: Meta -> Meta -> Bool -> Line
- Lambdas: Upcasted :: Meta -> Meta -> Meta -> Meta -> Line
- Lambdas: [_answer] :: Lambda -> Expression
- Lambdas: [_lenient] :: Lambda -> Bool
- Lambdas: [_terminators] :: Lambda -> [Text]
- Lambdas: joined :: Bool -> Expression -> Expression -> Int -> Maybe (Expression, [(Int, (Int, Int))], Int)
- Lambdas: message :: Expression -> Maybe Expression
- Lambdas: thrown :: [Text] -> Expression -> Bool
- Lambdas: upcasted :: Expression -> Expression -> Int -> Maybe (Int, Expression, [(Int, [Attribute])], Int)
- Morph: Unjoined :: Text -> ReduceException
- Morph: Unreminted :: Text -> ReduceException
- Morph: type Answer = (Expression, Expression)
+ Deps: EvBound :: Int -> Text -> Expression -> Expression -> Evaluation
+ Deps: EvDiverged :: Int -> (Expression, Expression) -> (Expression, Expression) -> [Attribute] -> Refusal -> Evaluation
+ Deps: EvFabricated :: Int -> (Expression, Expression) -> (Expression, Expression) -> Expression -> Evaluation
+ Deps: EvManufactured :: Int -> Int -> Evaluation
+ Deps: EvMorphed :: Int -> Expression -> Evaluation
+ Deps: EvReshaped :: Int -> Text -> Text -> Expression -> Expression -> Evaluation
+ Deps: EvSymbolized :: Int -> Int -> Bytes -> Evaluation
+ Deps: EvTaken :: Int -> [Expression] -> Expression -> Evaluation
+ Deps: dotted :: [Attribute] -> String
+ Deps: refused :: Refusal -> String
+ Forks: Holding :: Side -> Attribute -> Refusal
+ Forks: Lefthand :: Side
+ Forks: Righthand :: Side
+ Forks: Unformed :: Side -> Refusal
+ Forks: Unnamed :: Refusal
+ Forks: data Refusal
+ Forks: data Side
+ Forks: forked :: Function -> Either Int Bytes -> Expression -> Expression -> Either Refusal Expression
+ Forks: instance GHC.Classes.Eq Forks.Refusal
+ Forks: instance GHC.Classes.Eq Forks.Side
+ Forks: instance GHC.Show.Show Forks.Refusal
+ Forks: instance GHC.Show.Show Forks.Side
+ Forks: joined :: Expression -> Expression -> Int -> Either [Attribute] (Expression, [(Int, (Int, Int))], Int)
+ Lambdas: Answering :: Expression -> Ending
+ Lambdas: Deferred :: Meta -> Meta -> Line
+ Lambdas: Joining :: Either Meta Expression -> Expression -> Expression -> Maybe Function -> Ending
+ Lambdas: Morphed :: Meta -> Meta -> Line
+ Lambdas: [_ending] :: Lambda -> Ending
+ Lambdas: data Ending
+ Lambdas: rewritten :: RuleContext -> [Rule] -> Expression -> IO Expression
+ Morph: Answer :: Expression -> Expression -> Answer
+ Morph: Diverged :: Text -> ReduceException
+ Morph: Joined :: (Expression, Expression) -> Expression -> Answer
+ Morph: data Answer
+ Morph: ended :: Answer -> Expression
- Deps: EvJoined :: Int -> Int -> (Int, Int) -> Evaluation
+ Deps: EvJoined :: Int -> Either Text Expression -> Int -> (Int, Int) -> Evaluation
- Deps: EvLooped :: Int -> Judgment -> Acyclic -> Expression -> Expression -> Maybe (Int, Maybe Expression) -> Evaluation
+ Deps: EvLooped :: Int -> Judgment -> Acyclic -> Expression -> Expression -> Evaluation
- Deps: Nesting :: Int -> [(Int, Int)] -> [(Int, String)] -> Named Expression -> Map Int Int -> Map Int Int -> Maybe (Bytes, String) -> [(Int, (Judgment, Expression))] -> [(Int, Judgment, Expression)] -> [(Int, Expression, String)] -> Map Attribute Expression -> Map [Maybe Attribute] [(Expression, [Binding])] -> Int -> Nesting
+ Deps: Nesting :: Int -> [(Int, Int)] -> [(Int, String)] -> Named Expression -> Map Int Int -> Map Int Int -> Maybe (Either Int Bytes, String) -> [(Int, (Judgment, Expression))] -> [(Int, Judgment, Expression)] -> [(Int, Expression, String)] -> Map Attribute Expression -> Map [Maybe Attribute] [(Expression, [Binding])] -> Int -> Nesting
- Deps: Protocol :: Int -> Named Text -> [(Int, Int)] -> Bool -> Named Expression -> Map Int Int -> Map Int Int -> Map Int Int -> Maybe (Bytes, String) -> [(Int, Maybe String, Bool)] -> [(Int, Expression, String)] -> Protocol
+ Deps: Protocol :: Int -> Named Text -> [(Int, Int)] -> Bool -> Named Expression -> Map Int Int -> Map Int Int -> Map Int Int -> Maybe (Either Int Bytes, String) -> [(Int, Maybe String, String, Bool)] -> [(Int, Expression, String)] -> Protocol
- Deps: [_blocks] :: Protocol -> [(Int, Maybe String, Bool)]
+ Deps: [_blocks] :: Protocol -> [(Int, Maybe String, String, Bool)]
- Deps: [_found] :: Protocol -> Maybe (Bytes, String)
+ Deps: [_found] :: Protocol -> Maybe (Either Int Bytes, String)
- Deps: [_held] :: Nesting -> Maybe (Bytes, String)
+ Deps: [_held] :: Nesting -> Maybe (Either Int Bytes, String)
- Deps: found :: Either Int Bytes -> Maybe (Bytes, String) -> Maybe String
+ Deps: found :: Either Int Bytes -> Maybe (Either Int Bytes, String) -> Maybe String
- Lambdas: Lambda :: Text -> [(Meta, Expression)] -> [(Meta, Expression)] -> [Line] -> Bool -> [Text] -> Expression -> Lambda
+ Lambdas: Lambda :: Text -> [(Meta, Expression)] -> [(Meta, Expression)] -> [Line] -> Ending -> Lambda

Files

README.md view
@@ -34,2624 +34,2676 @@  ```bash cabal update-cabal install --overwrite-policy=always phino-0.0.155-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.155-  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.--An entry reads no key but `λ`, `dataize`, `morph`, `evaluate`, `unfold`,-`rewrite`, `stay`, `symbolize`, `upcast`, `join`, `lenient`, `terminators` and-`𝑛`, and any other key, such as a misspelled `lenent` or a `defer` that now-belongs to a line of `unfold`, refuses the whole file, so a typo never drops a-block or an option without a word.--### 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 another line-of the entry, 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)`.--### Unfolding an ordinary object--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. There is a-fourth block, `unfold`, and each line of it morphs the `φ` of such a term,-fires what that comes to, the way an `evaluate` line does, and binds an-expression meta of its own to the answer:--```yaml-- λ: L_fork-  morph:-    𝑛1: $.left-    𝑛2: $.right-  unfold:-    𝑛3: 𝑛1-  join:-    𝑛4: [𝑛3, 𝑛2]-  𝑛: 𝑛4-```--The right-hand side of a line names a meta bound by `morph` or by another line-of the entry, and nothing else. A term whose `φ` reads nothing of its own-object, such as `⟦ φ ↦ Φ.bool( … ) ⟧`, is bound as it stands, since morphing-it only expands the call, and so is a term that is no such formation at all.-The step 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.--That step gets stuck on a term that is a copy, such as-`Φ.step( tup ↦ Φ.items.tail )`, one of whose arguments reads to a bare-symbol `⟦ λ ⤍ 𝜎1 ⟧`: morphing its `φ`, `tup.next`, asks `𝜎1` for an-attribute nobody knows. A line that says `defer: true` does not enter such a-copy. It binds its meta to a fresh symbol instead and writes what the symbol-stands for:--```yaml-  unfold:-    𝑛3: {of: 𝑛2, defer: true}-```--```text-deferred(𝜎3) := Φ.step( tup ↦ 𝜎1:λ )  # 𝕄(𝑛2.φ)-```--The markup writes it as `<deferred symbol="𝜎3" … of="Φ.step">`, holding a-`<with>` element with an `<attr name="tup">𝜎1</attr>` per argument. When the-world declares no object the copy was made of, `of` names the `<formed>`-element that spells the copy out, and there is no `<with>`. A copy whose-arguments read to no bare symbol is entered as before. The short `𝑛3: 𝑛2`-stands for `𝑛3: {of: 𝑛2, defer: false}`.--An argument that is no bare symbol, such as `w ↦ Φ.inc( ⟦ Δ ⤍ 2A- ⟧ )`, is-worked out before the line is written. The entry morphs it in the copy, fires-what that comes to, the way an `evaluate` line does, and writes every step to-the protocol under `𝕄(𝑛2.w):`. The line then names the object the firing-answered, `Φ.box( x ↦ 𝜎1:λ, w ↦ 𝑛·2·2 )`, and so does the markup,-`<attr name="w">𝑛·2·3</attr>`, with a meta of its own count. A reader follows-the meta to the application that made the object. An argument whose morph gets-stuck, is cut or runs out of steps is written as the code spells it, and in the-markup by the meta of the element that named it earlier, such as the-`<applied>` of the call, or by an expression meta, such as `𝑒1`, when none-did.--A deferring line also answers a copy it did enter when `--acyclic` cuts the-firing its `φ` comes to. With-`rec(n) ↦ ⟦ φ ↦ Φ.fork( ⟦ λ ⤍ 𝜎1 ⟧, Φ.rec( ξ.n ) ) ⟧`, the copy-`Φ.rec( ⟦ φ ↦ ⟦ λ ⤍ 𝜎2 ⟧ ⟧ )` reads to no bare symbol, so the line enters it,-comes to the same fork, and fires it again, and the cut refuses that firing.-A line that does not defer loses the whole firing to the cut. A deferring one-binds its meta to a fresh symbol and writes it on the `looped` line:--```text-looped(𝑛·1·3) := 𝜎3  # 𝔼(𝑛2), proven-```--The markup writes it as `<looped symbol="𝜎3" … of="Φ.rec">` with the same-`<with>` element a `deferred` record holds. An argument whose `φ` holds a bare-symbol counts as that symbol, unless an element named it earlier: here-`<attr name="n">𝑛·0·1</attr>` names the `<formed>` element written for-`⟦ φ ↦ ⟦ λ ⤍ 𝜎2 ⟧ ⟧` earlier in the run, and that element holds `𝜎2`.--### Rewriting a term into another shape--There is a fifth 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 another line of the entry,-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.--A rule may still turn a large term into a small one made of symbols, through-`remint` in its `where` block. The function takes one expression and answers-the bare `⟦ λ ⤍ 𝜎k ⟧` the expression stands for:--```yaml-- λ: L_fork-  morph:-    𝑛1: $.left-  rewrite:-    𝑛2:-      of: 𝑛1-      rules:-        - name: small-tuple-          pattern: Φ.tuple( tail ↦ 𝑒1, head ↦ 𝑒2 )-          result: Φ.tuple( tail ↦ 𝑒3, head ↦ 𝑒4 )-          where:-            - meta: 𝑒3-              function: remint-              args: [𝑒1]-            - meta: 𝑒4-              function: remint-              args: [𝑒2]-  𝑛: 𝑛2-```--The result still writes no `𝜎`, and every symbol `remint` answers is one a-reader of the protocol builds without parsing a term. The function first works-the term out: it morphs the term in the formation it sits in, fires what that-comes to, follows the φ of a decorator such as `as-number`, and writes every-step to the protocol. Then it answers by what the term came to. A term whose-φ chain ends in a bare symbol is that symbol, and nothing is minted. A datum-is a fresh known symbol, `𝔻(𝜎4:λ) == 00-`, the way `symbolize` writes one. A-copy of an object of the world is a fresh deferred symbol, such as-`deferred(𝜎5) := Φ.l/.throw( message ↦ 𝜎4:λ )`, once every argument of the-copy came to a symbol the same way. Any other term has no symbol a reader-could build, so the step writes `unreminted(L_fork)` with the term, and the-firing gets stuck the way a fork whose branches do not join does.-`remint` is known only to the rules of a `rewrite` block, since nowhere else is-there a firing to mint for.--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. A `symbolize` line may read what a `rewrite`-line made 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.--### Dropping the ρ of a dispatch--There is a sixth block, `stay`, and it reduces nothing either. Each line of it-takes a term another meta of the entry is already bound to and binds an-expression meta of its own to that term with every `( ρ ↦ … )` dropped that is-applied to a dispatch:--```yaml-- λ: L_fork-  morph:-    𝑛1: $.left-    𝑛2: $.right-  stay:-    𝑛3: 𝑛1-    𝑛4: 𝑛2-  join:-    𝑛5: [𝑛3, 𝑛4]-  𝑛: 𝑛5-```--The right-hand side of a line names a meta bound by `morph` or by another line-of the entry, and nothing else. Every position of the term is walked, the-methods and the `ρ` of a formation among them, and `𝑒.𝜏( ρ ↦ 𝑒2 )` becomes-`𝑒.𝜏`. So does a dispatch heading a chain of applications that bind no `ρ`:-`𝑒.𝜏( φ ↦ 𝑒3 )( ρ ↦ 𝑒2 )` becomes-`𝑒.𝜏( φ ↦ 𝑒3 )`. A `ρ` applied to anything else, such as a formation, stays.--Dropping it changes no value. The `dot` rule answers a dispatch as-`𝑛1( ρ ↦ ⟦…⟧ )`, and then `copy` fills a void `ρ` of the body, `stay` keeps a-bound one and `skip` drops it where the body declares none. So once `𝑒.𝜏`-comes to a formation, a later `( ρ ↦ 𝑒2 )` meets a `ρ` that is bound or not-there, and `stay` or `skip` drops it. The block only drops it earlier, before-the dispatch is reduced.--This is how a fork brings two numbers to one shape. In EO `bytes.as-number`-reads its `data` as `ρ`, so a branch computed through it comes to-`Φ.number( φ ↦ Φ.bytes( φ ↦ 𝜎1 )( ρ ↦ ⟦ … ⟧ ) )`, with the copy hung on the-bytes as their `ρ`, while a branch made of no object comes to-`Φ.number( φ ↦ Φ.bytes( φ ↦ 𝜎2 ) )`. The join compares the two as written and-the tail refuses it; after `stay` they differ in their symbols alone. The-protocol writes each line of the block the way it writes a `rewrite` one,-commented with the meta it took.--### Standing data into unknowns--There is a seventh 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` or by another line-of the entry, 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.--### Casting a bare symbol into a shape--A bare symbol, `⟦ λ ⤍ 𝜎1 ⟧`, says nothing of the object it stands for, while-the other branch of a fork may be a copy of `Φ.tuple` with a symbol in `tail`,-in `head` and in `length`, as one in `bytes.array` is. There is an eighth-block, `upcast`, and it holds exactly two lines, each reading a meta the way-a line of any other block does. The block takes the two terms as one pair,-casts the smaller of them into the shape of the larger, and binds the metas-of its two lines to the two results, in the order the lines are written:--```yaml-- λ: L_fork-  morph:-    𝑛1: $.left-    𝑛2: $.right-  rewrite:-    𝑛3:-      of: 𝑛2-      rules:-        - name: called-          pattern: ⟦ tail ↦ 𝑒1, head ↦ 𝑒2, length ↦ 𝑒3, !B ⟧-          result: Φ.tuple( tail ↦ 𝑒1, head ↦ 𝑒2, length ↦ 𝑒3 )-  upcast:-    𝑛4: 𝑛1-    𝑛5: 𝑛3-  join:-    𝑛6: [𝑛4, 𝑛5]-  𝑛: 𝑛6-```--Today the smaller term is a bare symbol, and the larger one is a term it can-be cast into: every symbol of it is reached by a path, and the cast is a copy-of it where every symbol is a fresh one, read off the bare symbol by the path-to its place. The line that read the bare symbol binds the cast, here `𝑛4`,-and the other line binds its term as it is. The author of the entry does not-say which of the two is cast, since the terms tell. The block runs once both-metas it reads are bound, and a line reading either meta it binds runs after-it. A block with one line, or with more than two, is refused when the file is-read.--The `rewrite` line spells the tuple 𝕄 made as the call it was made of, for the-reason the end of this section gives. The `join` line then pairs the two-calls symbol by symbol, minting a symbol for every pair the usual way:--```text-upcast(𝜎1) := Φ.tuple( tail ↦ 𝜎5:λ, head ↦ 𝜎6:λ, length ↦ 𝜎7:λ )-  𝜎5 := 𝜎1.tail-  𝜎6 := 𝜎1.head-  𝜎7 := 𝜎1.length-𝔻(𝜎8:λ) ∈ { 𝔻(𝜎5:λ), 𝔻(𝜎2:λ) }-𝔻(𝜎9:λ) ∈ { 𝔻(𝜎6:λ), 𝔻(𝜎3:λ) }-𝔻(𝜎10:λ) ∈ { 𝔻(𝜎7:λ), 𝔻(𝜎4:λ) }-```--A program reading `length` off the answer gets `𝜎10`, which stands for either-length, and does not get stuck. A path walks through the attributes of a-formation and the arguments of a copy of an object of the world, such as-`Φ.tuple( … )`. A `φ` is an attribute like any other, so the symbol of-`Φ.number( φ ↦ Φ.bytes( φ ↦ ⟦ λ ⤍ 𝜎2 ⟧ ) )` is read as `𝜎1.φ.φ`. When both-terms are bare symbols, when neither is, or when the other term holds no-symbol, or one no path reaches, such as one in an argument of a call-`x.slice( start ↦ 𝜎2 )`, both lines bind their terms as they are and nothing-is written for the cast. The protocol writes the two bindings the way it-writes any other line, `𝑛4·1 := …  # 𝑛1`. The term alone does not prove-that both branches are objects of one kind. The record is what lets a reader-check that against what it knows about the program.--A `join` compares only the `φ` chain of two formations, so a cast into a-formation, which is what 𝕄 makes of a copy, pairs nothing but that chain.-An entry that needs the arguments paired spells the copy as the call it was-made of first, with a `rewrite` line such as the one above.--### 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 ninth 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` or by other lines of the entry, 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 its branches go through `symbolize` first: 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: []` beside its `join` block-lets it through:--```yaml-- λ: L_fork-  morph:-    𝑛1: $.left-    𝑛2: $.right-  join:-    𝑛3: [𝑛1, 𝑛2]-  lenient: []-  𝑛: 𝑛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. The list takes no option and stays-empty. A name in it is refused when the file is read, `defer` and `upcast`-among them, since an `unfold` line and an `upcast` block now do their work, and-so is a lenient entry with no `join` line.--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 bare symbol meeting a term with more symbols than one is no join, lenient-or not. The entry casts it into the shape of that term first, with an-`upcast` block, and a branch one call away from its value is fired first with-an `unfold` line.--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="𝑛·1·6"/>` in the markup,-where `message` names the `<applied meta="𝑛·1·6" of="𝑛·1·3.plus">` written for-`7.plus( 8 )`, 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.--### The order of the lines--The lines of `evaluate`, `unfold`, `rewrite`, `stay`, `symbolize`, `upcast`-and `join` run in the order of the metas they read, not in the order of their-blocks. A line runs once every meta it reads is bound, so this entry fires-what its `rewrite` line unwrapped:--```yaml-- λ: L_pick-  morph:-    𝑛1: $.x-  rewrite:-    𝑛2:-      of: 𝑛1-      rules:-        - name: unwrap-          pattern: ⟦ φ ↦ 𝑒1 ⟧-          result: 𝑒1-  evaluate:-    𝑛3: 𝑛2-  𝑛: 𝑛3-```--The `evaluate` line reads `𝑛2`, which the `rewrite` line binds, so the-`rewrite` line runs first. The `dataize` and `morph` blocks read no meta and-always run before all the others. Where no meta orders two lines, they run in-the order the list above gives their blocks, and the lines of one block in the-order of their metas, `𝑛2` before `𝑛10`. A line reading a meta nothing binds-is refused when the file is read, and so is a line that reads the meta it-binds, directly or through other lines, since it could never run.--### 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.--Five 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.-`unjoined(L_fork)` takes its place in the firing whose `join` line could not-pair its two branches, and carries them as the join last saw them,-`unjoined(L_fork)  # 𝜎1:λ:l vs ⟦ l ↦ 𝜎2:λ, x ↦ ⟦⟧ ⟧`, so a fork the program-left in two shapes never reads as an operand that ran out of steps; the-firings around it still close with `stuck(L_fork)`.-`unreminted(L_fork)` takes its place in the firing whose `remint` step found-no symbol for a term, and carries the term, `unreminted(L_fork)  # 𝜎2:λ:k`.-`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 `<stuck λ="L_outer"/>`,-`<unjoined λ="L_fork">𝑛·1·1 𝑛·1·3</unjoined>`,-`<unreminted λ="L_fork">𝑛·1·2</unreminted>`,-`<stall λ="L_outer"/>` and `<starved limit="3" by="dataize" at="Φ.a🌵2"/>`,-where `<unjoined>` names its two terms and `<unreminted>` its one by meta, the-way every other element names a term.--`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" of="Φ.bytes"><attr name="φ">40-14-00-00-00-00-00-00</attr></applied>-      <applied meta="𝑛·0·2" by="morph" at="Φ" of="Φ.number"><attr name="φ">𝑛·0·1</attr></applied>-      <applied meta="𝑛·0·3" of="Φ.bytes"><attr name="φ">40-18-00-00-00-00-00-00</attr></applied>-      <applied meta="𝑛·0·4" of="Φ.number"><attr name="φ">𝑛·0·3</attr></applied>-      <applied meta="𝑛·0·5" by="morph" at="Φ" of="𝑛·0·2.plus"><attr name="x">𝑛·0·4</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="φ">𝑛·0·1</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="φ">𝑛·0·3</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>-      <applied meta="𝑛·1·5" of="Φ.number"><attr name="φ">𝜎1</attr></applied>-      <built meta="𝑛·1·6">𝑛·1·5</built>-      <morph at="𝑛·1·6">-        <applied meta="𝑛·1·7" by="morph" at="𝑛·1·6" of="Φ.number"><attr name="φ">𝜎1</attr></applied>-      </morph>-      <answer meta="𝑛·1·8">𝑛·1·7</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:-the name of the 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 symbol of that unknown rather than the 42 standing for-it, `𝜎2` where the text format writes the formation `𝜎2:λ`. 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 name of the term the firing answered with, named the same way by its-own `meta`, and `<built>` before it holds the name of 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·7" by="morph" at="𝑛·1·6" 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, 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 argument. An application nothing reduced gets an-`<applied>` too, with no `by` and no `at`, right before the element that holds-it: `<applied meta="𝑛·0·1" of="Φ.bytes">` is `Φ.bytes( 40-14-… )`, the argument-of the `Φ.number` that `𝑛·0·2` applies. A later element holding an object holds-its name instead, so `<answer meta="𝑛·1·8">𝑛·1·7</answer>` says the firing-answered with it and `<morph at="𝑛·1·6">` that 𝕄 got into what the entry-built.--The markup is for a program, which never has to parse a term of 𝜑-calculus to-read it. Every value the markup holds, in an attribute or as the text of an-element, is one of a few kinds: a symbol such as `𝜎1`, a datum such as-`40-14-00-00-00-00-00-00`, a meta such as `𝑛·0·2` or `𝛿·1·1`, a path of-attribute names off `Φ`, `ξ`, `ρ` or a meta, such as `𝑛·0·2.plus`, a name such-as `tail` or `L_fork`, or `⊥`. A term of any other kind is named by an element-of its own, written before the first element that holds it, and is its meta-from then on: an application by an `<applied>`, and a formation by a-`<formed>`. A formation that copies an object of the world, binding its `φ` to-`𝜎196`, is `<formed meta="𝑛·3·9" of="Φ.string"><attr name="φ">𝜎196</attr>`:-`of` names the object by its path, and every `<attr>` is an attribute the copy-binds to a value other than the one the object has. A copy that binds nothing-new is named by its path alone. A formation that copies no object of the world-has no `of` and holds all its bindings: an `<attr>` for each bound attribute, a-`<void name="x"/>` for each void one, and its `λ` or `Δ`, if it has one, as an-attribute of the element, such as `<formed meta="𝑛·1·1" λ="L_fork">`. A term-the markup can name in none of these ways, such as an argument of a deferred-call that nothing worked out, is an expression meta, `𝑒1`: the same term gets-the same `𝑒` throughout the file and another term gets another, so a reader can-tell two holes apart, while what the term is stays in the text format alone.-Since the markup names terms the text format writes out, the two count their-metas each on their own: `𝑛·1·7` of the markup above is `𝑛·1·6` of the text.--`<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.--`<upcast symbol="𝜎1" of="𝑛·1·2"><path symbol="𝜎5">tail</path>…</upcast>` is a-bare symbol an `upcast` block cast into a copy of another term, which the-text format writes as `upcast(𝜎1) := …` with a line `𝜎5 := 𝜎1.tail` per fresh-symbol. The bare symbol stands in `symbol` and `of` names the copy. Every-`<path>` holds a fresh symbol of the copy in `symbol` and, as its text, the-attributes that read it off the bare one, joined by dots.--A λ name no entry answers is `<unanswered λ="…">`, standing where its-`<evaluate>` would have stood with the name of the formation 𝔼 was fired-against as its text, such as `𝑛·1·7.nope` below, 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" of="Φ.bytes"><attr name="φ">40-14-00-00-00-00-00-00</attr></applied>-      <applied meta="𝑛·0·2" by="morph" at="Φ" of="Φ.number"><attr name="φ">𝑛·0·1</attr></applied>-      <applied meta="𝑛·0·3" of="Φ.bytes"><attr name="φ">40-18-00-00-00-00-00-00</attr></applied>-      <applied meta="𝑛·0·4" of="Φ.number"><attr name="φ">𝑛·0·3</attr></applied>-      <applied meta="𝑛·0·5" by="morph" at="Φ" of="𝑛·0·2.plus"><attr name="x">𝑛·0·4</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="φ">𝑛·0·1</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="φ">𝑛·0·3</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>-        <applied meta="𝑛·1·5" of="Φ.number"><attr name="φ">𝜎1</attr></applied>-        <built meta="𝑛·1·6">𝑛·1·5</built>-        <morph at="𝑛·1·6">-          <applied meta="𝑛·1·7" by="morph" at="𝑛·1·6" of="Φ.number"><attr name="φ">𝜎1</attr></applied>-        </morph>-        <answer meta="𝑛·1·8">𝑛·1·7</answer>-      </evaluate>-    </morph>-    <unanswered λ="L_number_nope" by="dataize">𝑛·1·7.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 text protocol writes: a formation-longer than sixty-four characters keeps its `φ`, `Δ` and `λ` bindings and folds-the rest into a count. The markup writes no terms, only their names, so the-option leaves it as it is. 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" of="𝑛·0·1"/>`, with the site, the-mode and the name of the formation as attributes. 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 ⟧ ) ) )-⟧-```--The two styles may be mixed in one expression, so `[[ x -> ∅ ]]` reads the-same as `⟦ x ↦ ∅ ⟧`. The `--monoform` option forbids that: the first symbol-that has two spellings picks the style of the whole expression, and a later-symbol of the other style is an error. Under `--monoform`, `[[ x -> ∅ ]]`-fails, since `∅` at 1:9 mixes with the ASCII `[[` at 1:1. The option is-accepted by `rewrite`, `dataize`, `morph`, `merge` and `match`.--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.-* `remint` - accepts one expression and answers the symbol it stands for: the-  bare symbol its φ chain ends in, a known symbol for a datum, or a deferred-  symbol for a copy of an object of the world.-  It works only in a `rewrite` block of the `--symbolic` file, see-  [above](#rewriting-a-term-into-another-shape).--## 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:      1449859.357 μs-  avg:        144985.936 μs-  min:        127788.624 μs-  max:        180748.736 μs-  std dev:    18594.498 μs-=== parse/xmir ===-  warmup:     3 iterations-  batches:    10 x 1-  total:      6946940.912 μs-  avg:        694694.091 μs-  min:        628516.654 μs-  max:        800858.894 μs-  std dev:    55991.977 μs-=== rewrite/normalize ===-  warmup:     3 iterations-  batches:    10 x 1-  total:      11.436 μs-  avg:        1.144 μs-  min:        0.932 μs-  max:        1.542 μs-  std dev:    0.197 μs-=== print/sweet/multiline ===-  warmup:     3 iterations-  batches:    10 x 1-  total:      4445986.898 μs-  avg:        444598.690 μs-  min:        398258.495 μs-  max:        481107.673 μs-  std dev:    23991.734 μs-=== print/sweet/flat ===-  warmup:     3 iterations-  batches:    10 x 1-  total:      487593.672 μs-  avg:        48759.367 μs-  min:        43188.190 μs-  max:        64565.233 μs-  std dev:    7856.020 μs-=== print/salty/multiline ===-  warmup:     3 iterations-  batches:    10 x 1-  total:      13090120.822 μs-  avg:        1309012.082 μs-  min:        1242004.615 μs-  max:        1396091.419 μs-  std dev:    42788.620 μs-=== morph/symbolic/demo/e1 ===-  warmup:     3 iterations-  batches:    10 x 13-  total:      7326.444 μs-  avg:        56.357 μs-  min:        54.948 μs-  max:        59.799 μs-  std dev:    1.305 μs-=== morph/symbolic/demo/e2 ===-  warmup:     3 iterations-  batches:    10 x 256-  total:      150484.172 μs-  avg:        58.783 μs-  min:        58.107 μs-  max:        59.962 μs-  std dev:    0.638 μs-=== morph/symbolic/demo/e3 ===-  warmup:     3 iterations-  batches:    10 x 251-  total:      155622.704 μs-  avg:        62.001 μs-  min:        61.318 μs-  max:        63.528 μs-  std dev:    0.583 μs-=== morph/symbolic/demo/e4 ===-  warmup:     3 iterations-  batches:    10 x 250-  total:      157486.792 μs-  avg:        62.995 μs-  min:        62.575 μs-  max:        63.718 μs-  std dev:    0.372 μs-=== morph/symbolic/demo/e5 ===-  warmup:     3 iterations-  batches:    10 x 239-  total:      152736.856 μs-  avg:        63.907 μs-  min:        62.083 μs-  max:        75.524 μs-  std dev:    3.890 μs-=== morph/symbolic/native/e5 ===-  warmup:     3 iterations-  batches:    10 x 224-  total:      143868.258 μs-  avg:        64.227 μs-  min:        63.912 μs-  max:        64.886 μs-  std dev:    0.255 μs-=== morph/symbolic/accum/0 ===-  warmup:     3 iterations-  batches:    10 x 99-  total:      52382.352 μs-  avg:        52.911 μs-  min:        51.267 μs-  max:        55.604 μs-  std dev:    1.318 μs-=== morph/symbolic/accum/400 ===-  warmup:     3 iterations-  batches:    10 x 126-  total:      67542.276 μs-  avg:        53.605 μs-  min:        52.701 μs-  max:        54.352 μs-  std dev:    0.516 μs-```--The results were calculated in [this GHA job][benchmark-gha]-on 2026-10-14 at 19:08,-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/38620470424+cabal install --overwrite-policy=always phino-0.0.156+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.156+  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.+An operand whose reduction gets stuck, is cut or runs out of steps gets the+firing stuck right there: no later block runs, and `--partial` parks the+firing as a whole instead of handing the operand on as it was written.++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.++An entry reads no key but `λ`, `dataize`, `morph`, `evaluate`, `defer`, `stay`,+`rewrite`, `symbolize`, `join` and `𝑛`, and any other key, such as a+misspelled `joinn`, refuses the whole file, so a typo never drops a block+without a word.++### 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_apply+  morph:+    𝑛1: $.f+  evaluate:+    𝑛2: 𝑛1+  𝑛: 𝑛2+```++The right-hand side of a line names a meta bound by `morph` or by another line+of the entry, 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 `⊥`. So does a bare symbol, `⟦ λ ⤍ 𝜎1 ⟧`, since it+already stands for a value nobody worked out and no entry answers it. A λ name+no entry answers gets stuck, as it+does anywhere else, and `--partial` parks it. Any other term, such as a+dispatch a `rewrite` line, described below, took out of its formation, gets an+entry stuck when it computes an `𝑛`, while an entry that joins passes the term+on as it is, since a join compares whatever its sides come to. 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)`.++### Deferring a copy over an unknown++A term 𝕄 leaves may be a copy of an object of the world, such as+`Φ.step( tup ↦ Φ.items.tail )`, one of whose arguments reads to a bare symbol+`⟦ λ ⤍ 𝜎1 ⟧`. Nothing can be worked out about such a copy: its `φ` reads+`tup.next`, an attribute of `𝜎1` nobody knows, so morphing it gets stuck where+the symbol stands. There is a fourth block, `defer`, and each line of it reads+a meta and binds another, the way a line of `evaluate` does. When the term is a+copy one of whose arguments, `φ` and `ρ` aside, reads to a bare symbol, the+line binds its meta to a fresh symbol and writes what the symbol stands for:++```yaml+- λ: L_step+  morph:+    𝑛1: $.x+  defer:+    𝑛2: 𝑛1+  𝑛: 𝑛2+```++```text+deferred 𝜎3 := Φ.step( tup ↦ 𝜎1:λ )  # 𝕄(𝑛1.φ)+```++The markup writes it as `<deferred symbol="𝜎3" … of="Φ.step">`, holding a+`<with>` element with a `<symbol attr="tup">𝜎1</symbol>` per argument. When+the world declares no object the copy was made of, `of` names the `<formed>`+element that spells the copy out, and there is no `<with>`. Any other term,+a copy whose arguments read to no bare symbol among them, is bound as it+stands, as `𝑛2·1 := 𝑛·1·1  # 𝑛1`. A `defer` line asks nothing of the `φ` of a+copy, so it defers one whose `φ` reads nothing of its own object too, such as+`⟦ φ ↦ Φ.bool( … ) ⟧`.++An argument that is no bare symbol, such as `w ↦ Φ.inc( ⟦ Δ ⤍ 2A- ⟧ )`, is+worked out before the line is written. The entry morphs it in the copy, fires+what that comes to, the way an `evaluate` line does, and writes every step to+the protocol under `𝕄(𝑛1.w):`. The line then names the object the firing+answered, `Φ.box( x ↦ 𝜎1:λ, w ↦ 𝑛·2·2 )`, and so does the markup,+`<meta attr="w">𝑛·2·3</meta>`, with a meta of its own count. A reader follows+the meta to the application that made the object. An argument whose morph or+firing gets stuck, is cut or runs out of steps gets the firing stuck right+there, as an operand does, and no `deferred` line is written.++A `join` block, described below, defers nothing by itself. An entry that+joins defers a side with a `defer` line of its own, reading the meta the side+was morphed to, so a side that is such a copy comes to a fresh symbol rather+than getting stuck inside the unknown. A side written as a path from `$` is+morphed and taken as it comes, a copy over a bare symbol included.++### Joining on a condition nobody decided++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 joining two+different terms into one would make up a value no run of the program ever has.+`join` is the fifth block: it names the condition and the two sides, and the+entry answers the one term both sides join into, or gets stuck when they do+not join.++```yaml+- λ: L_fork+  dataize:+    𝛿1: $.φ+  join:+    on: 𝛿1+    left: $.left+    right: $.right+```++`on` names the condition, a bytes meta the `dataize` block bound or a path+from `$`, described below, and `left` and `right` are paths from `$`, read+against the formation being fired the way+an operand is, or metas the `morph` block or an `evaluate`, `stay`, `rewrite`,+`symbolize` or `defer` line bound, as described below. An entry+with a `join` block has no `𝑛`, since the term it joins is what it answers+with: an entry joins or it computes, never both. A block missing one of the+three keys, holding another but `otherwise`, described below, or naming under+`on` anything but a meta of `dataize` or a path from `$` is refused when the+file is read, and+so is a side reading a meta inside a term, such as `𝑛1.x`, or naming a meta+that none of `morph`, `evaluate`, `stay`, `rewrite`, `symbolize` and `defer`+binds.++A join morphs both of its sides first, each where it stands in the universe,+under a block of its own, `𝕄(Φ.a🌵3):` for the left side and `𝕄(Φ.a🌵4):` for+the right one. A side that is a meta was morphed already, by the `morph`+block, so the join takes the term the meta is bound to as it is and morphs+nothing again. When the two come to the same term, nothing depends on the+condition: the join mints nothing and answers that term, and the line binding+it is commented with both sides, `𝑛·2·5 := 𝑛·2·3  # 𝕄(ξ.left), 𝕄(ξ.right)`.+When the two differ in their symbols alone, such as `Φ.number( φ ↦ 𝜎3:λ )` and+`Φ.number( φ ↦ 𝜎4:λ )`, the join makes one term of them: every pair of symbols+standing at the same place gets a fresh symbol, which a line of its own says+is the left one where the condition holds and the right one where it does not:++```text+joined 𝜎5 := 𝛿1·2 ? 𝜎3 : 𝜎4+```++The condition is named by the meta its `dataize` operand bound, `𝛿1·2`,+since the join reads the datum that line holds and runs no 𝔻 of its own. A+pair met twice gets the same fresh symbol both times. Nothing is bound to the+condition, which stays the unknown it was.++Two sides that differ in anything else, a datum, a λ name or the shape of a+term, do not join, and neither do a side holding a datum and a side holding a+symbol, unless a `symbolize` line turned that datum into a symbol first, as+described below. Such a firing closes with a line naming the two sides it+could not join, the left one first:++```text+diverged 𝑛·2·3 vs 𝑛·2·5  # they differ at .x, and the join names no otherwise+```++A side is named by the line that bound it: the line its morph wrote, or the+line of `morph`, `evaluate`, `stay`, `rewrite`, `symbolize` or `defer` whose+meta it reads, such as `𝑛1·1`. A side that is a bare symbol is+written as that symbol, and a side no line named, such as a datum its morph+left as it was, as the term it is. Past+that line the firing gets stuck the way a λ function no entry answers does:+`--partial` parks the site the entry was fired at, and without it the command+fails with `No entry of --symbolic answers the λ function 'L_fork'`. A side+whose morph gets stuck or is cut gets the firing stuck at once, the way an+operand of `morph` does.++The comment of the line tells where the two sides differ and why the join+could not make one formation of them, so a reader knows what to change in+the file without rebuilding both sides by hand. The place is a path of+attributes, such as `.x` or `.φ.Δ`. It walks down both sides through the+formations they share and ends at the first attribute they bind differently,+or that only one of them binds. It stops at an application or a dispatch, and+the comment leaves it out when the two sides differ as a whole, such as a+formation and a dispatch. A ρ counts like any other attribute, so the comment+may name a `.ρ` that `--hide-rho` hides from the terms. The reason is one of+three: the join names no `otherwise`, described below, a side is no+formation, as in `the right side is no formation`, or a side has a Δ or a λ,+as in `the left side has λ`.++Two formations of different shapes may still stand for one object, when+whoever reads it asks it only for attributes both sides lead to alike. A+block may name a λ function under a fourth key, `otherwise`, and then such a+firing answers a formation binding every attribute of both sides to a copy+of that function, which asks a copy of each side for the attribute:++```yaml+  join:+    on: 𝛿1+    left: 𝑛1+    right: 𝑛2+    otherwise: L_join+```++```text+⟦+  x ↦ ∅,+  left🌵 ↦ 𝑛1( x ↦ ξ.x ),+  right🌵 ↦ 𝑛2,+  φ ↦ ⟦+    ρ ↦ ∅, λ ⤍ L_join, on🌵 ↦ ⟦ Δ ⤍ 𝛿1 ⟧,+    left🌵 ↦ ρ.left🌵.φ, right🌵 ↦ ρ.right🌵.φ+  ⟧,+  if ↦ ⟦+    ρ ↦ ∅, λ ⤍ L_join, left ↦ ∅, right ↦ ∅, on🌵 ↦ ⟦ Δ ⤍ 𝛿1 ⟧,+    left🌵 ↦ ρ.left🌵.if( left ↦ ξ.left, right ↦ ξ.right ),+    right🌵 ↦ ρ.right🌵.if( left ↦ ξ.left, right ↦ ξ.right )+  ⟧+⟧+```++Here `𝑛1` and `𝑛2` stand for the two sides, `x` for a void of `𝑛1`, `left`+and `right` for the voids of the formations both sides bind `if` to, and+`𝛿1` for the bytes the condition came down to. When the condition is a+symbol, such as `𝜎7`, `on🌵` holds that symbol, `⟦ λ ⤍ 𝜎7 ⟧`, and not the 42+every symbol is dataized to, so every copy still names the unknown it decides+by. So asking the formation for an attribute means the same as asking+`on🌵.if( left🌵, right🌵 )` for it. Every name the formation adds carries+the 🌵 mark, as the names phino makes up do, such as `a🌵14`, since no+program can spell it: an attribute a side binds, such as the `left` and+`right` voids of the `if` of a bool in EO, never clashes with them.++The formation leaves void every attribute either side leaves void, once even+when both sides leave it void, and holds each side under `left🌵` or+`right🌵` applied to its own voids. An argument handed to the formation thus+reaches the side through `ξ`, while the side keeps the ρ it binds, which its+own attributes may read. Every copy of the function asks a side for an+attribute through its ρ, which the formation fills. The ρ of either side gets+no copy of the function, nor does an attribute whose name ends with 🌵, since+a side may be a formation a join fabricated. An attribute only one side binds+gets its copy too, since the other side may reach it through `φ`. A side+holding a datum or a λ, such as a bare symbol `⟦ λ ⤍ 𝜎7 ⟧`, keeps what it+stands for where no attribute reaches it, so no such formation means the same+as the two sides, and the firing writes `diverged` as it does with no+`otherwise` at all, its comment naming the side and what it has, as in+`the left side has Δ`.++A copy of the function leaves void every void of the formations the sides+bind its attribute to, once even when both leave it void, and those of the+left side first, so an argument handed over by position reaches the same void+on both sides. It applies a side binding the attribute to a formation to the+voids of that formation alone, since applying a formation to an attribute it+lacks gives ⊥, and any other side to every void it leaves, since phino sees+no void of a term it has not morphed. A method of the formation thus takes+its arguments, as in `if( left ↦ …, right ↦ … )`, and hands them to both+sides.++The firing writes a line naming the formation by the two sides, in place of+`diverged`, and spells the formation out in its comment:++```text+fabricated 𝑛·2·8 := 𝑛1·1 and 𝑛2·1  # ⟦ left🌵 ↦ 𝑛1·1, …, φ ↦ ⟦ … ⟧, … ⟧+```++phino implements no `L_join`: the file defines it, as an entry that joins+`$.left🌵` and `$.right🌵` on `$.on🌵`:++```yaml+- λ: L_join+  join:+    on: $.on🌵+    left: $.left🌵+    right: $.right🌵+    otherwise: L_join+```++A path under `on` dataizes nothing: the firing reads the bytes or the symbol+the attribute holds off the formation it fires, so the condition is not+brought down a second time, and it gets stuck when the path leads to neither.+Its `joined`+line names the condition by the path, as `joined 𝜎4 := ξ.on🌵 ? 𝜎2 : 𝜎3`. A+side that is no formation, or a formation holding a datum, which no attribute+reaches, still gets the firing stuck.++A side may raise. EO raises with an object, 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. Such a+side is a term like any other, and it joins with no value the other side+has, so the firing gets stuck and `--partial` parks it:++```bash+$ cat raise.phi+⟦+  bytes ↦ ⟦ φ ↦ ∅ ⟧,+  number ↦ ⟦ φ ↦ ∅ ⟧,+  error ↦ ⟦ message ↦ ∅, λ ⤍ L_throw ⟧,+  bool ↦ ⟦ if ↦ ∅ ⟧,+  check ↦ ⟦ λ ⤍ L_check ⟧,+  demo ↦ ⟦ a ↦ Φ.check.if( 7, Φ.error( "negative" ) ) ⟧+⟧+$ cat raise.yaml+- λ: L_check+  𝑛: Φ.bool( if ↦ ⟦ λ ⤍ L_fork, then ↦ ∅, else ↦ ∅, φ ↦ ⟦ λ ⤍ 𝜎 ⟧ ⟧ )+- λ: L_fork+  dataize:+    𝛿1: $.φ+  join:+    on: 𝛿1+    left: $.then+    right: $.else+$ phino dataize --symbolic=raise.yaml --locator=Q.demo.a --partial \+    --sweet --hide-rho raise.phi+⟦ λ ⤍ L_fork, then ↦ 7, else ↦ Φ.error( "negative" ), φ ↦ 𝜎1:λ ⟧+```++The `then` side comes to the bytes of 7 and the `else` side to the copy of+`error`, so the protocol ends in `diverged 𝑛·2·1 vs 𝑛·2·2`, commented with+`they differ at .φ, and the join names no otherwise`, and the command prints+the `if` it parked.+Without `--partial` the command fails there, with+`No entry of --symbolic answers the λ function 'L_fork'`.++### Dropping the ρ of a dispatch++There is a sixth block, `stay`, and it reduces nothing. Each line of it takes+the term another meta of the entry is bound to and binds an expression meta of+its own to that term with every `( ρ ↦ … )` dropped that is applied to a+dispatch:++```yaml+- λ: L_fork+  dataize:+    𝛿1: $.φ+  morph:+    𝑛1: $.then+    𝑛2: $.else+  stay:+    𝑛3: 𝑛1+    𝑛4: 𝑛2+  join:+    on: 𝛿1+    left: 𝑛3+    right: 𝑛4+```++The right-hand side of a line names a meta bound by `morph` or by another line+of the entry, and nothing else. Every position of the term is walked, the+methods and the `ρ` of a formation among them, and `𝑒.𝜏( ρ ↦ 𝑒2 )` becomes+`𝑒.𝜏`. So does a dispatch heading a chain of applications that bind no `ρ`:+`𝑒.𝜏( φ ↦ 𝑒3 )( ρ ↦ 𝑒2 )` becomes `𝑒.𝜏( φ ↦ 𝑒3 )`. A `ρ` applied to anything+else, such as a formation, stays.++Dropping it changes no value. The `dot` rule answers a dispatch as+`𝑛1( ρ ↦ ⟦…⟧ )`, and then `copy` fills a void `ρ` of the body, `stay` keeps a+bound one and `skip` drops it where the body declares none. So once `𝑒.𝜏`+comes to a formation, a later `( ρ ↦ 𝑒2 )` meets a `ρ` that is bound or not+there, and `stay` or `skip` drops it. The block only drops it earlier, before+the dispatch is reduced.++This is how a join brings two numbers to one shape. In EO `bytes.as-number`+reads its `data` as `ρ`, so a branch computed through it comes to+`Φ.number( φ ↦ Φ.bytes( φ ↦ 𝜎1 )( ρ ↦ ⟦ … ⟧ ) )`, with the copy hung on the+bytes as their `ρ`, while a branch made of no object comes to+`Φ.number( φ ↦ Φ.bytes( φ ↦ 𝜎2 ) )`. The join compares the two as written,+finds a shape apart, and gets stuck; after `stay` they differ in their+symbols alone, and the join makes one term of them. The protocol writes each+line of the block. A line that dropped a `ρ` is commented `stay(…)` with the+meta it took, so a reader sees that the term changed and what changed it, while+a line that dropped nothing is commented with the meta alone:++```text+𝑛3·1 := Φ.number( φ ↦ Φ.bytes( φ ↦ 𝜎1:λ ) ):φ  # stay(𝑛1)+𝑛4·1 := 𝑛2·1  # 𝑛2+joined 𝜎4 := 𝛿1·1 ? 𝜎1 : 𝜎2+```++### Standing a symbol for a datum++There is a seventh block, `symbolize`, and it reduces nothing either. Each line+of it takes the term another meta of the entry is bound to and binds an+expression meta of its own to that term with every datum it holds turned into+a fresh symbol:++```yaml+- λ: L_fork+  dataize:+    𝛿1: $.φ+  morph:+    𝑛1: $.then+    𝑛2: $.else+  symbolize:+    𝑛3: 𝑛1+    𝑛4: 𝑛2+  join:+    on: 𝛿1+    left: 𝑛3+    right: 𝑛4+```++The right-hand side of a line names a meta bound by `morph` or by another line+of the entry, and nothing else. The whole term is walked, the way a `rewrite`+line walks it: every binding of every formation, `ρ` and methods among them,+every argument of every application, named or positional, and the target of+every dispatch. A `Δ ⤍ 𝑏` met anywhere becomes `λ ⤍ 𝜎4`, a symbol minted+fresh, the way a bare `𝜎` of an answer is. A datum off the `φ` chain counts+too, since a value may sit there: the bool a `rewrite` line makes of a literal+holds its datum inside the formation bound to `if`.++This is how a join makes one term of a literal and a value some firing+computed. In EO `x.gt( 0 ).if( 1, x )` has one side that is the literal `1`, a+number whose bytes hold the datum `3F-F0-…`, and another side that is a number+whose bytes hold a symbol. The two are numbers of one shape and differ in the+value alone, yet a datum against a symbol is no pair of symbols, so the two do+not join. After `symbolize` both sides hold a symbol where the value stands,+and the join makes one term of them. The protocol writes every symbol the+block minted with the datum it stands for, so a reader of the joined term can+tell that `𝜎4` was `1` on the left side. A line that turned a datum into a+symbol is commented `symbolize(…)` with the meta it took, while a line whose+term held no datum is commented with the meta alone:++```text+symbolized 𝜎4 := 3F-F0-00-00-00-00-00-00  # 1.0+𝑛3·1 := Φ.number( φ ↦ Φ.bytes( φ ↦ 𝜎4:λ ) ):φ  # symbolize(𝑛1)+𝑛4·1 := 𝑛2·1  # 𝑛2+joined 𝜎5 := 𝛿1·1 ? 𝜎4 : 𝜎2+```++A line sees its own side alone, so a datum both sides share becomes two+symbols and then one joined symbol, and the joined term loses that datum. A+file that wants to keep it leaves the block out, and an entry without+`symbolize` writes the same protocol it wrote before.++The blocks chain, and an entry that joins reads as a pipeline, every step of+it on its own line. A side may go through a `stay` line, then an `evaluate`+line, then a `symbolize` line, then a `defer` line, so that a call 𝕄 left+unfired is fired, a copy over a bare symbol is deferred, and two sides of one+shape still join:++```yaml+- λ: L_fork+  dataize:+    𝛿1: $.φ+  morph:+    𝑛1: $.then+    𝑛2: $.else+  stay:+    𝑛3: 𝑛1+    𝑛4: 𝑛2+  evaluate:+    𝑛5: 𝑛3+    𝑛6: 𝑛4+  symbolize:+    𝑛7: 𝑛5+    𝑛8: 𝑛6+  defer:+    𝑛9: 𝑛7+    𝑛10: 𝑛8+  join:+    on: 𝛿1+    left: 𝑛9+    right: 𝑛10+```++An `evaluate` line here works as it does in an entry that computes, except+that it passes on a term that is no formation instead of getting stuck. In EO+`bool.and` is `flag.if( FF-.eq( x ), false )`, so one side morphs down to the+`eq` of bytes with its λ function unfired, which could never join with the+bool on the other side. The line fires it, and the side comes to a bool too,+while the other side is a bool already, a formation with no λ function, and+its line leaves it as it is. A `defer` line here works as it does in an entry+that computes too: it answers a copy over a bare symbol with a fresh symbol,+writes the `deferred …` line sited at the meta it read, such as `# 𝕄(𝑛8.φ)`,+and binds any other term as it stands.++### Rewriting a term with rules++There is an eighth block, `rewrite`, and it reduces nothing either. Each line of+it takes the term another meta of the entry is bound to, named under `of`,+applies the rules listed under `rules`, spelled with the `pattern`, `result`,+`when`, `where` and meta variables a rule file uses, and binds an expression+meta of its own to the outcome:++```yaml+- λ: L_fork+  dataize:+    𝛿1: $.c+  morph:+    𝑛1: $.a+    𝑛2: $.b+  rewrite:+    𝑛3:+      of: 𝑛1+      rules: &bool+        - name: true-literal+          pattern: Φ.true+          result: Φ.bool( if ↦ ⟦ Δ ⤍ 01- ⟧ )+        - name: false-literal+          pattern: Φ.false+          result: Φ.bool( if ↦ ⟦ Δ ⤍ 00- ⟧ )+    𝑛4:+      of: 𝑛2+      rules: *bool+  join:+    on: 𝛿1+    left: 𝑛3+    right: 𝑛4+```++The meta under `of` is one bound by `morph` or by another line of the entry,+and nothing else. A line reads no key but `of` and `rules`, and two lines may+share their rules through a YAML anchor, as above. 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 symbol such as `𝜎4`+into its result, or reading a meta its pattern never binds, is refused when the+file is read, since only a firing mints a symbol.++This is how a join compares a literal with a bool a computation answered. In+EO the literal `Φ.true` stands where the other side holds `Φ.bool( … )`, the+same value in another shape, and the join gets stuck on the two as written.+After `rewrite` both sides are bools. A line that changed its term is+commented with the block and the meta it read, while a line that changed+nothing writes the plain line of a meta bound to another:++```text+𝑛3·1 := ⟦ φ ↦ 𝜎2:λ, kq ↦ Φ.bool( if ↦ 01-:Δ ) ⟧  # rewrite(𝑛1)+𝑛4·1 := 𝑛2·1  # 𝑛2+joined 𝜎4 := 𝛿1·1 ? 𝜎2 : 𝜎3+```++The markup writes both lines as a plain `<bound>`.++### Morphing what a line made++An operand of `morph` may name a meta another line binds instead of a path+from `$`. It reduces the term that meta is bound to through 𝕄, the way an+operand reading `$` is reduced, and binds a meta of its own to the normal+form:++```yaml+- λ: L_fork+  dataize:+    𝛿1: $.c+  morph:+    𝑛1: $.a+    𝑛2: $.b+    𝑛4: 𝑛3+  rewrite:+    𝑛3:+      of: 𝑛2+      rules:+        - name: unwrap+          pattern: ⟦ φ ↦ 𝑒1 ⟧+          result: 𝑒1+  join:+    on: 𝛿1+    left: 𝑛1+    right: 𝑛4+```++Such an operand is a line of the entry, so it runs once the meta it names is+bound, as described below. It takes the term as the other line bound it and+does not read it against the formation being fired. An operand reading a meta+inside a term, such as `𝑛1.x`, is refused when the file is read, and so is+one naming a meta nothing binds.++This is how a join compares a side that `rewrite` made with a side in its+normal form. Above, `$.b` comes down to `⟦ φ ↦ Φ.t ⟧`, and the rule `unwrap`+takes out `Φ.t`, a path the join cannot compare with the object on the left.+The morph of `𝑛3` brings the path down to the object it reads. In EO the same+happens to the `Φ.bool( … )` a `rewrite` line writes in place of `Φ.false`.+The protocol comments the line with the morph of the meta it read:++```text+𝑛3·1 := Φ.t  # rewrite(𝑛2)+𝑛4·1 := 𝜎3:λ:φ  # 𝕄(𝑛3)+joined 𝜎4 := 𝛿1·1 ? 𝜎2 : 𝜎3+```++### The order of the lines++The lines of `morph`, `evaluate`, `defer`, `stay`, `rewrite` and `symbolize`+run in the order of the metas they read, not in the order of their blocks. A+line of `morph` is an operand naming a meta. A line runs once every meta it+reads is bound, so this entry fires what `x` came to and then fires the answer,+one more call, although the line doing so stands first:++```yaml+- λ: L_twice+  morph:+    𝑛1: $.x+  evaluate:+    𝑛3: 𝑛2+    𝑛2: 𝑛1+  𝑛: 𝑛3+```++The line binding `𝑛3` reads `𝑛2`, which the line under it binds, so that line+runs first. The `dataize` block and the operands of `morph` reading `$` read+no meta and always run before all the others. Where no meta orders two lines,+they run in the order the list above gives their blocks, so a line of `morph`+runs right after the line binding the meta it names, and the lines of one+block run in the order of their metas, `𝑛2` before `𝑛10`. A line reading a+meta nothing binds is refused when the file is read, and so is a line that+reads the meta it binds, directly or through other lines, since it could never+run.++### 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 `join` block writes every symbol it minted,+`joined 𝜎5 := 𝛿1·2 ? 𝜎3 : 𝜎4`, naming the condition by the meta of its+operand line, and nothing is bound to the condition, which stays the unknown+it was. 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. A join that could not join closes its own+firing with `diverged …` instead, as described above, and the firings around+it with `stuck`.+`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 `<stuck λ="L_outer"/>`, `<stalled λ="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 join 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: $.φ+  join:+    on: 𝛿1+    left: $.then+    right: $.else+```++<!-- 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( 1 ).plus( ξ.x ), ξ.x.plus( 2 ).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 ).plus( 𝑛·1·1 ) )+    𝑛·0·5 := 𝑛·0·4( else ↦ 𝑛·1·1.plus( 2 ).plus( 𝑛·1·1 ) )+    𝔼(L_fork):+      𝛿1·2 := 𝔻(𝜎2:λ)  # 𝔻(ξ.φ)+      𝕄(Φ.a🌵3):+        𝑛·2·1 := Φ.number( φ ↦ 𝜎1:λ )+        𝑛·2·2 := 𝑛·2·1.plus( x ↦ 1 )+        𝔼(L_plus):+          𝔻(Φ.a🌵4):+            𝕄(Φ.a🌵4):+              𝑛·3·1 := Φ.number( φ ↦ 𝜎1:λ )+          𝛿1·3 := 𝔻(𝜎1:λ)  # 𝔻(ξ.ρ)+          𝔻(Φ.a🌵5):+            𝕄(Φ.a🌵5):+              𝑛·3·2 := 1+              𝑛·3·3 := Φ.bytes( φ ↦ 3F-F0-00-00-00-00-00-00:Δ )+            𝛿·3·1 := 3F-F0-00-00-00-00-00-00  # 1.0+          𝛿2·3 := 𝛿·3·1  # 𝔻(ξ.x)+          𝑛·3·4 := Φ.number( φ ↦ 𝜎3:λ )  # 𝑛+          𝕄(𝑛·3·4):+            𝑛·3·5 := Φ.number( φ ↦ 𝜎3:λ )+          𝑛·3·6 := 𝑛·3·5  # 𝕄(𝑛·3·4)+        𝑛·2·3 := 𝑛·3·5.plus( x ↦ 𝑛·3·1 )+      𝕄(Φ.a🌵6):+        𝑛·2·4 := Φ.number( φ ↦ 𝜎1:λ )+        𝑛·2·5 := 𝑛·2·4.plus( x ↦ 2 )+        𝔼(L_plus):+          𝔻(Φ.a🌵7):+            𝕄(Φ.a🌵7):+              𝑛·4·1 := Φ.number( φ ↦ 𝜎1:λ )+          𝛿1·4 := 𝔻(𝜎1:λ)  # 𝔻(ξ.ρ)+          𝔻(Φ.a🌵8):+            𝕄(Φ.a🌵8):+              𝑛·4·2 := 2+              𝑛·4·3 := Φ.bytes( φ ↦ 40-00-00-00-00-00-00-00:Δ )+            𝛿·4·1 := 40-00-00-00-00-00-00-00  # 2.0+          𝛿2·4 := 𝛿·4·1  # 𝔻(ξ.x)+          𝑛·4·4 := Φ.number( φ ↦ 𝜎4:λ )  # 𝑛+          𝕄(𝑛·4·4):+            𝑛·4·5 := Φ.number( φ ↦ 𝜎4:λ )+          𝑛·4·6 := 𝑛·4·5  # 𝕄(𝑛·4·4)+        𝑛·2·6 := 𝑛·4·5.plus( x ↦ 𝑛·4·1 )+      joined 𝜎5 := 𝛿1·2 ? 𝜎3 : 𝜎4+      𝑛·2·7 := ⟦ x ↦ 𝑛·4·1, λ ⤍ L_plus ⟧  # 𝕄(ξ.then), 𝕄(ξ.else)+    𝔼(L_plus):+      𝔻(Φ.a🌵9):+        𝕄(Φ.a🌵9):+          𝑛·5·1 := Φ.number( φ ↦ 𝜎5:λ )+      𝛿1·5 := 𝔻(𝜎5:λ)  # 𝔻(ξ.ρ)+      𝔻(Φ.a🌵10):+        𝕄(Φ.a🌵10):+          𝑛·5·2 := Φ.number( φ ↦ 𝜎1:λ )+      𝛿2·5 := 𝔻(𝜎1:λ)  # 𝔻(ξ.x)+      𝑛·5·3 := Φ.number( φ ↦ 𝜎6:λ )  # 𝑛+      𝕄(𝑛·5·3):+        𝑛·5·4 := Φ.number( φ ↦ 𝜎6:λ )+      𝑛·5·5 := 𝑛·5·4  # 𝕄(𝑛·5·3)+    𝑛·0·6 := 𝑛·5·4.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 -->++`𝜎2` is minted by the first firing and consumed by the join, which morphs+both branches where they stand in the universe, `𝕄(Φ.a🌵3):` and+`𝕄(Φ.a🌵6):`, and each morph fires the `plus` its branch starts with. `𝜎3` is+minted by the `plus( 1 )` of `then` and `𝜎4` by the `plus( 2 )` of `else`, and+nothing else tells the two branches apart, so the join mints `𝜎5` and writes,+on the `joined …` line, that `𝜎5` is `𝜎3` where `𝛿1·2` holds and `𝜎4`+where it does not, `𝛿1·2` being the operand line that brought `𝜎2` down. A+reader who knows the entry knows that `𝜎3` belongs to `then`. Nothing is+assigned to a `𝜎`, it being the name of a λ function, so the decision nobody+made stands on a line of its own. The `plus` both branches end in reads the+joined symbol as `𝔻(𝜎5:λ)`, and the `plus( 5 )` after it reads `𝔻(𝜎6:λ)`.++Two branches that differ in more than their symbols do not join. Had `else`+been `ξ.x.plus( 2 )`, its morph would come to `𝑛·2·4.plus( x ↦ 2 )`, holding+a datum where `then` holds a symbol, and the firing of `L_fork` would end in+`diverged 𝑛·2·3 vs 𝑛·2·5`, naming the two branches by the lines their morphs+wrote, since one term for both would make `foo` read as a program that+computes a condition, computes both branches and then makes up a value+neither of them has. The comment of that line says that the two differ at+`.x`, the argument each branch hands to its last `plus`, and that the join+names no `otherwise`.++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 join morphs that branch, so it stands inside `𝔼(L_fork)`. The+`plus` both branches end in reaches the join as a call 𝕄 left unfired, and is+fired once the join answered, by the morph of `Φ.demo.a`, so it stands beside+`𝔼(L_fork)` rather than inside it. The last `𝔼(L_plus)` is the one the outer+`𝔻` asked for after the morph.++### 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" of="Φ.bytes"><datum attr="φ">40-14-00-00-00-00-00-00</datum></applied>+      <applied meta="𝑛·0·2" by="morph" at="Φ" of="Φ.number"><meta attr="φ">𝑛·0·1</meta></applied>+      <applied meta="𝑛·0·3" of="Φ.bytes"><datum attr="φ">40-18-00-00-00-00-00-00</datum></applied>+      <applied meta="𝑛·0·4" of="Φ.number"><meta attr="φ">𝑛·0·3</meta></applied>+      <applied meta="𝑛·0·5" by="morph" at="Φ" of="𝑛·0·2.plus"><meta attr="x">𝑛·0·4</meta></applied>+      <morphed meta="𝑛·0·6">𝑛·0·5</morphed>+    </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"><meta attr="φ">𝑛·0·1</meta></applied>+          <morphed meta="𝑛·1·2">𝑛·1·1</morphed>+          <applied meta="𝑛·1·3" by="morph" at="Φ.a🌵0" of="Φ.bytes"><datum attr="φ">40-14-00-00-00-00-00-00</datum></applied>+          <morphed meta="𝑛·1·4">𝑛·1·3</morphed>+        </morph>+        <dataized meta="𝛿·1·1" number="5.0">40-14-00-00-00-00-00-00</dataized>+      </dataize>+      <bound meta="𝛿1·1">𝛿·1·1</bound>+      <dataize at="Φ.a🌵1">+        <morph at="Φ.a🌵1">+          <applied meta="𝑛·1·5" by="morph" at="Φ.a🌵1" of="Φ.number"><meta attr="φ">𝑛·0·3</meta></applied>+          <morphed meta="𝑛·1·6">𝑛·1·5</morphed>+          <applied meta="𝑛·1·7" by="morph" at="Φ.a🌵1" of="Φ.bytes"><datum attr="φ">40-18-00-00-00-00-00-00</datum></applied>+          <morphed meta="𝑛·1·8">𝑛·1·7</morphed>+        </morph>+        <dataized meta="𝛿·1·2" number="6.0">40-18-00-00-00-00-00-00</dataized>+      </dataize>+      <bound meta="𝛿2·1">𝛿·1·2</bound>+      <minted symbol="𝜎1">40-14-00-00-00-00-00-00 40-18-00-00-00-00-00-00</minted>+      <applied meta="𝑛·1·9" of="Φ.number"><symbol attr="φ">𝜎1</symbol></applied>+      <built meta="𝑛·1·10">𝑛·1·9</built>+      <morph at="𝑛·1·10">+        <applied meta="𝑛·1·11" by="morph" at="𝑛·1·10" of="Φ.number"><symbol attr="φ">𝜎1</symbol></applied>+        <morphed meta="𝑛·1·12">𝑛·1·11</morphed>+      </morph>+      <answered meta="𝑛·1·13">𝑛·1·11</answered>+    </evaluate>+    <manufactured meta="𝛿·0·1">𝜎1</manufactured>+  </dataize>+</protocol>+```++<!-- markdownlint-enable MD013 -->++The root is `<protocol>`, holding the run in one element, 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 command 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.+`<dataized>` closes the `<dataize>` element with the datum the `delta` rule+found, `meta` naming it the way the text format does, and the `<bound>` 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.+`<bound>` 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:+the name of the term where the operand was reduced with 𝕄, the datum itself+where a `dataize` operand came down to data. `<manufactured>` is the other+thing a dataization may come to, the datum manufactured for an unknown. It+closes the `<dataize>` element the way `<dataized>` does, `meta` naming it with+the same counter, and holds the symbol of that unknown rather than the 42+standing for it, `𝜎2` where the text format writes the formation `𝜎2:λ`. The+`<bound>` after the element holds that name, as it does after a `<dataized>`,+while the text format writes the whole line as `𝛿1·2 := 𝔻(𝜎2:λ)`. The name of+the element is what tells a manufactured datum from data, the way `𝔻(…)` does+in the text format, so a reader never looks at the text to tell the two apart.+`<answered>` holds the name of the term the firing answered with, named the same+way by its own `meta`, and `<built>` before it holds the name of the term the+entry wrote, the one 𝕄 made that answer of: two elements rather than two+attributes of one, for the same reason `<manufactured>` is no `<dataized>`.+`<morphed>` closes the `<morph>` element with the name of the term the 𝕄 came+to, named the same way by its own `meta`, so+`<morphed meta="𝑛·1·12">𝑛·1·11</morphed>` says the 𝕄 at `𝑛·1·10` came to the+object `𝑛·1·11`. Only the 𝕄 that opened the element writes one: an operand a+rule of 𝕄 morphs on its way stands inside the same element and closes nothing,+and a 𝕄 that was parked or cut came to nothing and writes none. Two runs of 𝕄+at one site stand in one element, the way the text format merges two blocks of+one heading, and each closes with its own `<morphed>`, so the last one is what+the element came to: inside `<morph at="Φ.a🌵0">` the first run came to the+`Φ.number` of `𝑛·1·1` and the second to the `Φ.bytes` of `𝑛·1·3`, which holds+the datum. The text format writes no line for it.++`<applied meta="𝑛·1·11" by="morph" at="𝑛·1·10" 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, counted with the `<built>`, the+`<morphed>` and the `<answered>` 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 element named after the kind of its+value, here a `<symbol>`, with `name` naming the attribute it fills and the+text holding the value. An application nothing reduced gets an `<applied>`+too, with no `by` and no `at`, right before the element that holds it:+`<applied meta="𝑛·0·1" of="Φ.bytes">` is `Φ.bytes( 40-14-… )`, the argument of+the `Φ.number` that `𝑛·0·2` applies. A later element holding an object holds+its name instead, so `<answered meta="𝑛·1·13">𝑛·1·11</answered>` says the+firing answered with it and `<morph at="𝑛·1·10">` that 𝕄 got into what the+entry built.++The markup is for a program, which never has to parse a term of 𝜑-calculus to+read it. Every value the markup holds, in an attribute or as the text of an+element, is one of a few kinds: a symbol such as `𝜎1`, a datum such as+`40-14-00-00-00-00-00-00`, a meta such as `𝑛·0·2` or `𝛿·1·1`, a path of+attribute names off `Φ`, `ξ`, `ρ` or a meta, such as `𝑛·0·2.plus`, a name such+as `tail` or `L_fork`, or `⊥`. Inside `<applied>`, `<formed>` and `<with>`, the+element holding a value is named after its kind, so a reader never looks at the+text to tell a symbol from a meta: `<symbol>`, `<datum>`, `<meta>`, `<ref>` for+a path, and the empty `<bottom attr="x"/>` for `⊥`. A term of any other kind is+named by an element of its own, written before the first element that holds+it, and is its meta from then on: an application by an `<applied>`, and a+formation by a `<formed>`. A formation that copies an object of the world,+binding its `φ` to `𝜎196`, is+`<formed meta="𝑛·3·9" of="Φ.string"><symbol attr="φ">𝜎196</symbol>`: `of`+names the object by its path, and every element inside is an attribute the+copy binds to a value other than the one the object has. A copy that binds+nothing new is named by its path alone. A formation that copies no object of+the world has no `of` and holds all its bindings: an element for each bound+attribute, named after the kind of its value, a `<void attr="x"/>` for each+void one, and its `λ` or `Δ`, if it has one, as an attribute of the element,+such as `<formed meta="𝑛·1·1" λ="L_fork">`. A term+the markup can name in none of these ways, such as an argument of a deferred+call that nothing worked out, is an expression meta, `𝑒1`: the same term gets+the same `𝑒` throughout the file and another term gets another, so a reader can+tell two holes apart, while what the term is stays in the text format alone.+Since the markup names terms the text format writes out, the two count their+metas each on their own: `𝑛·1·11` of the markup above is `𝑛·1·6` of the+text.++A join writes `<joined symbol="𝜎5" on="𝛿1·2" left="𝜎3" right="𝜎4"/>`,+one element per fresh symbol, which the text format writes as+`joined 𝜎5 := 𝛿1·2 ? 𝜎3 : 𝜎4`: the fresh symbol stands in `symbol`, the+meta of the condition in `on`, named the way `<bound>` names it, and the two+it stands for in `left` and `right`. The two `<morph>` elements before it are+the sides being morphed where they stand in the universe. Two sides that do+not join write+`<diverged left="𝑛·2·3" right="𝑛·2·6" at=".x" reason="the join names no otherwise"/>`+in its place, which the text format writes as+`diverged 𝑛·2·3 vs 𝑛·2·5  # they differ at .x, and the join names no otherwise`,+naming each side the way the markup names any other value. `at` holds the+path where the two sides differ, left out when they differ as a whole, and+`reason` says why the join made no formation of them.++`<symbolized symbol="𝜎4" number="1.0">3F-F0-00-00-00-00-00-00</symbolized>` is+one symbol a `symbolize` line minted, which the text format writes as+`symbolized 𝜎4 := 3F-F0-00-00-00-00-00-00  # 1.0`: the fresh symbol stands in+`symbol` and the datum it stands for is the text, with the double of eight+bytes in `number`, as in `<dataized>`. The elements stand ahead of the+`<bound>` of the line, and the markup binds the meta of that line as it binds+any other.++`<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` 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. 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 `<manufactured meta="𝛿·5·1">𝜎2</manufactured>` 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 join+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 one symbol it mints is written as `<joined>`.++A λ name no entry answers is `<unanswered λ="…">`, standing where its+`<evaluate>` would have stood with the name of the formation 𝔼 was fired+against as its text, such as `𝑛·1·11.nope` below, 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" of="Φ.bytes"><datum attr="φ">40-14-00-00-00-00-00-00</datum></applied>+      <applied meta="𝑛·0·2" by="morph" at="Φ" of="Φ.number"><meta attr="φ">𝑛·0·1</meta></applied>+      <applied meta="𝑛·0·3" of="Φ.bytes"><datum attr="φ">40-18-00-00-00-00-00-00</datum></applied>+      <applied meta="𝑛·0·4" of="Φ.number"><meta attr="φ">𝑛·0·3</meta></applied>+      <applied meta="𝑛·0·5" by="morph" at="Φ" of="𝑛·0·2.plus"><meta attr="x">𝑛·0·4</meta></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"><meta attr="φ">𝑛·0·1</meta></applied>+            <morphed meta="𝑛·1·2">𝑛·1·1</morphed>+            <applied meta="𝑛·1·3" by="morph" at="Φ.a🌵0" of="Φ.bytes"><datum attr="φ">40-14-00-00-00-00-00-00</datum></applied>+            <morphed meta="𝑛·1·4">𝑛·1·3</morphed>+          </morph>+          <dataized meta="𝛿·1·1" number="5.0">40-14-00-00-00-00-00-00</dataized>+        </dataize>+        <bound meta="𝛿1·1">𝛿·1·1</bound>+        <dataize at="Φ.a🌵1">+          <morph at="Φ.a🌵1">+            <applied meta="𝑛·1·5" by="morph" at="Φ.a🌵1" of="Φ.number"><meta attr="φ">𝑛·0·3</meta></applied>+            <morphed meta="𝑛·1·6">𝑛·1·5</morphed>+            <applied meta="𝑛·1·7" by="morph" at="Φ.a🌵1" of="Φ.bytes"><datum attr="φ">40-18-00-00-00-00-00-00</datum></applied>+            <morphed meta="𝑛·1·8">𝑛·1·7</morphed>+          </morph>+          <dataized meta="𝛿·1·2" number="6.0">40-18-00-00-00-00-00-00</dataized>+        </dataize>+        <bound meta="𝛿2·1">𝛿·1·2</bound>+        <minted symbol="𝜎1">40-14-00-00-00-00-00-00 40-18-00-00-00-00-00-00</minted>+        <applied meta="𝑛·1·9" of="Φ.number"><symbol attr="φ">𝜎1</symbol></applied>+        <built meta="𝑛·1·10">𝑛·1·9</built>+        <morph at="𝑛·1·10">+          <applied meta="𝑛·1·11" by="morph" at="𝑛·1·10" of="Φ.number"><symbol attr="φ">𝜎1</symbol></applied>+          <morphed meta="𝑛·1·12">𝑛·1·11</morphed>+        </morph>+        <answered meta="𝑛·1·13">𝑛·1·11</answered>+      </evaluate>+      <morphed meta="𝑛·0·6">𝑛·1·11.nope</morphed>+    </morph>+    <unanswered λ="L_number_nope" by="dataize">𝑛·1·11.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 text protocol writes: a formation+longer than sixty-four characters keeps its `φ`, `Δ` and `λ` bindings and folds+the rest into a count. The markup writes no terms, only their names, so the+option leaves it as it is. 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, so twenty levels stay far inside `--max-steps` and still fire a+million times. 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.+Here a step budget of a hundred thousand lets the recursion of `split.phi`+run longer than five seconds:++```bash+$ phino morph --symbolic=split.yaml --locator=Q.x --partial \+    --max-steps=100000 --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" of="𝑛·0·1"/>`, with the site, the+mode and the name of the formation as attributes. 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` joining on its+condition. Under `proven` the morph of every `right` fires the `if` inside it,+nesting one level deeper than the last until `--max-steps` runs out, since no+renaming of symbols turns a longer chain of pairs into a shorter one. Under+`plausible` it is cut on the first round:++<!-- markdownlint-disable MD013 -->++```bash+$ cat facta.phi+⟦+  if ↦ ⟦ c ↦ ∅, left ↦ ∅, right ↦ ∅, λ ⤍ L_if ⟧,+  zero ↦ ⟦ x ↦ ∅, λ ⤍ L_zero ⟧,+  dec ↦ ⟦ x ↦ ∅, λ ⤍ L_dec ⟧,+  box ↦ ⟦ v ↦ ∅, φ ↦ ξ.v ⟧,+  pair ↦ ⟦ head ↦ ∅, tail ↦ ∅, φ ↦ ξ.head ⟧,+  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 ↦ Φ.box( v ↦ ⟦ Δ ⤍ 00- ⟧ ) ).φ+⟧+$ cat facta.yaml+- λ: L_zero+  dataize:+    𝛿1: $.x+  𝑛: ⟦ λ ⤍ 𝜎 ⟧+- λ: L_dec+  dataize:+    𝛿1: $.x+  𝑛: ⟦ λ ⤍ 𝜎 ⟧+- λ: L_if+  dataize:+    𝛿1: $.c+  join:+    on: 𝛿1+    left: $.left+    right: $.right+$ 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 ↦ Φ.box( v ↦ 00-:Δ ), right ↦ Φ.fact( n ↦ Φ.dec( x ↦ 𝜎1:λ ), acc ↦ Φ.pair( head ↦ 𝜎1:λ, tail ↦ Φ.box( v ↦ 00-:Δ ) ) ).φ ).φ+$ grep 'looped\|diverged' 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 first `if` morphs both of its+sides before it joins them, and its `right` holds a second `if`, which holds+the first under one more `pair` and one more `dec`, so that morph is cut, and+its `looped …` line says `plausible`. A side cut that way gets the firing of+the first `if` stuck before it joins anything, so no `diverged …` line is+written, and the cut parks the `if` where it stands. 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. A join answered that way writes no `joined` line, since no line of its+own binds the condition and the first firing already said what every symbol+it minted stands for.+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 ⟧ ) ) )+⟧+```++The two styles may be mixed in one expression, so `[[ x -> ∅ ]]` reads the+same as `⟦ x ↦ ∅ ⟧`. The `--monoform` option forbids that: the first symbol+that has two spellings picks the style of the whole expression, and a later+symbol of the other style is an error. Under `--monoform`, `[[ x -> ∅ ]]`+fails, since `∅` at 1:9 mixes with the ASCII `[[` at 1:1. The option is+accepted by `rewrite`, `dataize`, `morph`, `merge` and `match`.++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:      2384353.897 μs+  avg:        238435.390 μs+  min:        215363.988 μs+  max:        278245.089 μs+  std dev:    23622.956 μs+=== parse/xmir ===+  warmup:     3 iterations+  batches:    10 x 1+  total:      9841499.835 μs+  avg:        984149.984 μs+  min:        914833.083 μs+  max:        1089305.395 μs+  std dev:    63059.625 μs+=== rewrite/normalize ===+  warmup:     3 iterations+  batches:    10 x 1+  total:      21.060 μs+  avg:        2.106 μs+  min:        1.743 μs+  max:        2.835 μs+  std dev:    0.353 μs+=== print/sweet/multiline ===+  warmup:     3 iterations+  batches:    10 x 1+  total:      5903216.355 μs+  avg:        590321.635 μs+  min:        558729.133 μs+  max:        647658.702 μs+  std dev:    30079.890 μs+=== print/sweet/flat ===+  warmup:     3 iterations+  batches:    10 x 1+  total:      725031.333 μs+  avg:        72503.133 μs+  min:        65432.404 μs+  max:        87819.934 μs+  std dev:    8921.233 μs+=== print/salty/multiline ===+  warmup:     3 iterations+  batches:    10 x 1+  total:      19159507.382 μs+  avg:        1915950.738 μs+  min:        1869384.073 μs+  max:        1995426.872 μs+  std dev:    37221.957 μs+=== morph/symbolic/demo/e1 ===+  warmup:     3 iterations+  batches:    10 x 8+  total:      10829.586 μs+  avg:        135.370 μs+  min:        128.488 μs+  max:        150.356 μs+  std dev:    6.143 μs+=== morph/symbolic/demo/e2 ===+  warmup:     3 iterations+  batches:    10 x 127+  total:      172246.803 μs+  avg:        135.627 μs+  min:        132.999 μs+  max:        140.016 μs+  std dev:    2.239 μs+=== morph/symbolic/demo/e3 ===+  warmup:     3 iterations+  batches:    10 x 132+  total:      180903.978 μs+  avg:        137.048 μs+  min:        135.244 μs+  max:        140.700 μs+  std dev:    1.678 μs+=== morph/symbolic/demo/e4 ===+  warmup:     3 iterations+  batches:    10 x 122+  total:      164908.704 μs+  avg:        135.171 μs+  min:        132.237 μs+  max:        139.304 μs+  std dev:    1.887 μs+=== morph/symbolic/demo/e5 ===+  warmup:     3 iterations+  batches:    10 x 124+  total:      165931.498 μs+  avg:        133.816 μs+  min:        132.648 μs+  max:        135.746 μs+  std dev:    0.863 μs+=== morph/symbolic/native/e5 ===+  warmup:     3 iterations+  batches:    10 x 5+  total:      61089.359 μs+  avg:        1221.787 μs+  min:        1040.609 μs+  max:        2303.375 μs+  std dev:    370.371 μs+=== morph/symbolic/accum/0 ===+  warmup:     3 iterations+  batches:    10 x 84+  total:      94035.149 μs+  avg:        111.947 μs+  min:        110.083 μs+  max:        113.534 μs+  std dev:    1.157 μs+=== morph/symbolic/accum/400 ===+  warmup:     3 iterations+  batches:    10 x 69+  total:      92238.501 μs+  avg:        133.679 μs+  min:        130.178 μs+  max:        137.564 μs+  std dev:    2.813 μs+```++The results were calculated in [this GHA job][benchmark-gha]+on 2026-10-21 at 16:09,+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/39630420210
benchmark/accum.yaml view
@@ -4,9 +4,9 @@ # yamllint disable rule:line-length # The λ functions the accumulator cases of the benchmark fire over # 'benchmark/accum.phi', the loop of #1453. Every entry answers symbolically,-# 'gt' with a fork the 'L_fork' entry joins, so the loop never learns where it-# stops and '--acyclic=plausible' is what cuts it, after the same twenty-six-# firings however many methods 'number' declares.+# 'gt' with a branch the 'L_fork' entry joins, so the loop never learns where+# it stops and '--acyclic=plausible' is what cuts it, after the same firings+# however many methods 'number' declares. - λ: L_bytes_size   dataize:     𝛿1: $.ρ@@ -29,15 +29,10 @@ - λ: L_fork   dataize:     𝛿1: $.φ-  morph:-    𝑛1: $.left-    𝑛2: $.right-  symbolize:-    𝑛3: 𝑛1-    𝑛4: 𝑛2   join:-    𝑛5: [𝑛3, 𝑛4]-  𝑛: 𝑛5+    on: 𝛿1+    left: $.left+    right: $.right - λ: L_entry   dataize:     𝛿1: $.n
benchmark/atoms.yaml view
@@ -18,7 +18,7 @@     𝛿2: $.x   𝑛: Φ.number( φ ↦ Φ.bytes( φ ↦ ⟦ λ ⤍ 𝜎 ⟧ ) ) -# Comparing two numbers answers a bool nobody has decided, carrying the fork+# Comparing two numbers answers a bool nobody has decided, carrying the join # below as its 'if', which is what the branching cases of the demo world then # dispatch. - λ: L_number_gt@@ -27,21 +27,16 @@     𝛿2: $.x   𝑛: Φ.bool( if ↦ ⟦ λ ⤍ L_fork, left ↦ ∅, right ↦ ∅, φ ↦ ⟦ λ ⤍ 𝜎 ⟧ ⟧ ) -# A branch answers neither of its sides: nobody has picked between the two, so-# both of them reach a normal form through 𝕄, are stood into unknowns and are-# joined into the one shape they share.+# A branch answers neither of its sides: nobody has picked between the two,+# so both of them reach a normal form through 𝕄 and are joined into the one+# term they share. - λ: L_fork   dataize:     𝛿1: $.φ-  morph:-    𝑛1: $.left-    𝑛2: $.right-  symbolize:-    𝑛3: 𝑛1-    𝑛4: 𝑛2   join:-    𝑛5: [𝑛3, 𝑛4]-  𝑛: 𝑛5+    on: 𝛿1+    left: $.left+    right: $.right  # An entry of the demo world: its number comes down through 𝔻 and the term it # marks reaches a normal form through 𝕄, which is the work the symbolic cases
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 \"\\\\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\")}]}"]+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 = \"n1\", operation = OpNormalize (ExDispatch (ExFormation [BiMeta \"B1\",BiTau \966 (ExMeta \"e2\"),BiMeta \"B2\"]) \966)},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.@@ -487,7 +487,7 @@ -- The dataization rule 'box'. dataizationBox :: Expression -> Expression -> [In.Premises Bytes] dataizationBox term universe =-  [ In.Contextualizes x7 (B.formed (concat [x2, [BiTau AtPhi x7], x5])) (\e3 -> pure (In.Concludes (In.Onward (In.Normalized (D.Contextualization, "contextualize")) e3 universe)))+  [ In.Concludes (In.Onward (In.Normalized (D.Dataization, "box")) (ExDispatch (B.formed (concat [x2, [BiTau AtPhi x7], x5])) AtPhi) universe)   | ExFormation x1 <- [term]   , (x2, x3) <- M.splits x1   , (x4 : x5) <- [x3]
phino.cabal view
@@ -1,6 +1,6 @@ cabal-version: 3.0 name: phino-version: 0.0.156+version: 0.0.157 license: MIT synopsis: Command-Line Manipulator of 𝜑-Calculus Expressions description: Please see the README on GitHub at <https://github.com/objectionary/phino#readme>@@ -64,6 +64,7 @@     Evaluate     Files     Filter+    Forks     Functions     Inference     Lambdas@@ -171,8 +172,10 @@     EvaluateSpec     FilesSpec     FilterSpec+    ForksSpec     Fixtures     FunctionsSpec+    Grown     InferenceSpec     LambdasSpec     LanguageSpec@@ -220,6 +223,7 @@     megaparsec >=9.5 && <9.9,     optparse-applicative >=0.18 && <0.20,     phino,+    QuickCheck >=2.14 && <2.20,     random >=1.2 && <1.4,     silently >=1.2.5 && <1.3,     text >=2.0.2 && <2.2,
resources/dataization/box.yaml view
@@ -10,12 +10,8 @@     - [Δ, λ]     - [𝐵1, 𝐵2] premises:-  - n-result: 𝑒3-    contextualize:-      - 𝑒2-      - ⟦𝐵1, φ ↦ 𝑒2, 𝐵2⟧   - n-result: 𝑛1-    normalize: 𝑒3+    normalize: ⟦𝐵1, φ ↦ 𝑒2, 𝐵2⟧.φ   - d-result: 𝛿1     dataize:       - 𝑛1
src/CLI/Helpers.hs view
@@ -83,8 +83,8 @@         | _outputFormat == LATEX = "tex"         | otherwise = show _outputFormat       render expr = do-        shown <- F.include' expr included >>= (`F.exclude'` excluded)-        printInFormat ctx ((if _canonize then canonizeExpr else id) shown)+        shown <- F.exclude' expr excluded >>= (`F.include'` included)+        printAnswer ctx shown       save :: SaveStepFunc       save expr = do         step <- atomicModifyIORef' counter (\value -> (value + 1, value + 1))@@ -139,7 +139,7 @@   ensuredFile file >>= readLambdas  started :: Expression -> ReduceContext -> IO ()-started expr ctx = writeIORef ctx._minted (taken expr)+started expr ctx = modifyIORef' ctx._minted (max (taken expr))  heading :: SaveEvalFunc -> PrintContext -> Judgment -> Expression -> IO () heading record ctx judgment locator =
src/CLI/Parsers.hs view
@@ -257,16 +257,13 @@             <> help               "Path to the YAML file of λ functions this run may fire, each entry keyed by a regular expression \               \over λ names under \"λ\", naming the operands it brings down to data under \"dataize\", the ones \-              \it reduces to a normal form under \"morph\", the terms it rewrites by rules of its own under \-              \\"rewrite\", the terms whose φ it fires the way \"evaluate\" does under \"unfold\", where \-              \\"defer: true\" has a copy one of whose arguments reads to a bare symbol, or a copy whose firing \-              \a cut refuses, answer a fresh symbol instead, the terms of those it stands the data of into \-              \unknowns under \"symbolize\", the pair of terms whose smaller it casts into the shape of the \-              \larger under \"upcast\", in two lines binding the two results, the pairs of branches it joins \-              \into one term under \"join\", whether a join takes a term and a formation whose φ is that term for one under \-              \\"lenient\", an empty list, the λ functions whose objects \-              \a join takes for a branch that raises under \"terminators\", and answering with the term under \-              \\"𝑛\""+              \it reduces to a normal form under \"morph\", the terms it fires through 𝔼 under \"evaluate\", \+              \the copies one of whose arguments reads to a bare symbol it answers with a fresh symbol under \+              \\"defer\", the terms it keeps with every ρ applied to a dispatch dropped under \"stay\", the terms \+              \it rewrites with rules spelled as in a rule file under \"rewrite\", the terms it keeps with \+              \every datum they hold turned into a fresh symbol under \"symbolize\", and \+              \answering with the term under \"𝑛\", or joining under \"join\" the two sides of a datum of \+              \\"dataize\" into one term, with a fresh symbol where they differ"         )     ) 
src/CLI/Runners.hs view
@@ -78,7 +78,7 @@   save <- saveStepFunc _stepsDir printCtx included excluded   let steps = map (stepOf linked) rules   (rewrittens, exceeded) <- rewrite expr steps (RewriteContext loc _maxDepth _maxCycles _depthSensitive Nothing (building linked) linked._normal (every steps) _must _breakpoint save dontSaveMade)-  rewrittens' <- include (if _sequence then NE.toList rewrittens else [NE.last rewrittens]) >>= exclude+  rewrittens' <- exclude (if _sequence then NE.toList rewrittens else [NE.last rewrittens]) >>= include   logDebug (printf "Printing rewritten 𝜑-expression as %s" (show _outputFormat))   exprs <- printRewrittens printCtx (rewrittens', exceeded)   output _targetFile exprs@@ -192,8 +192,8 @@           started universe aiming           dataize universe emptyState aiming       )-  when _sequence (include chain >>= exclude >>= \shown -> printRewrittens printCtx (shown, False) >>= putStrLn)-  unless _quiet (printOutcome printCtx (\residue -> F.include' residue included >>= (`F.exclude'` excluded)) outcome >>= putStrLn)+  when _sequence (exclude chain >>= include >>= \shown -> printRewrittens printCtx (shown, False) >>= putStrLn)+  unless _quiet (printOutcome printCtx (\residue -> F.exclude' residue excluded >>= (`F.include'` included)) outcome >>= putStrLn)   where     printOutcome :: PrintContext -> (Expression -> IO Expression) -> Outcome -> IO String     printOutcome _ _ (Dataized bytes) = pure (P.printBytes bytes)@@ -278,10 +278,10 @@     if _quiet       then pure Nothing       else do-        answer <- F.include' (if foc == ExRoot then morphed else maybe morphed fst (lastMaybe chain)) included >>= (`F.exclude'` excluded)+        answer <- F.exclude' (if foc == ExRoot then morphed else maybe morphed fst (lastMaybe chain)) excluded >>= (`F.include'` included)         validateXmirTopLevel _outputFormat answer         Just <$> printAnswer printCtx answer-  when _sequence (include chain >>= exclude >>= \shown -> printRewrittens printCtx (shown, False) >>= putStrLn)+  when _sequence (exclude chain >>= include >>= \shown -> printRewrittens printCtx (shown, False) >>= putStrLn)   mapM_ putStrLn printed   where     lastMaybe :: [a] -> Maybe a
src/CLI/Validators.hs view
@@ -94,13 +94,11 @@    in validateBoolOpts (zip bools' (map (printf "The --%s can be used only with --output=xmir") opts))  validateXmirTopLevel :: IOFormat -> Expression -> IO ()-validateXmirTopLevel XMIR (ExFormation [_]) = pure ()-validateXmirTopLevel XMIR (ExFormation [_, BiVoid AtRho]) = pure ()-validateXmirTopLevel XMIR (ExFormation [BiVoid AtRho, _]) = pure ()+validateXmirTopLevel XMIR (ExFormation (_ : _)) = pure () validateXmirTopLevel XMIR expr =   invalidCLIArguments     ( printf-        "Expression cannot be printed with --output=xmir: its top level must be a single binding, but got: %s"+        "Expression cannot be printed with --output=xmir: its top level must be a formation with at least one binding, but got: %s"         (printExpression expr)     ) validateXmirTopLevel _ _ = pure ()
src/CST.hs view
@@ -410,18 +410,12 @@         Just (label, _) -> label `elem` primitives         Nothing -> False       withoutRhosInPrimitives :: Expression -> [Argument] -> ([Argument], [Argument])-      withoutRhosInPrimitives _ [] = ([], [])-      withoutRhosInPrimitives obj@(BaseObject label) bds@(rho@(ArTau AtRho _) : rest)-        | label `elem` primitives =-            let (bds', rhos) = withoutRhosInPrimitives obj rest-             in (bds', rho : rhos)-        | otherwise = (bds, [])-      withoutRhosInPrimitives obj@(BaseObject label) bds@(bd : rest)-        | label `elem` primitives =-            let (bds', rhos) = withoutRhosInPrimitives obj rest-             in (bd : bds', rhos)-        | otherwise = (bds, [])+      withoutRhosInPrimitives (BaseObject label) (datum : rest)+        | label `elem` primitives = let (rhos, more) = span rho rest in (datum : more, rhos)       withoutRhosInPrimitives _ bds = (bds, [])+      rho :: Argument -> Bool+      rho (ArTau AtRho _) = True+      rho _ = False       applicationToPrimitive :: Expression -> Int -> [Argument] -> EXPRESSION       applicationToPrimitive (DataNumber bts) tabs rhos = case btsToNonFinite bts of         Just nonfinite -> EX_NONFINITE Φ nonfinite (TAB tabs) rhos@@ -488,7 +482,9 @@               (toCST (ExFormation (others ++ rest)) ctx)     where       positionless :: Binding -> Bool-      positionless BiVoid{} = True+      positionless (BiVoid (AtLabel _)) = True+      positionless (BiVoid AtPhi) = True+      positionless (BiVoid AtRho) = True       positionless BiLambda{} = True       positionless BiDelta{} = True       positionless _ = False
src/Dataize.hs view
@@ -50,7 +50,7 @@  dataize' :: Dataizable -> Expression -> State -> ReduceContext -> IO (Dataized, State) dataize' (expr, seq) univ state caller = do-  ctx <- opening Dataization =<< deeper =<< entering expr =<< universed univ caller{_judgment = Dataization}+  ctx <- opening Dataization =<< deeper =<< entering expr =<< parking seq state (universed univ caller{_judgment = Dataization})   parking seq state $ case unknown expr of     Just idx -> manufactured idx ctx     Nothing -> do@@ -70,6 +70,7 @@     unknown _ = Nothing     manufactured :: Int -> ReduceContext -> IO (Dataized, State)     manufactured idx ctx = do+      ctx._saveEval (EvManufactured ctx._nesting idx)       seq' <- leadsTo seq (Dataization, "symbol") (ExBytes datum) ctx       pure ((datum, NE.toList seq'), state{_manufactured = Just idx}) 
src/Deps.hs view
@@ -21,6 +21,7 @@ import Data.Maybe (fromMaybe, listToMaybe, mapMaybe) import qualified Data.Text as T import Files (overwrite)+import Forks (Refusal (..), Side (..)) import GHC.Clock (getMonotonicTime) import Logger (logDebug, logInfo) import Matcher@@ -99,33 +100,47 @@ certainty Proven = "proven" certainty Plausible = "plausible" +refused :: Refusal -> String+refused refusal = case refusal of+  Unnamed -> "the join names no otherwise"+  Unformed side -> printf "the %s side is no formation" (handed side)+  Holding side attr -> printf "the %s side has %s" (handed side) (printAttribute attr)+  where+    handed :: Side -> String+    handed Lefthand = "left"+    handed Righthand = "right"++dotted :: [Attribute] -> String+dotted = concatMap (('.' :) . printAttribute)+ data Evaluation   = EvRun Judgment T.Text   | EvFiring Int T.Text Judgment Expression   | EvStarted Int Judgment Expression   | EvDelta Int Bytes-  | EvLooped Int Judgment Acyclic Expression Expression (Maybe (Int, Maybe Expression))+  | EvManufactured Int Int+  | EvLooped Int Judgment Acyclic Expression Expression   | EvStuck Int T.Text Judgment Expression   | EvStall Int T.Text   | EvStuckOn Int T.Text-  | EvUnjoined Int T.Text Expression Expression-  | EvUnreminted Int T.Text Expression   | EvStarved Int Int Judgment Expression   | EvTimeout Int Int Judgment Expression   | EvSpent Int Int Judgment Expression   | EvData Int T.Text Expression (Either Int Bytes)   | 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)+  | EvBound Int T.Text Expression Expression+  | EvReshaped Int T.Text T.Text Expression Expression+  | EvSymbolized Int Int Bytes   | EvMinted Int Int [Either Int Bytes]   | EvDeferred Int Int Judgment Expression (Maybe Expression) Expression-  | EvUpcast Int Int Expression [(Int, [Attribute])]   | EvApplied Int Judgment Expression Expression Expression   | EvBuilt Int Expression   | EvAnswer Int Expression+  | EvMorphed Int Expression+  | EvJoined Int (Either T.Text Expression) Int (Int, Int)+  | EvDiverged Int (Expression, Expression) (Expression, Expression) [Attribute] Refusal+  | EvFabricated Int (Expression, Expression) (Expression, Expression) Expression+  | EvTaken Int [Expression] Expression   | EvWorld Expression  type SaveEvalFunc = Evaluation -> IO ()@@ -136,7 +151,7 @@     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)@@ -158,28 +173,29 @@ tier (EvFiring depth _ _ _) = depth tier (EvStarted depth _ _) = depth tier (EvDelta depth _) = depth-tier (EvLooped depth _ _ _ _ _) = depth+tier (EvManufactured depth _) = depth+tier (EvLooped depth _ _ _ _) = depth tier (EvStuck depth _ _ _) = depth tier (EvStall depth _) = depth tier (EvStuckOn depth _) = depth-tier (EvUnjoined depth _ _ _) = depth-tier (EvUnreminted depth _ _) = depth tier (EvStarved depth _ _ _) = depth tier (EvTimeout depth _ _ _) = depth tier (EvSpent depth _ _ _) = depth tier (EvData 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 (EvBound depth _ _ _) = depth+tier (EvReshaped depth _ _ _ _) = depth+tier (EvSymbolized depth _ _) = depth tier (EvMinted depth _ _) = depth tier (EvDeferred depth _ _ _ _ _) = depth-tier (EvUpcast depth _ _ _) = depth tier (EvApplied depth _ _ _ _) = depth tier (EvBuilt depth _) = depth tier (EvAnswer depth _) = depth+tier (EvMorphed depth _) = depth+tier (EvJoined depth _ _ _) = depth+tier (EvDiverged depth _ _ _ _) = depth+tier (EvFabricated depth _ _ _) = depth+tier (EvTaken depth _ _) = depth tier EvWorld{} = maxBound  type Named a = Map.Map Int [(Expression, a)]@@ -218,8 +234,8 @@   , _counted :: Map.Map Int Int   , _built :: Map.Map Int Int   , _deltas :: Map.Map Int Int-  , _found :: Maybe (Bytes, String)-  , _blocks :: [(Int, Maybe String, Bool)]+  , _found :: Maybe (Either Int Bytes, String)+  , _blocks :: [(Int, Maybe String, String, Bool)]   , _answered :: [(Int, Expression, String)]   } @@ -233,7 +249,7 @@   , _objects :: Named Expression   , _numbered :: Map.Map Int Int   , _datums :: Map.Map Int Int-  , _held :: Maybe (Bytes, String)+  , _held :: Maybe (Either Int Bytes, String)   , _heads :: [(Int, (Judgment, Expression))]   , _pending :: [(Int, Judgment, Expression)]   , _sources :: [(Int, Expression, String)]@@ -249,22 +265,22 @@  saveEval :: Handle -> IORef Protocol -> (Expression -> IO String) -> (Expression -> IO String) -> SaveEvalFunc saveEval handle cursor printed printed' report = do-  made <- atomicModify cursor (fmap (first (forgotten report) . headed) . written report . beside report . outer (tier report))+  made <- atomicModify cursor (fmap (first (forgotten report) . headed) . written (tier report) report . beside (tier report) report . outer (tier report))   mapM_ saved made   where     forgotten :: Evaluation -> Protocol -> Protocol     forgotten EvDelta{} protocol = protocol     forgotten EvWorld{} protocol = protocol     forgotten _ protocol = protocol{_found = Nothing}-    beside :: Evaluation -> Protocol -> Protocol-    beside EvStarted{} protocol = protocol-    beside EvMinted{} protocol = protocol-    beside report' protocol = protocol{_blocks = dropWhile (\(level, _, _) -> level >= tier report') protocol._blocks}+    beside :: Int -> Evaluation -> Protocol -> Protocol+    beside _ EvStarted{} protocol = protocol+    beside _ EvMinted{} protocol = protocol+    beside at _ protocol = protocol{_blocks = dropWhile (\(level, _, _, _) -> level >= at) protocol._blocks}     headed :: (Protocol, Maybe String) -> (Protocol, [String])     headed (protocol, Nothing) = (protocol, [])     headed (protocol, Just line) =-      ( protocol{_blocks = [(level, heading, True) | (level, heading, _) <- protocol._blocks]}-      , [indented level (heading ++ ":") | (level, Just heading, False) <- reverse protocol._blocks] ++ lines line+      ( protocol{_blocks = [(level, heading, opening, True) | (level, heading, opening, _) <- protocol._blocks]}+      , [opening | (_, _, opening, False) <- reverse protocol._blocks] ++ lines line       )     render :: Expression -> IO String     render term = do@@ -281,7 +297,7 @@       pure [said | innermost protocol /= Just said]     innermost :: Protocol -> Maybe String     innermost protocol = case protocol._blocks of-      (_, heading, _) : _ -> heading+      (_, heading, _, _) : _ -> heading       [] -> Nothing     remarked :: String -> [String] -> String     remarked line [] = line@@ -294,139 +310,131 @@     saved line = do       hPutStrLn handle line       logDebug (printf "Saved one line of the protocol: %s" (dropWhile (== ' ') line))-    written :: Evaluation -> Protocol -> IO (Protocol, Maybe String)-    written (EvRun judgment locator) protocol =-      pure (protocol{_begun = True, _blocks = [(0, Just heading, True)]}, Just (heading ++ ":"))+    written :: Int -> Evaluation -> Protocol -> IO (Protocol, Maybe String)+    written _ (EvRun judgment locator) protocol =+      pure (protocol{_begun = True, _blocks = [(0, Just heading, heading ++ ":", True)]}, Just (heading ++ ":"))       where         heading :: String         heading = printf "%s(%s)" (letter judgment) (T.unpack locator)-    written (EvFiring depth key judgment site) protocol = do-      remarks <- context protocol judgment site+    written at (EvFiring depth key judgment site) protocol = do+      opening <- indented at . remarked (printf "𝔼(%s):" (T.unpack key)) <$> context protocol judgment site       pure         ( protocol             { _fired = firings             , _open = (depth, firings) : protocol._open-            , _blocks = (depth, Nothing, True) : protocol._blocks+            , _blocks = (at, Nothing, opening, True) : protocol._blocks             }-        , Just (indented depth (remarked (printf "𝔼(%s):" (T.unpack key)) remarks))+        , Just opening         )       where         firings :: Int         firings = protocol._fired + 1-    written (EvStarted depth judgment site) protocol = do+    written at (EvStarted _ judgment site) protocol = do       locator <- located protocol site       let heading = printf "%s(%s)" (letter judgment) locator-          inner = dropWhile (\(level, _, _) -> level > depth) protocol._blocks+          inner = dropWhile (\(level, _, _, _) -> level > at) protocol._blocks       pure $ case inner of-        (level, Just heading', _) : _ | level == depth && heading' == heading -> (protocol{_blocks = inner}, Nothing)-        _ -> (protocol{_blocks = (depth, Just heading, False) : dropWhile (\(level, _, _) -> level >= depth) inner}, Nothing)-    written (EvDelta depth bytes) protocol = do+        (level, Just heading', _, _) : _ | level == at && heading' == heading -> (protocol{_blocks = inner}, Nothing)+        _ -> (protocol{_blocks = (at, Just heading, indented at (heading ++ ":"), False) : dropWhile (\(level, _, _, _) -> level >= at) inner}, Nothing)+    written at (EvDelta _ bytes) protocol = do       datum <- render (ExBytes bytes)       let index = maybe 1 (+ 1) (Map.lookup (opener protocol) protocol._deltas)           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+      pure (protocol{_deltas = Map.insert (opener protocol) index protocol._deltas, _found = Just (Right bytes, naming)}, Just (indented at (remarked (printf "%s := %s" naming datum) (maybe [] pure (ieee bytes)))))+    written _ EvManufactured{} protocol = pure (protocol, Nothing)+    written at (EvLooped _ 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))-    written (EvStuck depth key judgment self) protocol = do+      pure (protocol, Just (indented at (remarked (printf "looped %s" form) (remarks ++ [certainty mode]))))+    written at (EvStuck _ key judgment self) protocol = do       form <- render self-      pure (protocol, Just (indented depth (printf "unanswered(%s)  # %s(%s)" (T.unpack key) (letter judgment) form)))-    written (EvStall depth key) protocol =-      pure (protocol, Just (indented depth (printf "stall(%s)" (T.unpack key))))-    written (EvStuckOn depth key) protocol =-      pure (protocol, Just (indented depth (printf "stuck(%s)" (T.unpack key))))-    written (EvUnjoined depth key one two) protocol = do-      left <- render one-      right <- render two-      pure (protocol, Just (indented depth (printf "unjoined(%s)  # %s vs %s" (T.unpack key) left right)))-    written (EvUnreminted depth key term) protocol = do-      form <- render term-      pure (protocol, Just (indented depth (printf "unreminted(%s)  # %s" (T.unpack key) form)))-    written (EvStarved depth limit judgment site) protocol = do+      pure (protocol, Just (indented at (printf "unanswered %s  # %s(%s)" (T.unpack key) (letter judgment) form)))+    written at (EvStall _ key) protocol =+      pure (protocol, Just (indented at (printf "stall %s" (T.unpack key))))+    written at (EvStuckOn _ key) protocol =+      pure (protocol, Just (indented at (printf "stuck %s" (T.unpack key))))+    written at (EvStarved _ limit judgment site) protocol = do       remarks <- context protocol judgment site-      pure (protocol, Just (indented depth (remarked (printf "starved(%d)" limit) remarks)))-    written (EvTimeout depth limit judgment site) protocol = do+      pure (protocol, Just (indented at (remarked (printf "starved %d" limit) remarks)))+    written at (EvTimeout _ limit judgment site) protocol = do       remarks <- context protocol judgment site-      pure (protocol, Just (indented depth (remarked (printf "timeout(%d)" limit) remarks)))-    written (EvSpent depth limit judgment site) protocol = do+      pure (protocol, Just (indented at (remarked (printf "timeout %d" limit) remarks)))+    written at (EvSpent _ limit judgment site) protocol = do       remarks <- context protocol judgment site-      pure (protocol, Just (indented depth (remarked (printf "spent(%d)" limit) remarks)))-    written (EvData depth spelling operand value) protocol = do-      datum <- maybe (spelled value) pure (found value protocol._found)+      pure (protocol, Just (indented at (remarked (printf "spent %d" limit) remarks)))+    written at (EvData _ spelling operand value) protocol = do+      datum <- maybe (spelt value) pure (found value protocol._found)       line <- commented (printf "%s := %s" (labelled protocol spelling) datum) Dataization operand-      pure (protocol, Just (indented depth line))-      where-        spelled :: Either Int Bytes -> IO String-        spelled (Left symbol) = printf "𝔻(%s)" <$> render (standing symbol)-        spelled (Right bytes) = render (ExBytes bytes)-    written (EvTerm depth spelling judgment operand term) protocol = do+      pure (protocol, Just (indented at line))+    written at (EvTerm _ spelling judgment operand term) protocol = do       let naming = labelled protocol spelling       (protocol', value) <- valued protocol naming term       line <- commented (printf "%s := %s" naming value) judgment operand-      pure (protocol', Just (indented depth line))-    written (EvSymbolize depth spelling source term) protocol = do+      pure (protocol', Just (indented at line))+    written at (EvBound _ spelling source term) protocol = do       let naming = labelled protocol spelling       (protocol', value) <- valued protocol naming term       line <- commented' (printf "%s := %s" naming value) source-      pure (protocol', Just (indented depth line))-    written (EvKnown depth symbol bytes) protocol = do-      form <- render (standing symbol)-      value <- render (ExBytes bytes)-      pure (protocol, Just (indented depth (printf "𝔻(%s) == %s" form value)))-    written (EvJoin depth spelling (left, right) term) protocol = do+      pure (protocol', Just (indented at line))+    written at (EvReshaped _ spelling block source term) protocol = do       let naming = labelled protocol spelling       (protocol', value) <- valued protocol naming term-      pure (protocol', Just (indented depth (printf "%s := %s  # [%s, %s]" naming value (T.unpack left) (T.unpack right))))-    written (EvJoined depth fresh (one, two)) protocol = do-      form <- render (standing fresh)-      left <- render (standing one)-      right <- render (standing two)-      pure (protocol, Just (indented depth (printf "𝔻(%s) ∈ { 𝔻(%s), 𝔻(%s) }" form left right)))-    written (EvTerminate depth condition side raised message) protocol = do-      cond <- maybe (pure []) (fmap pure . spelled) condition-      said <- maybe (pure []) (fmap pure . render) message-      pure (protocol, Just (indented depth (printf "terminate(%s)  # %s" (intercalate ", " (cond ++ [T.unpack side] ++ said)) (T.unpack raised))))-      where-        spelled :: Either Int Bytes -> IO String-        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+      line <- printf "%s := %s  # %s(%s)" naming value (T.unpack block) <$> salted source+      pure (protocol', Just (indented at line))+    written at (EvSymbolized _ symbol bytes) protocol = do+      datum <- render (ExBytes bytes)+      pure (protocol, Just (indented at (remarked (printf "symbolized %s := %s" (printFunction (FnSymbol symbol)) datum) (maybe [] pure (ieee bytes)))))+    written _ EvMinted{} protocol = pure (protocol, Nothing)+    written at (EvDeferred _ 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 (EvUpcast depth bare cast paths) protocol = do-      form <- render cast-      pure (protocol, Just (intercalate "\n" (indented depth (printf "upcast(%s) := %s" (printFunction (FnSymbol bare)) form) : map path paths)))-      where-        path :: (Int, [Attribute]) -> String-        path (fresh, attrs) = indented (depth + 1) (printf "%s := %s" (printFunction (FnSymbol fresh)) (intercalate "." (printFunction (FnSymbol bare) : map printAttribute attrs)))-    written (EvApplied depth judgment call object site) protocol = do+      pure (protocol, Just (indented at (remarked (printf "deferred %s := %s" (printFunction (FnSymbol symbol)) form) remarks)))+    written at (EvApplied _ judgment call object site) protocol = do       let (index, counted) = numbered protocol           aliased :: Expression           aliased = alias (opener protocol) index       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 (EvBuilt depth term) protocol = do+      pure (counted{_made = namedInsert call aliased (namedInsert object aliased counted._made)}, Just (indented at (remarked (printf "%s·%d := %s" (labelled protocol answer) index form) remarks)))+    written at (EvBuilt depth term) protocol = do       let (index, counted) = numbered protocol       value <- borrowed protocol term       let naming = printf "%s·%d" (labelled protocol answer) index-      pure (counted{_built = Map.insert (opener protocol) index counted._built, _answered = (depth, term, naming) : counted._answered}, Just (indented depth (printf "%s := %s  # %s" naming value (T.unpack answer))))-    written (EvAnswer depth term) protocol = do+      pure (counted{_built = Map.insert (opener protocol) index counted._built, _answered = (depth, term, naming) : counted._answered}, Just (indented at (printf "%s := %s  # %s" naming value (T.unpack answer))))+    written at (EvAnswer _ term) protocol = do       let (index, counted) = numbered protocol           stem :: String           stem = labelled protocol answer           naming :: String           naming = printf "%s·%d" stem index       (protocol', value) <- valued counted naming term-      pure (protocol', Just (indented depth (printf "%s := %s  # 𝕄(%s·%d)" naming value stem (Map.findWithDefault 1 (opener protocol) protocol._built))))-    written EvWorld{} protocol = pure (protocol, Nothing)+      pure (protocol', Just (indented at (printf "%s := %s  # 𝕄(%s·%d)" naming value stem (Map.findWithDefault 1 (opener protocol) protocol._built))))+    written _ EvMorphed{} protocol = pure (protocol, Nothing)+    written at (EvJoined _ condition symbol (left, right)) protocol = do+      on <- either (pure . labelled protocol) salted condition+      pure (protocol, Just (indented at (printf "joined %s := %s ? %s : %s" (printFunction (FnSymbol symbol)) on (printFunction (FnSymbol left)) (printFunction (FnSymbol right)))))+    written at (EvDiverged _ left right path refusal) protocol = do+      one <- sided protocol left+      two <- sided protocol right+      pure (protocol, Just (indented at (printf "diverged %s vs %s  # %s" one two (explained path refusal))))+    written at (EvFabricated _ left right term) protocol = do+      one <- sided protocol left+      two <- sided protocol right+      body <- printed (abbreviatedInside protocol._made term)+      let (index, counted) = numbered protocol+      pure (counted{_made = namedInsert term (alias (opener protocol) index) counted._made}, Just (indented at (printf "fabricated %s·%d := %s and %s  # %s" (labelled protocol answer) index one two body)))+    written at (EvTaken _ sides term) protocol = do+      let (index, counted) = numbered protocol+          naming :: String+          naming = printf "%s·%d" (labelled protocol answer) index+      (protocol', value) <- valued counted naming term+      remarks <- mapM sourced sides+      pure (protocol', Just (indented at (remarked (printf "%s := %s" naming value) remarks)))+    written _ EvWorld{} protocol = pure (protocol, Nothing)+    spelt :: Either Int Bytes -> IO String+    spelt (Left symbol) = printf "𝔻(%s)" <$> render (standing symbol)+    spelt (Right bytes) = render (ExBytes bytes)     valued :: Protocol -> String -> Expression -> IO (Protocol, String)     valued protocol naming term = case denoted term of       Nothing -> (,) protocol <$> render term@@ -437,10 +445,20 @@     borrowed protocol term = case namedLookup term protocol._named of       Nothing -> render term       Just naming -> pure (T.unpack naming)+    explained :: [Attribute] -> Refusal -> String+    explained [] refusal = refused refusal+    explained path refusal = printf "they differ at %s, and %s" (dotted path) (refused refusal)+    sided :: Protocol -> (Expression, Expression) -> IO String+    sided _ (_, term@(ExFormation [BiLambda (FnSymbol _)])) = render term+    sided protocol (side@(ExMeta _), _) = labelled protocol . T.pack <$> salted side+    sided protocol (_, term) = borrowed protocol term     commented :: String -> Judgment -> Expression -> IO String     commented line judgment operand = printf "%s  # %s(%s)" line (letter judgment) <$> salted operand     commented' :: String -> Expression -> IO String     commented' line source = printf "%s  # %s" line <$> salted source+    sourced :: Expression -> IO String+    sourced side@(ExMeta _) = salted side+    sourced side = printf "%s(%s)" (letter Morphing) <$> salted side     outer :: Int -> Protocol -> Protocol     outer depth protocol = protocol{_open = dropWhile ((>= depth) . fst) protocol._open, _answered = dropWhile (\(level, _, _) -> level > depth) protocol._answered}     labelled :: Protocol -> T.Text -> String@@ -459,9 +477,9 @@   when protocol._begun $ do     now <- getMonotonicTime     let taken = milliseconds began now-    hPutStrLn handle (printf "msec(%d)" taken)-    hPutStrLn handle (printf "firings(%d)" protocol._fired)-    hPutStrLn handle (printf "fps(%d)" (perSecond protocol._fired taken))+    hPutStrLn handle (printf "msec %d" taken)+    hPutStrLn handle (printf "firings %d" protocol._fired)+    hPutStrLn handle (printf "fps %d" (perSecond protocol._fired taken))  milliseconds :: Double -> Double -> Int milliseconds began now = round ((now - began) * 1000)@@ -471,15 +489,15 @@  saveEvalXml :: Handle -> IORef Nesting -> (Expression -> IO String) -> SaveEvalFunc saveEvalXml handle cursor printed report = do-  written <- atomicModify cursor (flushed . beside report . outer (tier report) >=> opened')+  written <- atomicModify cursor (flushed . beside (tier report) report . outer (tier report) >=> opened' (tier report))   mapM_ (hPutStrLn handle) written   logDebug (printf "Saved %d line(s) of the XML protocol" (length written))   where-    opened' :: (Nesting, [String]) -> IO (Nesting, [String])-    opened' (nesting, openings) = bimap (forgotten report) (openings ++) <$> elements report nesting-    beside :: Evaluation -> Nesting -> Nesting-    beside EvStarted{} nesting = nesting-    beside report' nesting = nesting{_heads = dropWhile ((>= tier report') . fst) nesting._heads, _pending = dropWhile (\(level, _, _) -> level >= tier report') nesting._pending}+    opened' :: Int -> (Nesting, [String]) -> IO (Nesting, [String])+    opened' at (nesting, openings) = bimap (forgotten report) (openings ++) <$> elements at report nesting+    beside :: Int -> Evaluation -> Nesting -> Nesting+    beside _ EvStarted{} nesting = nesting+    beside at _ nesting = nesting{_heads = dropWhile ((>= at) . fst) nesting._heads, _pending = dropWhile (\(level, _, _) -> level >= at) nesting._pending}     flushed :: Nesting -> IO (Nesting, [String])     flushed nesting       | silent report = pure (nesting, [])@@ -494,6 +512,7 @@     silent _ = False     forgotten :: Evaluation -> Nesting -> Nesting     forgotten EvDelta{} nesting = nesting+    forgotten EvManufactured{} nesting = nesting     forgotten EvWorld{} nesting = nesting     forgotten _ nesting = nesting{_held = Nothing}     staged :: Int -> Nesting -> Naming String -> IO (Nesting, [String])@@ -504,88 +523,73 @@     placed depth nesting naming = second (closers ++) <$> staged depth nesting{_closing = kept} naming       where         (kept, closers) = closed depth nesting._closing-    elements :: Evaluation -> Nesting -> IO (Nesting, [String])-    elements (EvRun judgment locator) nesting =+    elements :: Int -> Evaluation -> Nesting -> IO (Nesting, [String])+    elements _ (EvRun judgment locator) nesting =       pure-        ( nesting{_closing = (0, opened judgment) : (-1, "protocol") : nesting._closing}-        ,-          [ "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"-          , "<protocol>"-          , indentedXml 0 (printf "<%s at=\"%s\">" (opened judgment) (quoted locator))-          ]+        ( nesting{_closing = (0, opened judgment) : (if null nesting._closing then [(-1, "protocol")] else kept)}+        , ["<?xml version=\"1.0\" encoding=\"UTF-8\"?>" | null nesting._closing]+            ++ ["<protocol>" | null nesting._closing]+            ++ closers+            ++ [run]         )-    elements (EvFiring depth key judgment site) nesting = do-      (fired, lines') <- placed depth nesting (printf "<evaluate λ=\"%s\" by=\"%s\" at=\"%s\">" (quoted key) (opened judgment) . escapeXML <$> located depth site)-      pure (fired{_fires = fired._fires + 1, _openedAt = (depth, fired._fires + 1) : fired._openedAt, _closing = (depth, "evaluate") : fired._closing}, lines')-    elements (EvStarted depth judgment site) nesting =+      where+        (kept, closers) = closed 0 nesting._closing+        run :: String+        run = indentedXml 0 (printf "<%s at=\"%s\">" (opened judgment) (quoted locator))+    elements at (EvFiring depth key judgment site) nesting = do+      (fired, lines') <- placed at nesting (printf "<evaluate λ=\"%s\" by=\"%s\" at=\"%s\">" (quoted key) (opened judgment) . escapeXML <$> located depth site)+      pure (fired{_fires = fired._fires + 1, _openedAt = (depth, fired._fires + 1) : fired._openedAt, _closing = (at, "evaluate") : fired._closing}, lines')+    elements at (EvStarted _ judgment site) nesting =       pure $ case inner of-        (level, opening) : _ | level == depth && opening == (judgment, site) -> (nesting{_heads = inner, _pending = pending}, [])-        _ -> (nesting{_heads = (depth, (judgment, site)) : dropWhile ((>= depth) . fst) inner, _pending = (depth, judgment, site) : dropWhile (\(level, _, _) -> level >= depth) pending, _closing = kept}, closers)+        (level, opening) : _ | level == at && opening == (judgment, site) -> (nesting{_heads = inner, _pending = pending}, [])+        _ -> (nesting{_heads = (at, (judgment, site)) : dropWhile ((>= at) . fst) inner, _pending = (at, judgment, site) : dropWhile (\(level, _, _) -> level >= at) pending, _closing = kept}, closers)       where         inner :: [(Int, (Judgment, Expression))]-        inner = dropWhile ((> depth) . fst) nesting._heads+        inner = dropWhile ((> at) . fst) nesting._heads         pending :: [(Int, Judgment, Expression)]-        pending = dropWhile (\(level, _, _) -> level > depth) nesting._pending-        (kept, closers) = closed depth nesting._closing-    elements (EvLooped depth judgment mode self site answered) nesting =-      placed depth nesting $ do+        pending = dropWhile (\(level, _, _) -> level > at) nesting._pending+        (kept, closers) = closed at nesting._closing+    elements at (EvLooped depth judgment mode self site) nesting =+      placed at nesting $ do         locator <- located depth site-        origin <- maybe (sole depth self) (called depth) (answered >>= snd)-        pure (enclosed "looped" (printf "<looped%s by=\"%s\" match=\"%s\" at=\"%s\"" (maybe "" symbolized answered) (opened judgment) (certainty mode) (escapeXML locator)) origin)-      where-        symbolized :: (Int, Maybe Expression) -> String-        symbolized (symbol, _) = printf " symbol=\"%s\"" (sigma symbol)-    elements (EvStuck depth key judgment self) nesting =-      placed depth nesting (printf "<unanswered λ=\"%s\" by=\"%s\">%s</unanswered>" (quoted key) (opened judgment) . escapeXMLText <$> leaf depth self)-    elements (EvStall depth key) nesting =-      placed depth nesting (pure (printf "<stall λ=\"%s\"/>" (quoted key)))-    elements (EvStuckOn depth key) nesting =-      placed depth nesting (pure (printf "<stuck λ=\"%s\"/>" (quoted key)))-    elements (EvUnjoined depth key one two) nesting =-      placed depth nesting $ do-        left <- leaf depth one-        right <- leaf depth two-        pure (printf "<unjoined λ=\"%s\">%s %s</unjoined>" (quoted key) (escapeXMLText left) (escapeXMLText right))-    elements (EvUnreminted depth key term) nesting =-      placed depth nesting (printf "<unreminted λ=\"%s\">%s</unreminted>" (quoted key) . escapeXMLText <$> leaf depth term)-    elements (EvStarved depth limit judgment site) nesting =-      placed depth nesting (printf "<starved limit=\"%d\" by=\"%s\" at=\"%s\"/>" limit (opened judgment) . escapeXML <$> located depth site)-    elements (EvTimeout depth limit judgment site) nesting =-      placed depth nesting (printf "<timeout limit=\"%d\" by=\"%s\" at=\"%s\"/>" limit (opened judgment) . escapeXML <$> located depth site)-    elements (EvSpent depth limit judgment site) nesting =-      placed depth nesting (printf "<spent limit=\"%d\" by=\"%s\" at=\"%s\"/>" limit (opened judgment) . escapeXML <$> located depth site)-    elements (EvDelta depth bytes) nesting = do+        origin <- sole depth self+        pure (enclosed "looped" (printf "<looped by=\"%s\" match=\"%s\" at=\"%s\"" (opened judgment) (certainty mode) (escapeXML locator)) origin)+    elements at (EvStuck depth key judgment self) nesting =+      placed at nesting (printf "<unanswered λ=\"%s\" by=\"%s\">%s</unanswered>" (quoted key) (opened judgment) . escapeXMLText <$> leaf depth self)+    elements at (EvStall _ key) nesting =+      placed at nesting (pure (printf "<stalled λ=\"%s\"/>" (quoted key)))+    elements at (EvStuckOn _ key) nesting =+      placed at nesting (pure (printf "<stuck λ=\"%s\"/>" (quoted key)))+    elements at (EvStarved depth limit judgment site) nesting =+      placed at nesting (printf "<starved limit=\"%d\" by=\"%s\" at=\"%s\"/>" limit (opened judgment) . escapeXML <$> located depth site)+    elements at (EvTimeout depth limit judgment site) nesting =+      placed at nesting (printf "<timeout limit=\"%d\" by=\"%s\" at=\"%s\"/>" limit (opened judgment) . escapeXML <$> located depth site)+    elements at (EvSpent depth limit judgment site) nesting =+      placed at nesting (printf "<spent limit=\"%d\" by=\"%s\" at=\"%s\"/>" limit (opened judgment) . escapeXML <$> located depth site)+    elements at (EvDelta _ bytes) nesting = do       datum <- printed (ExBytes bytes)-      let index = maybe 1 (+ 1) (Map.lookup (opener nesting) nesting._datums)-          naming :: String-          naming = printf "%s·%d" (labelled nesting sigil) index-      placed depth nesting{_datums = Map.insert (opener nesting) index nesting._datums, _held = Just (bytes, naming)} (pure (printf "<delta meta=\"%s\"%s>%s</delta>" (escapeXML naming) (maybe "" (printf " number=\"%s\"" . escapeXML) (ieee bytes) :: String) (escapeXMLText datum)))-    elements (EvData depth spelling _ value) nesting =-      placed depth nesting $ case (found value nesting._held, value) of+      let (naming, nesting') = stored (Right bytes) nesting+      placed at nesting' (pure (printf "<dataized meta=\"%s\"%s>%s</dataized>" (escapeXML naming) (maybe "" (printf " number=\"%s\"" . escapeXML) (ieee bytes) :: String) (escapeXMLText datum)))+    elements at (EvManufactured _ symbol) nesting = placed at nesting' (pure (printf "<manufactured meta=\"%s\">%s</manufactured>" (escapeXML naming) (sigma symbol)))+      where+        (naming, nesting') = stored (Left symbol) nesting+    elements at (EvData _ spelling _ value) nesting =+      placed at nesting $ case (found value nesting._held, value) of         (Just naming, _) -> pure (bound (escapeXMLText naming))-        (Nothing, Left symbol) -> pure (printf "<dataize meta=\"%s\">%s</dataize>" (escapeXML (labelled nesting spelling)) (sigma symbol))+        (Nothing, Left symbol) -> pure (bound (sigma symbol))         (Nothing, Right bytes) -> bound . escapeXMLText <$> lift (printed (ExBytes bytes))       where         bound :: String -> String-        bound = printf "<bind meta=\"%s\">%s</bind>" (escapeXML (labelled nesting spelling))-    elements (EvTerm depth spelling _ _ term) nesting = placed depth nesting (bind depth nesting spelling term)-    elements (EvSymbolize depth spelling _ term) nesting = placed depth nesting (bind depth nesting spelling term)-    elements (EvKnown depth symbol bytes) nesting = do-      value <- printed (ExBytes bytes)-      placed depth nesting (pure (printf "<known symbol=\"%s\">%s</known>" (sigma symbol) (escapeXMLText value)))-    elements (EvJoin depth spelling _ term) nesting = placed depth nesting (bind depth nesting spelling term)-    elements (EvJoined depth fresh (one, two)) nesting =-      placed depth nesting (pure (printf "<joined symbol=\"%s\">%s %s</joined>" (sigma fresh) (sigma one) (sigma two)))-    elements (EvTerminate depth condition side _ message) nesting =-      placed depth nesting $ do-        said <- maybe (pure "") (fmap (printf " message=\"%s\"" . escapeXML) . leaf depth) message :: Naming String-        case condition of-          Just (Left symbol) -> pure (printf "<terminate symbol=\"%s\" branch=\"%s\"%s/>" (sigma symbol) (quoted side) said)-          Just (Right bytes) -> printf "<terminate branch=\"%s\"%s>%s</terminate>" (quoted side) said . escapeXMLText <$> lift (printed (ExBytes bytes))-          Nothing -> pure (printf "<terminate branch=\"%s\"%s/>" (quoted side) said)-    elements (EvMinted depth symbol operands) nesting = do+        bound = printf "<bound meta=\"%s\">%s</bound>" (escapeXML (labelled nesting spelling))+    elements at (EvTerm depth spelling _ _ term) nesting = placed at nesting (bind depth nesting spelling term)+    elements at (EvBound depth spelling _ term) nesting = placed at nesting (bind depth nesting spelling term)+    elements at (EvReshaped depth spelling _ _ term) nesting = placed at nesting (bind depth nesting spelling term)+    elements at (EvSymbolized _ symbol bytes) nesting = do+      datum <- printed (ExBytes bytes)+      placed at nesting (pure (printf "<symbolized symbol=\"%s\"%s>%s</symbolized>" (sigma symbol) (maybe "" (printf " number=\"%s\"" . escapeXML) (ieee bytes) :: String) (escapeXMLText datum)))+    elements at (EvMinted _ symbol operands) nesting = do       spelledOperands <- mapM spelled' operands-      placed depth nesting (pure (minted spelledOperands))+      placed at nesting (pure (minted spelledOperands))       where         spelled' :: Either Int Bytes -> IO String         spelled' (Left fresh') = pure (sigma fresh')@@ -593,41 +597,59 @@         minted :: [String] -> String         minted [] = printf "<minted symbol=\"%s\"/>" (sigma symbol)         minted spelt = printf "<minted symbol=\"%s\">%s</minted>" (sigma symbol) (escapeXMLText (unwords spelt))-    elements (EvDeferred depth symbol judgment copy call site) nesting =-      placed depth nesting $ do+    elements at (EvDeferred depth symbol judgment copy call site) nesting =+      placed at nesting $ do         locator <- located depth site         origin <- maybe (sole depth copy) (called depth) call         pure (enclosed "deferred" (printf "<deferred symbol=\"%s\" by=\"%s\" at=\"%s\"" (sigma symbol) (opened judgment) (escapeXML locator)) origin)-    elements (EvUpcast depth bare cast paths) nesting =-      placed depth nesting $ do-        origin <- leaf depth cast-        pure (printf "<upcast symbol=\"%s\" of=\"%s\">%s</upcast>" (sigma bare) (escapeXML origin) (concatMap path paths))-      where-        path :: (Int, [Attribute]) -> String-        path (fresh', attrs) = printf "<path symbol=\"%s\">%s</path>" (sigma fresh') (escapeXMLText (intercalate "." (map printAttribute attrs)))-    elements (EvApplied depth judgment call object site) nesting =-      placed depth nesting $ do+    elements at (EvApplied depth judgment call object site) nesting =+      placed at nesting $ do         locator <- located depth site         (origin, given) <- parted depth call         (naming, aliased) <- counted         modify (first (\nesting' -> nesting'{_objects = namedInsert call aliased (namedInsert object aliased nesting'._objects)}))         pure (printf "<applied meta=\"%s\" by=\"%s\" at=\"%s\" of=\"%s\">%s</applied>" (escapeXML naming) (opened judgment) (escapeXML locator) (escapeXML origin) given)-    elements (EvBuilt depth term) nesting =-      placed depth nesting $ do+    elements at (EvBuilt depth term) nesting =+      placed at nesting $ do         body <- leaf depth term         (naming, _) <- counted         modify (first (\nesting' -> nesting'{_sources = (depth, term, naming) : nesting'._sources}))         pure (printf "<built meta=\"%s\">%s</built>" (escapeXML naming) (escapeXMLText body))-    elements (EvAnswer depth term) nesting =-      placed depth nesting $ do+    elements at (EvAnswer depth term) nesting = placed at nesting (concluded "answered" depth term)+    elements at (EvMorphed depth term) nesting = placed at nesting (concluded "morphed" depth term)+    elements at (EvJoined _ condition symbol (left, right)) nesting =+      placed at nesting $ do+        on <- either (pure . labelled nesting) (lift . traced) condition+        pure (printf "<joined symbol=\"%s\" on=\"%s\" left=\"%s\" right=\"%s\"/>" (sigma symbol) (escapeXML on) (sigma left) (sigma right))+    elements at (EvDiverged depth left right path refusal) nesting =+      placed at nesting $ do+        one <- sided depth nesting left+        two <- sided depth nesting right+        pure (printf "<diverged left=\"%s\" right=\"%s\"%s reason=\"%s\"/>" (escapeXML one) (escapeXML two) (if null path then "" else printf " at=\"%s\"" (escapeXML (dotted path)) :: String) (escapeXML (refused refusal)))+    elements at (EvFabricated depth left right term) nesting =+      placed at nesting $ do+        one <- sided depth nesting left+        two <- sided depth nesting right         body <- leaf depth term         (naming, _) <- counted-        pure (printf "<answer meta=\"%s\">%s</answer>" (escapeXML naming) (escapeXMLText body))-    elements (EvWorld world) nesting = pure (known world nesting, [])+        pure (printf "<fabricated meta=\"%s\" left=\"%s\" right=\"%s\">%s</fabricated>" (escapeXML naming) (escapeXML one) (escapeXML two) (escapeXMLText body))+    elements at (EvTaken depth _ term) nesting = placed at nesting (concluded "answered" depth term)+    elements _ (EvWorld world) nesting = pure (known world nesting, [])+    sided :: Int -> Nesting -> (Expression, Expression) -> Naming String+    sided depth _ (_, term@(ExFormation [BiLambda (FnSymbol _)])) = leaf depth term+    sided _ nesting (side@(ExMeta _), _) = labelled nesting . T.pack <$> lift (spelled side)+    sided depth _ (_, term) = leaf depth term     outer :: Int -> Nesting -> Nesting     outer depth nesting = nesting{_openedAt = dropWhile ((>= depth) . fst) nesting._openedAt, _sources = dropWhile (\(level, _, _) -> level > depth) nesting._sources}     labelled :: Nesting -> T.Text -> String     labelled nesting spelling = printf "%s·%d" (T.unpack spelling) (opener nesting)+    stored :: Either Int Bytes -> Nesting -> (String, Nesting)+    stored value nesting = (naming, nesting{_datums = Map.insert (opener nesting) index nesting._datums, _held = Just (value, naming)})+      where+        index :: Int+        index = maybe 1 (+ 1) (Map.lookup (opener nesting) nesting._datums)+        naming :: String+        naming = printf "%s·%d" (labelled nesting sigil) index     opener :: Nesting -> Int     opener nesting = maybe 0 snd (listToMaybe nesting._openedAt)     numbered :: Nesting -> (Int, Nesting)@@ -656,8 +678,13 @@     located depth site = do       (nesting, _) <- get       maybe (leaf depth site) pure (listToMaybe [naming | (_, built, naming) <- nesting._sources, built == site])+    concluded :: String -> Int -> Expression -> Naming String+    concluded tag depth term = do+      body <- leaf depth term+      (naming, _) <- counted+      pure (printf "<%s meta=\"%s\">%s</%s>" tag (escapeXML naming) (escapeXMLText body) tag)     bind :: Int -> Nesting -> T.Text -> Expression -> Naming String-    bind depth nesting spelling term = printf "<bind meta=\"%s\">%s</bind>" (escapeXML (labelled nesting spelling)) . escapeXMLText <$> leaf depth term+    bind depth nesting spelling term = printf "<bound meta=\"%s\">%s</bound>" (escapeXML (labelled nesting spelling)) . escapeXMLText <$> leaf depth term     leaf :: Int -> Expression -> Naming String     leaf depth term = titled depth term >>= lift . spelled     titled :: Int -> Expression -> Naming Expression@@ -689,7 +716,7 @@       pure path     cloned depth term (origin, bound) = do       path <- leaf depth origin-      given <- mapM (\(attr, value) -> attributed (printAttribute attr) <$> leaf depth value) bound+      given <- mapM (\(attr, value) -> titled depth value >>= lift . attributed (printAttribute attr)) bound       recorded term (\naming -> printf "<formed meta=\"%s\" of=\"%s\">%s</formed>" naming (escapeXML path) (concat given))     anonymous :: Int -> Expression -> [Binding] -> Naming Expression     anonymous depth term bds = do@@ -698,16 +725,26 @@           inner = concat (rights parts)       recorded term (\naming -> if null inner then printf "<formed meta=\"%s\"%s/>" naming marks else printf "<formed meta=\"%s\"%s>%s</formed>" naming marks inner)     part :: Int -> Binding -> Naming (Either String String)-    part depth (BiTau attr value) = Right . attributed (printAttribute attr) <$> leaf depth value-    part _ (BiVoid attr) = pure (Right (printf "<void name=\"%s\"/>" (escapeXML (printAttribute attr))))+    part depth (BiTau attr value) = Right <$> (titled depth value >>= lift . attributed (printAttribute attr))+    part _ (BiVoid attr) = pure (Right (printf "<void attr=\"%s\"/>" (escapeXML (printAttribute attr))))     part _ (BiLambda func) = pure (Left (printf " λ=\"%s\"" (escapeXML (printFunction func))))     part _ (BiDelta bytes) = Left . printf " Δ=\"%s\"" . escapeXML <$> lift (printed (ExBytes bytes))     part _ _ = pure (Right "")     argument :: Int -> Argument -> Naming String-    argument depth (ArTau attr value) = attributed (printAttribute attr) <$> leaf depth value-    argument depth (ArAlpha alpha value) = attributed (printAlpha alpha) <$> leaf depth value-    attributed :: String -> String -> String-    attributed name value = printf "<attr name=\"%s\">%s</attr>" (escapeXML name) (escapeXMLText value)+    argument depth (ArTau attr value) = titled depth value >>= lift . attributed (printAttribute attr)+    argument depth (ArAlpha alpha value) = titled depth value >>= lift . attributed (printAlpha alpha)+    attributed :: String -> Expression -> IO String+    attributed name ExTermination = pure (printf "<bottom attr=\"%s\"/>" (escapeXML name))+    attributed name value = tagged (kind value) . escapeXMLText <$> spelled value+      where+        tagged :: String -> String -> String+        tagged tag text = printf "<%s attr=\"%s\">%s</%s>" tag (escapeXML name) text tag+    kind :: Expression -> String+    kind (ExFormation [BiLambda (FnSymbol _)]) = "symbol"+    kind (ExFormation [BiDelta _]) = "datum"+    kind (ExBytes _) = "datum"+    kind (ExMeta _) = "meta"+    kind _ = "ref"     chained :: Named Expression -> Expression -> (Expression, [Argument])     chained objects (ExApplication inner arg) = case inner of       ExApplication _ _ | Nothing <- namedLookup inner objects -> second (++ [arg]) (chained objects inner)@@ -734,14 +771,14 @@     invoked (ExApplication term (ArTau attr value)) = second (++ [(attr, value)]) (invoked term)     invoked term = (term, [])     passed :: (Attribute, Expression) -> Naming String-    passed (attr, value) = attributed (printAttribute attr) <$> valued value-    valued :: Expression -> Naming String+    passed (attr, value) = valued value >>= lift . attributed (printAttribute attr)+    valued :: Expression -> Naming Expression     valued value = do       (nesting, _) <- get       case (namedLookup value nesting._objects, symbolic value) of-        (Just aliased, _) -> lift (spelled aliased)-        (Nothing, Just symbol) -> pure (sigma symbol)-        (Nothing, Nothing) -> opaque value >>= lift . spelled+        (Just aliased, _) -> pure aliased+        (Nothing, Just symbol) -> pure (standing symbol)+        (Nothing, Nothing) -> opaque value     symbolic :: Expression -> Maybe Int     symbolic (ExFormation [BiLambda (FnSymbol idx)]) = Just idx     symbolic (ExFormation bds) = listToMaybe [body | BiTau AtPhi body <- bds] >>= symbolic@@ -753,6 +790,10 @@     spelled (ExDispatch inner attr)       | sigmatic inner = printf "%s.%s" <$> spelled inner <*> pure (printAttribute attr)     spelled term = printed term+    traced :: Expression -> IO String+    traced ExXi = pure "ξ"+    traced (ExDispatch inner attr) = printf "%s.%s" <$> traced inner <*> pure (printAttribute attr)+    traced term = printed term     sigmatic :: Expression -> Bool     sigmatic (ExFormation [BiLambda (FnSymbol _)]) = True     sigmatic (ExDispatch inner _) = sigmatic inner@@ -864,9 +905,9 @@ sigil :: T.Text sigil = "𝛿" -found :: Either Int Bytes -> Maybe (Bytes, String) -> Maybe String-found (Right bytes) (Just (held, naming))-  | bytes == held = Just naming+found :: Either Int Bytes -> Maybe (Either Int Bytes, String) -> Maybe String+found value (Just (held, naming))+  | value == held = Just naming found _ _ = Nothing  alias :: Int -> Int -> Expression
src/Evaluate.hs view
@@ -17,15 +17,15 @@ import Data.List.NonEmpty (NonEmpty (..)) import Data.Maybe (fromMaybe, listToMaybe) import qualified Data.Text as T-import Deps (Acyclic, BuildTermFunc, BuildTermMethod, Evaluation (..), Judgment (Evaluation, Morphing), State (..), Term (TeExpression), dontSaveEval, dontSaveStep, resited)+import Deps (Evaluation (..), Judgment (Evaluation, Morphing), State (..), dontSaveEval, dontSaveStep, resited) import Engine (Engine (..))-import Lambdas (Lambda (..), Line (..), Meta (..), emptyLambdas, joined, matched, message, minted, stayed, symbolized, thrown, upcasted)+import Forks (Refusal (Unnamed), forked, joined)+import Lambdas (Ending (..), Lambda (..), Line (..), Meta (..), emptyLambdas, matched, minted, rewritten, stayed, symbolized) import Matcher (MetaValue (..), Subst, combine, substEmpty, substSingle, substSlot)-import Morph (Answer, Firing (..), Kept (..), ReduceContext (..), ReduceException (..), Steps (..), admitting, boxed, charged, counted, deeper, expired, isLambda, lambda, morphing, normalized, opening, recalled, refused, remember, remembered, retained, settled, starved, unparked)+import Morph (Answer (..), Firing (..), Kept (..), ReduceContext (..), ReduceException (..), Steps (..), boxed, charged, counted, deeper, ended, enter, expired, isLambda, lambda, morphing, normalized, opening, recalled, remember, remembered, retained, settled, starved, unparked) import Printer (printFunction)-import Rule (RuleContext (RuleContext), matchExpressionWithRule', xiFree)+import Rule (RuleContext (RuleContext)) 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@@ -72,13 +72,13 @@       outcome <- try $ do         (bound, dataized, conditions) <- foldM (down ctx) (substEmpty, state, []) entry._dataized         (bound', morphed, normals) <- foldM (through ctx) (bound, dataized, []) entry._morphed-        let firing = Firing func (reverse conditions) (reverse normals)+        firing <- Firing func (reverse conditions) . (reverse normals ++) <$> sides entry._ending         known <- remembered caller._memo firing         maybe (worked ctx entry firing bound' morphed) (\answer -> (answer, morphed) <$ shown ctx._nesting answer) known       case outcome of         Right (answer, state') -> do           retained caller._memo form stamp (Answered answer)-          pure (snd answer, state')+          pure (ended answer, state')         Left failure -> do           exhausted' <- starved caller._memo           mapM_ (retained caller._memo form stamp) (kept (exhausted' /= exhausted) (escaped failure))@@ -86,22 +86,28 @@           throwIO (escaped failure)     worked :: ReduceContext -> Lambda -> Firing -> Subst -> State -> IO (Answer, State)     worked ctx entry firing@(Firing _ operands _) bound state' = do-      (lined, state'') <- foldM (line ctx entry (listToMaybe operands)) (bound, state') entry._lines-      (answer, state''') <- answered ctx entry operands lined state''+      (lined, state'') <- foldM (line ctx entry._ending) (bound, state') entry._lines+      (answer, state''') <- concluded ctx entry operands lined state''       remember caller._memo firing answer       pure (answer, state''')-    line :: ReduceContext -> Lambda -> Maybe (Either Int Bytes) -> (Subst, State) -> Line -> IO (Subst, State)-    line ctx entry _ held (Evaluated meta source) = evaluated ctx entry held meta source-    line ctx entry _ held (Unfolded meta source deferring) = unfolded ctx entry held meta source deferring-    line ctx entry _ (bound, state') (Rewritten meta source rules) = (,state') <$> reshaped ctx entry state' bound meta source rules-    line ctx _ _ (bound, state') (Stayed meta source) = (,state') <$> trimmed ctx bound meta source-    line ctx _ _ (bound, state') (Symbolized meta source) = (,state') <$> masked ctx bound meta source-    line ctx _ _ (bound, state') (Upcasted one first two second) = (,state') <$> cast ctx bound (one, first) (two, second)-    line ctx entry condition held (Paired meta left right) = paired ctx entry condition held meta left right+    sides :: Ending -> IO [Expression]+    sides (Answering _) = pure []+    sides (Joining _ left right _) = traverse keyed [left, right]+    keyed :: Expression -> IO Expression+    keyed side@(ExMeta _) = pure side+    keyed side = operand side+    line :: ReduceContext -> Ending -> (Subst, State) -> Line -> IO (Subst, State)+    line ctx _ held (Morphed meta source) = reduced ctx held meta source+    line ctx ending held (Evaluated meta source) = evaluated ctx ending held meta source+    line ctx _ (bound, state') (Deferred meta source) = (,state') <$> deferred ctx state' meta source bound+    line ctx _ (bound, state') (Stayed meta source) = (,state') <$> reshaped ctx (EvReshaped ctx._nesting meta._spelling "stay") (pure . stayed) meta source bound+    line ctx _ (bound, state') (Rewritten meta source rules) = (,state') <$> reshaped ctx (EvReshaped ctx._nesting meta._spelling "rewrite") (rewritten (RuleContext ctx._buildTerm ctx._universe ctx._engine._normal) rules) meta source bound+    line ctx _ (bound, state') (Symbolized meta source) = (,state') <$> reshaped ctx (EvReshaped ctx._nesting meta._spelling "symbolize") (masked ctx) meta source bound     shown :: Int -> Answer -> IO ()-    shown depth (built, normal) = do+    shown depth (Answer built normal) = do       caller._saveEval (EvBuilt depth built)       caller._saveEval (EvAnswer depth normal)+    shown depth (Joined (left, right) normal) = caller._saveEval (EvTaken depth [left, right] normal)     kept :: Bool -> ReduceException -> Maybe Kept     kept _ (Looping term) = Just (Looped term)     kept _ (LoopingAt term _ _) = Just (Looped term)@@ -116,17 +122,16 @@     stranded (StuckAt name _ _) = Just name     stranded _ = Nothing     escaped :: ReduceException -> ReduceException-    escaped (Unjoined name) = Stuck name-    escaped (Unreminted name) = Stuck name+    escaped (Diverged name) = Stuck name     escaped failure = failure     told :: Kept -> IO (Expression, State)     told (Answered answer) = do       caller._saveEval (EvFiring caller._nesting func caller._judgment caller._site)       shown (caller._nesting + 1) answer-      pure (snd answer, state)+      pure (ended 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)@@ -150,106 +155,51 @@       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+    reduced :: ReduceContext -> (Subst, State) -> Meta -> Meta -> IO (Subst, State)+    reduced ctx (bound, state') meta source = do       term <- buildExpressionThrows (ExMeta source._name) bound-      (answer, state'') <- fire refused ctx entry (ExMeta source._name) state' term+      (normal, state'') <- morphing univ ctx term state'+      ctx._saveEval (EvTerm ctx._nesting meta._spelling Morphing (ExMeta source._name) normal)+      bound' <- bind meta (MvExpression normal) bound+      pure (bound', state'')+    reshaped :: ReduceContext -> (Expression -> Expression -> Evaluation) -> (Expression -> IO Expression) -> Meta -> Meta -> Subst -> IO Subst+    reshaped ctx record change meta source bound = do+      kept <- buildExpressionThrows (ExMeta source._name) bound+      term <- change kept+      ctx._saveEval+        ( if term == kept+            then EvBound ctx._nesting meta._spelling (ExMeta source._name) term+            else record (ExMeta source._name) term+        )+      bind meta (MvExpression term) bound+    masked :: ReduceContext -> Expression -> IO Expression+    masked ctx term = do+      (stood, known, spent) <- symbolized term <$> readIORef ctx._minted+      writeIORef ctx._minted spent+      mapM_ (\(fresh, bytes) -> ctx._saveEval (EvSymbolized ctx._nesting fresh bytes)) known+      pure stood+    evaluated :: ReduceContext -> Ending -> (Subst, State) -> Meta -> Meta -> IO (Subst, State)+    evaluated ctx ending (bound, state') meta source = do+      term <- buildExpressionThrows (ExMeta source._name) bound+      (answer, state'') <- kindled ctx ending (ExMeta source._name) state' term       ctx._saveEval (EvTerm ctx._nesting meta._spelling Evaluation (ExMeta source._name) answer)       bound' <- bind meta (MvExpression answer) bound       pure (bound', state'')-    fire :: (Expression -> ReduceContext -> (Acyclic, Expression) -> IO Expression) -> ReduceContext -> Lambda -> Expression -> State -> Expression -> IO (Expression, State)-    fire _ _ entry _ state' term-      | raising entry term = pure (term, state')-    fire cut ctx _ site state' term@(ExFormation _) = do-      let firing = ctx{_judgment = Evaluation, _site = site}-      admission <- admitting term firing-      case admission of-        Left refusal -> do-          answer <- cut term firing refusal-          pure (answer, state')-        Right admitted -> do-          entered <- deeper admitted-          evaluation entered state' term univ-    fire _ _ _ _ _ _ = throwIO (Stuck func)-    reshaped :: ReduceContext -> Lambda -> State -> Subst -> Meta -> Meta -> [Y.Rule] -> IO Subst-    reshaped ctx entry state' bound meta source rules = do-      term <- buildExpressionThrows (ExMeta source._name) bound-      shaped <- rewritten rules (\around -> RuleContext (reminting around) Nothing ctx._engine._normal) term-      ctx._saveEval (EvSymbolize ctx._nesting meta._spelling (ExMeta source._name) shaped)-      bind meta (MvExpression shaped) bound-      where-        reminting :: Maybe Expression -> BuildTermFunc-        reminting around "remint" = reminted around-        reminting _ func = ctx._buildTerm func-        reminted :: Maybe Expression -> BuildTermMethod-        reminted around [Y.ArgExpression expr] subst = do-          term <- buildExpressionThrows expr subst-          TeExpression <$> (stood ctx around term term >>= maybe (unreminted term) pure)-        reminted _ _ _ = throwIO (userError "Function remint() requires exactly 1 argument as expression")-        stood :: ReduceContext -> Maybe Expression -> Expression -> Expression -> IO (Maybe Expression)-        stood deep around site term = case ended term of-          Just found -> Just <$> pinned found-          Nothing -> do-            placed <- if xiFree term then pure term else maybe (pure term) (ctx._engine._contextualize term) around-            figured deep entry state' site placed >>= maybe (pure Nothing) (became deep site term)-        became :: ReduceContext -> Expression -> Expression -> Expression -> IO (Maybe Expression)-        became deep site term answer = case (ended answer, answer) of-          (Just found, _) -> Just <$> pinned found-          (_, ExFormation bds)-            | hollow bds -> deeper deep >>= \further -> stood further Nothing (ExDispatch site AtPhi) (ExDispatch answer AtPhi)-            | otherwise -> maybe (pure Nothing) (deferral deep site bds) (spelled term bds)-          _ -> pure Nothing-        spelled :: Expression -> [Binding] -> Maybe Expression-        spelled term bds = case located (stripped term) (fromMaybe univ caller._universe) of-          Just (ExFormation declared) -> Just (applied bds (stripped term) declared)-          _ -> called bds-          where-            stripped :: Expression -> Expression-            stripped (ExApplication target _) = stripped target-            stripped other = other-        hollow :: [Binding] -> Bool-        hollow bds = not (any isLambda bds) && or [True | BiTau AtPhi _ <- bds]-        deferral :: ReduceContext -> Expression -> [Binding] -> Expression -> IO (Maybe Expression)-        deferral deep site bds call = argued deep site bds call >>= traverse postponed-          where-            postponed :: Expression -> IO Expression-            postponed made = do-              fresh <- coined-              ctx._saveEval (EvDeferred ctx._nesting fresh Morphing (ExFormation bds) (Just made) site)-              pure (ExFormation [BiLambda (FnSymbol fresh)])-        argued :: ReduceContext -> Expression -> [Binding] -> Expression -> IO (Maybe Expression)-        argued deep site bds (ExApplication target (ArTau attr body)) = do-          target' <- argued deep site bds target-          case target' of-            Nothing -> pure Nothing-            Just made -> fmap (ExApplication made . ArTau attr) <$> stood deep (Just (ExFormation (filter (not . named attr) bds))) (ExDispatch site attr) body-        argued _ _ _ call = pure (Just call)-        pinned :: Either Int Bytes -> IO Expression-        pinned (Left idx) = pure (ExFormation [BiLambda (FnSymbol idx)])-        pinned (Right bytes) = do-          fresh <- coined-          ctx._saveEval (EvKnown ctx._nesting fresh bytes)-          pure (ExFormation [BiLambda (FnSymbol fresh)])-        unreminted :: Expression -> IO a-        unreminted term = do-          ctx._saveEval (EvUnreminted ctx._nesting func term)-          throwIO (Unreminted func)-    ended :: Expression -> Maybe (Either Int Bytes)-    ended (ExFormation [BiLambda (FnSymbol idx)]) = Just (Left idx)-    ended (ExFormation bds) = case [bytes | BiDelta bytes <- bds] of-      [bytes] -> Just (Right bytes)-      _ -> listToMaybe [body | BiTau AtPhi body <- bds] >>= ended-    ended (ExApplication _ (ArTau AtPhi body)) = ended body-    ended _ = Nothing-    figured :: ReduceContext -> Lambda -> State -> Expression -> Expression -> IO (Maybe Expression)-    figured ctx entry state' site placed = do-      outcome <- try $ do-        (normal, morphed) <- settled placed univ state' =<< opening Morphing ctx{_site = site}-        fst <$> fire refused ctx entry site morphed normal-      case outcome of-        Right answer -> pure (Just answer)-        Left (OutOfTime cap) -> throwIO (OutOfTime cap)-        Left _ -> pure Nothing+    kindled :: ReduceContext -> Ending -> Expression -> State -> Expression -> IO (Expression, State)+    kindled _ _ _ state' term@(ExFormation [BiLambda (FnSymbol _)]) = pure (term, state')+    kindled ctx _ site state' term@(ExFormation _) = fire ctx site state' term+    kindled _ Joining{} _ state' term = pure (term, state')+    kindled ctx _ site state' term = fire ctx site state' term+    fire :: ReduceContext -> Expression -> State -> Expression -> IO (Expression, State)+    fire _ _ state' ExTermination = pure (ExTermination, state')+    fire ctx site state' term@(ExFormation _) = do+      entered <- deeper =<< enter term ctx{_judgment = Evaluation, _site = site}+      evaluation entered state' term univ+    fire _ _ _ _ = throwIO (Stuck func)+    figured :: ReduceContext -> State -> Expression -> Expression -> IO Expression+    figured ctx state' site placed = unparked $ do+      (normal, morphed) <- settled placed univ state' =<< opening Morphing ctx{_site = site}+      fst <$> fire ctx site morphed normal     named :: Attribute -> Binding -> Bool     named attr (BiTau attr' _) = attr == attr'     named _ _ = False@@ -286,98 +236,20 @@     erased (ExApplication target _) = erased target     erased (ExDispatch target attr) = ExDispatch (erased target) attr     erased other = other-    trimmed :: ReduceContext -> Subst -> Meta -> Meta -> IO Subst-    trimmed ctx bound meta source = do-      term <- stayed <$> buildExpressionThrows (ExMeta source._name) bound-      ctx._saveEval (EvSymbolize ctx._nesting meta._spelling (ExMeta source._name) term)-      bind meta (MvExpression term) bound-    masked :: ReduceContext -> Subst -> Meta -> Meta -> IO Subst-    masked ctx bound meta source = do-      reduced <- buildExpressionThrows (ExMeta source._name) bound-      (stood, known, spent) <- symbolized reduced <$> readIORef ctx._minted-      writeIORef ctx._minted spent-      mapM_ (ctx._saveEval . fact) known-      ctx._saveEval (EvSymbolize ctx._nesting meta._spelling (ExMeta source._name) stood)-      bind meta (MvExpression stood) bound-      where-        fact :: (Int, Bytes) -> Evaluation-        fact (fresh, bytes) = EvKnown ctx._nesting fresh bytes-    cast :: ReduceContext -> Subst -> (Meta, Meta) -> (Meta, Meta) -> IO Subst-    cast ctx bound (one, first) (two, second) = do-      left <- buildExpressionThrows (ExMeta first._name) bound-      right <- buildExpressionThrows (ExMeta second._name) bound-      spent <- readIORef ctx._minted-      (left', right') <- case (upcasted left right spent, upcasted right left spent) of-        (Just outcome, _) -> (,right) <$> recorded outcome-        (_, Just outcome) -> (left,) <$> recorded outcome-        _ -> pure (left, right)-      written one first left' bound >>= written two second right'-      where-        recorded :: (Int, Expression, [(Int, [Attribute])], Int) -> IO Expression-        recorded (bare, made, paths, spent) = do-          writeIORef ctx._minted spent-          ctx._saveEval (EvUpcast ctx._nesting bare made paths)-          pure made-        written :: Meta -> Meta -> Expression -> Subst -> IO Subst-        written meta source term subst = do-          ctx._saveEval (EvSymbolize ctx._nesting meta._spelling (ExMeta source._name) term)-          bind meta (MvExpression term) subst-    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 entry one, raising entry two) of-        (True, False) -> terminating "left" left one two-        (False, True) -> terminating "right" right two one-        _ -> both one two-      where-        both :: Expression -> Expression -> IO (Subst, State)-        both one two = readIORef ctx._minted >>= maybe (unjoined one two) written . joined entry._lenient one two-        unjoined :: Expression -> Expression -> IO a-        unjoined one two = do-          ctx._saveEval (EvUnjoined ctx._nesting func one two)-          throwIO (Unjoined func)-        written :: (Expression, [(Int, (Int, Int))], Int) -> IO (Subst, State)-        written (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')-        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)-          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-    unfolded :: ReduceContext -> Lambda -> (Subst, State) -> Meta -> Meta -> Bool -> IO (Subst, State)-    unfolded ctx entry (bound, state') meta source deferring = do+    deferred :: ReduceContext -> State -> Meta -> Meta -> Subst -> IO Subst+    deferred ctx state' meta source bound = do       term <- buildExpressionThrows (ExMeta source._name) bound-      (answer, state'') <- reduced term-      bound' <- bind meta (MvExpression answer) bound-      pure (bound', state'')+      answer <- postponing ctx state' (ExMeta source._name) term >>= maybe (kept term) pure+      bind meta (MvExpression answer) bound       where-        reduced :: Expression -> IO (Expression, State)-        reduced term@(ExFormation bds)-          | hanging term && deferring && copied bds = deferred bds >>= maybe (entered (severed bds) term) (\answer -> pure (answer, state'))-          | hanging term = entered refused term-        reduced term = do-          ctx._saveEval (EvSymbolize ctx._nesting meta._spelling (ExMeta source._name) term)-          pure (term, state')-        entered :: (Expression -> ReduceContext -> (Acyclic, Expression) -> IO Expression) -> Expression -> IO (Expression, State)-        entered cut term = do-          (normal, morphed) <- morphing univ ctx (ExDispatch term AtPhi) state'-          ctx._saveEval (EvTerm ctx._nesting meta._spelling Morphing (ExDispatch (ExMeta source._name) AtPhi) normal)-          (answer, state'') <- fire cut ctx entry (ExMeta source._name) morphed normal-          ctx._saveEval (EvTerm ctx._nesting meta._spelling Evaluation (ExMeta source._name) answer)-          pure (answer, state'')-        hanging :: Expression -> Bool-        hanging (ExFormation bds) = not (any isLambda bds) && or [not (xiFree body) | BiTau AtPhi body <- bds]-        hanging _ = False+        kept :: Expression -> IO Expression+        kept term = term <$ ctx._saveEval (EvBound ctx._nesting meta._spelling (ExMeta source._name) term)+    postponing :: ReduceContext -> State -> Expression -> Expression -> IO (Maybe Expression)+    postponing ctx state' site (ExFormation bds)+      | copied bds = do+          known <- mapM resolved bds+          if any bare known then Just <$> postponed known else pure Nothing+      where         copied :: [Binding] -> Bool         copied bds = boxed bds && not (any void bds) && or [not (formed body) | BiTau AtPhi body <- bds]           where@@ -387,68 +259,101 @@             formed :: Expression -> Bool             formed (ExFormation _) = True             formed _ = False-        deferred :: [Binding] -> IO (Maybe Expression)-        deferred bds = do-          known <- mapM resolved bds-          if any bare known then Just <$> postponed known else pure Nothing-          where-            bare :: Binding -> Bool-            bare (BiTau attr (ExFormation [BiLambda (FnSymbol _)])) = attr /= AtPhi && attr /= AtRho-            bare _ = False-            resolved :: Binding -> IO Binding-            resolved bd@(BiTau attr body)-              | attr /= AtPhi && attr /= AtRho && reducible body = do-                  placed <- ctx._engine._contextualize body (ExFormation (filter (not . named attr) bds))-                  outcome <- try (settled placed univ state' reading)-                  case outcome of-                    Right (sole@(ExFormation [BiLambda (FnSymbol _)]), _) -> pure (BiTau attr sole)-                    Left (OutOfTime cap) -> expired ctx cap-                    _ -> pure bd-            resolved bd = pure bd-            reducible :: Expression -> Bool-            reducible (ExDispatch _ _) = True-            reducible (ExApplication _ _) = True-            reducible _ = False-            reading :: ReduceContext-            reading = ctx{_symbolic = emptyLambdas, _memo = Nothing, _tally = Nothing, _acyclic = Nothing, _saveStep = dontSaveStep, _saveEval = dontSaveEval}-            postponed :: [Binding] -> IO Expression-            postponed known = do-              call <- traverse (argued known) (called known)-              fresh <- coined-              ctx._saveEval (EvDeferred ctx._nesting fresh Morphing (ExFormation known) call (ExDispatch (ExMeta source._name) AtPhi))-              pure (ExFormation [BiLambda (FnSymbol fresh)])-        severed :: [Binding] -> Expression -> ReduceContext -> (Acyclic, Expression) -> IO Expression-        severed bds _ firing (mode, before) = do-          call <- traverse (argued bds) (called bds)+        bare :: Binding -> Bool+        bare (BiTau attr (ExFormation [BiLambda (FnSymbol _)])) = attr /= AtPhi && attr /= AtRho+        bare _ = False+        resolved :: Binding -> IO Binding+        resolved bd@(BiTau attr body)+          | attr /= AtPhi && attr /= AtRho && reducible body = do+              placed <- ctx._engine._contextualize body (ExFormation (filter (not . named attr) bds))+              outcome <- try (settled placed univ state' reading)+              case outcome of+                Right (sole@(ExFormation [BiLambda (FnSymbol _)]), _) -> pure (BiTau attr sole)+                Left (OutOfTime cap) -> expired ctx cap+                _ -> pure bd+        resolved bd = pure bd+        reducible :: Expression -> Bool+        reducible (ExDispatch _ _) = True+        reducible (ExApplication _ _) = True+        reducible _ = False+        reading :: ReduceContext+        reading = ctx{_symbolic = emptyLambdas, _memo = Nothing, _tally = Nothing, _acyclic = Nothing, _saveStep = dontSaveStep, _saveEval = dontSaveEval}+        postponed :: [Binding] -> IO Expression+        postponed known = do+          call <- argued ctx state' site known           fresh <- coined-          firing._saveEval (EvLooped firing._nesting firing._judgment mode before firing._site (Just (fresh, call)))+          ctx._saveEval (EvDeferred ctx._nesting fresh Morphing (ExFormation known) call (ExDispatch site AtPhi))           pure (ExFormation [BiLambda (FnSymbol fresh)])-        argued :: [Binding] -> Expression -> IO Expression-        argued bds (ExApplication target (ArTau attr body)) = do-          target' <- argued bds target-          ExApplication target' . ArTau attr <$> reckoned bds attr body-        argued _ call = pure call-        reckoned :: [Binding] -> Attribute -> Expression -> IO Expression-        reckoned _ _ body@(ExFormation [BiLambda (FnSymbol _)]) = pure body-        reckoned bds attr body = do+    postponing _ _ _ _ = pure Nothing+    argued :: ReduceContext -> State -> Expression -> [Binding] -> IO (Maybe Expression)+    argued ctx state' site bds = traverse go (called bds)+      where+        go :: Expression -> IO Expression+        go (ExApplication target (ArTau attr body)) = do+          target' <- go target+          ExApplication target' . ArTau attr <$> reckoned attr body+        go call = pure call+        reckoned :: Attribute -> Expression -> IO Expression+        reckoned _ body@(ExFormation [BiLambda (FnSymbol _)]) = pure body+        reckoned attr body = do           placed <- ctx._engine._contextualize body (ExFormation (filter (not . named attr) bds))-          fromMaybe body <$> figured ctx entry state' (ExDispatch (ExMeta source._name) attr) placed+          figured ctx state' (ExDispatch site attr) placed     coined :: IO Int     coined = atomicModifyIORef' caller._minted (\count -> (succ count, succ count))-    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+    concluded :: ReduceContext -> Lambda -> [Either Int Bytes] -> Subst -> State -> IO (Answer, State)+    concluded ctx entry operands bound state' = case entry._ending of+      Answering template -> answered ctx template operands bound state'+      Joining on left right fallback -> merged ctx bound (zip (map (_name . fst) entry._dataized) operands) on left right fallback state'+    answered :: ReduceContext -> Expression -> [Either Int Bytes] -> Subst -> State -> IO (Answer, State)+    answered ctx template operands bound state' = do+      (fresh, spent) <- minted template <$> readIORef ctx._minted       writeIORef ctx._minted spent       mapM_ (\idx -> ctx._saveEval (EvMinted ctx._nesting idx operands)) [idx | (_, FnSymbol idx) <- fresh]       symbolic <- foldM mint bound fresh-      built <- buildExpressionThrows entry._answer symbolic+      built <- buildExpressionThrows template symbolic       ctx._saveEval (EvBuilt ctx._nesting built)       (normal, state'') <- settled built univ state' =<< opening Morphing ctx{_saveEval = ctx._saveEval . resited ctx._site built}       ctx._saveEval (EvAnswer ctx._nesting normal)-      pure ((built, normal), state'')+      pure (Answer built normal, state'')+    merged :: ReduceContext -> Subst -> [(T.Text, Either Int Bytes)] -> Either Meta Expression -> Expression -> Expression -> Maybe Function -> State -> IO (Answer, State)+    merged ctx bound operands on left right fallback state' = do+      condition <- weighed operands on+      (one, state'') <- reached ctx bound left state'+      (two, state''') <- reached ctx bound right state''+      spent <- readIORef ctx._minted+      case joined one two spent of+        Right (term, made, spent') -> do+          writeIORef ctx._minted spent'+          mapM_ (\(fresh, pair) -> ctx._saveEval (EvJoined ctx._nesting (either (Left . _spelling) Right on) fresh pair)) made+          ctx._saveEval (EvTaken ctx._nesting [left, right] term)+          pure (Joined (left, right) term, state''')+        Left path -> case maybe (Left Unnamed) (\name -> forked name condition one two) fallback of+          Right term -> do+            ctx._saveEval (EvFabricated ctx._nesting (left, one) (right, two) term)+            pure (Joined (left, right) term, state''')+          Left refusal -> do+            ctx._saveEval (EvDiverged ctx._nesting (left, one) (right, two) path refusal)+            throwIO (Diverged func)+    weighed :: [(T.Text, Either Int Bytes)] -> Either Meta Expression -> IO (Either Int Bytes)+    weighed operands (Left meta) =+      maybe+        (throwIO (userError (printf "The meta '%s' of λ function '%s' is bound by no operand of 'dataize'" (T.unpack meta._spelling) (T.unpack func))))+        pure+        (lookup meta._name operands)+    weighed _ (Right path) = operand path >>= maybe (throwIO (Stuck func)) pure . held+      where+        held :: Expression -> Maybe (Either Int Bytes)+        held term = case walked term of+          Just (ExFormation bds) -> listToMaybe ([Left idx | BiLambda (FnSymbol idx) <- bds] ++ [Right bytes | BiDelta bytes <- bds])+          _ -> Nothing+        walked :: Expression -> Maybe Expression+        walked (ExDispatch base attr) = case walked base of+          Just (ExFormation bds) -> listToMaybe [term | BiTau attr' term <- bds, attr' == attr]+          _ -> Nothing+        walked term = Just term+    reached :: ReduceContext -> Subst -> Expression -> State -> IO (Expression, State)+    reached _ bound (ExMeta name) state' = (,state') <$> buildExpressionThrows (ExMeta name) bound+    reached ctx _ side state' = (\placed -> morphing univ ctx placed state') =<< operand side     mint :: Subst -> (Slot, Function) -> IO Subst     mint bound (slot, fresh) = case combine (substSlot slot (MvFunction fresh)) bound of       Just bound' -> pure bound'@@ -461,33 +366,3 @@       Nothing ->         throwIO           (userError (printf "The meta '%s' of λ function '%s' clashes with an existing binding" (T.unpack meta._spelling) (T.unpack func)))--rewritten :: [Y.Rule] -> (Maybe Expression -> RuleContext) -> Expression -> IO Expression-rewritten rules ctx = goExpr Nothing-  where-    goExpr :: Maybe Expression -> Expression -> IO Expression-    goExpr around expr = goRules rules-      where-        goRules :: [Y.Rule] -> IO Expression-        goRules [] = inside around expr-        goRules (rule : rest) = do-          substs <- matchExpressionWithRule' [substEmpty] expr rule (ctx (enclosing expr))-          case substs of-            subst : _ -> buildExpressionThrows rule.result subst-            [] -> goRules rest-        enclosing :: Expression -> Maybe Expression-        enclosing form@(ExFormation _) = Just form-        enclosing _ = around-    inside :: Maybe Expression -> Expression -> IO Expression-    inside _ form@(ExFormation bds) = ExFormation <$> mapM (goBinding (Just form)) bds-    inside around (ExApplication expr arg) = ExApplication <$> goExpr around expr <*> goArgument around arg-    inside around (ExDispatch expr attr) = (`ExDispatch` attr) <$> goExpr around expr-    inside around (ExPhiMeet prefix idx expr) = ExPhiMeet prefix idx <$> goExpr around expr-    inside around (ExPhiAgain prefix idx expr) = ExPhiAgain prefix idx <$> goExpr around expr-    inside _ expr = pure expr-    goBinding :: Maybe Expression -> Binding -> IO Binding-    goBinding around (BiTau attr expr) = BiTau attr <$> goExpr around expr-    goBinding _ bd = pure bd-    goArgument :: Maybe Expression -> Argument -> IO Argument-    goArgument around (ArTau attr expr) = ArTau attr <$> goExpr around expr-    goArgument around (ArAlpha alpha expr) = ArAlpha alpha <$> goExpr around expr
src/Filter.hs view
@@ -7,17 +7,21 @@  import AST import Control.Exception (throwIO)+import Data.Maybe (fromMaybe) import Locator (LocatorException (CanNotFindObjectByLocator, CanNotHideWholeProgram, InvalidLocatorProvided)) import Misc import Rewriter  exclude' :: Expression -> [Expression] -> IO Expression-exclude' expr [] = pure expr-exclude' _ (ExRoot : _) = throwIO (CanNotHideWholeProgram ExRoot)-exclude' expr (fqn : remaining) = case fqnToAttrs fqn of-  Just attrs@(_ : _) -> maybe (throwIO (CanNotFindObjectByLocator fqn)) (`exclude'` remaining) (excludedFormation expr attrs)-  _ -> throwIO (InvalidLocatorProvided fqn)+exclude' expr fqns = foldl hidden expr <$> traverse located fqns   where+    located :: Expression -> IO [Attribute]+    located ExRoot = throwIO (CanNotHideWholeProgram ExRoot)+    located fqn = case fqnToAttrs fqn of+      Just attrs@(_ : _) -> maybe (throwIO (CanNotFindObjectByLocator fqn)) (const (pure attrs)) (excludedFormation expr attrs)+      _ -> throwIO (InvalidLocatorProvided fqn)+    hidden :: Expression -> [Attribute] -> Expression+    hidden current attrs = fromMaybe current (excludedFormation current attrs)     excludedFormation :: Expression -> [Attribute] -> Maybe Expression     excludedFormation (ExFormation bindings) [at]       | any ((== Just at) . attributeFromBinding) bindings = Just (ExFormation [bd | bd <- bindings, attributeFromBinding bd /= Just at])
+ src/Forks.hs view
@@ -0,0 +1,104 @@+{-# LANGUAGE OverloadedStrings #-}++-- SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com+-- SPDX-License-Identifier: MIT++module Forks (Refusal (..), Side (..), forked, joined) where++import AST (Argument (..), Attribute (AtDelta, AtLabel, AtLambda, AtRho), Binding (..), Bytes, Expression (ExApplication, ExDispatch, ExFormation, ExPhiAgain, ExPhiMeet, ExXi), Function (FnSymbol), attributeFromBinding)+import Data.Bifunctor (bimap, first)+import Data.List (nub)+import Data.Map.Strict (Map)+import qualified Data.Map.Strict as Map+import Data.Maybe (fromMaybe, listToMaybe, mapMaybe)+import Data.Text (Text)+import qualified Data.Text as T++type Joining = (Int, Map (Int, Int) Int, [(Int, (Int, Int))])++data Side = Lefthand | Righthand+  deriving (Eq, Show)++data Refusal+  = Unnamed+  | Unformed Side+  | Holding Side Attribute+  deriving (Eq, Show)++joined :: Expression -> Expression -> Int -> Either [Attribute] (Expression, [(Int, (Int, Int))], Int)+joined left right spent = (\(term, (spent', _, made)) -> (term, reverse made, spent')) <$> goExpr left right (spent, Map.empty, [])+  where+    goExpr :: Expression -> Expression -> Joining -> Either [Attribute] (Expression, Joining)+    goExpr (ExFormation one) (ExFormation two) joining = first ExFormation <$> goBindings one two joining+    goExpr (ExApplication one arg) (ExApplication two arg') joining = within $ do+      (target, joining') <- goExpr one two joining+      first (ExApplication target) <$> goArgument arg arg' joining'+    goExpr (ExDispatch one attr) (ExDispatch two attr') joining+      | attr == attr' = within (first (`ExDispatch` attr) <$> goExpr one two joining)+    goExpr (ExPhiMeet prefix idx one) (ExPhiMeet prefix' idx' two) joining+      | prefix == prefix' && idx == idx' = within (first (ExPhiMeet prefix idx) <$> goExpr one two joining)+    goExpr (ExPhiAgain prefix idx one) (ExPhiAgain prefix' idx' two) joining+      | prefix == prefix' && idx == idx' = within (first (ExPhiAgain prefix idx) <$> goExpr one two joining)+    goExpr one two joining = same one two joining+    goBinding :: Binding -> Binding -> Joining -> Either [Attribute] (Binding, Joining)+    goBinding (BiTau attr one) (BiTau attr' two) joining+      | attr == attr' = bimap (attr :) (first (BiTau attr)) (goExpr one two joining)+    goBinding (BiLambda (FnSymbol one)) (BiLambda (FnSymbol two)) joining+      | one /= two = Right (first (BiLambda . FnSymbol) (picked (one, two) joining))+    goBinding one two joining = first (const (maybe [] pure (attributeFromBinding one))) (same one two joining)+    goArgument :: Argument -> Argument -> Joining -> Either [Attribute] (Argument, Joining)+    goArgument (ArTau attr one) (ArTau attr' two) joining+      | attr == attr' = first (ArTau attr) <$> goExpr one two joining+    goArgument (ArAlpha alpha one) (ArAlpha alpha' two) joining+      | alpha == alpha' = first (ArAlpha alpha) <$> goExpr one two joining+    goArgument one two joining = same one two joining+    goBindings :: [Binding] -> [Binding] -> Joining -> Either [Attribute] ([Binding], Joining)+    goBindings [] [] joining = Right ([], joining)+    goBindings (one : ones) (two : twos) joining+      | attributeFromBinding one == attributeFromBinding two = do+          (item, joining') <- goBinding one two joining+          first (item :) <$> goBindings ones twos joining'+    goBindings ones twos _ = Left (take 1 (absent ones twos ++ absent twos ones ++ mapMaybe attributeFromBinding ones))+    absent :: [Binding] -> [Binding] -> [Attribute]+    absent bds others = [attr | Just attr <- map attributeFromBinding (take 1 bds), attr `notElem` mapMaybe attributeFromBinding others]+    within :: Either [Attribute] (Expression, Joining) -> Either [Attribute] (Expression, Joining)+    within = first (const [])+    same :: (Eq item) => item -> item -> Joining -> Either [Attribute] (item, Joining)+    same one two joining+      | one == two = Right (one, joining)+      | otherwise = Left []+    picked :: (Int, Int) -> Joining -> (Int, Joining)+    picked pair joining@(spent', names, made) = case Map.lookup pair names of+      Just name -> (name, joining)+      Nothing -> (succ spent', (succ spent', Map.insert pair (succ spent') names, (succ spent', pair) : made))++forked :: Function -> Either Int Bytes -> Expression -> Expression -> Either Refusal Expression+forked func condition left right = fabricated <$> formed Lefthand left <*> formed Righthand right+  where+    formed :: Side -> Expression -> Either Refusal [Binding]+    formed side (ExFormation bds) = maybe (Right bds) (Left . Holding side) (listToMaybe [attr | Just attr <- map attributeFromBinding bds, attr == AtDelta || attr == AtLambda])+    formed side _ = Left (Unformed side)+    fabricated :: [Binding] -> [Binding] -> Expression+    fabricated one two = ExFormation (map BiVoid voids ++ [copy "left🌵" left one, copy "right🌵" right two] ++ map branch (filter (`notElem` voids) (nub (attributes one ++ attributes two))))+      where+        voids :: [Attribute]+        voids = nub (vacant one ++ vacant two)+        branch :: Attribute -> Binding+        branch attr = BiTau attr (ExFormation ([BiVoid AtRho, BiLambda func] ++ map BiVoid (inner attr) ++ [BiTau (AtLabel "on🌵") (ExFormation [either (BiLambda . FnSymbol) BiDelta condition]), ask "left🌵" one attr, ask "right🌵" two attr]))+        inner :: Attribute -> [Attribute]+        inner attr = nub (concat (mapMaybe (`held` attr) [one, two]))+        ask :: Text -> [Binding] -> Attribute -> Binding+        ask name bds attr = BiTau (AtLabel name) (applied (ExDispatch (ExDispatch (ExDispatch ExXi AtRho) (AtLabel name)) attr) (fromMaybe (inner attr) (held bds attr)))+    vacant :: [Binding] -> [Attribute]+    vacant bds = [attr | BiVoid attr <- bds, own attr]+    attributes :: [Binding] -> [Attribute]+    attributes bds = [attr | Just attr <- map attributeFromBinding bds, own attr]+    own :: Attribute -> Bool+    own (AtLabel label) = not ("🌵" `T.isSuffixOf` label)+    own attr = attr `notElem` [AtRho, AtLambda, AtDelta]+    copy :: Text -> Expression -> [Binding] -> Binding+    copy name side bds = BiTau (AtLabel name) (applied side (vacant bds))+    applied :: Expression -> [Attribute] -> Expression+    applied = foldl (\term attr -> ExApplication term (ArTau attr (ExDispatch ExXi attr)))+    held :: [Binding] -> Attribute -> Maybe [Attribute]+    held bds attr = listToMaybe [vacant inside | BiTau label (ExFormation inside) <- bds, label == attr]
src/Inference.hs view
@@ -56,7 +56,7 @@   Just concl@(Y.Premise _ (Y.OpDataize arg universe)) -> case producer arg rule.premises of     Just normal@(Y.Premise _ (Y.OpNormalize inner)) ->       let side = rule.premises `excluding` [concl, normal]-       in Right (side, Onward (Normalized (labelled (Dataization, "") side)) inner universe)+       in Right (side, Onward (Normalized (labelled (Dataization, rule.name) side)) inner universe)     Just morphed@(Y.Premise _ (Y.OpMorph inner scene)) ->       Right (rule.premises `excluding` [concl, morphed], Onward (Staged scene) inner universe)     _ ->
src/Lambdas.hs view
@@ -7,33 +7,34 @@ -- SPDX-License-Identifier: MIT  module Lambdas-  ( Lambda (..)+  ( Ending (..)+  , Lambda (..)   , LambdaException (..)   , Lambdas   , Line (..)   , Meta (..)   , emptyLambdas-  , joined   , matched-  , message   , minted   , readLambdas+  , rewritten   , stayed   , symbolized   , taken-  , thrown-  , upcasted   ) where  import AST-import Control.Applicative ((<|>))+import Builder (buildExpressionThrows) import Control.Exception (Exception, throwIO)-import Data.Aeson (FromJSON (parseJSON), Key, Object, Value (Object, String), withObject, (.!=), (.:), (.:?))+import Control.Monad.Trans.State.Strict (State, runState, state)+import Data.Aeson (FromJSON (parseJSON), Key, Object, withObject, (.!=), (.:?)) import qualified Data.Aeson.Key as Key import qualified Data.Aeson.KeyMap as KeyMap+import Data.Aeson.Types (JSONPathElement (Key), (<?>)) import Data.Char (isDigit)-import Data.List (find, nub, sortOn)+import Data.Either (partitionEithers)+import Data.List (find, sortOn) import Data.Map.Strict (Map) import qualified Data.Map.Strict as Map import Data.Maybe (fromMaybe, listToMaybe)@@ -43,8 +44,10 @@ import qualified Data.Yaml as Yaml import Language (Language, language, shared) import Logger (logDebug)+import Matcher (substEmpty) import Metas (Metas (metas))-import Parser (parseBytes, parseExpression)+import Parser (parseBinding, parseBytes, parseExpression)+import Rule (RuleContext, matchExpressionWithRule') import Slots (Slots (slots)) import Text.Printf (printf) import Text.Regex.PCRE (matchTest)@@ -58,22 +61,23 @@   }  data Line-  = Evaluated Meta Meta-  | Unfolded Meta Meta Bool-  | Rewritten Meta Meta [Y.Rule]+  = Morphed Meta Meta+  | Evaluated Meta Meta+  | Deferred Meta Meta   | Stayed Meta Meta+  | Rewritten Meta Meta [Y.Rule]   | Symbolized Meta Meta-  | Upcasted Meta Meta Meta Meta-  | Paired Meta Meta Meta +data Ending+  = Answering Expression+  | Joining (Either Meta Expression) Expression Expression (Maybe Function)+ data Lambda = Lambda   { _key :: Text   , _dataized :: [(Meta, Expression)]   , _morphed :: [(Meta, Expression)]   , _lines :: [Line]-  , _lenient :: Bool-  , _terminators :: [Text]-  , _answer :: Expression+  , _ending :: Ending   }  newtype Lambdas = Lambdas [(Regex, Lambda)]@@ -89,23 +93,18 @@ instance FromJSON Lambda where   parseJSON = withObject "Lambda" $ \entry -> do     key <- entry .:? "λ" >>= maybe (fail "The entry has no 'λ' key") pure-    answer <- entry .:? "𝑛" >>= maybe (fail "The entry has no '𝑛' key") pure     unknown (T.unpack key) entry     dataized <- operands key bytesMeta entry "dataize"-    morphed <- operands key expressionMeta entry "morph"+    ending <- concluded (T.unpack key) dataized entry+    (morphed, reading) <- partitionEithers <$> reductions key entry     lambda <-       Lambda key dataized morphed-        <$> (written (T.unpack key) entry >>= ordered (T.unpack key) (map (_name . fst) morphed))-        <*> lenient (T.unpack key) entry-        <*> entry .:? "terminators" .!= []-        <*> pure answer-    sigmas (T.unpack key) lambda._answer-    dataless (T.unpack key) lambda._answer+        <$> (written (T.unpack key) entry >>= ordered (T.unpack key) (map (_name . fst) morphed) . (reading ++))+        <*> pure ending     once (T.unpack key) lambda-    mapM_ (metaless (T.unpack key) "dataize") lambda._dataized-    mapM_ (metaless (T.unpack key) "morph") lambda._morphed+    mapM_ (metaless (T.unpack key) "dataize" "a path from '$'") lambda._dataized+    mapM_ (metaless (T.unpack key) "morph" "a path from '$' or a meta alone") lambda._morphed     answered (T.unpack key) lambda-    lonely (T.unpack key) lambda     pure lambda     where       unknown :: String -> Object -> Yaml.Parser ()@@ -114,7 +113,44 @@         name : _ -> fail (printf "The key '%s' of λ function '%s' is none of the keys an entry reads: %s" (T.unpack name) key (T.unpack (T.intercalate ", " known)))         where           known :: [Text]-          known = ["λ", "dataize", "morph", "evaluate", "unfold", "rewrite", "stay", "symbolize", "upcast", "join", "lenient", "terminators", "𝑛"]+          known = ["λ", "dataize", "morph", "evaluate", "defer", "stay", "rewrite", "symbolize", "join", "𝑛"]+      concluded :: String -> [(Meta, Expression)] -> Object -> Yaml.Parser Ending+      concluded key dataized entry = do+        answer <- entry .:? "𝑛"+        block <- entry .:? "join"+        case (answer, block) of+          (Nothing, Nothing) -> fail (printf "The entry of λ function '%s' has neither an '𝑛' key nor a 'join' block" key)+          (Just _, Just _) -> fail (printf "The entry of λ function '%s' has both an '𝑛' key and a 'join' block, while a join answers the term it joins" key)+          (Just term, Nothing) -> Answering term <$ sigmas key term <* dataless key term+          (Nothing, Just joint) -> joining key dataized joint+      joining :: String -> [(Meta, Expression)] -> Object -> Yaml.Parser Ending+      joining key dataized block = do+        case filter (`notElem` ["on", "left", "right", "otherwise"]) (map Key.toText (KeyMap.keys block)) of+          [] -> pure ()+          name : _ -> fail (printf "The key '%s' of the 'join' block of λ function '%s' is none of its keys: on, left, right, otherwise" (T.unpack name) key)+        on <- required "on" >>= condition+        Joining on <$> (required "left" >>= side "left") <*> (required "right" >>= side "right") <*> (block .:? "otherwise" >>= traverse fallback)+        where+          required :: FromJSON value => Key -> Yaml.Parser value+          required name = block .:? name >>= maybe (fail (printf "The 'join' block of λ function '%s' has no '%s' key" key (Key.toString name))) pure+          condition :: Text -> Yaml.Parser (Either Meta Expression)+          condition spelling = case (parseBytes (T.unpack spelling), parseExpression (T.unpack spelling)) of+            (Right (BtMeta name), _) | name `elem` map (_name . fst) dataized -> pure (Left (Meta spelling name))+            (_, Right path) | pathed path -> pure (Right path)+            _ -> fail (printf "The 'on' of the 'join' block of λ function '%s' is '%s', while it must name a bytes meta of 'dataize', such as '𝛿1', or be a path from '$', such as '$.on'" key (T.unpack spelling))+          pathed :: Expression -> Bool+          pathed ExXi = True+          pathed (ExDispatch inner _) = pathed inner+          pathed _ = False+          side :: String -> Expression -> Yaml.Parser Expression+          side _ term@(ExMeta _) = pure term+          side name term = case metas term of+            [] -> pure term+            meta : _ -> fail (printf "The side '%s' of the 'join' block of λ function '%s' reads the meta '%s' inside a term, while a side is a path from '$' or a meta alone" name key (T.unpack meta))+          fallback :: Text -> Yaml.Parser Function+          fallback spelling = case parseBinding (printf "λ ⤍ %s" (T.unpack spelling)) of+            Right (BiLambda func@(Function _)) -> pure func+            _ -> fail (printf "The 'otherwise' of the 'join' block of λ function '%s' is '%s', while it must name a λ function, such as 'L_join'" key (T.unpack spelling))       operands :: Text -> (Text -> Yaml.Parser Meta) -> Object -> Key -> Yaml.Parser [(Meta, Expression)]       operands key kind entry name = do         mapping <- entry .:? name .!= (Map.empty :: Map Text Expression)@@ -124,6 +160,18 @@           bound (meta, term) = do             referenceless (T.unpack key) (T.unpack meta) term             kind meta >>= \named -> pure (named, term)+      reductions :: Text -> Object -> Yaml.Parser [Either (Meta, Expression) Line]+      reductions key entry = do+        mapping <- entry .:? "morph" .!= (Map.empty :: Map Text Text)+        mapM reduction (numbered mapping)+        where+          reduction :: (Text, Text) -> Yaml.Parser (Either (Meta, Expression) Line)+          reduction (meta, source) = do+            named <- expressionMeta meta+            case parseExpression (T.unpack source) of+              Right (ExMeta name) -> pure (Right (Morphed named (Meta source name)))+              Right term -> Left (named, term) <$ referenceless (T.unpack key) (T.unpack meta) term+              Left failure -> fail failure <?> Key (Key.fromText meta) <?> Key "morph"       expressionMeta :: Text -> Yaml.Parser Meta       expressionMeta meta = case parseExpression (T.unpack meta) of         Right (ExMeta name) -> pure (Meta meta name)@@ -132,81 +180,12 @@       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))-      lenient :: String -> Object -> Yaml.Parser Bool-      lenient key entry = entry .:? "lenient" >>= maybe (pure False) (\names -> True <$ mapM_ (option key) (names :: [Text]))-      option :: String -> Text -> Yaml.Parser ()-      option key "defer" = fail (printf "The option 'defer' of 'lenient' of λ function '%s' is gone, while an 'unfold' line saying 'defer: true' does its work" key)-      option key "upcast" = fail (printf "The option 'upcast' of 'lenient' of λ function '%s' is gone, while an 'upcast' block does its work" key)-      option key name = fail (printf "The option '%s' of 'lenient' of λ function '%s' is unknown, while the list takes no option" (T.unpack name) key)       written :: String -> Object -> Yaml.Parser [Line]-      written key entry =-        concat-          <$> sequence-            [ links Evaluated entry "evaluate"-            , unfolds key entry-            , rewrites key entry-            , links Stayed entry "stay"-            , links Symbolized entry "symbolize"-            , casts key entry-            , pairs key entry-            ]+      written key entry = concat <$> sequence [links Evaluated entry "evaluate", links Deferred entry "defer", links Stayed entry "stay", rewrites key entry, links Symbolized entry "symbolize"]       links :: (Meta -> Meta -> Line) -> Object -> Key -> Yaml.Parser [Line]       links kind entry name = do         mapping <- entry .:? name .!= (Map.empty :: Map Text Text)         mapM (\(meta, source) -> kind <$> expressionMeta meta <*> expressionMeta source) (numbered mapping)-      unfolds :: String -> Object -> Yaml.Parser [Line]-      unfolds key entry = do-        mapping <- entry .:? "unfold" .!= (Map.empty :: Map Text Value)-        mapM line (numbered mapping)-        where-          line :: (Text, Value) -> Yaml.Parser Line-          line (meta, String source) = Unfolded <$> expressionMeta meta <*> expressionMeta source <*> pure False-          line (meta, Object body) = Unfolded <$> expressionMeta meta <*> (body .: "of" >>= expressionMeta) <*> body .:? "defer" .!= False-          line (meta, _) =-            fail-              ( printf-                  "The line '%s' of 'unfold' of λ function '%s' names neither a meta nor a mapping with 'of', such as '𝑛2: 𝑛1' or '𝑛2: {of: 𝑛1, defer: true}'"-                  (T.unpack meta)-                  key-              )-      casts :: String -> Object -> Yaml.Parser [Line]-      casts key entry = do-        mapping <- entry .:? "upcast" .!= (Map.empty :: Map Text Value)-        case numbered mapping of-          [] -> pure []-          [(one, first), (two, second)] -> (: []) <$> (Upcasted <$> expressionMeta one <*> plain one first <*> expressionMeta two <*> plain two second)-          lines' ->-            fail-              ( printf-                  "The 'upcast' block of λ function '%s' holds %d %s, while it casts one pair and takes exactly two"-                  key-                  (length lines')-                  (if length lines' == 1 then "line" else "lines" :: String)-              )-        where-          plain :: Text -> Value -> Yaml.Parser Meta-          plain _ (String source) = expressionMeta source-          plain meta _ =-            fail-              ( printf-                  "The line '%s' of 'upcast' of λ function '%s' names no meta, such as '𝑛3: 𝑛1', while the block itself finds which of its two terms to cast"-                  (T.unpack meta)-                  key-              )-      pairs :: String -> Object -> Yaml.Parser [Line]-      pairs key entry = do-        mapping <- entry .:? "join" .!= (Map.empty :: Map Text [Text])-        mapM joins (numbered mapping)-        where-          joins :: (Text, [Text]) -> Yaml.Parser Line-          joins (meta, [left, right]) = Paired <$> expressionMeta meta <*> expressionMeta left <*> expressionMeta right-          joins (meta, _) =-            fail-              ( printf-                  "The operand '%s' of λ function '%s' must join exactly two metas, such as '[𝑛1, 𝑛2]'"-                  (T.unpack meta)-                  key-              )       rewrites :: String -> Object -> Yaml.Parser [Line]       rewrites key entry = do         mapping <- entry .:? "rewrite" .!= (Map.empty :: Map Text Object)@@ -214,41 +193,23 @@         where           line :: (Text, Object) -> Yaml.Parser Line           line (meta, body) = do-            named <- expressionMeta meta-            source <- body .: "of" >>= expressionMeta-            listed <- body .: "rules"-            Rewritten named source <$> mapM rule listed-          rule :: Object -> Yaml.Parser Y.Rule-          rule body = do-            result <- body .: "result"-            symbolless result-            parsed <- parseJSON (Object body)-            bound parsed-            pure parsed-          symbolless :: Expression -> Yaml.Parser ()-          symbolless result-            | null (symbols result) && null [kind | Slot kind _ <- slots result, kind == "S"] = pure ()-            | otherwise = fail (printf "A rule of the 'rewrite' block of λ function '%s' writes a symbol 𝜎 into its result" key)-          bound :: Y.Rule -> Yaml.Parser ()-          bound parsed = case filter (`notElem` known) (metas parsed.result) of-            [] -> pure ()-            meta : _ ->-              fail-                ( printf-                    "The rule '%s' of the 'rewrite' block of λ function '%s' reads the meta '%s' it never binds"-                    parsed.name-                    key-                    (T.unpack meta)-                )+            case filter (`notElem` ["of", "rules"]) (map Key.toText (KeyMap.keys body)) of+              [] -> pure ()+              name : _ -> fail (printf "The key '%s' of the line '%s' of the 'rewrite' block of λ function '%s' is none of its keys: of, rules" (T.unpack name) (T.unpack meta) key)+            Rewritten <$> expressionMeta meta <*> (required "of" >>= expressionMeta) <*> (required "rules" >>= mapM symbolless)             where-              known :: [Text]-              known = metas parsed.pattern ++ concatMap (metas . (.meta)) (concat parsed.where_)+              required :: FromJSON value => Key -> Yaml.Parser value+              required name = body .:? name >>= maybe (fail (printf "The line '%s' of the 'rewrite' block of λ function '%s' has no '%s' key" (T.unpack meta) key (Key.toString name))) pure+          symbolless :: Y.Rule -> Yaml.Parser Y.Rule+          symbolless rule = case symbols rule.result of+            [] -> pure rule+            idx : _ -> fail (printf "The rule '%s' of the 'rewrite' block of λ function '%s' writes the symbol '𝜎%d' into its result, while only a firing mints a symbol" rule.name key idx)       ordered :: String -> [Text] -> [Line] -> Yaml.Parser [Line]-      ordered key reduced waiting = mapM_ bound (concatMap sources waiting) >> go reduced waiting+      ordered key reduced waiting = mapM_ (bound . source) waiting >> go reduced waiting         where           bound :: Meta -> Yaml.Parser ()           bound meta-            | meta._name `elem` reduced ++ map _name (concatMap bindings waiting) = pure ()+            | meta._name `elem` reduced ++ map (_name . binding) waiting = pure ()             | otherwise =                 fail                   ( printf@@ -258,8 +219,8 @@                   )           go :: [Text] -> [Line] -> Yaml.Parser [Line]           go _ [] = pure []-          go known pending@(first : _) = case break (all ((`elem` known) . _name) . sources) pending of-            (before, line : after) -> (line :) <$> go (map _name (bindings line) ++ known) (before ++ after)+          go known pending@(first : _) = case break ((`elem` known) . _name . source) pending of+            (before, line : after) -> (line :) <$> go ((binding line)._name : known) (before ++ after)             (_, []) ->               fail                 ( printf@@ -273,26 +234,21 @@                 | meta._name `elem` seen = meta                 | otherwise = goCircle (meta._name : seen) (fromMaybe meta (listToMaybe (feeding meta)))               feeding :: Meta -> [Meta]-              feeding meta = [source | line <- pending, meta._name `elem` map _name (bindings line), source <- sources line, source._name `notElem` known]-          sources :: Line -> [Meta]-          sources (Evaluated _ source) = [source]-          sources (Unfolded _ source _) = [source]-          sources (Rewritten _ source _) = [source]-          sources (Stayed _ source) = [source]-          sources (Symbolized _ source) = [source]-          sources (Upcasted _ first _ second) = [first, second]-          sources (Paired _ left right) = [left, right]+              feeding meta = [source line | line <- pending, meta._name == (binding line)._name, (source line)._name `notElem` known]+          source :: Line -> Meta+          source (Morphed _ meta) = meta+          source (Evaluated _ meta) = meta+          source (Deferred _ meta) = meta+          source (Stayed _ meta) = meta+          source (Rewritten _ meta _) = meta+          source (Symbolized _ meta) = meta       binding :: Line -> Meta+      binding (Morphed meta _) = meta       binding (Evaluated meta _) = meta-      binding (Unfolded meta _ _) = meta-      binding (Rewritten meta _ _) = meta+      binding (Deferred meta _) = meta       binding (Stayed meta _) = meta+      binding (Rewritten meta _ _) = meta       binding (Symbolized meta _) = meta-      binding (Upcasted meta _ _ _) = meta-      binding (Paired meta _ _) = meta-      bindings :: Line -> [Meta]-      bindings (Upcasted one _ two _) = [one, two]-      bindings line = [binding line]       sigmas :: String -> Expression -> Yaml.Parser ()       sigmas key answer = case ([kind | Slot kind _ <- slots answer, kind /= "S"], symbols answer) of         ([], []) -> pure ()@@ -301,42 +257,43 @@       once :: String -> Lambda -> Yaml.Parser ()       once key lambda = case twice [] bound of         Nothing -> pure ()-        Just meta -> fail (printf "The meta '%s' of λ function '%s' is bound by more than one line, while each meta may be bound once" (T.unpack meta) key)+        Just meta -> fail (printf "The meta '%s' of λ function '%s' is bound by more than one line, while each meta may be bound once" (T.unpack meta._spelling) key)         where-          bound :: [Text]-          bound =-            map (_spelling . fst) lambda._morphed ++ map _spelling (concatMap bindings lambda._lines)-          twice :: [Text] -> [Text] -> Maybe Text+          bound :: [Meta]+          bound = map fst lambda._morphed ++ map binding lambda._lines+          twice :: [Text] -> [Meta] -> Maybe Meta           twice _ [] = Nothing           twice seen (meta : rest)-            | meta `elem` seen = Just meta-            | otherwise = twice (meta : seen) rest-      metaless :: String -> String -> (Meta, Expression) -> Yaml.Parser ()-      metaless key block (meta, term) = case metas term of+            | meta._name `elem` seen = Just meta+            | otherwise = twice (meta._name : seen) rest+      metaless :: String -> String -> String -> (Meta, Expression) -> Yaml.Parser ()+      metaless key block reducible (meta, term) = case metas term of         [] -> pure ()         name : _ ->           fail             ( printf-                "The operand '%s' of '%s' of λ function '%s' reads the meta '%s', while only a path from '$' can be reduced there"+                "The operand '%s' of '%s' of λ function '%s' reads the meta '%s', while only %s can be reduced there"                 (T.unpack meta._spelling)                 block                 key                 (T.unpack name)+                reducible             )       answered :: String -> Lambda -> Yaml.Parser ()-      answered key lambda = case filter (`notElem` ("S" : known)) (metas lambda._answer) of-        [] -> pure ()-        name : _ -> fail (printf "The '𝑛' of λ function '%s' reads the meta '%s' that no block binds" key (T.unpack name))+      answered key lambda = case lambda._ending of+        Joining _ left right _ -> sided "left" left >> sided "right" right+        Answering term -> case filter (`notElem` ("S" : known)) (metas term) of+          [] -> pure ()+          name : _ -> fail (printf "The '𝑛' of λ function '%s' reads the meta '%s' that no block binds" key (T.unpack name))         where           known :: [Text]-          known =-            map (_name . fst) lambda._dataized-              ++ map (_name . fst) lambda._morphed-              ++ map (_name . binding) lambda._lines-      lonely :: String -> Lambda -> Yaml.Parser ()-      lonely key lambda-        | lambda._lenient && null [meta | Paired meta _ _ <- lambda._lines] = fail (printf "The λ function '%s' is lenient but has no 'join' line to be lenient in" key)-        | otherwise = pure ()+          known = map (_name . fst) lambda._dataized ++ reduced+          reduced :: [Text]+          reduced = map (_name . fst) lambda._morphed ++ map (_name . binding) lambda._lines+          sided :: String -> Expression -> Yaml.Parser ()+          sided name side = case filter (`notElem` reduced) (metas side) of+            [] -> pure ()+            meta : _ -> fail (printf "The side '%s' of the 'join' block of λ function '%s' reads the meta '%s' that none of 'morph', 'evaluate', 'stay', 'rewrite', 'symbolize' and 'defer' binds" name key (T.unpack meta))       dataless :: String -> Expression -> Yaml.Parser ()       dataless key answer         | computes answer = fail (printf "The '𝑛' of λ function '%s' reads data, while a symbolic answer may mention nothing but 𝜎" key)@@ -382,7 +339,6 @@   mapM_ (unique entries) entries   registered <- mapM keyed entries   overlaps path registered-  mapM_ (unanswered registered) entries   logDebug (printf "Loaded %d λ function(s) from '%s'" (length entries) path)   pure (Lambdas registered)   where@@ -399,16 +355,6 @@     unreadable :: Text -> (a, String) -> IO b     unreadable key (_, failure) =       throwIO (BrokenLambdas path (printf "the key '%s' is not a regular expression: %s" (T.unpack key) failure))-    unanswered :: [(Regex, Lambda)] -> Lambda -> IO ()-    unanswered registered entry = case [(name, other) | name <- entry._terminators, Just other <- [matched (Lambdas registered) name]] of-      [] -> pure ()-      (name, other) : _ ->-        throwIO-          ( BrokenLambdas-              path-              (printf "the terminator '%s' of the key '%s' is answered by the key '%s'" (T.unpack name) (T.unpack entry._key) (T.unpack other._key))-          )-     overlaps :: FilePath -> [(Regex, Lambda)] -> IO ()     overlaps _ [_] = pure ()     overlaps file registered = mapM (spoken . snd) registered >>= check@@ -436,23 +382,15 @@ matched :: Lambdas -> Text -> Maybe Lambda matched (Lambdas entries) func = snd <$> find (\(key, _) -> matchTest key (encodeUtf8 func)) entries -thrown :: [Text] -> Expression -> Bool-thrown names (ExApplication expr _) = thrown names expr-thrown names (ExFormation bds) = any (`elem` [BiLambda (Function name) | name <- names]) bds-thrown _ _ = False--message :: Expression -> Maybe Expression-message (ExApplication _ (ArTau (AtLabel "message") body)) = Just body-message (ExApplication expr _) = message expr-message (ExFormation bds) = listToMaybe [body | BiTau (AtLabel "message") body <- bds]-message _ = Nothing- minted :: Expression -> Int -> ([(Slot, Function)], Int) minted answer spent = (zip fresh [FnSymbol idx | idx <- [spent + 1 ..]], spent + length fresh)   where     fresh :: [Slot]     fresh = [slot | slot@(Slot kind _) <- slots answer, kind == "S"] +taken :: Expression -> Int+taken program = maximum (0 : symbols program)+ stayed :: Expression -> Expression stayed = goExpr   where@@ -476,219 +414,49 @@     dispatched (ExApplication expr _) = dispatched expr     dispatched _ = False +rewritten :: RuleContext -> [Y.Rule] -> Expression -> IO Expression+rewritten ctx rules = goExpr+  where+    goExpr :: Expression -> IO Expression+    goExpr expr = goRules expr rules+    goRules :: Expression -> [Y.Rule] -> IO Expression+    goRules expr [] = goInside expr+    goRules expr (rule : rest) = do+      substs <- matchExpressionWithRule' [substEmpty] expr rule ctx+      case substs of+        subst : _ -> buildExpressionThrows rule.result subst+        [] -> goRules expr rest+    goInside :: Expression -> IO Expression+    goInside (ExFormation bds) = ExFormation <$> mapM goBinding bds+    goInside (ExApplication expr arg) = ExApplication <$> goExpr expr <*> goArgument arg+    goInside (ExDispatch expr attr) = (`ExDispatch` attr) <$> goExpr expr+    goInside (ExPhiMeet prefix idx expr) = ExPhiMeet prefix idx <$> goExpr expr+    goInside (ExPhiAgain prefix idx expr) = ExPhiAgain prefix idx <$> goExpr expr+    goInside expr = pure expr+    goBinding :: Binding -> IO Binding+    goBinding (BiTau attr expr) = BiTau attr <$> goExpr expr+    goBinding bd = pure bd+    goArgument :: Argument -> IO Argument+    goArgument (ArTau attr expr) = ArTau attr <$> goExpr expr+    goArgument (ArAlpha alpha expr) = ArAlpha alpha <$> goExpr expr+ type Minting = (Int, [(Int, Bytes)])  symbolized :: Expression -> Int -> (Expression, [(Int, Bytes)], Int)-symbolized term spent = case goExpr term (spent, []) of+symbolized term spent = case runState (goExpr term) (spent, []) of   (masked, (spent', known)) -> (masked, reverse known, spent')   where-    goExpr :: Expression -> Minting -> (Expression, Minting)-    goExpr (ExFormation bds) minting =-      let (bds', minting') = goBindings bds minting-       in (ExFormation bds', minting')-    goExpr (ExApplication expr arg) minting =-      let (expr', minting') = goExpr expr minting-          (arg', minting'') = goArgument arg minting'-       in (ExApplication expr' arg', minting'')-    goExpr (ExDispatch expr attr) minting =-      let (expr', minting') = goExpr expr minting-       in (ExDispatch expr' attr, minting')-    goExpr (ExPhiMeet prefix idx expr) minting =-      let (expr', minting') = goExpr expr minting-       in (ExPhiMeet prefix idx expr', minting')-    goExpr (ExPhiAgain prefix idx expr) minting =-      let (expr', minting') = goExpr expr minting-       in (ExPhiAgain prefix idx expr', minting')-    goExpr expr minting = (expr, minting)-    goBindings :: [Binding] -> Minting -> ([Binding], Minting)-    goBindings [] minting = ([], minting)-    goBindings (bd : rest) minting =-      let (bd', minting') = goBinding bd minting-          (rest', minting'') = goBindings rest minting'-       in (bd' : rest', minting'')-    goBinding :: Binding -> Minting -> (Binding, Minting)-    goBinding (BiDelta bts) (spent', known) =-      (BiLambda (FnSymbol fresh), (fresh, (fresh, bts) : known))-      where-        fresh :: Int-        fresh = spent' + 1-    goBinding (BiTau AtPhi expr) minting =-      let (expr', minting') = goExpr expr minting-       in (BiTau AtPhi expr', minting')-    goBinding bd minting = (bd, minting)-    goArgument :: Argument -> Minting -> (Argument, Minting)-    goArgument (ArTau attr expr) minting =-      let (expr', minting') = goExpr expr minting-       in (ArTau attr expr', minting')-    goArgument (ArAlpha alpha expr) minting =-      let (expr', minting') = goExpr expr minting-       in (ArAlpha alpha expr', minting')--type Joining = (Int, Map (Int, Int) Int, [(Int, (Int, Int))])--joined :: Bool -> Expression -> Expression -> Int -> Maybe (Expression, [(Int, (Int, Int))], Int)-joined lenient left right spent = taking <$> goExpr left right (spent, Map.empty, [])-  where-    taking :: (Expression, Joining) -> (Expression, [(Int, (Int, Int))], Int)-    taking (term, (spent', _, made)) = (term, reverse made, spent')-    goExpr :: Expression -> Expression -> Joining -> Maybe (Expression, Joining)-    goExpr one two joining = goAlike one two joining <|> goSole one two joining <|> goDecorated one two joining-    goAlike :: Expression -> Expression -> Joining -> Maybe (Expression, Joining)-    goAlike one@(ExFormation _) two@(ExFormation _) joining-      | bare one /= bare two = do-          mine <- ending one-          theirs <- ending two-          (bd, joining') <- goBinding mine theirs joining-          pure (ExFormation [bd], joining')-    goAlike (ExFormation one) (ExFormation two) joining = do-      (bds, joining') <- goBindings one two joining-      pure (ExFormation bds, joining')-    goAlike (ExApplication one arg) (ExApplication two arg') joining = do-      (expr, joining') <- goExpr one two joining-      (applied, joining'') <- goArgument arg arg' joining'-      pure (ExApplication expr applied, joining'')-    goAlike (ExDispatch one attr) (ExDispatch two attr') joining-      | attr == attr' = do-          (expr, joining') <- goExpr one two joining-          pure (ExDispatch expr attr, joining')-    goAlike (ExPhiMeet prefix idx one) (ExPhiMeet prefix' idx' two) joining-      | prefix == prefix' && idx == idx' = do-          (expr, joining') <- goExpr one two joining-          pure (ExPhiMeet prefix idx expr, joining')-    goAlike (ExPhiAgain prefix idx one) (ExPhiAgain prefix' idx' two) joining-      | prefix == prefix' && idx == idx' = do-          (expr, joining') <- goExpr one two joining-          pure (ExPhiAgain prefix idx expr, joining')-    goAlike one two joining-      | one == two = Just (one, joining)-      | otherwise = Nothing-    goSole :: Expression -> Expression -> Joining -> Maybe (Expression, Joining)-    goSole (ExFormation [BiLambda (FnSymbol mine)]) two joining-      | lenient, [theirs] <- nub (symbols two) = Just (renamed theirs two (picked (mine, theirs) joining))-    goSole one (ExFormation [BiLambda (FnSymbol theirs)]) joining-      | lenient, [mine] <- nub (symbols one) = Just (renamed mine one (picked (mine, theirs) joining))-    goSole _ _ _ = Nothing-    renamed :: Int -> Expression -> (Int, Joining) -> (Expression, Joining)-    renamed old term (fresh, joining) = (lifted (old - 1) (fresh - old) term, joining)-    goDecorated :: Expression -> Expression -> Joining -> Maybe (Expression, Joining)-    goDecorated one two joining-      | bare one || bare two = Nothing-      | lenient && depth one < depth two = peeled (goExpr one) two-      | lenient && depth two < depth one = peeled (`goExpr` two) one-      | otherwise = Nothing-      where-        peeled :: (Expression -> Joining -> Maybe (Expression, Joining)) -> Expression -> Maybe (Expression, Joining)-        peeled pairing decorator = do-          inner <- decorated decorator-          (expr, joining') <- pairing inner joining-          pure (ExFormation [BiTau AtPhi expr], joining')-    goBindings :: [Binding] -> [Binding] -> Joining -> Maybe ([Binding], Joining)-    goBindings [] [] joining = Just ([], joining)-    goBindings (one : rest) (two : rest') joining = do-      (bd, joining') <- goBinding one two joining-      (bds, joining'') <- goBindings rest rest' joining'-      pure (bd : bds, joining'')-    goBindings _ _ _ = Nothing-    goBinding :: Binding -> Binding -> Joining -> Maybe (Binding, Joining)-    goBinding (BiLambda (FnSymbol one)) (BiLambda (FnSymbol two)) joining-      | one /= two = case picked (one, two) joining of-          (fresh, joining') -> Just (BiLambda (FnSymbol fresh), joining')-    goBinding (BiTau AtPhi one) (BiTau AtPhi two) joining = do-      (expr, joining') <- goExpr one two joining-      pure (BiTau AtPhi expr, joining')-    goBinding bd@(BiTau attr _) (BiTau attr' _) joining-      | attr == attr' = Just (bd, joining)-    goBinding one two joining-      | one == two = Just (one, joining)-      | otherwise = Nothing-    bare :: Expression -> Bool-    bare (ExFormation [BiLambda (FnSymbol _)]) = True-    bare _ = False-    ending :: Expression -> Maybe Binding-    ending (ExFormation [bd@(BiLambda (FnSymbol _))]) = Just bd-    ending term = decorated term >>= ending-    decorated :: Expression -> Maybe Expression-    decorated (ExFormation bds) = listToMaybe [body | BiTau AtPhi body <- bds]-    decorated _ = Nothing-    depth :: Expression -> Int-    depth term = maybe 0 (succ . depth) (decorated term)-    goArgument :: Argument -> Argument -> Joining -> Maybe (Argument, Joining)-    goArgument (ArTau attr one) (ArTau attr' two) joining-      | attr == attr' = do-          (expr, joining') <- goExpr one two joining-          pure (ArTau attr expr, joining')-    goArgument (ArAlpha alpha one) (ArAlpha alpha' two) joining-      | alpha == alpha' = do-          (expr, joining') <- goExpr one two joining-          pure (ArAlpha alpha expr, joining')-    goArgument one two joining-      | one == two = Just (one, joining)-      | otherwise = Nothing-    picked :: (Int, Int) -> Joining -> (Int, Joining)-    picked pair joining@(spent', names, made)-      | Just name <- Map.lookup pair names = (name, joining)-      | otherwise = (fresh, (fresh, Map.insert pair fresh names, (fresh, pair) : made))-      where-        fresh :: Int-        fresh = spent' + 1--upcasted :: Expression -> Expression -> Int -> Maybe (Int, Expression, [(Int, [Attribute])], Int)-upcasted (ExFormation [BiLambda (FnSymbol _)]) (ExFormation [BiLambda (FnSymbol _)]) _ = Nothing-upcasted (ExFormation [BiLambda (FnSymbol bare)]) shape spent = do-  paths@(_ : _) <- goReached [] shape-  let fresh = [spent + 1 .. spent + length paths]-  pure (bare, swapped fresh shape, zip fresh (map snd paths), spent + length paths)-  where-    goReached :: [Attribute] -> Expression -> Maybe [(Int, [Attribute])]-    goReached _ term-      | null (symbols term) = Just []-    goReached path (ExFormation bds) = concat <$> mapM goReachedBinding bds-      where-        goReachedBinding :: Binding -> Maybe [(Int, [Attribute])]-        goReachedBinding (BiLambda (FnSymbol idx)) = Just [(idx, path)]-        goReachedBinding (BiTau attr expr) = goReached (path ++ [attr]) expr-        goReachedBinding _ = Just []-    goReached path (ExApplication expr (ArTau attr arg))-      | copied expr = (++) <$> goReached path expr <*> goReached (path ++ [attr]) arg-      where-        copied :: Expression -> Bool-        copied (ExApplication target _) = copied target-        copied target = global target-        global :: Expression -> Bool-        global ExRoot = True-        global (ExDispatch target _) = global target-        global _ = False-    goReached _ _ = Nothing-    swapped :: [Int] -> Expression -> Expression-    swapped fresh term = fst (goSwapped term fresh)-      where-        goSwapped :: Expression -> [Int] -> (Expression, [Int])-        goSwapped (ExFormation bds) rest =-          let (bds', rest') = goSwappedBindings bds rest-           in (ExFormation bds', rest')-        goSwapped (ExApplication expr arg) rest =-          let (expr', rest') = goSwapped expr rest-              (arg', rest'') = goSwappedArgument arg rest'-           in (ExApplication expr' arg', rest'')-        goSwapped expr rest = (expr, rest)-        goSwappedBindings :: [Binding] -> [Int] -> ([Binding], [Int])-        goSwappedBindings [] rest = ([], rest)-        goSwappedBindings (bd : bds) rest =-          let (bd', rest') = goSwappedBinding bd rest-              (bds', rest'') = goSwappedBindings bds rest'-           in (bd' : bds', rest'')-        goSwappedBinding :: Binding -> [Int] -> (Binding, [Int])-        goSwappedBinding (BiLambda (FnSymbol _)) (idx : rest) = (BiLambda (FnSymbol idx), rest)-        goSwappedBinding (BiTau attr expr) rest =-          let (expr', rest') = goSwapped expr rest-           in (BiTau attr expr', rest')-        goSwappedBinding bd rest = (bd, rest)-        goSwappedArgument :: Argument -> [Int] -> (Argument, [Int])-        goSwappedArgument (ArTau attr expr) rest =-          let (expr', rest') = goSwapped expr rest-           in (ArTau attr expr', rest')-        goSwappedArgument arg rest = (arg, rest)-upcasted _ _ _ = Nothing--taken :: Expression -> Int-taken program = maximum (0 : symbols program)+    goExpr :: Expression -> State Minting Expression+    goExpr (ExFormation bds) = ExFormation <$> mapM goBinding bds+    goExpr (ExApplication expr arg) = ExApplication <$> goExpr expr <*> goArgument arg+    goExpr (ExDispatch expr attr) = (`ExDispatch` attr) <$> goExpr expr+    goExpr (ExPhiMeet prefix idx expr) = ExPhiMeet prefix idx <$> goExpr expr+    goExpr (ExPhiAgain prefix idx expr) = ExPhiAgain prefix idx <$> goExpr expr+    goExpr expr = pure expr+    goBinding :: Binding -> State Minting Binding+    goBinding (BiDelta bts) = state (\(count, known) -> (BiLambda (FnSymbol (succ count)), (succ count, (succ count, bts) : known)))+    goBinding (BiTau attr expr) = BiTau attr <$> goExpr expr+    goBinding bd = pure bd+    goArgument :: Argument -> State Minting Argument+    goArgument (ArTau attr expr) = ArTau attr <$> goExpr expr+    goArgument (ArAlpha alpha expr) = ArAlpha alpha <$> goExpr expr
src/Morph.hs view
@@ -10,7 +10,7 @@ -- SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com -- SPDX-License-Identifier: MIT -module Morph (Answer, Deadline (..), Firing (..), Kept (..), ReduceContext (..), ReduceException (..), EvaluationFunc, Memo (..), ReductionFunc, Morphed, Steps (..), Tally (..), admitting, boxed, charged, counted, deeper, emptyState, enter, entering, execBuildTerm, expired, 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 (..), admitting, boxed, charged, counted, deeper, emptyState, ended, enter, entering, execBuildTerm, expired, 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  import AST import Builder (buildExpressionThrows, nameIn)@@ -66,8 +66,14 @@  data Memo = Memo (IORef (Store (Int, Kept))) (IORef (Map.Map Firing Answer)) (IORef Int) (IORef Int) -type Answer = (Expression, Expression)+data Answer+  = Answer Expression Expression+  | Joined (Expression, Expression) Expression +ended :: Answer -> Expression+ended (Answer _ normal) = normal+ended (Joined _ normal) = normal+ data Firing = Firing T.Text [Either Int Bytes] [Expression]   deriving (Eq, Ord) @@ -118,9 +124,8 @@   | OutOfTime Int   | Stuck T.Text   | StuckAt T.Text (NonEmpty Rewritten) State-  | Unjoined T.Text-  | Unreminted T.Text   | OutOfStepsAt Budget (NonEmpty Rewritten) State+  | Diverged T.Text   | Looping Expression   | LoopingAt Expression (NonEmpty Rewritten) State   | Undataizable Expression State@@ -139,8 +144,7 @@     printf "Evaluation did not finish before reaching the limit of seconds: --max-seconds=%d" limit   show (Stuck func) = printf "No entry of --symbolic answers the λ function '%s'" (T.unpack func)   show (StuckAt func _ _) = show (Stuck func)-  show (Unjoined func) = printf "The λ function '%s' could not join its two branches" (T.unpack func)-  show (Unreminted func) = printf "The λ function '%s' could not remint a term as a symbol" (T.unpack func)+  show (Diverged func) = printf "The λ function '%s' could not join its two sides" (T.unpack func)   show (Looping term) = printf "Reduction entered a formation it is already inside: %s" (printExpression term)   show (LoopingAt term _ _) = show (Looping term)   show (Undataizable ExTermination _) = "dataization reached the terminator ⊥, which signals an error and cannot be dataized"@@ -263,7 +267,7 @@  refused :: Expression -> ReduceContext -> (Acyclic, Expression) -> IO a refused form ctx (mode, before) = do-  ctx._saveEval (EvLooped ctx._nesting ctx._judgment mode before ctx._site Nothing)+  ctx._saveEval (EvLooped ctx._nesting ctx._judgment mode before ctx._site)   throwIO (Looping form)  admitted :: Expression -> ReduceContext -> IO (Either (Acyclic, Expression) ReduceContext)@@ -315,15 +319,19 @@ isLambda _ = False  morph' :: Morphed -> Expression -> State -> ReduceContext -> IO (Morphed, State)-morph' start univ state entry = go start univ state entry+morph' = morph'' True++morph'' :: Bool -> Morphed -> Expression -> State -> ReduceContext -> IO (Morphed, State)+morph'' owner start univ state entry = go start univ state entry   where     go :: Morphed -> Expression -> State -> ReduceContext -> IO (Morphed, State)     go (expr, seq) univ state caller = do-      ctx <- opening Morphing =<< deeper =<< entering expr =<< universed univ caller{_judgment = Morphing}+      ctx <- opening Morphing =<< deeper =<< entering expr =<< parking seq state (universed univ caller{_judgment = Morphing})       parking seq state $ do         reached <- inferred expr univ state ctx ctx._engine._morphing         case reached of           Just (In.Answered step built, state') -> do+            when owner (ctx._saveEval (EvMorphed ctx._nesting built))             seq' <- leadsTo seq step built ctx             pure ((built, seq'), state')           Just (In.Onward way built world, state') -> do@@ -333,16 +341,22 @@  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') -> 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+  prepared <- try (universed universe caller)+  case prepared of+    Left (OutOfSteps _) | _partial -> pure (expr, [(universe, Nothing)], state)     Left failure -> throwIO (failure :: ReduceException)+    Right univCtx -> reduced expr =<< opening Morphing univCtx   where+    reduced :: Expression -> ReduceContext -> IO (Expression, [Rewritten], State)+    reduced expr ctx = do+      result <- try (morph' (expr, (universe, Nothing) :| []) universe state ctx)+      case result of+        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)     residue :: NonEmpty Rewritten -> State -> IO (Expression, [Rewritten], State)     residue seq parked = do       expr <- locatedExpression _locator (fst (NE.head seq))@@ -440,8 +454,10 @@ morphing :: Expression -> ReduceContext -> Expression -> State -> IO (Expression, State) morphing univ ctx expr state = do   (universe, aiming) <- insideUniverse expr univ ctx-  (morphed, _, state') <- morph universe state aiming-  pure (morphed, state')+  normal <- locatedExpression aiming._locator universe+  unparked $ do+    ((morphed, _), state') <- morph' (normal, (universe, Nothing) :| []) universe state =<< opening Morphing =<< universed universe aiming+    pure (morphed, state')  execBuildTerm :: Expression -> ReduceContext -> BuildTermFunc execBuildTerm _ ctx "evaluate" = evaluated ctx@@ -463,5 +479,5 @@  detached :: Expression -> Expression -> State -> ReduceContext -> IO (Expression, State) detached expr univ state ctx = unparked $ do-  ((morphed, _), state') <- morph' (expr, (univ, Nothing) :| []) univ state ctx+  ((morphed, _), state') <- morph'' (ctx._judgment /= Morphing) (expr, (univ, Nothing) :| []) univ state ctx   pure (morphed, state')
src/Yaml.hs view
@@ -107,12 +107,16 @@   parseJSON (Number num)     | isInteger num && ((round num :: Integer) < toInteger (minBound :: Int) || (round num :: Integer) > toInteger (maxBound :: Int)) =         fail (printf "The literal %s does not fit into Int" (show num))-  parseJSON v =-    asum+  parseJSON v@(Number _) = CmpNum <$> parseJSON v+  parseJSON v@(Object _) = CmpNum <$> parseJSON v+  parseJSON v@(String _) =+    foldr1+      (<|>)       [ CmpAttr <$> parseJSON v       , CmpNum <$> parseJSON v       , CmpExpr <$> parseJSON v       ]+  parseJSON _ = fail "An argument of 'eq' or 'gt' is a number, an attribute, an expression, or an object of 'length' or 'domain'"  instance FromJSON Condition where   parseJSON = withObject "Condition" parseCondition@@ -173,19 +177,22 @@     several val = parseJSON val <|> (pure <$> parseJSON val)  instance FromJSON ExtraArgument where-  parseJSON v =-    asum+  parseJSON v@(String _) =+    foldr1+      (<|>)       [ ArgAttribute <$> parseJSON v       , ArgBinding <$> parseJSON v-      , ArgExpression <$> parseJSON v       , ArgBytes <$> parseJSON v+      , ArgExpression <$> parseJSON v       ]+  parseJSON _ = fail "An argument of a 'where' function is a string holding an attribute, a binding, bytes or an expression"  instance FromJSON Extra where   parseJSON =     withObject       "Extra"-      ( \o ->+      ( \o -> do+          validateKnownYamlKeys o ["meta", "function", "args"]           Extra             <$> o .: "meta"             <*> o .: "function"
test-resources/atoms.yaml view
@@ -19,7 +19,7 @@   𝑛: Φ.number( φ ↦ ⟦ λ ⤍ 𝜎 ⟧ )  # Comparing two numbers answers a bool nobody has decided, which is what the-# fork below then branches on.+# join below then branches on. - λ: L_number_gt   dataize:     𝛿1: $.ρ@@ -39,15 +39,15 @@     𝛿1: $.ρ   𝑛: Φ.bytes( φ ↦ ⟦ λ ⤍ 𝜎 ⟧ ) -# A branch is where a 'morph' operand earns its place: the condition comes-# down through 𝔻, both branches reach a normal form through 𝕄, and the answer-# is one of them. This one answers the first, which is what an entry naming a-# meta it bound does; an entry whose answer is to stand for either branch joins-# the two of them in a 'join' block first and answers the meta that makes.+# A join is how a branch answers when nobody has decided its condition: the+# condition comes down through 𝔻 and answers a symbol, both sides reach a+# normal form through 𝕄, and the answer of the firing is the term the two+# share, with a fresh symbol wherever they hold two different symbols. Two+# sides that differ in anything else get the firing stuck. - λ: L_fork   dataize:     𝛿1: $.ρ-  morph:-    𝑛1: $.then-    𝑛2: $.else-  𝑛: 𝑛1+  join:+    on: 𝛿1+    left: $.then+    right: $.else
+ test-resources/dataization-packs/dataizes-a-decorator-whose-phi-reads-its-rho.yaml view
@@ -0,0 +1,13 @@+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com+# SPDX-License-Identifier: MIT+---+# A decorator dataizes as what it decorates (#2034): 'box' takes the φ of 'k'+# as the dispatch 'dot' takes for 'Φ.k.φ', so the φ formation gets 'k' as its+# ρ and its 'ξ.ρ.v' reads the datum instead of ⊥.+location: 'Q.k'+input: |+  ⟦+    k ↦ ⟦ ρ ↦ ∅, v ↦ ⟦ Δ ⤍ 07- ⟧, φ ↦ ⟦ ρ ↦ ∅, φ ↦ ξ.ρ.v ⟧ ⟧,+    m ↦ Φ.k.φ+  ⟧+result: '07-'
− test-resources/dataization-packs/dataizes-the-branch-an-undecided-condition-picked.yaml
@@ -1,10 +0,0 @@-# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com-# SPDX-License-Identifier: MIT-----# Nobody decided the comparison, so the fork cannot know which branch the-# program takes; it answers the first one, whose value is a number the program-# wrote and not a symbol at all.-model: true-symbolic: true-input: '5.gt( 6 ).if( 1, 2 )'-result: '3F-F0-00-00-00-00-00-00'
+ test-resources/dataization-packs/dataizes-the-one-branch-an-undecided-condition-joins.yaml view
@@ -0,0 +1,9 @@+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com+# SPDX-License-Identifier: MIT+---+# Nobody decided the comparison, yet both branches are the same number, so+# the join answers that number and the program needs no decision at all.+model: true+symbolic: true+input: '5.gt( 6 ).if( 1, 1 )'+result: '3F-F0-00-00-00-00-00-00'
+ test-resources/dataization-packs/gets-stuck-on-branches-an-undecided-condition-cannot-join.yaml view
@@ -0,0 +1,10 @@+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com+# SPDX-License-Identifier: MIT+---+# Nobody decided the comparison, and the two branches are numbers that differ+# in more than a symbol, so no join makes one term of them and the firing of+# 'if' gets stuck instead of picking a branch (#1996).+model: true+symbolic: true+input: '5.gt( 6 ).if( 1, 2 )'+fails: "No entry of --symbolic answers the λ function 'L_fork'"
− test-resources/evaluate-packs/answers-a-copy-a-cut-stops-with-a-fresh-symbol-when-deferring.yaml
@@ -1,46 +0,0 @@-# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com-# SPDX-License-Identifier: MIT-----# yamllint disable rule:line-length-# The cut of #1735 without the walk of --deep: the branch 'b' of the fork is-# the copy 'rec( n ↦ ⟦ φ ↦ 𝜎2 ⟧ )', whose argument reads to no bare symbol, so-# an 'unfold' line enters it, morphs its φ to the same fork and fires it.-# '--acyclic=proven' refuses that firing, since the run is already inside the-# fork. A line that says 'defer: true' answers the copy with a fresh symbol-# there and writes it on the 'looped' line, and the 'join' line pairs it with-# the symbol the other branch holds, instead of losing the whole entry (#1895,-# #1921).-symbolic: |-  - λ: L_fork-    morph:-      𝑛1: $.a-      𝑛2: $.b-    unfold:-      𝑛3: {of: 𝑛2, defer: true}-    join:-      𝑛4: [𝑛1, 𝑛3]-    𝑛: ⟦ φ ↦ 𝑛4 ⟧-location: 'Q.y'-acyclic: proven-input: '⟦ fork(a, b) ↦ ⟦ λ ⤍ L_fork ⟧, rec(n) ↦ ⟦ φ ↦ Φ.fork( ⟦ λ ⤍ 𝜎1 ⟧, Φ.rec( ξ.n ) ) ⟧, y ↦ Φ.rec( ⟦ φ ↦ ⟦ λ ⤍ 𝜎2 ⟧ ⟧ ).φ.φ ⟧'-result: '𝜎4:λ'-protocol: |-  𝕄(Φ.y):-    𝑛·0·1 := Φ.rec( n ↦ 𝜎2:λ:φ )-    𝑛·0·2 := Φ.fork( a ↦ 𝜎1:λ )-    𝑛·0·3 := 𝑛·0·2( b ↦ Φ.rec( 𝜎2:λ:φ ) )-    𝔼(L_fork):-      𝑛1·1 := 𝜎1:λ  # 𝕄(ξ.a)-      𝕄(Φ.a🌵1):-        𝑛·1·1 := Φ.rec( n ↦ 𝜎2:λ:φ )-      𝑛2·1 := 𝑛·1·1  # 𝕄(ξ.b)-      𝕄(Φ.a🌵2):-        𝑛·1·2 := Φ.fork( a ↦ 𝜎1:λ )-        𝑛·1·3 := 𝑛·1·2( b ↦ Φ.rec( 𝜎2:λ:φ ) )-      𝑛3·1 := 𝑛·1·3  # 𝕄(𝑛2.φ)-      looped(𝑛·1·3) := 𝜎3  # 𝔼(𝑛2), proven-      𝑛3·1 := 𝜎3:λ  # 𝔼(𝑛2)-      𝔻(𝜎4:λ) ∈ { 𝔻(𝜎1:λ), 𝔻(𝜎3:λ) }-      𝑛4·1 := 𝜎4:λ  # [𝑛1, 𝑛3]-      𝑛·1·4 := 𝜎4:λ:φ  # 𝑛-      𝑛·1·5 := 𝜎4:λ:φ  # 𝕄(𝑛·1·4)
+ test-resources/evaluate-packs/answers-its-own-sides-on-a-condition-another-join-read.yaml view
@@ -0,0 +1,104 @@+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com+# SPDX-License-Identifier: MIT+---+# yamllint disable rule:line-length+# Two joins on one condition answer each its own sides (#1940). The memo of+# 'plausible' keeps the answer of a firing by the λ name and the operands it+# came down to, and a join reads its sides as well as its condition, so the+# second join, on the condition the first one read, reduces its own sides and+# answers 70 instead of the 7 of the first join.+symbolic: |+  - λ: L_number_gt+    dataize:+      𝛿1: $.ρ+      𝛿2: $.x+    𝑛: Φ.bool( φ ↦ ⟦ λ ⤍ 𝜎 ⟧ )+  - λ: L_pick+    dataize:+      𝛿1: $.x+    morph:+      𝑛1: $.y+    evaluate:+      𝑛2: 𝑛1+    𝑛: 𝑛2+  - λ: L_fork+    dataize:+      𝛿1: $.ρ+    join:+      on: 𝛿1+      left: $.then+      right: $.else+location: 'Q.@'+acyclic: plausible+input: >+  ⟦ bytes(φ) ↦ ⟦⟧,+    bool(φ) ↦ ⟦ if ↦ ⟦ ρ ↦ ∅, then ↦ ∅, else ↦ ∅, λ ⤍ L_fork ⟧ ⟧,+    number(φ) ↦ ⟦ gt(ρ, x) ↦ ⟦ λ ⤍ L_number_gt ⟧ ⟧,+    pick(x, y) ↦ ⟦ λ ⤍ L_pick ⟧,+    φ ↦ Φ.pick( 5.gt( 0 ).if( 7, 7 ), 5.gt( 0 ).if( 70, 70 ) ).φ ⟧+result: '⟦ φ ↦ ⟦ Δ ⤍ 40-51-80-00-00-00-00-00 ⟧ ⟧'+protocol: |+  𝕄(Φ.φ):+    𝑛·0·1 := Φ.pick( x ↦ 5.gt( 0 ).if( 7, 7 ) )+    𝑛·0·2 := 𝑛·0·1( y ↦ 5.gt( 0 ).if( 70, 70 ) )+    𝔼(L_pick):+      𝔻(Φ.a🌵0):+        𝕄(Φ.a🌵0):+          𝑛·1·1 := 5+          𝑛·1·2 := 𝑛·1·1.gt( x ↦ 0 )+          𝔼(L_number_gt):+            𝔻(Φ.a🌵1):+              𝕄(Φ.a🌵1):+                𝑛·2·1 := 5+                𝑛·2·2 := Φ.bytes( φ ↦ 40-14-00-00-00-00-00-00:Δ )+              𝛿·2·1 := 40-14-00-00-00-00-00-00  # 5.0+            𝛿1·2 := 𝛿·2·1  # 𝔻(ξ.ρ)+            𝔻(Φ.a🌵2):+              𝕄(Φ.a🌵2):+                𝑛·2·3 := 0+                𝑛·2·4 := Φ.bytes( φ ↦ 00-00-00-00-00-00-00-00:Δ )+              𝛿·2·2 := 00-00-00-00-00-00-00-00  # 0.0+            𝛿2·2 := 𝛿·2·2  # 𝔻(ξ.x)+            𝑛·2·5 := Φ.bool( φ ↦ 𝜎1:λ )  # 𝑛+            𝕄(𝑛·2·5):+              𝑛·2·6 := Φ.bool( φ ↦ 𝜎1:λ )+            𝑛·2·7 := 𝑛·2·6  # 𝕄(𝑛·2·5)+          𝑛·1·3 := 𝑛·2·6.if( then ↦ 7 )+          𝑛·1·4 := 𝑛·1·3( else ↦ 7 )+        𝔼(L_fork):+          𝔻(Φ.a🌵3):+            𝕄(Φ.a🌵3):+              𝑛·3·1 := Φ.bool( φ ↦ 𝜎1:λ )+          𝛿1·3 := 𝔻(𝜎1:λ)  # 𝔻(ξ.ρ)+          𝕄(Φ.a🌵4):+            𝑛·3·2 := 7+          𝕄(Φ.a🌵5):+            𝑛·3·3 := 7+          𝑛·3·4 := 𝑛·3·3  # 𝕄(ξ.then), 𝕄(ξ.else)+        𝕄(Φ.a🌵0):+          𝑛·1·5 := Φ.bytes( φ ↦ 40-1C-00-00-00-00-00-00:Δ )+        𝛿·1·1 := 40-1C-00-00-00-00-00-00  # 7.0+      𝛿1·1 := 𝛿·1·1  # 𝔻(ξ.x)+      𝕄(Φ.a🌵6):+        𝑛·1·6 := Φ.number( φ ↦ 𝑛·2·2 )+        𝑛·1·7 := 𝑛·1·6.gt( x ↦ 𝑛·2·3 )+        𝔼(L_number_gt):+          𝑛·4·1 := 𝑛·3·1  # 𝑛+          𝑛·4·2 := 𝑛·2·7  # 𝕄(𝑛·4·1)+        𝑛·1·8 := 𝑛·3·1.if( then ↦ 70 )+        𝑛·1·9 := 𝑛·1·8( else ↦ 70 )+      𝑛1·1 := 𝑛·1·9  # 𝕄(ξ.y)+      𝔼(L_fork):  # 𝔼(𝑛1)+        𝔻(Φ.a🌵7):+          𝕄(Φ.a🌵7):+            𝑛·5·1 := Φ.bool( φ ↦ 𝜎1:λ )+        𝛿1·5 := 𝔻(𝜎1:λ)  # 𝔻(ξ.ρ)+        𝕄(Φ.a🌵8):+          𝑛·5·2 := 70+        𝕄(Φ.a🌵9):+          𝑛·5·3 := 70+        𝑛·5·4 := 𝑛·5·3  # 𝕄(ξ.then), 𝕄(ξ.else)+      𝑛2·1 := 𝑛·5·3  # 𝔼(𝑛1)+      𝑛·1·10 := 𝑛·5·3  # 𝑛+      𝑛·1·11 := 𝑛·5·3  # 𝕄(𝑛·1·10)+    𝑛·0·3 := Φ.bytes( φ ↦ 40-51-80-00-00-00-00-00:Δ )
+ test-resources/evaluate-packs/binds-a-copy-over-no-bare-symbol-as-it-is.yaml view
@@ -0,0 +1,27 @@+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com+# SPDX-License-Identifier: MIT+---+# yamllint disable rule:line-length+# A 'defer' line defers only a copy one of whose arguments reads to a bare+# symbol (#1892, #1933). Here 'Φ.items.tail' reads to an object that knows+# its 'next', so the line binds the copy as it is and the answer carries the+# copy whole. No 'deferred' line is written and no symbol is minted for it.+symbolic: |+  - λ: L_hold+    morph:+      𝑛1: $.b+    defer:+      𝑛2: 𝑛1+    𝑛: ⟦ φ ↦ 𝑛2 ⟧+location: 'Q.y'+input: '⟦ step(tup) ↦ ⟦ φ ↦ tup.next ⟧, items ↦ ⟦ tail ↦ ⟦ next ↦ ⟦ φ ↦ ⟦ λ ⤍ 𝜎1 ⟧ ⟧ ⟧ ⟧, y ↦ ⟦ b ↦ Φ.step( tup ↦ Φ.items.tail ), λ ⤍ L_hold ⟧.φ ⟧'+result: '⟦ tup ↦ Φ.items.tail, φ ↦ tup.next ⟧'+protocol: |+  𝕄(Φ.y):+    𝔼(L_hold):+      𝕄(Φ.a🌵0):+        𝑛·1·1 := Φ.step( tup ↦ Φ.items.tail )+      𝑛1·1 := 𝑛·1·1  # 𝕄(ξ.b)+      𝑛2·1 := 𝑛·1·1  # 𝑛1+      𝑛·1·2 := 𝑛·1·1:φ  # 𝑛+      𝑛·1·3 := 𝑛·1·1:φ  # 𝕄(𝑛·1·2)
test-resources/evaluate-packs/blames-the-lambda-the-operand-was-parked-on.yaml view
@@ -18,5 +18,5 @@     𝔼(L_entry):       𝔻(Φ.a🌵0):         𝕄(Φ.a🌵0):-          unanswered(L_none)  # 𝕄(L_none:λ)-      stuck(L_none)+          unanswered L_none  # 𝕄(L_none:λ)+      stuck L_none
− test-resources/evaluate-packs/cuts-a-branch-whose-phi-leads-back-to-it.yaml
@@ -1,36 +0,0 @@-# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com-# SPDX-License-Identifier: MIT-----# yamllint disable rule:line-length-# The step of an 'unfold' line (#1885, #1921), 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 line 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-    unfold:-      𝑛3: 𝑛1-    join:-      𝑛4: [𝑛3, 𝑛2]-    𝑛: 𝑛4-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)-      𝑛3·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)-        𝑛3·2 := ⟦ λ ⤍ L_fork, a ↦ Φ.z, b ↦ 𝜎1:λ:φ ⟧  # 𝕄(𝑛1.φ)-        looped(⟦ λ ⤍ L_fork, a ↦ Φ.z, b ↦ 𝜎1:λ:φ ⟧)  # 𝔼(𝑛1), proven
test-resources/evaluate-packs/cuts-a-recursion-running-through-evaluate-lines.yaml view
@@ -25,4 +25,4 @@   𝕄(Φ.app):     𝔼(L_loop):       𝑛1·1 := ⟦ next ↦ Φ.loop, λ ⤍ L_loop ⟧  # 𝕄(ξ.next)-      looped(⟦ next ↦ Φ.loop, λ ⤍ L_loop ⟧)  # 𝔼(𝑛1), proven+      looped ⟦ next ↦ Φ.loop, λ ⤍ L_loop ⟧  # 𝔼(𝑛1), proven
test-resources/evaluate-packs/cuts-a-recursion-that-differs-by-symbols-only.yaml view
@@ -30,4 +30,4 @@       𝕄(𝑛·1·1):         𝑛·1·2 := Φ.down( x ↦ 𝜎2:λ )       𝑛·1·3 := 𝑛·1·2  # 𝕄(𝑛·1·1)-    looped(𝑛·0·1)  # proven+    looped 𝑛·0·1  # proven
+ test-resources/evaluate-packs/defers-a-copy-a-join-passed-through-stay-and-symbolize.yaml view
@@ -0,0 +1,64 @@+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com+# SPDX-License-Identifier: MIT+---+# yamllint disable rule:line-length+# The whole pipeline of #1982: the sides of the join go through 'morph',+# 'stay', 'symbolize' and 'defer', one line per step. The right side is the+# copy 'step( tup ↦ Φ.items.tail )', and 'Φ.items.tail' reads to the bare+# symbol '𝜎1', an object nobody knows. The 'stay' and 'symbolize' lines leave+# the copy as it is, and the 'defer' line answers it with a fresh symbol and+# writes a 'deferred' line naming the call the copy stands for, so the firing+# never gets stuck inside the unknown. The left side is the datum '01-', which+# the 'symbolize' line turns into a symbol, so the two sides differ in their+# symbols alone, and the join makes one term of them.+symbolic: |+  - λ: L_use+    dataize:+      𝛿1: $.x+    𝑛: Φ.num( φ ↦ ⟦ λ ⤍ 𝜎 ⟧ )+  - λ: L_fork+    dataize:+      𝛿1: $.c+    morph:+      𝑛1: $.a+      𝑛2: $.b+    stay:+      𝑛3: 𝑛1+      𝑛4: 𝑛2+    symbolize:+      𝑛5: 𝑛3+      𝑛6: 𝑛4+    defer:+      𝑛7: 𝑛5+      𝑛8: 𝑛6+    join:+      on: 𝛿1+      left: 𝑛7+      right: 𝑛8+location: 'Q.y'+input: '⟦ step(tup) ↦ ⟦ φ ↦ tup.next ⟧, items ↦ ⟦ tail ↦ ⟦ λ ⤍ 𝜎1 ⟧ ⟧, num(φ) ↦ ⟦⟧, y ↦ ⟦ x ↦ ⟦ c ↦ ⟦ λ ⤍ 𝜎2 ⟧, a ↦ ⟦ Δ ⤍ 01- ⟧, b ↦ Φ.step( tup ↦ Φ.items.tail ), λ ⤍ L_fork ⟧, λ ⤍ L_use ⟧.φ ⟧'+result: '𝜎6:λ'+protocol: |+  𝕄(Φ.y):+    𝔼(L_use):+      𝔻(Φ.a🌵0):+        𝔼(L_fork):+          𝛿1·2 := 𝔻(𝜎2:λ)  # 𝔻(ξ.c)+          𝑛1·2 := 01-:Δ  # 𝕄(ξ.a)+          𝕄(Φ.a🌵3):+            𝑛·2·1 := Φ.step( tup ↦ Φ.items.tail )+          𝑛2·2 := 𝑛·2·1  # 𝕄(ξ.b)+          𝑛3·2 := 01-:Δ  # 𝑛1+          𝑛4·2 := 𝑛·2·1  # 𝑛2+          symbolized 𝜎3 := 01-+          𝑛5·2 := 𝜎3:λ  # symbolize(𝑛3)+          𝑛7·2 := 𝑛5·2  # 𝑛5+          𝑛6·2 := 𝑛·2·1  # 𝑛4+          deferred 𝜎4 := Φ.step( tup ↦ 𝜎1:λ )  # 𝕄(𝑛6.φ)+          joined 𝜎5 := 𝛿1·2 ? 𝜎3 : 𝜎4+          𝑛·2·2 := 𝜎5:λ  # 𝑛7, 𝑛8+      𝛿1·1 := 𝔻(𝜎5:λ)  # 𝔻(ξ.x)+      𝑛·1·1 := Φ.num( φ ↦ 𝜎6:λ )  # 𝑛+      𝕄(𝑛·1·1):+        𝑛·1·2 := Φ.num( φ ↦ 𝜎6:λ )+      𝑛·1·3 := 𝑛·1·2  # 𝕄(𝑛·1·1)
− test-resources/evaluate-packs/defers-a-copy-whose-argument-reads-to-a-bare-symbol.yaml
@@ -1,37 +0,0 @@-# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com-# SPDX-License-Identifier: MIT-----# yamllint disable rule:line-length-# The loop of #1729 without the walk of --deep: the branch 'b' of the fork is-# the copy 'step( tup ↦ Φ.items.tail )', and 'Φ.items.tail' reads to the bare-# symbol '𝜎1', an object nobody knows. Unfolding the copy would morph its φ,-# which asks '𝜎1' for 'next' and gets stuck. An 'unfold' line that says-# 'defer: true' does not enter such a copy: it answers it with a fresh-# symbol, writes a 'deferred' line naming the call the copy stands for, and-# the 'join' line pairs that symbol with the one the other branch holds-# (#1892, #1921).-symbolic: |-  - λ: L_fork-    morph:-      𝑛1: $.a-      𝑛2: $.b-    unfold:-      𝑛3: {of: 𝑛2, defer: true}-    join:-      𝑛4: [𝑛1, 𝑛3]-    𝑛: ⟦ φ ↦ 𝑛4 ⟧-location: 'Q.y'-input: '⟦ step(tup) ↦ ⟦ φ ↦ tup.next ⟧, items ↦ ⟦ tail ↦ ⟦ λ ⤍ 𝜎1 ⟧ ⟧, y ↦ ⟦ a ↦ ⟦ λ ⤍ 𝜎2 ⟧, b ↦ Φ.step( tup ↦ Φ.items.tail ), λ ⤍ L_fork ⟧.φ ⟧'-result: '𝜎4:λ'-protocol: |-  𝕄(Φ.y):-    𝔼(L_fork):-      𝑛1·1 := 𝜎2:λ  # 𝕄(ξ.a)-      𝕄(Φ.a🌵1):-        𝑛·1·1 := Φ.step( tup ↦ Φ.items.tail )-      𝑛2·1 := 𝑛·1·1  # 𝕄(ξ.b)-      deferred(𝜎3) := Φ.step( tup ↦ 𝜎1:λ )  # 𝕄(𝑛2.φ)-      𝔻(𝜎4:λ) ∈ { 𝔻(𝜎2:λ), 𝔻(𝜎3:λ) }-      𝑛4·1 := 𝜎4:λ  # [𝑛1, 𝑛3]-      𝑛·1·2 := 𝜎4:λ:φ  # 𝑛-      𝑛·1·3 := 𝜎4:λ:φ  # 𝕄(𝑛·1·2)
+ test-resources/evaluate-packs/defers-a-copy-whose-phi-reads-nothing-of-its-own.yaml view
@@ -0,0 +1,28 @@+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com+# SPDX-License-Identifier: MIT+---+# yamllint disable rule:line-length+# The copy 'step( tup ↦ Φ.items.tail )' of a 'step' whose φ reads nothing of+# its own object. A 'defer' line asks no such thing of a copy (#1933): 'tup'+# reads to the bare symbol '𝜎1', so the line answers the copy with a fresh+# symbol and writes a 'deferred' line naming the call the copy stands for,+# whatever the φ of the copy would have come to.+symbolic: |+  - λ: L_hold+    morph:+      𝑛1: $.b+    defer:+      𝑛2: 𝑛1+    𝑛: ⟦ φ ↦ 𝑛2 ⟧+location: 'Q.y'+input: '⟦ done ↦ ⟦⟧, step(tup) ↦ ⟦ φ ↦ Φ.done ⟧, items ↦ ⟦ tail ↦ ⟦ λ ⤍ 𝜎1 ⟧ ⟧, y ↦ ⟦ b ↦ Φ.step( tup ↦ Φ.items.tail ), λ ⤍ L_hold ⟧.φ ⟧'+result: '𝜎2:λ'+protocol: |+  𝕄(Φ.y):+    𝔼(L_hold):+      𝕄(Φ.a🌵0):+        𝑛·1·1 := Φ.step( tup ↦ Φ.items.tail )+      𝑛1·1 := 𝑛·1·1  # 𝕄(ξ.b)+      deferred 𝜎2 := Φ.step( tup ↦ 𝜎1:λ )  # 𝕄(𝑛1.φ)+      𝑛·1·2 := 𝜎2:λ:φ  # 𝑛+      𝑛·1·3 := 𝜎2:λ:φ  # 𝕄(𝑛·1·2)
+ test-resources/evaluate-packs/diverges-on-a-side-holding-a-datum-though-it-may-fabricate.yaml view
@@ -0,0 +1,25 @@+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com+# SPDX-License-Identifier: MIT+---+# yamllint disable rule:line-length+# The block names 'L_join' under 'otherwise', but the left side is a datum,+# which no dispatch reaches, so no formation asking both sides for their+# attributes means the same as the two of them. The firing writes 'diverged'+# and gets stuck, as it does with no 'otherwise' at all (#2024).+symbolic: |+  - λ: L_fork+    dataize:+      𝛿1: $.c+    join:+      on: 𝛿1+      left: $.a+      right: $.b+      otherwise: L_join+location: 'Q.y'+input: '⟦ y ↦ ⟦ c ↦ ⟦ λ ⤍ 𝜎1 ⟧, a ↦ ⟦ Δ ⤍ 01- ⟧, b ↦ ⟦ φ ↦ Φ.zu ⟧, λ ⤍ L_fork ⟧.φ ⟧'+fails: "No entry of --symbolic answers the λ function 'L_fork'"+protocol: |+  𝕄(Φ.y):+    𝔼(L_fork):+      𝛿1·1 := 𝔻(𝜎1:λ)  # 𝔻(ξ.c)+      diverged 01-:Δ vs Φ.zu:φ  # they differ at .Δ, and the left side has Δ
+ test-resources/evaluate-packs/diverges-on-a-side-holding-a-symbol-though-it-may-fabricate.yaml view
@@ -0,0 +1,27 @@+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com+# SPDX-License-Identifier: MIT+---+# yamllint disable rule:line-length+# The block names 'L_join' under 'otherwise', but the left side is a bare+# symbol, which keeps what it stands for in its λ, where no dispatch reaches.+# A formation asking both sides for their attributes would drop that λ and+# ask the symbol for 'φ' and 'ke' it does not bind, so it would not mean the+# same as the two of them. The firing writes 'diverged' and gets stuck, as it+# does with a side holding a datum (#2041).+symbolic: |+  - λ: L_fork+    dataize:+      𝛿1: $.c+    join:+      on: 𝛿1+      left: $.a+      right: $.b+      otherwise: L_join+location: 'Q.y'+input: '⟦ y ↦ ⟦ c ↦ ⟦ λ ⤍ 𝜎1 ⟧, a ↦ ⟦ λ ⤍ 𝜎2 ⟧, b ↦ ⟦ φ ↦ Φ.zu, ke ↦ Φ.ty ⟧, λ ⤍ L_fork ⟧.ke ⟧'+fails: "No entry of --symbolic answers the λ function 'L_fork'"+protocol: |+  𝕄(Φ.y):+    𝔼(L_fork):+      𝛿1·1 := 𝔻(𝜎1:λ)  # 𝔻(ξ.c)+      diverged 𝜎2:λ vs ⟦ φ ↦ Φ.zu, ke ↦ Φ.ty ⟧  # they differ at .λ, and the left side has λ
+ test-resources/evaluate-packs/diverges-on-two-sides-of-metas-that-differ.yaml view
@@ -0,0 +1,29 @@+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com+# SPDX-License-Identifier: MIT+---+# yamllint disable rule:line-length+# The sides of a join may be metas 'morph' bound (#1967). The join takes them+# as they are and morphs nothing again. They differ in a datum, so no join+# makes one term of them, and the firing writes 'diverged' with the metas of+# the lines that bound the two sides, instead of splitting the run (#1996).+symbolic: |+  - λ: L_fork+    dataize:+      𝛿1: $.c+    morph:+      𝑛1: $.a+      𝑛2: $.b+    join:+      on: 𝛿1+      left: 𝑛1+      right: 𝑛2+location: 'Q.y'+input: '⟦ y ↦ ⟦ c ↦ ⟦ λ ⤍ 𝜎1 ⟧, a ↦ ⟦ φ ↦ ⟦ Δ ⤍ 01- ⟧ ⟧, b ↦ ⟦ φ ↦ ⟦ Δ ⤍ 02- ⟧ ⟧, λ ⤍ L_fork ⟧.φ ⟧'+fails: "No entry of --symbolic answers the λ function 'L_fork'"+protocol: |+  𝕄(Φ.y):+    𝔼(L_fork):+      𝛿1·1 := 𝔻(𝜎1:λ)  # 𝔻(ξ.c)+      𝑛1·1 := 01-:Δ:φ  # 𝕄(ξ.a)+      𝑛2·1 := 02-:Δ:φ  # 𝕄(ξ.b)+      diverged 𝑛1·1 vs 𝑛2·1  # they differ at .φ.Δ, and the join names no otherwise
+ test-resources/evaluate-packs/diverges-on-two-sides-that-do-not-join.yaml view
@@ -0,0 +1,74 @@+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com+# SPDX-License-Identifier: MIT+---+# yamllint disable rule:line-length+# A join on a condition nobody decided: the condition comes down through 𝔻+# and answers a symbol nobody has decided. The join brings both sides to a+# normal form through 𝕄 first, each a call of 'plus' nobody has fired+# (#1954). The calls add other numbers, so no join makes one term of them,+# and the firing writes 'diverged' with the metas of the applications that+# made the two sides, instead of splitting the run (#1996). Its comment tells+# that the two copies of 'plus' first differ at the ρ the protocol hides, and+# that the join names no function to fabricate a formation with (#2043).+symbolic: |+  - λ: L_number_gt+    dataize:+      𝛿1: $.ρ+      𝛿2: $.x+    𝑛: Φ.bool( φ ↦ ⟦ λ ⤍ 𝜎 ⟧ )+  - λ: L_number_plus+    dataize:+      𝛿1: $.ρ+      𝛿2: $.x+    𝑛: Φ.number( φ ↦ ⟦ λ ⤍ 𝜎 ⟧ )+  - λ: L_fork+    dataize:+      𝛿1: $.ρ+    join:+      on: 𝛿1+      left: $.then+      right: $.else+location: 'Q.@'+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 ) ).φ ⟧+fails: "No entry of --symbolic answers the λ function 'L_fork'"+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.plus( 2 ) )+    𝑛·0·4 := 𝑛·0·3( else ↦ 3.plus( 4 ) )+    𝔼(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 )+      𝕄(Φ.a🌵4):+        𝑛·2·4 := 3+        𝑛·2·5 := 𝑛·2·4.plus( x ↦ 4 )+      diverged 𝑛·2·3 vs 𝑛·2·5  # they differ at .ρ, and the join names no otherwise
− test-resources/evaluate-packs/enters-a-copy-whose-argument-reads-to-no-bare-symbol-when-deferring.yaml
@@ -1,35 +0,0 @@-# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com-# SPDX-License-Identifier: MIT-----# yamllint disable rule:line-length-# An 'unfold' line that says 'defer: true' defers only a copy one of whose-# arguments reads to a bare symbol (#1892, #1921). Here 'Φ.items.tail' reads-# to an object that knows its 'next', so the line enters the copy (#1885): it-# morphs its φ and fires what it comes to, and the 'join' line pairs two-# symbols. No 'deferred' line is written and no symbol is minted for the copy.-symbolic: |-  - λ: L_fork-    morph:-      𝑛1: $.a-      𝑛2: $.b-    unfold:-      𝑛3: {of: 𝑛2, defer: true}-    join:-      𝑛4: [𝑛1, 𝑛3]-    𝑛: ⟦ φ ↦ 𝑛4 ⟧-location: 'Q.y'-input: '⟦ step(tup) ↦ ⟦ φ ↦ tup.next ⟧, items ↦ ⟦ tail ↦ ⟦ next ↦ ⟦ φ ↦ ⟦ λ ⤍ 𝜎1 ⟧ ⟧ ⟧ ⟧, y ↦ ⟦ a ↦ ⟦ λ ⤍ 𝜎2 ⟧, b ↦ Φ.step( tup ↦ Φ.items.tail ), λ ⤍ L_fork ⟧.φ ⟧'-result: '𝜎3:λ'-protocol: |-  𝕄(Φ.y):-    𝔼(L_fork):-      𝑛1·1 := 𝜎2:λ  # 𝕄(ξ.a)-      𝕄(Φ.a🌵1):-        𝑛·1·1 := Φ.step( tup ↦ Φ.items.tail )-      𝑛2·1 := 𝑛·1·1  # 𝕄(ξ.b)-      𝑛3·1 := 𝜎1:λ:φ  # 𝕄(𝑛2.φ)-      𝑛3·1 := 𝑛3·1  # 𝔼(𝑛2)-      𝔻(𝜎3:λ) ∈ { 𝔻(𝜎2:λ), 𝔻(𝜎1:λ) }-      𝑛4·1 := 𝜎3:λ  # [𝑛1, 𝑛3]-      𝑛·1·2 := 𝜎3:λ:φ  # 𝑛-      𝑛·1·3 := 𝜎3:λ:φ  # 𝕄(𝑛·1·2)
+ test-resources/evaluate-packs/fabricates-a-formation-holding-the-bytes-its-condition-came-down-to.yaml view
@@ -0,0 +1,28 @@+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com+# SPDX-License-Identifier: MIT+---+# yamllint disable rule:line-length+# The condition of the join comes down to real bytes, not to a symbol, and the+# two sides differ in shape, so the firing fabricates a formation whose every+# attribute is a copy of 'L_join' holding those bytes as a datum under 'on🌵'+# (#2026). The program asks that formation for 'pe', which the right side+# binds alone, so its copy asks the left side for 'pe' too.+symbolic: |+  - λ: L_fork+    dataize:+      𝛿1: $.c+    join:+      on: 𝛿1+      left: $.a+      right: $.b+      otherwise: L_join+location: 'Q.y'+input: '⟦ y ↦ ⟦ c ↦ ⟦ Δ ⤍ 3A- ⟧, a ↦ ⟦ φ ↦ Φ.wu ⟧, b ↦ ⟦ φ ↦ Φ.ol, pe ↦ Φ.ki ⟧, λ ⤍ L_fork ⟧.pe ⟧'+result: '⟦ λ ⤍ L_join, on🌵 ↦ ⟦ Δ ⤍ 3A- ⟧, left🌵 ↦ ρ.left🌵.pe, right🌵 ↦ ρ.right🌵.pe ⟧'+protocol: |+  𝕄(Φ.y):+    𝔼(L_fork):+      𝔻(Φ.a🌵0):+        𝛿·1·1 := 3A-+      𝛿1·1 := 𝛿·1·1  # 𝔻(ξ.c)+      fabricated 𝑛·1·1 := Φ.wu:φ and ⟦ φ ↦ Φ.ol, pe ↦ Φ.ki ⟧  # ⟦ left🌵 ↦ Φ.wu:φ, right🌵 ↦ ⟦ φ ↦ Φ.ol, pe ↦ Φ.ki ⟧, φ ↦ ⟦ λ ⤍ L_join, on🌵 ↦ 3A-:Δ, left🌵 ↦ ρ.left🌵.φ, right🌵 ↦ ρ.right🌵.φ ⟧, pe ↦ ⟦ λ ⤍ L_join, on🌵 ↦ 3A-:Δ, left🌵 ↦ ρ.left🌵.pe, right🌵 ↦ ρ.right🌵.pe ⟧ ⟧
+ test-resources/evaluate-packs/fabricates-a-formation-holding-the-symbol-a-path-of-its-condition-reaches.yaml view
@@ -0,0 +1,24 @@+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com+# SPDX-License-Identifier: MIT+---+# yamllint disable rule:line-length+# The entry of 'L_join' reads its condition off 'on🌵', dataizing nothing, and+# the formation it fires binds 'on🌵' to a symbol, as a formation an earlier+# join fabricated does. The two sides differ in shape, so the firing+# fabricates a formation whose every attribute is a copy of 'L_join' holding+# that symbol under 'on🌵', not the 42 it is dataized to (#2026). The program+# asks that formation for 'tu', which the right side binds alone.+symbolic: |+  - λ: L_join+    join:+      on: $.on🌵+      left: $.left🌵+      right: $.right🌵+      otherwise: L_join+location: 'Q.y'+input: '⟦ y ↦ ⟦ on🌵 ↦ ⟦ λ ⤍ 𝜎5 ⟧, left🌵 ↦ ⟦ φ ↦ Φ.jo ⟧, right🌵 ↦ ⟦ φ ↦ Φ.ma, tu ↦ Φ.ip ⟧, λ ⤍ L_join ⟧.tu ⟧'+result: '⟦ λ ⤍ L_join, on🌵 ↦ ⟦ λ ⤍ 𝜎5 ⟧, left🌵 ↦ ρ.left🌵.tu, right🌵 ↦ ρ.right🌵.tu ⟧'+protocol: |+  𝕄(Φ.y):+    𝔼(L_join):+      fabricated 𝑛·1·1 := Φ.jo:φ and ⟦ φ ↦ Φ.ma, tu ↦ Φ.ip ⟧  # ⟦ left🌵 ↦ Φ.jo:φ, right🌵 ↦ ⟦ φ ↦ Φ.ma, tu ↦ Φ.ip ⟧, φ ↦ ⟦ λ ⤍ L_join, on🌵 ↦ 𝜎5:λ, left🌵 ↦ ρ.left🌵.φ, right🌵 ↦ ρ.right🌵.φ ⟧, tu ↦ ⟦ λ ⤍ L_join, on🌵 ↦ 𝜎5:λ, left🌵 ↦ ρ.left🌵.tu, right🌵 ↦ ρ.right🌵.tu ⟧ ⟧
+ test-resources/evaluate-packs/fabricates-a-formation-of-two-sides-of-different-shapes.yaml view
@@ -0,0 +1,32 @@+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com+# SPDX-License-Identifier: MIT+---+# yamllint disable rule:line-length+# The two sides of a join are formations of different shapes: the left one+# binds 'φ' and 'ke', the right one 'φ' alone. No join makes one term of them,+# but the block names 'L_join' under 'otherwise', so the firing fabricates a+# formation holding every attribute of both sides, each of them a copy of+# 'L_join' that holds the symbol the condition came down to, not the 42 that+# symbol is dataized to (#2026), and asks for that attribute the copies of+# both sides the formation holds under 'left🌵' and 'right🌵' (#2029), and+# writes 'fabricated' in place of 'diverged', with that formation in its+# comment (#2024). The+# program asks that formation for 'ke', which the left side binds alone, so+# its copy asks the right side for 'ke' too, through the 'φ' of that side.+symbolic: |+  - λ: L_fork+    dataize:+      𝛿1: $.c+    join:+      on: 𝛿1+      left: $.a+      right: $.b+      otherwise: L_join+location: 'Q.y'+input: '⟦ y ↦ ⟦ c ↦ ⟦ λ ⤍ 𝜎1 ⟧, a ↦ ⟦ φ ↦ Φ.hx, ke ↦ Φ.ty ⟧, b ↦ ⟦ φ ↦ Φ.zu ⟧, λ ⤍ L_fork ⟧.ke ⟧'+result: '⟦ λ ⤍ L_join, on🌵 ↦ ⟦ λ ⤍ 𝜎1 ⟧, left🌵 ↦ ρ.left🌵.ke, right🌵 ↦ ρ.right🌵.ke ⟧'+protocol: |+  𝕄(Φ.y):+    𝔼(L_fork):+      𝛿1·1 := 𝔻(𝜎1:λ)  # 𝔻(ξ.c)+      fabricated 𝑛·1·1 := ⟦ φ ↦ Φ.hx, ke ↦ Φ.ty ⟧ and Φ.zu:φ  # ⟦ left🌵 ↦ ⟦ φ ↦ Φ.hx, ke ↦ Φ.ty ⟧, right🌵 ↦ Φ.zu:φ, φ ↦ ⟦ λ ⤍ L_join, on🌵 ↦ 𝜎1:λ, left🌵 ↦ ρ.left🌵.φ, right🌵 ↦ ρ.right🌵.φ ⟧, ke ↦ ⟦ λ ⤍ L_join, on🌵 ↦ 𝜎1:λ, left🌵 ↦ ρ.left🌵.ke, right🌵 ↦ ρ.right🌵.ke ⟧ ⟧
− test-resources/evaluate-packs/fails-on-a-bare-symbol-and-a-branch-holding-one-symbol-when-strict.yaml
@@ -1,33 +0,0 @@-# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com-# SPDX-License-Identifier: MIT-----# yamllint disable rule:line-length-# The fork of 'bool.and' from #1817 under an entry with no 'lenient' key. A-# strict join pairs a bare symbol only with the symbol a φ chain ends in, and-# the chain of the second branch stops at the call of 'Φ.bool', so there is-# no symbol to pair it with and the firing gets stuck.-symbolic: |-  - λ: L_fork-    morph:-      𝑛1: $.a-      𝑛2: $.b-    join:-      𝑛3: [𝑛1, 𝑛2]-    𝑛: 𝑛3-location: 'Q.y'-input: |-  ⟦-    bool(if) ↦ ⟦ φ ↦ ξ.if( α0 ↦ ⟦ Δ ⤍ FF- ⟧, α1 ↦ ⟦ Δ ⤍ 00- ⟧ ) ⟧,-    y ↦ ⟦-      a ↦ ⟦ λ ⤍ 𝜎1 ⟧,-      b ↦ ⟦ φ ↦ Φ.bool( if ↦ ⟦ λ ⤍ L_fork, left ↦ ∅, right ↦ ∅, φ ↦ ⟦ λ ⤍ 𝜎2 ⟧ ⟧ ) ⟧,-      λ ⤍ L_fork-    ⟧.φ-  ⟧-fails: "No entry of --symbolic answers the λ function 'L_fork'"-protocol: |-  𝕄(Φ.y):-    𝔼(L_fork):-      𝑛1·1 := 𝜎1:λ  # 𝕄(ξ.a)-      𝑛2·1 := Φ.bool( if(left, right) ↦ ⟦ λ ⤍ L_fork, φ ↦ 𝜎2:λ ⟧ ):φ  # 𝕄(ξ.b)-      unjoined(L_fork)  # 𝜎1:λ vs Φ.bool( if(left, right) ↦ ⟦ λ ⤍ L_fork, φ ↦ 𝜎2:λ ⟧ ):φ
− test-resources/evaluate-packs/fails-on-a-bare-symbol-and-a-branch-holding-two-symbols-when-lenient.yaml
@@ -1,34 +0,0 @@-# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com-# SPDX-License-Identifier: MIT-----# yamllint disable rule:line-length-# A lenient join answers a copy of the branch a bare symbol stands against only-# when that branch holds exactly one symbol (#1817), since only then is it-# clear which symbol the bare one pairs with. Here the call of 'Φ.bool' holds-# two, '𝜎2' and '𝜎3', so the join is refused and the firing gets stuck.-symbolic: |-  - λ: L_fork-    morph:-      𝑛1: $.a-      𝑛2: $.b-    join:-      𝑛3: [𝑛1, 𝑛2]-    lenient: []-    𝑛: 𝑛3-location: 'Q.y'-input: |-  ⟦-    bool(if) ↦ ⟦ φ ↦ ξ.if( α0 ↦ ⟦ Δ ⤍ FF- ⟧, α1 ↦ ⟦ Δ ⤍ 00- ⟧ ) ⟧,-    y ↦ ⟦-      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):-    𝔼(L_fork):-      𝑛1·1 := 𝜎1:λ  # 𝕄(ξ.a)-      𝑛2·1 := Φ.bool( if(left, right) ↦ ⟦ λ ⤍ L_fork, φ ↦ 𝜎2:λ, was ↦ 𝜎3:λ ⟧ ):φ  # 𝕄(ξ.b)-      unjoined(L_fork)  # 𝜎1:λ vs Φ.bool( if(left, right) ↦ ⟦ λ ⤍ L_fork, φ ↦ 𝜎2:λ, was ↦ 𝜎3:λ ⟧ ):φ
− test-resources/evaluate-packs/fails-on-a-branch-and-a-decorator-of-it-when-strict.yaml
@@ -1,25 +0,0 @@-# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com-# SPDX-License-Identifier: MIT-----# yamllint disable rule:line-length-# The fork the lenient join of #1815 brings together, under an entry with no-# 'lenient' key. A copy and a decorator of it differ in shape, so the strict-# join refuses them and the firing gets stuck, as an entry is strict unless it-# says otherwise.-symbolic: |-  - λ: L_fork-    morph:-      𝑛1: $.a-      𝑛2: $.b-    join:-      𝑛3: [𝑛1, 𝑛2]-    𝑛: 𝑛3-location: 'Q.y'-input: '⟦ y ↦ ⟦ a ↦ ⟦ φ ↦ ⟦ λ ⤍ 𝜎1 ⟧, neg ↦ ⟦⟧ ⟧, b ↦ ⟦ φ ↦ ⟦ φ ↦ ⟦ λ ⤍ 𝜎2 ⟧, neg ↦ ⟦⟧ ⟧ ⟧, λ ⤍ L_fork ⟧.φ ⟧'-fails: "No entry of --symbolic answers the λ function 'L_fork'"-protocol: |-  𝕄(Φ.y):-    𝔼(L_fork):-      𝑛1·1 := ⟦ φ ↦ 𝜎1:λ, neg ↦ ⟦⟧ ⟧  # 𝕄(ξ.a)-      𝑛2·1 := ⟦ φ ↦ 𝜎2:λ, neg ↦ ⟦⟧ ⟧:φ  # 𝕄(ξ.b)-      unjoined(L_fork)  # ⟦ φ ↦ 𝜎1:λ, neg ↦ ⟦⟧ ⟧ vs ⟦ φ ↦ 𝜎2:λ, neg ↦ ⟦⟧ ⟧:φ
− test-resources/evaluate-packs/fails-on-a-branch-whose-phi-reads-its-own-object-when-strict.yaml
@@ -1,27 +0,0 @@-# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com-# SPDX-License-Identifier: MIT-----# yamllint disable rule:line-length-# The fork an 'unfold' line gets through (#1885, #1921), under an entry with-# no such line. The branch that is an ordinary object stays as 𝕄 left it, a-# formation whose φ reads its own attribute, so the 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)-      unjoined(L_fork)  # ⟦ φ ↦ eq, eq ↦ L_eq:λ ⟧ vs 𝜎1:λ:φ
− test-resources/evaluate-packs/fails-on-a-copy-whose-argument-reads-to-a-bare-symbol-when-not-deferring.yaml
@@ -1,31 +0,0 @@-# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com-# SPDX-License-Identifier: MIT-----# yamllint disable rule:line-length-# The fork the deferral of #1892 gets through, under an 'unfold' line that-# does not say 'defer: true' (#1921). The line morphs the φ of the copy-# 'step( tup ↦ Φ.items.tail )', which asks the bare symbol '𝜎1' for 'next',-# and the firing gets stuck on that symbol, as it did since '--deep' was gone.-symbolic: |-  - λ: L_fork-    morph:-      𝑛1: $.a-      𝑛2: $.b-    unfold:-      𝑛3: 𝑛2-    join:-      𝑛4: [𝑛1, 𝑛3]-    𝑛: ⟦ φ ↦ 𝑛4 ⟧-location: 'Q.y'-input: '⟦ step(tup) ↦ ⟦ φ ↦ tup.next ⟧, items ↦ ⟦ tail ↦ ⟦ λ ⤍ 𝜎1 ⟧ ⟧, y ↦ ⟦ a ↦ ⟦ λ ⤍ 𝜎2 ⟧, b ↦ Φ.step( tup ↦ Φ.items.tail ), λ ⤍ L_fork ⟧.φ ⟧'-fails: "No entry of --symbolic answers the λ function '𝜎1'"-protocol: |-  𝕄(Φ.y):-    𝔼(L_fork):-      𝑛1·1 := 𝜎2:λ  # 𝕄(ξ.a)-      𝕄(Φ.a🌵1):-        𝑛·1·1 := Φ.step( tup ↦ Φ.items.tail )-      𝑛2·1 := 𝑛·1·1  # 𝕄(ξ.b)-      𝕄(Φ.a🌵2):-        unanswered(𝜎1)  # 𝕄(𝜎1:λ)-      stuck(𝜎1)
− test-resources/evaluate-packs/fails-on-a-fork-whose-branches-do-not-join.yaml
@@ -1,26 +0,0 @@-# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com-# SPDX-License-Identifier: MIT-----# yamllint disable rule:line-length-# The very fork '--partial' parks, without the option: a firing with nothing to-# answer fails the run, exactly as a λ function no entry of the '--symbolic'-# file answers does. The protocol keeps what the firing had reached before it-# got stuck, since it records what 𝔼 was asked for and not only what it could-# answer.-symbolic: |-  - λ: L_fork-    morph:-      𝑛1: $.a-      𝑛2: $.b-    join:-      𝑛3: [𝑛1, 𝑛2]-    𝑛: 𝑛3-location: 'Q.y'-input: '⟦ y ↦ ⟦ a ↦ ⟦ l ↦ ⟦ λ ⤍ 𝜎1 ⟧ ⟧, b ↦ ⟦ l ↦ ⟦ λ ⤍ 𝜎2 ⟧, x ↦ ⟦⟧ ⟧, λ ⤍ L_fork ⟧.l ⟧'-fails: "No entry of --symbolic answers the λ function 'L_fork'"-protocol: |-  𝕄(Φ.y):-    𝔼(L_fork):-      𝑛1·1 := 𝜎1:λ:l  # 𝕄(ξ.a)-      𝑛2·1 := ⟦ l ↦ 𝜎2:λ, x ↦ ⟦⟧ ⟧  # 𝕄(ξ.b)-      unjoined(L_fork)  # 𝜎1:λ:l vs ⟦ l ↦ 𝜎2:λ, x ↦ ⟦⟧ ⟧
test-resources/evaluate-packs/fails-on-a-lambda-no-entry-answers.yaml view
@@ -14,4 +14,4 @@ fails: "No entry of --symbolic answers the λ function 'L_none'" protocol: |   𝕄(Φ.x):-    unanswered(L_none)  # 𝕄(L_none:λ)+    unanswered L_none  # 𝕄(L_none:λ)
− test-resources/evaluate-packs/fails-on-a-remint-of-a-term-that-comes-to-no-symbol.yaml
@@ -1,36 +0,0 @@-# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com-# SPDX-License-Identifier: MIT-----# yamllint disable rule:line-length-# The tail of the pair below comes to a formation that is no copy of an object-# of the world and whose φ chain ends in nothing, so a 'remint' step has no-# symbol to answer for it. It writes an 'unreminted' line naming the term-# instead of minting a symbol nobody can build, and the firing gets stuck the-# way one whose 'join' line could not pair its branches does (#1923).-symbolic: |-  - λ: L_shrink-    morph:-      𝑛1: $.x-    rewrite:-      𝑛2:-        of: 𝑛1-        rules:-          - name: small-pair-            pattern: ⟦ !B1, head ↦ 𝑒1, tail ↦ 𝑒2, !B2 ⟧-            result: ⟦ !B1, head ↦ 𝑒3, tail ↦ 𝑒4, !B2 ⟧-            where:-              - meta: 𝑒3-                function: remint-                args: [𝑒1]-              - meta: 𝑒4-                function: remint-                args: [𝑒2]-    𝑛: 𝑛2-location: 'Q.y'-input: '⟦ y ↦ ⟦ x ↦ ⟦ head ↦ ⟦ λ ⤍ 𝜎1 ⟧, tail ↦ ⟦ k ↦ ⟦ λ ⤍ 𝜎2 ⟧ ⟧ ⟧, λ ⤍ L_shrink ⟧.tail ⟧'-fails: "No entry of --symbolic answers the λ function 'L_shrink'"-protocol: |-  𝕄(Φ.y):-    𝔼(L_shrink):-      𝑛1·1 := ⟦ head ↦ 𝜎1:λ, tail ↦ 𝜎2:λ:k ⟧  # 𝕄(ξ.x)-      unreminted(L_shrink)  # 𝜎2:λ:k
− test-resources/evaluate-packs/fails-on-a-remint-step-given-two-terms.yaml
@@ -1,29 +0,0 @@-# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com-# SPDX-License-Identifier: MIT-----# yamllint disable rule:line-length-# A 'remint' step mints one symbol for one term, so a step handing it two terms-# is refused the moment the rule matches, instead of minting for either (#1909).-symbolic: |-  - λ: L_shrink-    morph:-      𝑛1: $.x-    rewrite:-      𝑛2:-        of: 𝑛1-        rules:-          - name: small-pair-            pattern: ⟦ !B1, head ↦ 𝑒1, tail ↦ 𝑒2, !B2 ⟧-            result: ⟦ !B1, head ↦ 𝑒3, !B2 ⟧-            where:-              - meta: 𝑒3-                function: remint-                args: [𝑒1, 𝑒2]-    𝑛: 𝑛2-location: 'Q.y'-input: '⟦ y ↦ ⟦ x ↦ ⟦ head ↦ ⟦ λ ⤍ 𝜎1 ⟧, tail ↦ ⟦ λ ⤍ 𝜎2 ⟧ ⟧, λ ⤍ L_shrink ⟧.head ⟧'-fails: 'Function remint() requires exactly 1 argument as expression'-protocol: |-  𝕄(Φ.y):-    𝔼(L_shrink):-      𝑛1·1 := ⟦ head ↦ 𝜎1:λ, tail ↦ 𝜎2:λ ⟧  # 𝕄(ξ.x)
+ test-resources/evaluate-packs/fails-on-a-side-whose-morph-got-stuck.yaml view
@@ -0,0 +1,28 @@+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com+# SPDX-License-Identifier: MIT+---+# yamllint disable rule:line-length+# The 'morph' of '$.a' gets stuck on 'L_none', which no entry answers. A join+# keeps no failure for later, so the firing gets stuck at once, before the+# 'morph' of '$.b' runs (#1996).+symbolic: |+  - λ: L_fork+    dataize:+      𝛿1: $.c+    morph:+      𝑛1: $.a+      𝑛2: $.b+    join:+      on: 𝛿1+      left: 𝑛1+      right: 𝑛2+location: 'Q.y'+input: '⟦ y ↦ ⟦ c ↦ ⟦ λ ⤍ 𝜎1 ⟧, a ↦ ⟦ λ ⤍ L_none ⟧.x, b ↦ ⟦ Δ ⤍ 05- ⟧, λ ⤍ L_fork ⟧.φ ⟧'+fails: "No entry of --symbolic answers the λ function 'L_none'"+protocol: |+  𝕄(Φ.y):+    𝔼(L_fork):+      𝛿1·1 := 𝔻(𝜎1:λ)  # 𝔻(ξ.c)+      𝕄(Φ.a🌵1):+        unanswered L_none  # 𝕄(L_none:λ)+      stuck L_none
− test-resources/evaluate-packs/fires-a-formation-again-where-the-budget-stalled-it.yaml
@@ -1,68 +0,0 @@-# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com-# SPDX-License-Identifier: MIT-----# yamllint disable rule:line-length-# 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:-      𝛿1: ξ.arg-    𝑛: ⟦ λ ⤍ 𝜎 ⟧-  - λ: L_wrap-    dataize:-      𝛿1: ξ.w-    𝑛: ⟦ λ ⤍ 𝜎 ⟧-  - λ: L_pair-    morph:-      𝑛1: $.x-      𝑛2: $.f-    evaluate:-      𝑛3: 𝑛2-    𝑛: ⟦ φ ↦ 𝑛3 ⟧-location: 'Q.r'-partial: true-acyclic: plausible-steps: 12-input: >-  ⟦ 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: |-  𝕄(Φ.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-stalled-formation-again-after-what-its-own-firing-answered.yaml
@@ -1,64 +0,0 @@-# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com-# SPDX-License-Identifier: MIT-----# 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. '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:-      𝛿1: ξ.first-      𝛿2: ξ.second-    𝑛: ⟦ λ ⤍ 𝜎 ⟧-  - λ: L_inner-    𝑛: ⟦ Δ ⤍ 01- ⟧-  - λ: L_pair-    morph:-      𝑛1: $.x-      𝑛2: $.y-    𝑛: ⟦ φ ↦ ⟦ λ ⤍ 𝜎 ⟧ ⟧-location: 'Q.p'-partial: true-acyclic: plausible-input: >-  ⟦ 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: |-  𝕄(Φ.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
@@ -1,72 +0,0 @@-# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com-# SPDX-License-Identifier: MIT-----# yamllint disable rule:line-length-# The memo of '--acyclic=plausible' keeps a stall only while nothing new was-# 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:-      𝛿1: $.arg-    𝑛: ⟦ λ ⤍ 𝜎 ⟧-  - λ: L_g-    𝑛: ⟦ λ ⤍ 𝜎 ⟧-  - λ: L_pair-    morph:-      𝑛1: $.x-      𝑛2: $.g-      𝑛3: $.f-    evaluate:-      𝑛4: 𝑛2-      𝑛5: 𝑛3-    𝑛: ⟦ φ ↦ 𝑛5 ⟧-location: 'Q.p'-partial: true-acyclic: plausible-input: >-  ⟦ 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: |-  𝕄(Φ.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-the-arguments-of-a-copy-a-cut-stops-when-deferring.yaml
@@ -1,57 +0,0 @@-# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com-# SPDX-License-Identifier: MIT-----# yamllint disable rule:line-length-# The cut of #1895 over a recursion whose argument changes every round: the-# copy 'rec( n ↦ Φ.inc( … ) )' is cut by '--acyclic=plausible', and its-# argument is a call of 'L_inc' nothing has fired yet. Before it writes the-# 'looped' line, an 'unfold' line that says 'defer: true' morphs that argument-# at '𝑛2.n' and fires what it comes to, so the protocol shows the object-# 'Φ.num' the argument stands for, and the markup names it in the call of the-# cut (#1916, #1921).-symbolic: |-  - λ: L_fork-    morph:-      𝑛1: $.a-      𝑛2: $.b-    unfold:-      𝑛3: {of: 𝑛2, defer: true}-    join:-      𝑛4: [𝑛1, 𝑛3]-    𝑛: ⟦ φ ↦ 𝑛4 ⟧-  - λ: L_inc-    dataize:-      𝛿1: $.x-    𝑛: Φ.num( φ ↦ ⟦ λ ⤍ 𝜎 ⟧ )-location: 'Q.y'-acyclic: plausible-input: '⟦ fork(a, b) ↦ ⟦ λ ⤍ L_fork ⟧, num(φ) ↦ ⟦⟧, inc(x) ↦ ⟦ λ ⤍ L_inc ⟧, rec(n) ↦ ⟦ φ ↦ Φ.fork( ⟦ λ ⤍ 𝜎1 ⟧, Φ.rec( Φ.inc( ξ.n ) ) ) ⟧, y ↦ Φ.rec( ⟦ φ ↦ ⟦ λ ⤍ 𝜎2 ⟧ ⟧ ).φ.φ ⟧'-result: '𝜎5:λ'-protocol: |-  𝕄(Φ.y):-    𝑛·0·1 := Φ.rec( n ↦ 𝜎2:λ:φ )-    𝑛·0·2 := Φ.fork( a ↦ 𝜎1:λ )-    𝑛·0·3 := 𝑛·0·2( b ↦ Φ.rec( Φ.inc( 𝜎2:λ:φ ) ) )-    𝔼(L_fork):-      𝑛1·1 := 𝜎1:λ  # 𝕄(ξ.a)-      𝕄(Φ.a🌵1):-        𝑛·1·1 := Φ.rec( n ↦ Φ.inc( 𝜎2:λ:φ ) )-      𝑛2·1 := 𝑛·1·1  # 𝕄(ξ.b)-      𝕄(Φ.a🌵2):-        𝑛·1·2 := Φ.fork( a ↦ 𝜎1:λ )-        𝑛·1·3 := 𝑛·1·2( b ↦ Φ.rec( Φ.inc( Φ.inc( 𝜎2:λ:φ ) ) ) )-      𝑛3·1 := 𝑛·1·3  # 𝕄(𝑛2.φ)-      𝕄(𝑛2.n):-        𝑛·1·4 := Φ.inc( x ↦ 𝜎2:λ:φ )-      𝔼(L_inc):  # 𝔼(𝑛2.n)-        𝛿1·2 := 𝔻(𝜎2:λ)  # 𝔻(ξ.x)-        𝑛·2·1 := Φ.num( φ ↦ 𝜎3:λ )  # 𝑛-        𝕄(𝑛·2·1):-          𝑛·2·2 := Φ.num( φ ↦ 𝜎3:λ )-        𝑛·2·3 := 𝑛·2·2  # 𝕄(𝑛·2·1)-      looped(𝑛·0·3) := 𝜎4  # 𝔼(𝑛2), plausible-      𝑛3·1 := 𝜎4:λ  # 𝔼(𝑛2)-      𝔻(𝜎5:λ) ∈ { 𝔻(𝜎1:λ), 𝔻(𝜎4:λ) }-      𝑛4·1 := 𝜎5:λ  # [𝑛1, 𝑛3]-      𝑛·1·5 := 𝜎5:λ:φ  # 𝑛-      𝑛·1·6 := 𝜎5:λ:φ  # 𝕄(𝑛·1·5)
+ test-resources/evaluate-packs/fires-the-arguments-of-a-copy-a-defer-line-defers.yaml view
@@ -0,0 +1,63 @@+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com+# SPDX-License-Identifier: MIT+---+# yamllint disable rule:line-length+# The deferral of #1892 over a copy with two arguments: 'x' reads to the bare+# symbol '𝜎1', so the 'defer' line reading the right side defers the copy,+# and 'w' is a call of 'L_inc'. Before it writes the 'deferred' line, the+# line morphs 'w' in the copy the meta '𝑛2' is bound to, at '𝑛2.w', and+# fires what it comes to, so the line names the object 'Φ.num' the argument+# stands for, instead of the call as the code spells it (#1916, #1933,+# and #1982). The left side is the bare symbol '𝜎3', so the two sides differ+# in their symbols alone, and the join makes one term of them.+symbolic: |+  - λ: L_inc+    dataize:+      𝛿1: $.x+    𝑛: Φ.num( φ ↦ ⟦ λ ⤍ 𝜎 ⟧ )+  - λ: L_fork+    dataize:+      𝛿1: $.c+    morph:+      𝑛1: $.a+      𝑛2: $.b+    defer:+      𝑛3: 𝑛1+      𝑛4: 𝑛2+    join:+      on: 𝛿1+      left: 𝑛3+      right: 𝑛4+location: 'Q.y'+input: '⟦ box(x, w) ↦ ⟦ φ ↦ x.next ⟧, num(φ) ↦ ⟦⟧, inc(x) ↦ ⟦ λ ⤍ L_inc ⟧, items ↦ ⟦ tail ↦ ⟦ λ ⤍ 𝜎1 ⟧ ⟧, y ↦ ⟦ x ↦ ⟦ c ↦ ⟦ λ ⤍ 𝜎2 ⟧, a ↦ ⟦ λ ⤍ 𝜎3 ⟧, b ↦ Φ.box( x ↦ Φ.items.tail, w ↦ Φ.inc( ⟦ Δ ⤍ 2A- ⟧ ) ), λ ⤍ L_fork ⟧, λ ⤍ L_inc ⟧.φ ⟧'+result: '𝜎7:λ'+protocol: |+  𝕄(Φ.y):+    𝔼(L_inc):+      𝔻(Φ.a🌵0):+        𝔼(L_fork):+          𝛿1·2 := 𝔻(𝜎2:λ)  # 𝔻(ξ.c)+          𝑛1·2 := 𝜎3:λ  # 𝕄(ξ.a)+          𝕄(Φ.a🌵3):+            𝑛·2·1 := Φ.box( x ↦ Φ.items.tail )+            𝑛·2·2 := 𝑛·2·1( w ↦ Φ.inc( 2A-:Δ ) )+          𝑛2·2 := 𝑛·2·2  # 𝕄(ξ.b)+          𝑛3·2 := 𝑛1·2  # 𝑛1+          𝕄(𝑛2.w):+            𝑛·2·3 := Φ.inc( x ↦ 2A-:Δ )+          𝔼(L_inc):  # 𝔼(𝑛2.w)+            𝔻(Φ.a🌵4):+              𝛿·3·1 := 2A-+            𝛿1·3 := 𝛿·3·1  # 𝔻(ξ.x)+            𝑛·3·1 := Φ.num( φ ↦ 𝜎4:λ )  # 𝑛+            𝕄(𝑛·3·1):+              𝑛·3·2 := Φ.num( φ ↦ 𝜎4:λ )+            𝑛·3·3 := 𝑛·3·2  # 𝕄(𝑛·3·1)+          deferred 𝜎5 := Φ.box( x ↦ 𝜎1:λ, w ↦ 𝑛·3·2 )  # 𝕄(𝑛2.φ)+          joined 𝜎6 := 𝛿1·2 ? 𝜎3 : 𝜎5+          𝑛·2·4 := 𝜎6:λ  # 𝑛3, 𝑛4+      𝛿1·1 := 𝔻(𝜎6:λ)  # 𝔻(ξ.x)+      𝑛·1·1 := Φ.num( φ ↦ 𝜎7:λ )  # 𝑛+      𝕄(𝑛·1·1):+        𝑛·1·2 := Φ.num( φ ↦ 𝜎7:λ )+      𝑛·1·3 := 𝑛·1·2  # 𝕄(𝑛·1·1)
− test-resources/evaluate-packs/fires-the-arguments-of-a-copy-it-defers.yaml
@@ -1,49 +0,0 @@-# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com-# SPDX-License-Identifier: MIT-----# yamllint disable rule:line-length-# The deferral of #1892 over a copy with two arguments: 'x' reads to the bare-# symbol '𝜎1', so the copy is deferred, and 'w' is a call of 'L_inc'. Before it-# writes the 'deferred' line, the 'unfold' line morphs 'w' at '𝑛2.w' and fires-# what it comes to, so the line names the object 'Φ.num' the argument stands-# for, instead of the call as the code spells it (#1916, #1921).-symbolic: |-  - λ: L_fork-    morph:-      𝑛1: $.a-      𝑛2: $.b-    unfold:-      𝑛3: {of: 𝑛2, defer: true}-    join:-      𝑛4: [𝑛1, 𝑛3]-    𝑛: ⟦ φ ↦ 𝑛4 ⟧-  - λ: L_inc-    dataize:-      𝛿1: $.x-    𝑛: Φ.num( φ ↦ ⟦ λ ⤍ 𝜎 ⟧ )-location: 'Q.y'-input: '⟦ box(x, w) ↦ ⟦ φ ↦ x.next ⟧, num(φ) ↦ ⟦⟧, inc(x) ↦ ⟦ λ ⤍ L_inc ⟧, items ↦ ⟦ tail ↦ ⟦ λ ⤍ 𝜎1 ⟧ ⟧, y ↦ ⟦ a ↦ ⟦ λ ⤍ 𝜎2 ⟧, b ↦ Φ.box( x ↦ Φ.items.tail, w ↦ Φ.inc( ⟦ Δ ⤍ 2A- ⟧ ) ), λ ⤍ L_fork ⟧.φ ⟧'-result: '𝜎5:λ'-protocol: |-  𝕄(Φ.y):-    𝔼(L_fork):-      𝑛1·1 := 𝜎2:λ  # 𝕄(ξ.a)-      𝕄(Φ.a🌵1):-        𝑛·1·1 := Φ.box( x ↦ Φ.items.tail )-        𝑛·1·2 := 𝑛·1·1( w ↦ Φ.inc( 2A-:Δ ) )-      𝑛2·1 := 𝑛·1·2  # 𝕄(ξ.b)-      𝕄(𝑛2.w):-        𝑛·1·3 := Φ.inc( x ↦ 2A-:Δ )-      𝔼(L_inc):  # 𝔼(𝑛2.w)-        𝔻(Φ.a🌵2):-          𝛿·2·1 := 2A--        𝛿1·2 := 𝛿·2·1  # 𝔻(ξ.x)-        𝑛·2·1 := Φ.num( φ ↦ 𝜎3:λ )  # 𝑛-        𝕄(𝑛·2·1):-          𝑛·2·2 := Φ.num( φ ↦ 𝜎3:λ )-        𝑛·2·3 := 𝑛·2·2  # 𝕄(𝑛·2·1)-      deferred(𝜎4) := Φ.box( x ↦ 𝜎1:λ, w ↦ 𝑛·2·2 )  # 𝕄(𝑛2.φ)-      𝔻(𝜎5:λ) ∈ { 𝔻(𝜎2:λ), 𝔻(𝜎4:λ) }-      𝑛4·1 := 𝜎5:λ  # [𝑛1, 𝑛3]-      𝑛·1·4 := 𝜎5:λ:φ  # 𝑛-      𝑛·1·5 := 𝜎5:λ:φ  # 𝕄(𝑛·1·4)
test-resources/evaluate-packs/fires-what-a-rewrite-line-unwrapped.yaml view
@@ -4,7 +4,9 @@ # yamllint disable rule:line-length # The lines of an entry run in the order of the metas they read, not of their # blocks: the 'evaluate' line fires what the 'rewrite' line unwrapped, so the-# 'rewrite' line runs first, although its block comes later (#1903).+# 'rewrite' line runs first, although its block comes later (#1903, #2005).+# The rule leaves what it made as it is, without normalizing it, and the+# 'evaluate' line fires it. symbolic: |   - λ: L_inner     𝑛: ⟦ φ ↦ ⟦ λ ⤍ 𝜎 ⟧ ⟧@@ -28,7 +30,7 @@   𝕄(Φ.y):     𝔼(L_pick):       𝑛1·1 := L_inner:λ:φ  # 𝕄(ξ.x)-      𝑛2·1 := L_inner:λ  # 𝑛1+      𝑛2·1 := L_inner:λ  # rewrite(𝑛1)       𝔼(L_inner):  # 𝔼(𝑛2)         𝑛·2·1 := 𝜎1:λ:φ  # 𝑛         𝑛·2·2 := 𝜎1:λ:φ  # 𝕄(𝑛·2·1)
+ test-resources/evaluate-packs/gets-a-join-stuck-when-the-path-of-its-condition-holds-no-datum.yaml view
@@ -0,0 +1,21 @@+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com+# SPDX-License-Identifier: MIT+---+# yamllint disable rule:line-length+# The entry of 'L_join' reads the bytes of its condition off 'on🌵', dataizing+# nothing, but the formation it fires binds 'on🌵' to a path and not to a datum.+# The join has no bytes to decide by, so the firing gets stuck before it+# morphs either side, as a firing no entry answers does (#2024).+symbolic: |+  - λ: L_join+    join:+      on: $.on🌵+      left: $.left🌵+      right: $.right🌵+location: 'Q.y'+input: '⟦ ku ↦ ⟦ Δ ⤍ 01- ⟧, y ↦ ⟦ on🌵 ↦ Φ.ku, left🌵 ↦ ⟦ φ ↦ Φ.hx ⟧, right🌵 ↦ ⟦ φ ↦ Φ.zu ⟧, λ ⤍ L_join ⟧.φ ⟧'+fails: "No entry of --symbolic answers the λ function 'L_join'"+protocol: |+  𝕄(Φ.y):+    𝔼(L_join):+      stuck L_join
+ test-resources/evaluate-packs/hands-a-named-argument-to-both-sides-of-an-attribute-of-a-fabricated-formation.yaml view
@@ -0,0 +1,38 @@+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com+# SPDX-License-Identifier: MIT+---+# yamllint disable rule:line-length+# The two sides of 'L_fork' differ in shape, so the firing fabricates a+# formation (#2024). Both sides bind 'ke' to a formation leaving 'x' void, so+# the copy of 'L_join' under 'ke' leaves 'x' void too and asks each side for+# 'ke' applied to 'x' (#2031). The program hands 'x' over by name and asks the+# answer for 'k': the copy takes 'x', both sides read it, and the join meets+# two sides that are the same.+symbolic: |+  - λ: L_fork+    dataize:+      𝛿1: $.c+    join:+      on: 𝛿1+      left: $.a+      right: $.b+      otherwise: L_join+  - λ: L_join+    join:+      on: $.on🌵+      left: $.left🌵+      right: $.right🌵+      otherwise: L_join+location: 'Q.y'+input: '⟦ y ↦ ⟦ c ↦ ⟦ λ ⤍ 𝜎1 ⟧, a ↦ ⟦ φ ↦ Φ.hx, ke ↦ ⟦ x ↦ ∅, k ↦ ξ.x ⟧ ⟧, b ↦ ⟦ ke ↦ ⟦ x ↦ ∅, k ↦ ξ.x ⟧ ⟧, λ ⤍ L_fork ⟧.ke( x ↦ ⟦ λ ⤍ 𝜎2 ⟧ ).k ⟧'+result: '𝜎2:λ'+protocol: |+  𝕄(Φ.y):+    𝔼(L_fork):+      𝛿1·1 := 𝔻(𝜎1:λ)  # 𝔻(ξ.c)+      fabricated 𝑛·1·1 := ⟦ φ ↦ Φ.hx, ke(x) ↦ x:k ⟧ and ⟦ ke(x) ↦ x:k ⟧  # ⟦ left🌵 ↦ ⟦ φ ↦ Φ.hx, ke(x) ↦ x:k ⟧, right🌵 ↦ ⟦ ke(x) ↦ x:k ⟧, φ ↦ ⟦ λ ⤍ L_join, on🌵 ↦ 𝜎1:λ, left🌵 ↦ ρ.left🌵.φ, right🌵 ↦ ρ.right🌵.φ ⟧, ke(x) ↦ ⟦ λ ⤍ L_join, on🌵 ↦ 𝜎1:λ, left🌵 ↦ ρ.left🌵.ke( x ↦ x ), right🌵 ↦ ρ.right🌵.ke( x ↦ x ) ⟧ ⟧+    𝑛·0·1 := ⟦ λ ⤍ L_join, x ↦ ∅, on🌵 ↦ 𝜎1:λ, left🌵 ↦ ρ.left🌵.ke( x ↦ x ), right🌵 ↦ ρ.right🌵.ke( x ↦ x ) ⟧( x ↦ 𝜎2:λ )+    𝔼(L_join):+      𝑛·2·1 := ⟦ x ↦ ∅, k ↦ x ⟧( x ↦ 𝜎2:λ )  # 𝕄(Φ.a🌵3)+      𝑛·2·2 := ⟦ x ↦ ∅, k ↦ x ⟧( x ↦ 𝜎2:λ )  # 𝕄(Φ.a🌵4)+      𝑛·2·3 := 𝑛·2·2  # 𝕄(ξ.left🌵), 𝕄(ξ.right🌵)
+ test-resources/evaluate-packs/hands-a-positional-argument-to-both-sides-of-an-attribute-of-a-fabricated-formation.yaml view
@@ -0,0 +1,38 @@+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com+# SPDX-License-Identifier: MIT+---+# yamllint disable rule:line-length+# The two sides of 'L_fork' differ in shape, so the firing fabricates a+# formation (#2024). Both sides bind 'ke' to a formation leaving 'x' void, so+# the copy of 'L_join' under 'ke' leaves 'x' void too, as the first void it+# holds, and asks each side for 'ke' applied to 'x' (#2031). The program hands+# 'x' over by position and asks the answer for 'k': the copy takes 'x', both+# sides read it, and the join meets two sides that are the same.+symbolic: |+  - λ: L_fork+    dataize:+      𝛿1: $.c+    join:+      on: 𝛿1+      left: $.a+      right: $.b+      otherwise: L_join+  - λ: L_join+    join:+      on: $.on🌵+      left: $.left🌵+      right: $.right🌵+      otherwise: L_join+location: 'Q.y'+input: '⟦ y ↦ ⟦ c ↦ ⟦ λ ⤍ 𝜎1 ⟧, a ↦ ⟦ φ ↦ Φ.hx, ke ↦ ⟦ x ↦ ∅, k ↦ ξ.x ⟧ ⟧, b ↦ ⟦ ke ↦ ⟦ x ↦ ∅, k ↦ ξ.x ⟧ ⟧, λ ⤍ L_fork ⟧.ke( ⟦ λ ⤍ 𝜎2 ⟧ ).k ⟧'+result: '𝜎2:λ'+protocol: |+  𝕄(Φ.y):+    𝔼(L_fork):+      𝛿1·1 := 𝔻(𝜎1:λ)  # 𝔻(ξ.c)+      fabricated 𝑛·1·1 := ⟦ φ ↦ Φ.hx, ke(x) ↦ x:k ⟧ and ⟦ ke(x) ↦ x:k ⟧  # ⟦ left🌵 ↦ ⟦ φ ↦ Φ.hx, ke(x) ↦ x:k ⟧, right🌵 ↦ ⟦ ke(x) ↦ x:k ⟧, φ ↦ ⟦ λ ⤍ L_join, on🌵 ↦ 𝜎1:λ, left🌵 ↦ ρ.left🌵.φ, right🌵 ↦ ρ.right🌵.φ ⟧, ke(x) ↦ ⟦ λ ⤍ L_join, on🌵 ↦ 𝜎1:λ, left🌵 ↦ ρ.left🌵.ke( x ↦ x ), right🌵 ↦ ρ.right🌵.ke( x ↦ x ) ⟧ ⟧+    𝑛·0·1 := ⟦ λ ⤍ L_join, x ↦ ∅, on🌵 ↦ 𝜎1:λ, left🌵 ↦ ρ.left🌵.ke( x ↦ x ), right🌵 ↦ ρ.right🌵.ke( x ↦ x ) ⟧( x ↦ 𝜎2:λ )+    𝔼(L_join):+      𝑛·2·1 := ⟦ x ↦ ∅, k ↦ x ⟧( x ↦ 𝜎2:λ )  # 𝕄(Φ.a🌵3)+      𝑛·2·2 := ⟦ x ↦ ∅, k ↦ x ⟧( x ↦ 𝜎2:λ )  # 𝕄(Φ.a🌵4)+      𝑛·2·3 := 𝑛·2·2  # 𝕄(ξ.left🌵), 𝕄(ξ.right🌵)
+ test-resources/evaluate-packs/hands-a-side-of-a-fabricated-formation-only-the-voids-its-attribute-leaves.yaml view
@@ -0,0 +1,43 @@+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com+# SPDX-License-Identifier: MIT+---+# yamllint disable rule:line-length+# The two sides of 'L_fork' differ in shape, so the firing fabricates a+# formation (#2024). The left side binds 'ke' to a formation leaving 'x' void+# and the right one to a formation leaving 'x' and 'w' void, so the copy of+# 'L_join' under 'ke' leaves both void and applies each side to the voids it+# leaves alone (#2031), since 'miss' turns an application to an attribute a+# formation lacks into ⊥. The program hands 'x' over, so the right side gets+# 'w' bound to ⊥, the two sides differ in shape and are fabricated again, and+# the copy under 'k' finds 'x' on both of them.+symbolic: |+  - λ: L_fork+    dataize:+      𝛿1: $.c+    join:+      on: 𝛿1+      left: $.a+      right: $.b+      otherwise: L_join+  - λ: L_join+    join:+      on: $.on🌵+      left: $.left🌵+      right: $.right🌵+      otherwise: L_join+location: 'Q.y'+input: '⟦ y ↦ ⟦ c ↦ ⟦ λ ⤍ 𝜎1 ⟧, a ↦ ⟦ φ ↦ Φ.hx, ke ↦ ⟦ x ↦ ∅, k ↦ ξ.x ⟧ ⟧, b ↦ ⟦ ke ↦ ⟦ x ↦ ∅, w ↦ ∅, k ↦ ξ.x ⟧ ⟧, λ ⤍ L_fork ⟧.ke( x ↦ ⟦ φ ↦ ⟦ λ ⤍ 𝜎2 ⟧ ⟧ ).k.φ ⟧'+result: '𝜎2:λ'+protocol: |+  𝕄(Φ.y):+    𝔼(L_fork):+      𝛿1·1 := 𝔻(𝜎1:λ)  # 𝔻(ξ.c)+      fabricated 𝑛·1·1 := ⟦ φ ↦ Φ.hx, ke(x) ↦ x:k ⟧ and ⟦ ke(x, w) ↦ x:k ⟧  # ⟦ left🌵 ↦ ⟦ φ ↦ Φ.hx, ke(x) ↦ x:k ⟧, right🌵 ↦ ⟦ ke(x, w) ↦ x:k ⟧, φ ↦ ⟦ λ ⤍ L_join, on🌵 ↦ 𝜎1:λ, left🌵 ↦ ρ.left🌵.φ, right🌵 ↦ ρ.right🌵.φ ⟧, ke(x, w) ↦ ⟦ λ ⤍ L_join, on🌵 ↦ 𝜎1:λ, left🌵 ↦ ρ.left🌵.ke( x ↦ x ), right🌵 ↦ ρ.right🌵.ke( x ↦ x, w ↦ w ) ⟧ ⟧+    𝑛·0·1 := ⟦ λ ⤍ L_join, x ↦ ∅, w ↦ ∅, on🌵 ↦ 𝜎1:λ, left🌵 ↦ ρ.left🌵.ke( x ↦ x ), right🌵 ↦ ρ.right🌵.ke( x ↦ x, w ↦ w ) ⟧( x ↦ 𝜎2:λ:φ )+    𝔼(L_join):+      𝑛·2·1 := ⟦ x ↦ ∅, k ↦ x ⟧( x ↦ 𝜎2:λ:φ )  # 𝕄(Φ.a🌵3)+      𝑛·2·2 := ⟦ x ↦ ∅, w ↦ ∅, k ↦ x ⟧( x ↦ 𝜎2:λ:φ )  # 𝕄(Φ.a🌵4)+      𝑛·2·3 := 𝑛·2·2( w ↦ ⊥ )  # 𝕄(Φ.a🌵4)+      fabricated 𝑛·2·4 := 𝑛·2·1 and 𝑛·2·3  # ⟦ left🌵 ↦ 𝑛·2·1, right🌵 ↦ 𝑛·2·3, x ↦ ⟦ λ ⤍ L_join, on🌵 ↦ 𝜎1:λ, left🌵 ↦ ρ.left🌵.x, right🌵 ↦ ρ.right🌵.x ⟧, k ↦ ⟦ λ ⤍ L_join, on🌵 ↦ 𝜎1:λ, left🌵 ↦ ρ.left🌵.k, right🌵 ↦ ρ.right🌵.k ⟧, w ↦ ⟦ λ ⤍ L_join, on🌵 ↦ 𝜎1:λ, left🌵 ↦ ρ.left🌵.w, right🌵 ↦ ρ.right🌵.w ⟧ ⟧+    𝔼(L_join):+      𝑛·3·1 := 𝜎2:λ:φ  # 𝕄(ξ.left🌵), 𝕄(ξ.right🌵)
− test-resources/evaluate-packs/joins-a-bare-symbol-with-a-branch-holding-one-symbol-when-lenient.yaml
@@ -1,39 +0,0 @@-# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com-# SPDX-License-Identifier: MIT-----# yamllint disable rule:line-length-# The fork of 'bool.and' from #1817: one branch is a bare symbol, the other a-# call of 'Φ.bool', whose φ is code and not a formation, so its φ chain ends in-# no symbol. The call holds one symbol, '𝜎2', so a lenient join answers a copy-# of the call with '𝜎2' replaced by a fresh '𝜎3', which stands for either-# '𝜎1' or '𝜎2'. The join reads nothing outside the two branches.-symbolic: |-  - λ: L_fork-    morph:-      𝑛1: $.a-      𝑛2: $.b-    join:-      𝑛3: [𝑛1, 𝑛2]-    lenient: []-    𝑛: 𝑛3-location: 'Q.y'-input: |-  ⟦-    bool(if) ↦ ⟦ φ ↦ ξ.if( α0 ↦ ⟦ Δ ⤍ FF- ⟧, α1 ↦ ⟦ Δ ⤍ 00- ⟧ ) ⟧,-    y ↦ ⟦-      a ↦ ⟦ λ ⤍ 𝜎1 ⟧,-      b ↦ ⟦ φ ↦ Φ.bool( if ↦ ⟦ λ ⤍ L_fork, left ↦ ∅, right ↦ ∅, φ ↦ ⟦ λ ⤍ 𝜎2 ⟧ ⟧ ) ⟧,-      λ ⤍ L_fork-    ⟧.φ-  ⟧-result: '⟦ if(left, right) ↦ ⟦ λ ⤍ L_fork, φ ↦ 𝜎3:λ ⟧, φ ↦ if( FF-:Δ, 00-:Δ ) ⟧'-protocol: |-  𝕄(Φ.y):-    𝔼(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-bool-literal-a-rewrite-line-brought-into-the-shape-of-the-other-side.yaml view
@@ -0,0 +1,52 @@+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com+# SPDX-License-Identifier: MIT+---+# yamllint disable rule:line-length+# The left side holds the literal 'Φ.true', the way 'bool.not' of EO comes+# down, while the right side holds the bool a computation answers, a 'Φ.bool'+# applied to its datum. The two are shapes apart, so the join could never make+# one term of them (#2005). A 'rewrite' line rewrites 'Φ.true' wherever it+# stands in the left side into the shape of the right one, and leaves the right+# side as it is, since none of its rules matches anything there. The two sides+# then differ in their symbols alone, and the join makes one term of them.+# The protocol writes the line of '𝑛3', which a rule changed, commented with+# the block and the meta it read, and the line of '𝑛4', which changed nothing,+# as the plain line of a meta bound to what another meta holds.+symbolic: |+  - λ: L_fork+    dataize:+      𝛿1: $.c+    morph:+      𝑛1: $.a+      𝑛2: $.b+    rewrite:+      𝑛3:+        of: 𝑛1+        rules: &bool+          - name: true-literal+            pattern: Φ.true+            result: Φ.bool( if ↦ ⟦ Δ ⤍ 01- ⟧ )+          - name: false-literal+            pattern: Φ.false+            result: Φ.bool( if ↦ ⟦ Δ ⤍ 00- ⟧ )+      𝑛4:+        of: 𝑛2+        rules: *bool+    join:+      on: 𝛿1+      left: 𝑛3+      right: 𝑛4+location: 'Q.y'+hide-rho: false+input: '⟦ y ↦ ⟦ c ↦ ⟦ λ ⤍ 𝜎1 ⟧, a ↦ ⟦ φ ↦ ⟦ λ ⤍ 𝜎2 ⟧, kq ↦ Φ.true ⟧, b ↦ ⟦ φ ↦ ⟦ λ ⤍ 𝜎3 ⟧, kq ↦ Φ.bool( if ↦ ⟦ Δ ⤍ 01- ⟧ ) ⟧, λ ⤍ L_fork ⟧.φ ⟧'+result: '𝜎4:λ'+protocol: |+  𝕄(Φ.y):+    𝔼(L_fork):+      𝛿1·1 := 𝔻(𝜎1:λ)  # 𝔻(ξ.c)+      𝑛1·1 := ⟦ φ ↦ 𝜎2:λ, kq ↦ Φ.true ⟧  # 𝕄(ξ.a)+      𝑛2·1 := ⟦ φ ↦ 𝜎3:λ, kq ↦ Φ.bool( if ↦ 01-:Δ ) ⟧  # 𝕄(ξ.b)+      𝑛3·1 := ⟦ φ ↦ 𝜎2:λ, kq ↦ Φ.bool( if ↦ 01-:Δ ) ⟧  # rewrite(𝑛1)+      𝑛4·1 := 𝑛2·1  # 𝑛2+      joined 𝜎4 := 𝛿1·1 ? 𝜎2 : 𝜎3+      𝑛·1·1 := ⟦ φ ↦ 𝜎4:λ, kq ↦ Φ.bool( if ↦ 01-:Δ ) ⟧  # 𝑛3, 𝑛4
− test-resources/evaluate-packs/joins-a-branch-it-unfolded-whose-phi-reads-its-own-object.yaml
@@ -1,40 +0,0 @@-# 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 'unfold' line takes the step the-# join used to hide (#1921): it morphs the φ of that branch, fires what it-# comes to through 𝔼, the way an 'evaluate' line does, and binds the answer,-# so the 'join' line pairs two bare symbols (#1885).-symbolic: |-  - λ: L_eq-    𝑛: ⟦ φ ↦ ⟦ λ ⤍ 𝜎 ⟧ ⟧-  - λ: L_fork-    morph:-      𝑛1: $.a-      𝑛2: $.b-    unfold:-      𝑛3: 𝑛1-    join:-      𝑛4: [𝑛3, 𝑛2]-    𝑛: 𝑛4-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)-      𝑛3·1 := L_eq:λ  # 𝕄(𝑛1.φ)-      𝔼(L_eq):  # 𝔼(𝑛1)-        𝑛·2·1 := 𝜎2:λ:φ  # 𝑛-        𝑛·2·2 := 𝜎2:λ:φ  # 𝕄(𝑛·2·1)-      𝑛3·1 := 𝑛·2·2  # 𝔼(𝑛1)-      𝔻(𝜎3:λ) ∈ { 𝔻(𝜎2:λ), 𝔻(𝜎1:λ) }-      𝑛4·1 := 𝜎3:λ:φ  # [𝑛3, 𝑛2]-      𝑛·1·1 := 𝑛4·1  # 𝑛-      𝑛·1·2 := 𝑛4·1  # 𝕄(𝑛·1·1)
− test-resources/evaluate-packs/joins-a-branch-reached-from-a-literal.yaml
@@ -1,38 +0,0 @@-# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com-# SPDX-License-Identifier: MIT-----# yamllint disable rule:line-length-# A datum is never joined with anything, which is why a branch carrying one-# goes through 'symbolize' first: a value somebody worked out and a value-# nobody did are a difference in kind and not in value, and after that stage-# both branches carry ⟦ λ ⤍ 𝜎 ⟧ where they differ. The symbol the literal-# stood into is a symbol like any other here, so what the fork answers is the-# join of the two and what is known about the literal stays on the line the-# 'symbolize' stage wrote it on. Both stages walk the φ chain and nothing-# else, that being where the value of a branch is reached (#1293).-symbolic: |-  - λ: L_fork-    morph:-      𝑛1: $.a-      𝑛2: $.b-    symbolize:-      𝑛3: 𝑛1-      𝑛4: 𝑛2-    join:-      𝑛5: [𝑛3, 𝑛4]-    𝑛: 𝑛5-location: 'Q.y'-input: '⟦ y ↦ ⟦ a ↦ ⟦ φ ↦ ⟦ Δ ⤍ 01- ⟧ ⟧, b ↦ ⟦ φ ↦ ⟦ λ ⤍ 𝜎7 ⟧ ⟧, λ ⤍ L_fork ⟧.φ ⟧'-result: '𝜎9:λ'-protocol: |-  𝕄(Φ.y):-    𝔼(L_fork):-      𝑛1·1 := 01-:Δ:φ  # 𝕄(ξ.a)-      𝑛2·1 := 𝜎7:λ:φ  # 𝕄(ξ.b)-      𝔻(𝜎8:λ) == 01--      𝑛3·1 := 𝜎8:λ:φ  # 𝑛1-      𝑛4·1 := 𝑛2·1  # 𝑛2-      𝔻(𝜎9:λ) ∈ { 𝔻(𝜎8:λ), 𝔻(𝜎7:λ) }-      𝑛5·1 := 𝜎9:λ:φ  # [𝑛3, 𝑛4]-      𝑛·1·1 := 𝑛5·1  # 𝑛-      𝑛·1·2 := 𝑛5·1  # 𝕄(𝑛·1·1)
− test-resources/evaluate-packs/joins-a-branch-with-a-decorator-of-it-when-lenient.yaml
@@ -1,30 +0,0 @@-# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com-# SPDX-License-Identifier: MIT-----# yamllint disable rule:line-length-# A fork whose one branch calls a method often meets a decorator: one branch-# comes to a copy, the other to a formation whose φ is that copy. The two-# differ in shape, so a strict join refuses them. An entry that says-# 'lenient: []' joins them into the decorator, with a fresh symbol minted-# inside it the usual way (#1815).-symbolic: |-  - λ: L_fork-    morph:-      𝑛1: $.a-      𝑛2: $.b-    join:-      𝑛3: [𝑛1, 𝑛2]-    lenient: []-    𝑛: 𝑛3-location: 'Q.y'-input: '⟦ y ↦ ⟦ a ↦ ⟦ φ ↦ ⟦ λ ⤍ 𝜎1 ⟧, neg ↦ ⟦⟧ ⟧, b ↦ ⟦ φ ↦ ⟦ φ ↦ ⟦ λ ⤍ 𝜎2 ⟧, neg ↦ ⟦⟧ ⟧ ⟧, λ ⤍ L_fork ⟧.φ ⟧'-result: '⟦ φ ↦ 𝜎3:λ, neg ↦ ⟦⟧ ⟧'-protocol: |-  𝕄(Φ.y):-    𝔼(L_fork):-      𝑛1·1 := ⟦ φ ↦ 𝜎1:λ, neg ↦ ⟦⟧ ⟧  # 𝕄(ξ.a)-      𝑛2·1 := ⟦ φ ↦ 𝜎2:λ, neg ↦ ⟦⟧ ⟧:φ  # 𝕄(ξ.b)-      𝔻(𝜎3:λ) ∈ { 𝔻(𝜎1:λ), 𝔻(𝜎2:λ) }-      𝑛3·1 := ⟦ φ ↦ 𝜎3:λ, neg ↦ ⟦⟧ ⟧:φ  # [𝑛1, 𝑛2]-      𝑛·1·1 := 𝑛3·1  # 𝑛-      𝑛·1·2 := 𝑛3·1  # 𝕄(𝑛·1·1)
− test-resources/evaluate-packs/joins-a-decorator-with-the-branch-it-decorates-standing-second-when-lenient.yaml
@@ -1,29 +0,0 @@-# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com-# SPDX-License-Identifier: MIT-----# yamllint disable rule:line-length-# The lenient join of #1815 does not care which branch is the decorator: with-# the decorator on the left and the copy it decorates on the right, it answers-# the same decorator. A copy is a decorator of its own symbol too, so the join-# takes the branch with the longer φ chain for the decorator.-symbolic: |-  - λ: L_fork-    morph:-      𝑛1: $.a-      𝑛2: $.b-    join:-      𝑛3: [𝑛1, 𝑛2]-    lenient: []-    𝑛: 𝑛3-location: 'Q.y'-input: '⟦ y ↦ ⟦ a ↦ ⟦ φ ↦ ⟦ φ ↦ ⟦ λ ⤍ 𝜎1 ⟧, neg ↦ ⟦⟧ ⟧ ⟧, b ↦ ⟦ φ ↦ ⟦ λ ⤍ 𝜎2 ⟧, neg ↦ ⟦⟧ ⟧, λ ⤍ L_fork ⟧.φ ⟧'-result: '⟦ φ ↦ 𝜎3:λ, neg ↦ ⟦⟧ ⟧'-protocol: |-  𝕄(Φ.y):-    𝔼(L_fork):-      𝑛1·1 := ⟦ φ ↦ 𝜎1:λ, neg ↦ ⟦⟧ ⟧:φ  # 𝕄(ξ.a)-      𝑛2·1 := ⟦ φ ↦ 𝜎2:λ, neg ↦ ⟦⟧ ⟧  # 𝕄(ξ.b)-      𝔻(𝜎3:λ) ∈ { 𝔻(𝜎1:λ), 𝔻(𝜎2:λ) }-      𝑛3·1 := ⟦ φ ↦ 𝜎3:λ, neg ↦ ⟦⟧ ⟧:φ  # [𝑛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
@@ -1,113 +0,0 @@-# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com-# SPDX-License-Identifier: MIT-----# 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.-# 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:-      𝛿1: $.ρ-      𝛿2: $.x-    𝑛: Φ.bool( φ ↦ ⟦ λ ⤍ 𝜎 ⟧ )-  - λ: L_number_plus-    dataize:-      𝛿1: $.ρ-      𝛿2: $.x-    𝑛: Φ.number( φ ↦ ⟦ λ ⤍ 𝜎 ⟧ )-  - λ: L_fork-    dataize:-      𝛿1: $.ρ-    morph:-      𝑛1: $.then-      𝑛2: $.else-    evaluate:-      𝑛3: 𝑛1-      𝑛4: 𝑛2-    join:-      𝑛5: [𝑛3, 𝑛4]-    𝑛: 𝑛5-location: 'Q.@'-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:λ'-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.plus( 2 ) )-    𝑛·0·4 := 𝑛·0·3( else ↦ ⊥ )-    𝔼(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)-      𝑛2·2 := ⊥  # 𝕄(ξ.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)-      terminate(𝔻(𝜎1:λ), right)  # 𝑛4-      𝑛5·2 := 𝑛3·2  # [𝑛3, 𝑛4]-      𝑛·2·4 := 𝑛5·2  # 𝑛-      𝑛·2·5 := 𝑛5·2  # 𝕄(𝑛·2·4)-    𝑛·0·5 := 𝑛·3·6.plus( x ↦ 6 )-    𝔼(L_number_plus):-      𝔻(Φ.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-one-branch-of-which-throws.yaml
@@ -1,119 +0,0 @@-# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com-# SPDX-License-Identifier: MIT-----# 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 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:-      𝛿1: $.ρ-      𝛿2: $.x-    𝑛: Φ.bool( φ ↦ ⟦ λ ⤍ 𝜎 ⟧ )-  - λ: L_number_plus-    dataize:-      𝛿1: $.ρ-      𝛿2: $.x-    𝑛: Φ.number( φ ↦ ⟦ λ ⤍ 𝜎 ⟧ )-  - λ: L_fork-    dataize:-      𝛿1: $.ρ-    morph:-      𝑛1: $.then-      𝑛2: $.else-    evaluate:-      𝑛3: 𝑛1-      𝑛4: 𝑛2-    join:-      𝑛5: [𝑛3, 𝑛4]-    terminators: [L_throw]-    𝑛: 𝑛5-location: 'Q.@'-input: >-  ⟦ bytes(φ) ↦ ⟦⟧,-    error(message) ↦ ⟦ λ ⤍ L_throw ⟧,-    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 ↦ Φ.error( message ↦ 7.plus( 8 ) ) ).plus( 6 ).φ ⟧-result: '𝜎3:λ'-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.plus( 2 ) )-    𝑛·0·4 := 𝑛·0·3( else ↦ Φ.error( message ↦ 7.plus( 8 ) ) )-    𝔼(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 := Φ.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·5 := 𝑛5·2  # 𝑛-      𝑛·2·6 := 𝑛5·2  # 𝕄(𝑛·2·5)-    𝑛·0·5 := 𝑛·3·6.plus( x ↦ 6 )-    𝔼(L_number_plus):-      𝔻(Φ.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
@@ -1,112 +0,0 @@-# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com-# SPDX-License-Identifier: MIT-----# 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 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:-      𝛿1: $.ρ-      𝛿2: $.x-    𝑛: Φ.bool( φ ↦ ⟦ λ ⤍ 𝜎 ⟧ )-  - λ: L_number_plus-    dataize:-      𝛿1: $.ρ-      𝛿2: $.x-    𝑛: Φ.number( φ ↦ ⟦ λ ⤍ 𝜎 ⟧ )-  - λ: L_fork-    dataize:-      𝛿1: $.ρ-    morph:-      𝑛1: $.then-      𝑛2: $.else-    evaluate:-      𝑛3: 𝑛1-      𝑛4: 𝑛2-    join:-      𝑛5: [𝑛3, 𝑛4]-    𝑛: 𝑛5-location: 'Q.@'-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:λ'-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 ↦ ⊥ )-    𝑛·0·4 := 𝑛·0·3( else ↦ 1.plus( 2 ) )-    𝔼(L_fork):-      𝔻(Φ.a🌵2):-        𝕄(Φ.a🌵2):-          𝑛·2·1 := Φ.bool( φ ↦ 𝜎1:λ )-      𝛿1·2 := 𝔻(𝜎1:λ)  # 𝔻(ξ.ρ)-      𝑛1·2 := ⊥  # 𝕄(ξ.then)-      𝕄(Φ.a🌵4):-        𝑛·2·2 := 1-        𝑛·2·3 := 𝑛·2·2.plus( x ↦ 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·4 := 𝑛5·2  # 𝑛-      𝑛·2·5 := 𝑛5·2  # 𝕄(𝑛·2·4)-    𝑛·0·5 := 𝑛·3·6.plus( x ↦ 6 )-    𝔼(L_number_plus):-      𝔻(Φ.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-whose-branch-an-evaluate-line-fired.yaml
@@ -1,39 +0,0 @@-# 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-fork-whose-branches-a-rewrite-brought-to-one-shape.yaml
@@ -1,42 +0,0 @@-# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com-# SPDX-License-Identifier: MIT-----# yamllint disable rule:line-length-# The very fork that does not join, with a 'rewrite' block in front of the-# join: the second branch carries an 'x' the first does not, and a rule of the-# entry drops it, so the two have one shape once they are compared and the-# fork answers their join. Each 'rewrite' line binds a meta of its own, which-# the protocol writes like a 'symbolize' one, commented with the meta it was-# rewritten from; a branch no rule matches is bound as it was (#1409).-symbolic: |-  - λ: L_fork-    morph:-      𝑛1: $.a-      𝑛2: $.b-    rewrite:-      𝑛3:-        of: 𝑛1-        rules: &shape-          - name: no-x-            pattern: ⟦ !B1, x ↦ ⟦⟧, !B2 ⟧-            result: ⟦ !B1, !B2 ⟧-      𝑛4:-        of: 𝑛2-        rules: *shape-    join:-      𝑛5: [𝑛3, 𝑛4]-    𝑛: 𝑛5-location: 'Q.y'-input: '⟦ y ↦ ⟦ a ↦ ⟦ φ ↦ ⟦ λ ⤍ 𝜎1 ⟧ ⟧, b ↦ ⟦ φ ↦ ⟦ λ ⤍ 𝜎2 ⟧, x ↦ ⟦⟧ ⟧, λ ⤍ L_fork ⟧.φ ⟧'-result: '𝜎3:λ'-protocol: |-  𝕄(Φ.y):-    𝔼(L_fork):-      𝑛1·1 := 𝜎1:λ:φ  # 𝕄(ξ.a)-      𝑛2·1 := ⟦ φ ↦ 𝜎2:λ, x ↦ ⟦⟧ ⟧  # 𝕄(ξ.b)-      𝑛3·1 := 𝑛1·1  # 𝑛1-      𝑛4·1 := 𝜎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-fork-whose-branches-a-stay-brought-to-one-shape.yaml
@@ -1,41 +0,0 @@-# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com-# SPDX-License-Identifier: MIT-----# yamllint disable rule:line-length-# A branch reading the ρ of a copy, the way 'as-number' of EO reads its 'data',-# comes to a dispatch the copy hangs on as its ρ, '( ρ ↦ ⟦ … ⟧ )', while the-# other branch, made of no object, carries no such tail. Both are numbers made-# of one symbol, yet the join compares the terms as written and the tail makes-# their shapes differ. A 'stay' line drops every ρ a dispatch is applied to,-# which 'stay' or 'skip' of normalization drops anyway once the dispatch comes-# to a formation, so the two have one shape and the fork answers their join.-# Each 'stay' line binds a meta of its own, which the protocol writes like a-# 'rewrite' one, commented with the meta it kept; a branch with no tail is-# bound as it was (#1902).-symbolic: |-  - λ: L_fork-    morph:-      𝑛1: $.a-      𝑛2: $.b-    stay:-      𝑛3: 𝑛1-      𝑛4: 𝑛2-    join:-      𝑛5: [𝑛3, 𝑛4]-    𝑛: 𝑛5-location: 'Q.y'-hide-rho: false-input: '⟦ number ↦ ⟦ φ ↦ ∅ ⟧, bytes ↦ ⟦ φ ↦ ∅ ⟧, y ↦ ⟦ a ↦ ⟦ φ ↦ Φ.number( φ ↦ ⟦ ρ ↦ Φ.bytes( φ ↦ ⟦ λ ⤍ 𝜎1 ⟧ ), x ↦ ∅ ⟧.ρ ) ⟧, b ↦ ⟦ φ ↦ Φ.number( φ ↦ Φ.bytes( φ ↦ ⟦ λ ⤍ 𝜎2 ⟧ ) ) ⟧, λ ⤍ L_fork ⟧.φ ⟧'-result: 'Φ.bytes( φ ↦ 𝜎3:λ ):φ'-protocol: |-  𝕄(Φ.y):-    𝔼(L_fork):-      𝑛1·1 := Φ.number( φ ↦ Φ.bytes( φ ↦ 𝜎1:λ, ρ ↦ ⟦ ρ ↦ Φ.bytes( φ ↦ 𝜎1:λ ), x ↦ ∅ ⟧ ) ):φ  # 𝕄(ξ.a)-      𝑛2·1 := Φ.number( φ ↦ Φ.bytes( φ ↦ 𝜎2:λ ) ):φ  # 𝕄(ξ.b)-      𝑛3·1 := Φ.number( φ ↦ Φ.bytes( φ ↦ 𝜎1:λ ) ):φ  # 𝑛1-      𝑛4·1 := 𝑛2·1  # 𝑛2-      𝔻(𝜎3:λ) ∈ { 𝔻(𝜎1:λ), 𝔻(𝜎2:λ) }-      𝑛5·1 := Φ.number( φ ↦ Φ.bytes( φ ↦ 𝜎3:λ ) ):φ  # [𝑛3, 𝑛4]-      𝑛·1·1 := 𝑛5·1  # 𝑛-      𝑛·1·2 := 𝑛5·1  # 𝕄(𝑛·1·1)-    𝑛·0·1 := Φ.number( φ ↦ Φ.bytes( φ ↦ 𝜎3:λ ) )
− test-resources/evaluate-packs/joins-a-negated-bool-whose-literal-a-rewrite-brought-to-one-shape.yaml
@@ -1,151 +0,0 @@-# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com-# SPDX-License-Identifier: MIT-----# yamllint disable rule:line-length-# The program '!(x > 0)' of eo-runtime: 'not' is 'if. false true', so one-# branch of the fork is the literal 'false' and the other the literal 'true',-# 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 (#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:-      𝛿1: $.ρ-      𝛿2: $.b-    𝑛: ⟦ φ ↦ Φ.bool( if ↦ ⟦ λ ⤍ L_fork, left ↦ ∅, right ↦ ∅, φ ↦ ⟦ λ ⤍ 𝜎 ⟧ ⟧ ) ⟧-  - λ: L_entry-    dataize:-      𝛿1: $.n-    morph:-      𝑛1: $.v.φ-    evaluate:-      𝑛2: 𝑛1-    𝑛: Φ.l🌵.root( 𝑛2 )-  - λ: L_root-    dataize:-      𝛿1: $.v-    𝑛: ⟦ ⟧-  - λ: 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- ⟧ ⟧ ) ⟧-          - name: true-literal-            pattern: ⟦ !B1, φ ↦ Φ.bool( if ↦ ⟦ ρ ↦ ∅, left ↦ ∅, right ↦ ∅, φ ↦ ξ.left ⟧ ), !B2 ⟧-            result: ⟦ φ ↦ Φ.bool( if ↦ ⟦ λ ⤍ L_fork, left ↦ ∅, right ↦ ∅, φ ↦ ⟦ Δ ⤍ FF- ⟧ ⟧ ) ⟧-          - name: bool-without-rho-            pattern: ⟦ !B1, φ ↦ Φ.bool( if ↦ ⟦ λ ⤍ L_fork, left ↦ ∅, right ↦ ∅, φ ↦ 𝑒1 ⟧ ), !B2 ⟧-            result: ⟦ φ ↦ Φ.bool( if ↦ ⟦ λ ⤍ L_fork, left ↦ ∅, right ↦ ∅, φ ↦ 𝑒1 ⟧ ) ⟧-      𝑛4:-        of: 𝑛2-        rules: *bool-    symbolize:-      𝑛5: 𝑛3-      𝑛6: 𝑛4-    join:-      𝑛7: [𝑛5, 𝑛6]-    𝑛: 𝑛7-location: 'Q.l🌵.e1'-input: |-  ⟦-    number ↦ ⟦ φ ↦ ∅, gt ↦ ⟦ b ↦ ∅, λ ⤍ L_number_gt, ρ ↦ ∅ ⟧ ⟧,-    bytes ↦ ⟦ φ ↦ ∅ ⟧,-    bool ↦ ⟦ if ↦ ∅, φ ↦ ξ.if( left ↦ Φ.bytes( φ ↦ ⟦ Δ ⤍ FF- ⟧ ), right ↦ Φ.bytes( φ ↦ ⟦ Δ ⤍ 00- ⟧ ) ), not ↦ ξ.if( left ↦ Φ.false, right ↦ Φ.true ) ⟧,-    true ↦ ⟦ φ ↦ Φ.bool( if ↦ ⟦ ρ ↦ ∅, left ↦ ∅, right ↦ ∅, φ ↦ ξ.left ⟧ ) ⟧,-    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 ⟧ ) ) ) ).φ ⟧-  ⟧-protocol: |-  𝕄(Φ.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-a-side-a-morph-line-brought-down-from-what-a-rewrite-line-unwrapped.yaml view
@@ -0,0 +1,46 @@+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com+# SPDX-License-Identifier: MIT+---+# yamllint disable rule:line-length+# The right side morphs down to a formation holding nothing but a path in φ,+# and a 'rewrite' line takes the path out of it. The path is no normal form, so+# the join could never make one term of it and the left side, the way the+# 'Φ.bool( … )' a 'rewrite' line writes into a side of the 'L_fork' of EO is+# none (#2016). An operand of 'morph' naming the meta the 'rewrite' line bound+# reduces the path to the object it reads, at the point the meta is bound, and+# the join makes one term of the two objects, which differ in their symbols+# alone. The protocol writes the line of '𝑛4' right after the line of '𝑛3' it+# read, commented with the morph of that meta.+symbolic: |+  - λ: L_fork+    dataize:+      𝛿1: $.c+    morph:+      𝑛1: $.a+      𝑛2: $.b+      𝑛4: 𝑛3+    rewrite:+      𝑛3:+        of: 𝑛2+        rules:+          - name: unwrap+            pattern: ⟦ φ ↦ 𝑒1 ⟧+            result: 𝑒1+    join:+      on: 𝛿1+      left: 𝑛1+      right: 𝑛4+location: 'Q.y'+hide-rho: false+input: '⟦ y ↦ ⟦ c ↦ ⟦ λ ⤍ 𝜎1 ⟧, a ↦ ⟦ φ ↦ ⟦ λ ⤍ 𝜎2 ⟧ ⟧, b ↦ ⟦ φ ↦ Φ.t ⟧, λ ⤍ L_fork ⟧.φ, t ↦ ⟦ φ ↦ ⟦ λ ⤍ 𝜎3 ⟧ ⟧ ⟧'+result: '𝜎4:λ'+protocol: |+  𝕄(Φ.y):+    𝔼(L_fork):+      𝛿1·1 := 𝔻(𝜎1:λ)  # 𝔻(ξ.c)+      𝑛1·1 := 𝜎2:λ:φ  # 𝕄(ξ.a)+      𝑛2·1 := Φ.t:φ  # 𝕄(ξ.b)+      𝑛3·1 := Φ.t  # rewrite(𝑛2)+      𝑛4·1 := 𝜎3:λ:φ  # 𝕄(𝑛3)+      joined 𝜎4 := 𝛿1·1 ? 𝜎2 : 𝜎3+      𝑛·1·1 := 𝜎4:λ:φ  # 𝑛1, 𝑛4
+ test-resources/evaluate-packs/joins-a-side-an-evaluate-line-fired-with-a-bool-it-left-as-it-is.yaml view
@@ -0,0 +1,46 @@+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com+# SPDX-License-Identifier: MIT+---+# yamllint disable rule:line-length+# The sides of a join may go through 'evaluate' lines (#1999). The left side+# morphs down to a call of 'L_eq' 𝕄 left unfired, which could never join with+# a bool, so its 'evaluate' line fires it and binds the bool the entry+# answers. The right side is a bool already, a formation with no λ, so its+# 'evaluate' line leaves it as it is. The two bools differ in their symbols+# alone, and the join makes one term of them.+symbolic: |+  - λ: L_eq+    𝑛: Φ.bool( φ ↦ ⟦ λ ⤍ 𝜎 ⟧ )+  - λ: L_fork+    dataize:+      𝛿1: $.c+    morph:+      𝑛1: $.a+      𝑛2: $.b+    evaluate:+      𝑛3: 𝑛1+      𝑛4: 𝑛2+    join:+      on: 𝛿1+      left: 𝑛3+      right: 𝑛4+location: 'Q.y'+input: '⟦ bool(φ) ↦ ⟦⟧, y ↦ ⟦ c ↦ ⟦ λ ⤍ 𝜎1 ⟧, a ↦ ⟦ λ ⤍ L_eq ⟧, b ↦ Φ.bool( φ ↦ ⟦ λ ⤍ 𝜎2 ⟧ ), λ ⤍ L_fork ⟧.φ ⟧'+result: '𝜎4:λ'+protocol: |+  𝕄(Φ.y):+    𝔼(L_fork):+      𝛿1·1 := 𝔻(𝜎1:λ)  # 𝔻(ξ.c)+      𝑛1·1 := L_eq:λ  # 𝕄(ξ.a)+      𝕄(Φ.a🌵2):+        𝑛·1·1 := Φ.bool( φ ↦ 𝜎2:λ )+      𝑛2·1 := 𝑛·1·1  # 𝕄(ξ.b)+      𝔼(L_eq):  # 𝔼(𝑛1)+        𝑛·2·1 := Φ.bool( φ ↦ 𝜎3:λ )  # 𝑛+        𝕄(𝑛·2·1):+          𝑛·2·2 := Φ.bool( φ ↦ 𝜎3:λ )+        𝑛·2·3 := 𝑛·2·2  # 𝕄(𝑛·2·1)+      𝑛3·1 := 𝑛·2·3  # 𝔼(𝑛1)+      𝑛4·1 := 𝑛2·1  # 𝔼(𝑛2)+      joined 𝜎4 := 𝛿1·1 ? 𝜎3 : 𝜎2+      𝑛·1·2 := 𝜎4:λ:φ  # 𝑛3, 𝑛4
+ test-resources/evaluate-packs/joins-a-side-holding-a-datum-a-symbolize-turned-into-a-symbol.yaml view
@@ -0,0 +1,42 @@+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com+# SPDX-License-Identifier: MIT+---+# yamllint disable rule:line-length+# The left side is a number written as a literal, so 𝕄 brings it to a number+# whose bytes hold a datum. The right side is a number some firing computed,+# so its bytes hold a symbol. Both are numbers of one shape and differ in the+# value alone, yet no join makes one term of a datum and a symbol (#1968). A+# 'symbolize' line turns every datum of a term into a fresh symbol, so '𝑛3'+# holds 𝜎4 where '𝑛1' held the datum, and the protocol writes that 𝜎4 stands+# for that datum. The line of '𝑛4', whose term holds no datum, is commented+# with '𝑛2' alone. The join takes the metas the 'symbolize' lines bound and+# joins 𝜎4 with 𝜎2 into one fresh symbol 𝜎5.+symbolic: |+  - λ: L_fork+    dataize:+      𝛿1: $.c+    morph:+      𝑛1: $.a+      𝑛2: $.b+    symbolize:+      𝑛3: 𝑛1+      𝑛4: 𝑛2+    join:+      on: 𝛿1+      left: 𝑛3+      right: 𝑛4+location: 'Q.y'+input: '⟦ number ↦ ⟦ φ ↦ ∅ ⟧, bytes ↦ ⟦ φ ↦ ∅ ⟧, y ↦ ⟦ c ↦ ⟦ λ ⤍ 𝜎3 ⟧, a ↦ ⟦ φ ↦ Φ.number( φ ↦ Φ.bytes( φ ↦ ⟦ Δ ⤍ 3F-F0-00-00-00-00-00-00 ⟧ ) ) ⟧, b ↦ ⟦ φ ↦ Φ.number( φ ↦ Φ.bytes( φ ↦ ⟦ λ ⤍ 𝜎2 ⟧ ) ) ⟧, λ ⤍ L_fork ⟧.φ ⟧'+result: 'Φ.bytes( φ ↦ 𝜎5:λ ):φ'+protocol: |+  𝕄(Φ.y):+    𝔼(L_fork):+      𝛿1·1 := 𝔻(𝜎3:λ)  # 𝔻(ξ.c)+      𝑛1·1 := 1:φ  # 𝕄(ξ.a)+      𝑛2·1 := Φ.number( φ ↦ Φ.bytes( φ ↦ 𝜎2:λ ) ):φ  # 𝕄(ξ.b)+      symbolized 𝜎4 := 3F-F0-00-00-00-00-00-00  # 1.0+      𝑛3·1 := Φ.number( φ ↦ Φ.bytes( φ ↦ 𝜎4:λ ) ):φ  # symbolize(𝑛1)+      𝑛4·1 := 𝑛2·1  # 𝑛2+      joined 𝜎5 := 𝛿1·1 ? 𝜎4 : 𝜎2+      𝑛·1·1 := Φ.number( φ ↦ Φ.bytes( φ ↦ 𝜎5:λ ) ):φ  # 𝑛3, 𝑛4+    𝑛·0·1 := Φ.number( φ ↦ Φ.bytes( φ ↦ 𝜎5:λ ) )
+ test-resources/evaluate-packs/joins-a-side-reading-the-rho-it-binds-in-a-fabricated-formation.yaml view
@@ -0,0 +1,38 @@+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com+# SPDX-License-Identifier: MIT+---+# yamllint disable rule:line-length+# The left side of 'L_fork' is a copy of 'Φ.t', so morphing it binds its ρ to+# 'Φ', as morphing binds the ρ of most objects of EO, and its 'φ' reads 's'+# through that ρ. The fabricated formation holds that side applied to its void+# 'tu' (#2029), so the copy keeps the ρ the side binds: the program asks the+# formation for 'φ' and the copy of 'L_join' it finds there for 'k', which asks+# the left copy for 'φ', and that copy still finds 's' of 'Φ', so the join+# meets the symbol 's' holds under 'k' and the one the right side holds.+symbolic: |+  - λ: L_fork+    dataize:+      𝛿1: $.c+    join:+      on: 𝛿1+      left: $.a+      right: $.b+      otherwise: L_join+  - λ: L_join+    join:+      on: $.on🌵+      left: $.left🌵+      right: $.right🌵+      otherwise: L_join+location: 'Q.y'+input: '⟦ s ↦ ⟦ k ↦ ⟦ λ ⤍ 𝜎2 ⟧ ⟧, t(ρ, tu) ↦ ⟦ φ ↦ ξ.ρ.s ⟧, y ↦ ⟦ c ↦ ⟦ λ ⤍ 𝜎1 ⟧, a ↦ Φ.t, b ↦ ⟦ φ ↦ ⟦ k ↦ ⟦ λ ⤍ 𝜎3 ⟧ ⟧ ⟧, λ ⤍ L_fork ⟧.φ.k ⟧'+result: '𝜎4:λ'+protocol: |+  𝕄(Φ.y):+    𝔼(L_fork):+      𝛿1·1 := 𝔻(𝜎1:λ)  # 𝔻(ξ.c)+      fabricated 𝑛·1·1 := ⟦ tu ↦ ∅, φ ↦ ρ.s ⟧ and 𝜎3:λ:k:φ  # ⟦ tu ↦ ∅, left🌵 ↦ ⟦ tu ↦ ∅, φ ↦ ρ.s ⟧( tu ↦ tu ), right🌵 ↦ 𝜎3:λ:k:φ, φ ↦ ⟦ λ ⤍ L_join, on🌵 ↦ 𝜎1:λ, left🌵 ↦ ρ.left🌵.φ, right🌵 ↦ ρ.right🌵.φ ⟧ ⟧+    𝔼(L_join):+      𝑛·2·1 := Φ.t( tu ↦ ⊥ )  # 𝕄(Φ.a🌵3)+      joined 𝜎4 := ξ.on🌵 ? 𝜎2 : 𝜎3+      𝑛·2·2 := 𝜎4:λ:k  # 𝕄(ξ.left🌵), 𝕄(ξ.right🌵)
+ test-resources/evaluate-packs/joins-a-side-whose-void-the-fabricated-formation-holds.yaml view
@@ -0,0 +1,39 @@+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com+# SPDX-License-Identifier: MIT+---+# yamllint disable rule:line-length+# The two sides of 'L_fork' differ in shape, so the firing fabricates a+# formation (#2024). The left side leaves 'tu' void, so the fabricated+# formation leaves 'tu' void too and holds that side applied to 'tu' under+# 'left🌵', reading it off the fabricated formation (#2029). The program asks+# the formation for 'φ' and the copy of 'L_join' it finds there for 'k', which+# asks both copies for 'φ' through its ρ. Nobody handed 'tu' over, so the left+# copy binds it to ⊥, which its 'φ' never reads, and the join meets the two+# symbols the sides hold under 'k'.+symbolic: |+  - λ: L_fork+    dataize:+      𝛿1: $.c+    join:+      on: 𝛿1+      left: $.a+      right: $.b+      otherwise: L_join+  - λ: L_join+    join:+      on: $.on🌵+      left: $.left🌵+      right: $.right🌵+      otherwise: L_join+location: 'Q.y'+input: '⟦ y ↦ ⟦ c ↦ ⟦ λ ⤍ 𝜎1 ⟧, a ↦ ⟦ φ ↦ ⟦ k ↦ ⟦ λ ⤍ 𝜎2 ⟧ ⟧, tu ↦ ∅ ⟧, b ↦ ⟦ φ ↦ ⟦ k ↦ ⟦ λ ⤍ 𝜎3 ⟧ ⟧ ⟧, λ ⤍ L_fork ⟧.φ.k ⟧'+result: '𝜎4:λ'+protocol: |+  𝕄(Φ.y):+    𝔼(L_fork):+      𝛿1·1 := 𝔻(𝜎1:λ)  # 𝔻(ξ.c)+      fabricated 𝑛·1·1 := ⟦ φ ↦ 𝜎2:λ:k, tu ↦ ∅ ⟧ and 𝜎3:λ:k:φ  # ⟦ tu ↦ ∅, left🌵 ↦ ⟦ φ ↦ 𝜎2:λ:k, tu ↦ ∅ ⟧( tu ↦ tu ), right🌵 ↦ 𝜎3:λ:k:φ, φ ↦ ⟦ λ ⤍ L_join, on🌵 ↦ 𝜎1:λ, left🌵 ↦ ρ.left🌵.φ, right🌵 ↦ ρ.right🌵.φ ⟧ ⟧+    𝔼(L_join):+      𝑛·2·1 := ⟦ φ ↦ 𝜎2:λ:k, tu ↦ ∅ ⟧( tu ↦ ⊥ )  # 𝕄(Φ.a🌵3)+      joined 𝜎4 := ξ.on🌵 ? 𝜎2 : 𝜎3+      𝑛·2·2 := 𝜎4:λ:k  # 𝕄(ξ.left🌵), 𝕄(ξ.right🌵)
+ test-resources/evaluate-packs/joins-again-the-attribute-of-a-fabricated-formation.yaml view
@@ -0,0 +1,41 @@+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com+# SPDX-License-Identifier: MIT+---+# yamllint disable rule:line-length+# The two sides of 'L_fork' differ in shape, so the firing fabricates a+# formation whose every attribute is a copy of 'L_join' (#2024). The program+# asks that formation for 'φ' and asks the copy it finds there for 'φ' too,+# which fires 'L_join': its entry reads the condition off 'on🌵', the symbol+# 'L_fork' came down to (#2026), dataizing nothing, and morphs the 'φ' of both+# sides, two copies of 'hx' that differ in a symbol alone, so its join mints a+# fresh symbol for them.+symbolic: |+  - λ: L_fork+    dataize:+      𝛿1: $.c+    join:+      on: 𝛿1+      left: $.a+      right: $.b+      otherwise: L_join+  - λ: L_join+    join:+      on: $.on🌵+      left: $.left🌵+      right: $.right🌵+      otherwise: L_join+location: 'Q.y'+input: '⟦ hx(ke) ↦ ⟦ φ ↦ ke ⟧, y ↦ ⟦ c ↦ ⟦ λ ⤍ 𝜎1 ⟧, a ↦ ⟦ φ ↦ Φ.hx( ⟦ λ ⤍ 𝜎2 ⟧ ), ob ↦ Φ.hx ⟧, b ↦ ⟦ φ ↦ Φ.hx( ⟦ λ ⤍ 𝜎3 ⟧ ) ⟧, λ ⤍ L_fork ⟧.φ.φ ⟧'+result: '𝜎4:λ'+protocol: |+  𝕄(Φ.y):+    𝔼(L_fork):+      𝛿1·1 := 𝔻(𝜎1:λ)  # 𝔻(ξ.c)+      fabricated 𝑛·1·1 := ⟦ φ ↦ Φ.hx( 𝜎2:λ ), ob ↦ Φ.hx ⟧ and Φ.hx( 𝜎3:λ ):φ  # ⟦ left🌵 ↦ ⟦ φ ↦ Φ.hx( 𝜎2:λ ), ob ↦ Φ.hx ⟧, right🌵 ↦ Φ.hx( 𝜎3:λ ):φ, φ ↦ ⟦ λ ⤍ L_join, on🌵 ↦ 𝜎1:λ, left🌵 ↦ ρ.left🌵.φ, right🌵 ↦ ρ.right🌵.φ ⟧, ob ↦ ⟦ λ ⤍ L_join, on🌵 ↦ 𝜎1:λ, left🌵 ↦ ρ.left🌵.ob, right🌵 ↦ ρ.right🌵.ob ⟧ ⟧+    𝔼(L_join):+      𝕄(Φ.a🌵3):+        𝑛·2·1 := Φ.hx( ke ↦ 𝜎2:λ )+      𝕄(Φ.a🌵4):+        𝑛·2·2 := Φ.hx( ke ↦ 𝜎3:λ )+      joined 𝜎4 := ξ.on🌵 ? 𝜎2 : 𝜎3+      𝑛·2·3 := ⟦ ke ↦ 𝜎4:λ, φ ↦ ke ⟧  # 𝕄(ξ.left🌵), 𝕄(ξ.right🌵)
+ test-resources/evaluate-packs/joins-an-attribute-named-left-of-a-copy-in-a-fabricated-formation.yaml view
@@ -0,0 +1,38 @@+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com+# SPDX-License-Identifier: MIT+---+# yamllint disable rule:line-length+# The two sides of 'L_fork' differ in shape, so the firing fabricates a+# formation whose every attribute is a copy of 'L_join' (#2024). Both sides+# bind 'ke' to an object with an attribute named 'left', as the 'if' of a bool+# in EO has a void named 'left'. The program asks the copy under 'ke' for+# 'left', which the copy names none of its own attributes, since those carry+# the 🌵 mark (#2027), so the copy fires 'L_join', whose entry joins the 'ke'+# of both sides, two objects that differ in a symbol alone, and the program+# takes the 'left' of what the join made.+symbolic: |+  - λ: L_fork+    dataize:+      𝛿1: $.c+    join:+      on: 𝛿1+      left: $.a+      right: $.b+      otherwise: L_join+  - λ: L_join+    join:+      on: $.on🌵+      left: $.left🌵+      right: $.right🌵+      otherwise: L_join+location: 'Q.y'+input: '⟦ y ↦ ⟦ c ↦ ⟦ λ ⤍ 𝜎1 ⟧, a ↦ ⟦ φ ↦ Φ.hx, ke ↦ ⟦ left ↦ ⟦ λ ⤍ 𝜎2 ⟧ ⟧ ⟧, b ↦ ⟦ ke ↦ ⟦ left ↦ ⟦ λ ⤍ 𝜎3 ⟧ ⟧ ⟧, λ ⤍ L_fork ⟧.ke.left ⟧'+result: '𝜎4:λ'+protocol: |+  𝕄(Φ.y):+    𝔼(L_fork):+      𝛿1·1 := 𝔻(𝜎1:λ)  # 𝔻(ξ.c)+      fabricated 𝑛·1·1 := ⟦ φ ↦ Φ.hx, ke ↦ 𝜎2:λ:left ⟧ and 𝜎3:λ:left:ke  # ⟦ left🌵 ↦ ⟦ φ ↦ Φ.hx, ke ↦ 𝜎2:λ:left ⟧, right🌵 ↦ 𝜎3:λ:left:ke, φ ↦ ⟦ λ ⤍ L_join, on🌵 ↦ 𝜎1:λ, left🌵 ↦ ρ.left🌵.φ, right🌵 ↦ ρ.right🌵.φ ⟧, ke ↦ ⟦ λ ⤍ L_join, on🌵 ↦ 𝜎1:λ, left🌵 ↦ ρ.left🌵.ke, right🌵 ↦ ρ.right🌵.ke ⟧ ⟧+    𝔼(L_join):+      joined 𝜎4 := ξ.on🌵 ? 𝜎2 : 𝜎3+      𝑛·2·1 := 𝜎4:λ:left  # 𝕄(ξ.left🌵), 𝕄(ξ.right🌵)
− test-resources/evaluate-packs/joins-the-branches-a-program-forks-into.yaml
@@ -1,137 +0,0 @@-# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com-# SPDX-License-Identifier: MIT-----# yamllint disable rule:line-length-# The same fork as the pack beside this one, answering the join of its two-# branches instead of the first of them, with one more firing standing after-# 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. 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:-      𝛿1: $.ρ-      𝛿2: $.x-    𝑛: Φ.bool( φ ↦ ⟦ λ ⤍ 𝜎 ⟧ )-  - λ: L_number_plus-    dataize:-      𝛿1: $.ρ-      𝛿2: $.x-    𝑛: Φ.number( φ ↦ ⟦ λ ⤍ 𝜎 ⟧ )-  - λ: L_fork-    dataize:-      𝛿1: $.ρ-    morph:-      𝑛1: $.then-      𝑛2: $.else-    evaluate:-      𝑛3: 𝑛1-      𝑛4: 𝑛2-    join:-      𝑛5: [𝑛3, 𝑛4]-    𝑛: 𝑛5-location: 'Q.@'-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:λ'-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.plus( 2 ) )-    𝑛·0·4 := 𝑛·0·3( else ↦ 3.plus( 4 ) )-    𝔼(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·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·6 := 𝑛5·2  # 𝑛-      𝑛·2·7 := 𝑛5·2  # 𝕄(𝑛·2·6)-    𝑛·0·5 := Φ.number( φ ↦ 𝜎4:λ ).plus( x ↦ 6 )-    𝔼(L_number_plus):-      𝔻(Φ.a🌵9):-        𝕄(Φ.a🌵9):-          𝑛·5·1 := Φ.number( φ ↦ 𝜎4:λ )-      𝛿1·5 := 𝔻(𝜎4:λ)  # 𝔻(ξ.ρ)-      𝔻(Φ.a🌵10):-        𝕄(Φ.a🌵10):-          𝑛·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)-      𝑛·5·4 := Φ.number( φ ↦ 𝜎5:λ )  # 𝑛-      𝕄(𝑛·5·4):-        𝑛·5·5 := Φ.number( φ ↦ 𝜎5:λ )-      𝑛·5·6 := 𝑛·5·5  # 𝕄(𝑛·5·4)
− test-resources/evaluate-packs/joins-the-two-branches-of-a-fork.yaml
@@ -1,32 +0,0 @@-# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com-# SPDX-License-Identifier: MIT-----# yamllint disable rule:line-length-# A fork answers neither of its branches: nobody has picked between the two, so-# handing one of them through would say that the branch point is not there at-# all. What it answers with is the join of the two — the shape both of them-# have, with a fresh symbol wherever they carry two different unknowns — and-# what that fresh symbol stands for stands on a line of its own, since a 𝜎 is-# the name of a λ function and nothing is assigned to it. The branches differ-# under φ, that being where the value of a branch is reached and so the only-# place the join looks at all (#1293).-symbolic: |-  - λ: L_fork-    morph:-      𝑛1: $.a-      𝑛2: $.b-    join:-      𝑛3: [𝑛1, 𝑛2]-    𝑛: 𝑛3-location: 'Q.y'-input: '⟦ y ↦ ⟦ a ↦ ⟦ φ ↦ ⟦ λ ⤍ 𝜎1 ⟧ ⟧, b ↦ ⟦ φ ↦ ⟦ λ ⤍ 𝜎2 ⟧ ⟧, λ ⤍ L_fork ⟧.φ ⟧'-result: '𝜎3:λ'-protocol: |-  𝕄(Φ.y):-    𝔼(L_fork):-      𝑛1·1 := 𝜎1:λ:φ  # 𝕄(ξ.a)-      𝑛2·1 := 𝜎2:λ:φ  # 𝕄(ξ.b)-      𝔻(𝜎3:λ) ∈ { 𝔻(𝜎1:λ), 𝔻(𝜎2:λ) }-      𝑛3·1 := 𝜎3:λ:φ  # [𝑛1, 𝑛2]-      𝑛·1·1 := 𝑛3·1  # 𝑛-      𝑛·1·2 := 𝑛3·1  # 𝕄(𝑛·1·1)
− test-resources/evaluate-packs/joins-two-branches-nothing-tells-apart.yaml
@@ -1,27 +0,0 @@-# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com-# SPDX-License-Identifier: MIT-----# yamllint disable rule:line-length-# Two branches nothing tells apart are the answer themselves: there is nothing-# to pick between them, so the join mints no symbol at all and the firing-# answers the very term both of them are. A fork is a branch point in the-# program and not necessarily one in the value it stands for.-symbolic: |-  - λ: L_fork-    morph:-      𝑛1: $.a-      𝑛2: $.b-    join:-      𝑛3: [𝑛1, 𝑛2]-    𝑛: 𝑛3-location: 'Q.y'-input: '⟦ y ↦ ⟦ a ↦ ⟦ l ↦ ⟦ λ ⤍ 𝜎1 ⟧ ⟧, b ↦ ⟦ l ↦ ⟦ λ ⤍ 𝜎1 ⟧ ⟧, λ ⤍ L_fork ⟧.l ⟧'-result: '𝜎1:λ'-protocol: |-  𝕄(Φ.y):-    𝔼(L_fork):-      𝑛1·1 := 𝜎1:λ:l  # 𝕄(ξ.a)-      𝑛2·1 := 𝜎1:λ:l  # 𝕄(ξ.b)-      𝑛3·1 := 𝜎1:λ:l  # [𝑛1, 𝑛2]-      𝑛·1·1 := 𝜎1:λ:l  # 𝑛-      𝑛·1·2 := 𝜎1:λ:l  # 𝕄(𝑛·1·1)
+ test-resources/evaluate-packs/joins-two-sides-a-stay-brought-to-one-shape.yaml view
@@ -0,0 +1,44 @@+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com+# SPDX-License-Identifier: MIT+---+# yamllint disable rule:line-length+# The left side reads the ρ of a copy, the way 'as-number' of EO reads its+# 'data', so 𝕄 brings it to a number whose bytes carry the copy as an extra+# '( ρ ↦ ⟦ … ⟧ )' tail. The right side is a number made of no object and has+# no such tail. Both are numbers made of one symbol, yet the tail kept the+# join from making one term of them (#1967). A 'stay' line drops every ρ+# applied to a dispatch, which 'stay' or 'skip' of normalization drops anyway+# once the dispatch comes to a formation, so the two sides differ in their+# symbols alone. The join takes the metas the 'stay' lines bound as they are+# and makes one term of them with a fresh symbol.+# The protocol comments the line of '𝑛3', which dropped a tail, with+# 'stay(𝑛1)', and the line of '𝑛4', which dropped nothing, with '𝑛2' alone.+symbolic: |+  - λ: L_fork+    dataize:+      𝛿1: $.c+    morph:+      𝑛1: $.a+      𝑛2: $.b+    stay:+      𝑛3: 𝑛1+      𝑛4: 𝑛2+    join:+      on: 𝛿1+      left: 𝑛3+      right: 𝑛4+location: 'Q.y'+hide-rho: false+input: '⟦ number ↦ ⟦ φ ↦ ∅ ⟧, bytes ↦ ⟦ φ ↦ ∅ ⟧, y ↦ ⟦ c ↦ ⟦ λ ⤍ 𝜎3 ⟧, a ↦ ⟦ φ ↦ Φ.number( φ ↦ ⟦ ρ ↦ Φ.bytes( φ ↦ ⟦ λ ⤍ 𝜎1 ⟧ ), x ↦ ∅ ⟧.ρ ) ⟧, b ↦ ⟦ φ ↦ Φ.number( φ ↦ Φ.bytes( φ ↦ ⟦ λ ⤍ 𝜎2 ⟧ ) ) ⟧, λ ⤍ L_fork ⟧.φ ⟧'+result: 'Φ.bytes( φ ↦ 𝜎4:λ ):φ'+protocol: |+  𝕄(Φ.y):+    𝔼(L_fork):+      𝛿1·1 := 𝔻(𝜎3:λ)  # 𝔻(ξ.c)+      𝑛1·1 := Φ.number( φ ↦ Φ.bytes( φ ↦ 𝜎1:λ, ρ ↦ ⟦ ρ ↦ Φ.bytes( φ ↦ 𝜎1:λ ), x ↦ ∅ ⟧ ) ):φ  # 𝕄(ξ.a)+      𝑛2·1 := Φ.number( φ ↦ Φ.bytes( φ ↦ 𝜎2:λ ) ):φ  # 𝕄(ξ.b)+      𝑛3·1 := Φ.number( φ ↦ Φ.bytes( φ ↦ 𝜎1:λ ) ):φ  # stay(𝑛1)+      𝑛4·1 := 𝑛2·1  # 𝑛2+      joined 𝜎4 := 𝛿1·1 ? 𝜎1 : 𝜎2+      𝑛·1·1 := Φ.number( φ ↦ Φ.bytes( φ ↦ 𝜎4:λ ) ):φ  # 𝑛3, 𝑛4+    𝑛·0·1 := Φ.number( φ ↦ Φ.bytes( φ ↦ 𝜎4:λ ) )
+ test-resources/evaluate-packs/joins-two-sides-differing-in-a-datum-a-symbolize-found-off-the-phi-chain.yaml view
@@ -0,0 +1,41 @@+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com+# SPDX-License-Identifier: MIT+---+# yamllint disable rule:line-length+# Both sides hold a bool in the method 'kq', the way the 'rewrite' rules bring+# 'Φ.false' into the shape of a computed bool when EO lowers 'number.floor'.+# The datum of the left one sits in the formation bound to 'if', which is no+# φ of anything, so a 'symbolize' walking the φ chain alone left it a datum+# and the sides never joined (#2008). The line turns every datum of the term+# into a fresh symbol, wherever it stands, so '𝑛3' holds 𝜎3 where '𝑛1' held+# 'FF-', the two sides differ in their symbols alone, and the join makes one+# term of them, joining 𝜎3 with 𝜎2 into 𝜎4.+symbolic: |+  - λ: L_fork+    dataize:+      𝛿1: $.c+    morph:+      𝑛1: $.a+      𝑛2: $.b+    symbolize:+      𝑛3: 𝑛1+      𝑛4: 𝑛2+    join:+      on: 𝛿1+      left: 𝑛3+      right: 𝑛4+location: 'Q.y'+hide-rho: false+input: '⟦ y ↦ ⟦ c ↦ ⟦ λ ⤍ 𝜎1 ⟧, a ↦ ⟦ φ ↦ ⟦⟧, kq ↦ Φ.bool( if ↦ ⟦ φ ↦ ⟦ Δ ⤍ FF- ⟧ ⟧ ) ⟧, b ↦ ⟦ φ ↦ ⟦⟧, kq ↦ Φ.bool( if ↦ ⟦ φ ↦ ⟦ λ ⤍ 𝜎2 ⟧ ⟧ ) ⟧, λ ⤍ L_fork ⟧.φ ⟧'+result: '⟦⟧'+protocol: |+  𝕄(Φ.y):+    𝔼(L_fork):+      𝛿1·1 := 𝔻(𝜎1:λ)  # 𝔻(ξ.c)+      𝑛1·1 := ⟦ φ ↦ ⟦⟧, kq ↦ Φ.bool( if ↦ FF-:Δ:φ ) ⟧  # 𝕄(ξ.a)+      𝑛2·1 := ⟦ φ ↦ ⟦⟧, kq ↦ Φ.bool( if ↦ 𝜎2:λ:φ ) ⟧  # 𝕄(ξ.b)+      symbolized 𝜎3 := FF-+      𝑛3·1 := ⟦ φ ↦ ⟦⟧, kq ↦ Φ.bool( if ↦ 𝜎3:λ:φ ) ⟧  # symbolize(𝑛1)+      𝑛4·1 := ⟦ φ ↦ ⟦⟧, kq ↦ Φ.bool( if ↦ 𝜎2:λ:φ ) ⟧  # 𝑛2+      joined 𝜎4 := 𝛿1·1 ? 𝜎3 : 𝜎2+      𝑛·1·1 := ⟦ φ ↦ ⟦⟧, kq ↦ Φ.bool( if ↦ 𝜎4:λ:φ ) ⟧  # 𝑛3, 𝑛4
+ test-resources/evaluate-packs/joins-two-sides-that-differ-only-in-symbols.yaml view
@@ -0,0 +1,124 @@+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com+# SPDX-License-Identifier: MIT+---+# yamllint disable rule:line-length+# A join brings both of its sides to a normal form through 𝕄 first (#1954).+# Each side here is a call of 'plus' over the answer of another 'plus', which+# 𝕄 fires: 𝜎2 on the left, 𝜎3 on the right, and nothing else tells the two+# sides apart. So the join mints 𝜎4, writes that 𝜎4 is 𝜎2 when the condition+# holds and 𝜎3 when it does not, and answers the joined call, whose ρ holds+# 𝜎4; the last 'plus' reads 𝜎4.+symbolic: |+  - λ: L_number_gt+    dataize:+      𝛿1: $.ρ+      𝛿2: $.x+    𝑛: Φ.bool( φ ↦ ⟦ λ ⤍ 𝜎 ⟧ )+  - λ: L_number_plus+    dataize:+      𝛿1: $.ρ+      𝛿2: $.x+    𝑛: Φ.number( φ ↦ ⟦ λ ⤍ 𝜎 ⟧ )+  - λ: L_fork+    dataize:+      𝛿1: $.ρ+    join:+      on: 𝛿1+      left: $.then+      right: $.else+location: 'Q.@'+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( 0 ), 3.plus( 4 ).plus( 0 ) ).φ ⟧+result: '𝜎5:λ'+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.plus( 2 ).plus( 𝑛·1·3 ) )+    𝑛·0·4 := 𝑛·0·3( else ↦ 3.plus( 4 ).plus( 𝑛·1·3 ) )+    𝔼(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 )+        𝔼(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 := 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)+        𝑛·2·4 := 𝑛·3·6.plus( x ↦ 𝑛·1·3 )+      𝕄(Φ.a🌵6):+        𝑛·2·5 := 3+        𝑛·2·6 := 𝑛·2·5.plus( x ↦ 4 )+        𝔼(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 := 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)+        𝑛·2·7 := 𝑛·4·6.plus( x ↦ 𝑛·1·3 )+      joined 𝜎4 := 𝛿1·2 ? 𝜎2 : 𝜎3+      𝑛·2·8 := ⟦ x ↦ 𝑛·1·3, λ ⤍ L_number_plus ⟧  # 𝕄(ξ.then), 𝕄(ξ.else)+    𝔼(L_number_plus):+      𝔻(Φ.a🌵9):+        𝕄(Φ.a🌵9):+          𝑛·5·1 := Φ.number( φ ↦ 𝜎4:λ )+      𝛿1·5 := 𝔻(𝜎4:λ)  # 𝔻(ξ.ρ)+      𝔻(Φ.a🌵10):+        𝕄(Φ.a🌵10):+          𝑛·5·2 := Φ.number( φ ↦ 𝑛·1·4 )+          𝑛·5·3 := Φ.bytes( φ ↦ 00-00-00-00-00-00-00-00:Δ )+        𝛿·5·1 := 00-00-00-00-00-00-00-00  # 0.0+      𝛿2·5 := 𝛿·5·1  # 𝔻(ξ.x)+      𝑛·5·4 := Φ.number( φ ↦ 𝜎5:λ )  # 𝑛+      𝕄(𝑛·5·4):+        𝑛·5·5 := Φ.number( φ ↦ 𝜎5:λ )+      𝑛·5·6 := 𝑛·5·5  # 𝕄(𝑛·5·4)
− test-resources/evaluate-packs/keeps-a-bare-symbol-no-path-casts-for-a-lenient-join.yaml
@@ -1,45 +0,0 @@-# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com-# SPDX-License-Identifier: MIT-----# yamllint disable rule:line-length-# An 'upcast' block casts the bare symbol of its pair only when a path reaches-# every symbol of the other term (#1921). The one symbol of the branch 'b' sits-# under the dispatch '.not', which no path walks into, so both lines bind their-# terms as they are and nothing is written for the cast. The 'join' line of the-# lenient entry then pairs the bare symbol with that one symbol, the way a-# lenient join always did (#1817).-symbolic: |-  - λ: L_fork-    morph:-      𝑛1: $.a-      𝑛2: $.b-    upcast:-      𝑛3: 𝑛1-      𝑛4: 𝑛2-    join:-      𝑛5: [𝑛3, 𝑛4]-    lenient: []-    𝑛: 𝑛5-location: 'Q.y'-input: |-  ⟦-    bool(if) ↦ ⟦ φ ↦ ξ.if( α0 ↦ ⟦ Δ ⤍ FF- ⟧, α1 ↦ ⟦ Δ ⤍ 00- ⟧ ) ⟧,-    y ↦ ⟦-      a ↦ ⟦ λ ⤍ 𝜎1 ⟧,-      b ↦ ⟦ φ ↦ Φ.bool( if ↦ ⟦ λ ⤍ L_fork, left ↦ ∅, right ↦ ∅, φ ↦ ⟦ λ ⤍ 𝜎2 ⟧.not ⟧ ) ⟧,-      λ ⤍ L_fork-    ⟧.φ-  ⟧-result: '⟦ if(left, right) ↦ ⟦ λ ⤍ L_fork, φ ↦ 𝜎3:λ.not ⟧, φ ↦ if( FF-:Δ, 00-:Δ ) ⟧'-protocol: |-  𝕄(Φ.y):-    𝔼(L_fork):-      𝑛1·1 := 𝜎1:λ  # 𝕄(ξ.a)-      𝑛2·1 := Φ.bool( if(left, right) ↦ ⟦ λ ⤍ L_fork, φ ↦ 𝜎2:λ.not ⟧ ):φ  # 𝕄(ξ.b)-      𝑛3·1 := 𝑛1·1  # 𝑛1-      𝑛4·1 := Φ.bool( if(left, right) ↦ ⟦ λ ⤍ L_fork, φ ↦ 𝜎2:λ.not ⟧ ):φ  # 𝑛2-      𝔻(𝜎3:λ) ∈ { 𝔻(𝜎1:λ), 𝔻(𝜎2:λ) }-      𝑛5·1 := Φ.bool( if(left, right) ↦ ⟦ λ ⤍ L_fork, φ ↦ 𝜎3:λ.not ⟧ ):φ  # [𝑛3, 𝑛4]-      𝑛·1·1 := Φ.bool( if(left, right) ↦ ⟦ λ ⤍ L_fork, φ ↦ 𝜎3:λ.not ⟧ ):φ  # 𝑛-      𝑛·1·2 := Φ.bool( if(left, right) ↦ ⟦ λ ⤍ L_fork, φ ↦ 𝜎3:λ.not ⟧ ):φ  # 𝕄(𝑛·1·1)-    𝑛·0·1 := Φ.bool( if(left, right) ↦ ⟦ λ ⤍ L_fork, φ ↦ 𝜎3:λ.not ⟧ )
− test-resources/evaluate-packs/keeps-a-cut-met-inside-a-firing-for-the-next-firing.yaml
@@ -1,47 +0,0 @@-# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com-# SPDX-License-Identifier: MIT-----# yamllint disable rule:line-length-# The memo of '--acyclic=plausible' keeps a cut the way it keeps an answer.-# '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-    𝑛: ⟦ λ ⤍ 𝜎 ⟧-  - λ: L_pair-    morph:-      𝑛1: $.x-      𝑛2: $.y-    𝑛: ⟦ φ ↦ ⟦ λ ⤍ 𝜎 ⟧ ⟧-location: 'Q.p'-acyclic: plausible-input: >-  ⟦ cyc ↦ ⟦ x ↦ ∅, φ ↦ Φ.cyc( ξ.x ) ⟧,-    p ↦ ⟦-      x ↦ ⟦ arg ↦ Φ.cyc( ⟦⟧ ), λ ⤍ L_outer ⟧.φ,-      y ↦ ⟦ arg ↦ Φ.cyc( ⟦⟧ ), λ ⤍ L_outer ⟧.φ,-      λ ⤍ L_pair ⟧.φ ⟧-protocol: |-  𝕄(Φ.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
@@ -1,45 +0,0 @@-# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com-# SPDX-License-Identifier: MIT-----# yamllint disable rule:line-length-# The memo of '--acyclic=plausible' keeps a stuck firing the way it keeps a-# 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-    𝑛: ⟦ λ ⤍ 𝜎 ⟧-  - λ: L_pair-    morph:-      𝑛1: $.x-      𝑛2: $.y-    𝑛: ⟦ φ ↦ ⟦ λ ⤍ 𝜎 ⟧ ⟧-location: 'Q.p'-partial: true-acyclic: plausible-input: >-  ⟦ p ↦ ⟦-      x ↦ ⟦ arg ↦ ⟦ λ ⤍ L_none ⟧, λ ⤍ L_outer ⟧.φ,-      y ↦ ⟦ arg ↦ ⟦ λ ⤍ L_none ⟧, λ ⤍ L_outer ⟧.φ,-      λ ⤍ L_pair ⟧.φ ⟧-protocol: |-  𝕄(Φ.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
@@ -1,31 +0,0 @@-# 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-answer-a-firing-already-gave.yaml view
@@ -16,4 +16,4 @@     𝔼(L_answers):       𝑛·1·1 := L_none:λ  # 𝑛       𝑛·1·2 := L_none:λ  # 𝕄(𝑛·1·1)-    unanswered(L_none)  # 𝕄(L_none:λ)+    unanswered L_none  # 𝕄(L_none:λ)
+ test-resources/evaluate-packs/leaves-a-bare-symbol-an-evaluate-line-meets-as-it-is.yaml view
@@ -0,0 +1,25 @@+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com+# SPDX-License-Identifier: MIT+---+# yamllint disable rule:line-length+# The 'morph' operand comes down to a bare symbol, ⟦ λ ⤍ 𝜎4 ⟧, which already+# stands for a value nobody worked out. No entry answers a symbol, so firing+# it could only get the whole entry stuck; the 'evaluate' line hands it back+# as it is, the way it hands back ⊥ (#2036).+symbolic: |+  - λ: L_keep+    morph:+      𝑛1: $.x+    evaluate:+      𝑛2: 𝑛1+    𝑛: ⟦ φ ↦ 𝑛2 ⟧+location: 'Q.y'+input: '⟦ y ↦ ⟦ x ↦ ⟦ λ ⤍ 𝜎4 ⟧, λ ⤍ L_keep ⟧.φ ⟧'+result: '𝜎4:λ'+protocol: |+  𝕄(Φ.y):+    𝔼(L_keep):+      𝑛1·1 := 𝜎4:λ  # 𝕄(ξ.x)+      𝑛2·1 := 𝑛1·1  # 𝔼(𝑛1)+      𝑛·1·1 := 𝜎4:λ:φ  # 𝑛+      𝑛·1·2 := 𝜎4:λ:φ  # 𝕄(𝑛·1·1)
− test-resources/evaluate-packs/leaves-a-terminator-an-evaluate-line-meets-unfired.yaml
@@ -1,38 +0,0 @@-# 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-the-literals-of-a-method-out-of-a-join.yaml
@@ -1,45 +0,0 @@-# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com-# SPDX-License-Identifier: MIT-----# yamllint disable rule:line-length-# Each branch of a fork reaches the join as the whole formation of its carrier,-# methods included, and a carrier of any size declares methods full of-# literals. Neither stage has any business with them: a term carries the value-# it stands for where its φ chain ends, so the '-1' of a 'neg' nobody has-# called is code and not data, and standing it into an unknown would mint a-# symbol nobody ever reads — dozens of them per fork over a real object model,-# each one a line of the protocol and a fact a reader has to know to skip-# (#1293).-#-# Here both branches carry a literal under φ, which is the value of the branch-# and is stood, and another inside 'neg', which is not and is left alone. The-# two branches differ inside 'neg' as well, and that difference is no fork:-# the join takes the method of the first branch verbatim, mints one symbol for-# the one pair of values that differ, and the answer keeps its methods so the-# program can go on dispatching on it.-symbolic: |-  - λ: L_fork-    morph:-      𝑛1: $.a-      𝑛2: $.b-    symbolize:-      𝑛3: 𝑛1-      𝑛4: 𝑛2-    join:-      𝑛5: [𝑛3, 𝑛4]-    𝑛: 𝑛5-location: 'Q.y'-input: '⟦ y ↦ ⟦ a ↦ ⟦ φ ↦ ⟦ Δ ⤍ 01- ⟧, neg ↦ ⟦ φ ↦ ⟦ Δ ⤍ 0A- ⟧ ⟧ ⟧, b ↦ ⟦ φ ↦ ⟦ Δ ⤍ 02- ⟧, neg ↦ ⟦ φ ↦ ⟦ Δ ⤍ 0B- ⟧ ⟧ ⟧, λ ⤍ L_fork ⟧.φ ⟧'-protocol: |-  𝕄(Φ.y):-    𝔼(L_fork):-      𝑛1·1 := ⟦ φ ↦ 01-:Δ, neg ↦ 0A-:Δ:φ ⟧  # 𝕄(ξ.a)-      𝑛2·1 := ⟦ φ ↦ 02-:Δ, neg ↦ 0B-:Δ:φ ⟧  # 𝕄(ξ.b)-      𝔻(𝜎1:λ) == 01--      𝑛3·1 := ⟦ φ ↦ 𝜎1:λ, neg ↦ 0A-:Δ:φ ⟧  # 𝑛1-      𝔻(𝜎2:λ) == 02--      𝑛4·1 := ⟦ φ ↦ 𝜎2:λ, neg ↦ 0B-:Δ:φ ⟧  # 𝑛2-      𝔻(𝜎3:λ) ∈ { 𝔻(𝜎1:λ), 𝔻(𝜎2:λ) }-      𝑛5·1 := ⟦ φ ↦ 𝜎3:λ, neg ↦ 0A-:Δ:φ ⟧  # [𝑛3, 𝑛4]-      𝑛·1·1 := 𝑛5·1  # 𝑛-      𝑛·1·2 := 𝑛5·1  # 𝕄(𝑛·1·1)
− test-resources/evaluate-packs/mints-past-the-symbols-a-stuck-firing-minted.yaml
@@ -1,34 +0,0 @@-# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com-# SPDX-License-Identifier: MIT-----# yamllint disable rule:line-length-# The firing of 'L_inner' mints 𝜎1 for its answer and then gets stuck, since-# nothing answers a dispatch off a symbol, and '--partial' parks its site. The-# protocol keeps what that firing wrote, so 𝜎1 is spelled there already. Before-# #1738 the parked frame handed back the state it started from, the count of-# minted symbols went back with it, and 'L_outer' minted 𝜎1 once more, for-# another value. Now the count never goes back, and 'L_outer' mints 𝜎2.-symbolic: |-  - λ: L_inner-    𝑛: ⟦ λ ⤍ 𝜎 ⟧.foo-  - λ: L_outer-    morph:-      𝑛1: $.a-    𝑛: ⟦ λ ⤍ 𝜎 ⟧-location: 'Q.y'-partial: true-input: '⟦ y ↦ ⟦ a ↦ ⟦ λ ⤍ L_inner ⟧.v, λ ⤍ L_outer ⟧.z ⟧'-result: '𝜎2:λ.z'-protocol: |-  𝕄(Φ.y):-    𝔼(L_outer):-      𝕄(Φ.a🌵0):-        𝔼(L_inner):-          𝑛·2·1 := 𝜎1:λ.foo  # 𝑛-          𝕄(𝑛·2·1):-            unanswered(𝜎1)  # 𝕄(𝜎1:λ)-          stuck(𝜎1)-      𝑛1·1 := L_inner:λ.v  # 𝕄(ξ.a)-      𝑛·1·1 := 𝜎2:λ  # 𝑛-      𝑛·1·2 := 𝜎2:λ  # 𝕄(𝑛·1·1)-    unanswered(𝜎2)  # 𝕄(𝜎2:λ)
test-resources/evaluate-packs/parks-a-dispatch-off-a-symbol.yaml view
@@ -18,4 +18,4 @@ result: '𝜎1:λ.plus( 5 )' protocol: |   𝕄(Φ.a):-    unanswered(𝜎1)  # 𝕄(𝜎1:λ)+    unanswered 𝜎1  # 𝕄(𝜎1:λ)
+ test-resources/evaluate-packs/parks-a-firing-whose-deferred-argument-got-stuck.yaml view
@@ -0,0 +1,42 @@+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com+# SPDX-License-Identifier: MIT+---+# yamllint disable rule:line-length+# A 'defer' line works out every argument of a copy that is no bare symbol+# before it writes the call. Here the argument 'x' fires 'L_outer', whose+# operand gets stuck on 'L_none'. Under '--partial' the firing of 'L_pair'+# gets stuck right there, so no 'deferred' line is written and the site of+# 'L_pair' is parked. Before #2014 the argument was kept as written and the+# entry went on as if it were worked out.+symbolic: |+  - λ: L_outer+    dataize:+      𝛿1: ξ.arg+    𝑛: ⟦ λ ⤍ 𝜎 ⟧+  - λ: L_pair+    morph:+      𝑛1: $.c+    defer:+      𝑛2: 𝑛1+    𝑛: 𝑛2+location: 'Q.p'+partial: true+input: >+  ⟦ box ↦ ⟦ s ↦ ∅, x ↦ ∅, φ ↦ ξ.s.next ⟧,+    p ↦ ⟦+      c ↦ Φ.box( ⟦ λ ⤍ 𝜎1 ⟧, ⟦ arg ↦ ⟦ λ ⤍ L_none ⟧, λ ⤍ L_outer ⟧.φ ),+      λ ⤍ L_pair ⟧.φ ⟧+result: '⟦ c ↦ Φ.box( ⟦ λ ⤍ 𝜎1 ⟧, ⟦ arg ↦ ⟦ λ ⤍ L_none ⟧, λ ⤍ L_outer ⟧.φ ), λ ⤍ L_pair ⟧.φ'+protocol: |+  𝕄(Φ.p):+    𝔼(L_pair):+      𝕄(Φ.a🌵0):+        𝑛·1·1 := Φ.box( s ↦ 𝜎1:λ )+        𝑛·1·2 := 𝑛·1·1( x ↦ ⟦ arg ↦ L_none:λ, λ ⤍ L_outer ⟧.φ )+      𝑛1·1 := 𝑛·1·2  # 𝕄(ξ.c)+      𝕄(𝑛1.x):+        𝔼(L_outer):+          𝔻(Φ.a🌵1):+            unanswered L_none  # 𝔻(L_none:λ)+          stuck L_none+      stuck L_none
+ test-resources/evaluate-packs/parks-a-firing-whose-join-side-got-stuck.yaml view
@@ -0,0 +1,27 @@+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com+# SPDX-License-Identifier: MIT+---+# yamllint disable rule:line-length+# The left side of the join is a path, '$.a', whose morph gets stuck on+# 'L_none'. Under '--partial' the firing gets stuck right there, so the right+# side is never morphed and nothing is compared, and the site the fork stands+# at is parked (#2014).+symbolic: |+  - λ: L_fork+    dataize:+      𝛿1: $.c+    join:+      on: 𝛿1+      left: $.a+      right: $.b+location: 'Q.y'+partial: true+input: '⟦ y ↦ ⟦ c ↦ ⟦ λ ⤍ 𝜎1 ⟧, a ↦ ⟦ λ ⤍ L_none ⟧.x, b ↦ ⟦ Δ ⤍ 05- ⟧, λ ⤍ L_fork ⟧.φ ⟧'+result: '⟦ c ↦ ⟦ λ ⤍ 𝜎1 ⟧, a ↦ ⟦ λ ⤍ L_none ⟧.x, b ↦ ⟦ Δ ⤍ 05- ⟧, λ ⤍ L_fork ⟧.φ'+protocol: |+  𝕄(Φ.y):+    𝔼(L_fork):+      𝛿1·1 := 𝔻(𝜎1:λ)  # 𝔻(ξ.c)+      𝕄(Φ.a🌵1):+        unanswered L_none  # 𝕄(L_none:λ)+      stuck L_none
+ test-resources/evaluate-packs/parks-a-firing-whose-morph-operand-diverged.yaml view
@@ -0,0 +1,36 @@+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com+# SPDX-License-Identifier: MIT+---+# yamllint disable rule:line-length+# The 'morph' of '$.a' fires an inner fork whose two sides, two different+# data, do not join. Under '--partial' the inner firing writes 'diverged' and+# the outer firing gets stuck right there, so the 'morph' of '$.b' and the+# outer join never run and the site of the outer fork is parked. Before #2014+# the outer join compared the unmorphed '$.a' with '$.b' and wrote a second+# 'diverged' that only echoed the first one.+symbolic: |+  - λ: L_fork+    dataize:+      𝛿1: $.c+    morph:+      𝑛1: $.a+      𝑛2: $.b+    join:+      on: 𝛿1+      left: 𝑛1+      right: 𝑛2+location: 'Q.y'+partial: true+input: '⟦ y ↦ ⟦ c ↦ ⟦ λ ⤍ 𝜎1 ⟧, a ↦ ⟦ c ↦ ⟦ λ ⤍ 𝜎2 ⟧, a ↦ ⟦ Δ ⤍ 01- ⟧, b ↦ ⟦ Δ ⤍ 02- ⟧, λ ⤍ L_fork ⟧.φ, b ↦ ⟦ Δ ⤍ 05- ⟧, λ ⤍ L_fork ⟧.φ ⟧'+result: '⟦ c ↦ ⟦ λ ⤍ 𝜎1 ⟧, a ↦ ⟦ c ↦ ⟦ λ ⤍ 𝜎2 ⟧, a ↦ ⟦ Δ ⤍ 01- ⟧, b ↦ ⟦ Δ ⤍ 02- ⟧, λ ⤍ L_fork ⟧.φ, b ↦ ⟦ Δ ⤍ 05- ⟧, λ ⤍ L_fork ⟧.φ'+protocol: |+  𝕄(Φ.y):+    𝔼(L_fork):+      𝛿1·1 := 𝔻(𝜎1:λ)  # 𝔻(ξ.c)+      𝕄(Φ.a🌵1):+        𝔼(L_fork):+          𝛿1·2 := 𝔻(𝜎2:λ)  # 𝔻(ξ.c)+          𝑛1·2 := 01-:Δ  # 𝕄(ξ.a)+          𝑛2·2 := 02-:Δ  # 𝕄(ξ.b)+          diverged 𝑛1·2 vs 𝑛2·2  # they differ at .Δ, and the join names no otherwise+      stuck L_fork
+ test-resources/evaluate-packs/parks-a-firing-whose-morph-operand-got-stuck.yaml view
@@ -0,0 +1,31 @@+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com+# SPDX-License-Identifier: MIT+---+# yamllint disable rule:line-length+# The 'morph' of '$.a' gets stuck on 'L_none', which no entry answers. Under+# '--partial' the firing gets stuck right there, as it does without the+# option, so neither the 'morph' of '$.b' nor the join runs, and the site the+# fork stands at is parked. Before #2014 the operand came back as the term it+# was parked at and the entry went on as if it were morphed.+symbolic: |+  - λ: L_fork+    dataize:+      𝛿1: $.c+    morph:+      𝑛1: $.a+      𝑛2: $.b+    join:+      on: 𝛿1+      left: 𝑛1+      right: 𝑛2+location: 'Q.y'+partial: true+input: '⟦ y ↦ ⟦ c ↦ ⟦ λ ⤍ 𝜎1 ⟧, a ↦ ⟦ λ ⤍ L_none ⟧.x, b ↦ ⟦ Δ ⤍ 05- ⟧, λ ⤍ L_fork ⟧.φ ⟧'+result: '⟦ c ↦ ⟦ λ ⤍ 𝜎1 ⟧, a ↦ ⟦ λ ⤍ L_none ⟧.x, b ↦ ⟦ Δ ⤍ 05- ⟧, λ ⤍ L_fork ⟧.φ'+protocol: |+  𝕄(Φ.y):+    𝔼(L_fork):+      𝛿1·1 := 𝔻(𝜎1:λ)  # 𝔻(ξ.c)+      𝕄(Φ.a🌵1):+        unanswered L_none  # 𝕄(L_none:λ)+      stuck L_none
test-resources/evaluate-packs/parks-a-firing-whose-operand-dataizes-in-circles.yaml view
@@ -28,4 +28,4 @@         𝕄(Φ.a🌵0):           𝑛·1·1 := Φ.cyc( x ↦ ⟦⟧ )           𝑛·1·2 := Φ.cyc( x ↦ ⟦⟧ )-        looped(𝑛·1·2)  # proven+        looped 𝑛·1·2  # proven
test-resources/evaluate-packs/parks-a-firing-whose-operand-loops.yaml view
@@ -28,4 +28,4 @@           𝔼(L_loop):             𝑛·2·1 := L_loop:λ  # 𝑛             𝑛·2·2 := L_loop:λ  # 𝕄(𝑛·2·1)-          looped(L_loop:λ)  # proven+          looped L_loop:λ  # proven
test-resources/evaluate-packs/parks-a-firing-whose-operand-never-came-down.yaml view
@@ -20,5 +20,5 @@     𝔼(L_pair):       𝔻(Φ.a🌵0):         𝕄(Φ.a🌵0):-          unanswered(L_none)  # 𝕄(L_none:λ)-      stuck(L_none)+          unanswered L_none  # 𝕄(L_none:λ)+      stuck L_none
test-resources/evaluate-packs/parks-a-firing-whose-operand-no-rule-dataizes.yaml view
@@ -17,5 +17,5 @@ protocol: |   𝕄(Φ.y):     𝔼(L_pair):-      unanswered(⊥)  # 𝔻(∅:φ)-      stuck(⊥)+      unanswered ⊥  # 𝔻(∅:φ)+      stuck ⊥
test-resources/evaluate-packs/parks-a-firing-whose-operand-reaches-the-terminator.yaml view
@@ -6,10 +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, 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).+# writes 'unanswered(⊥)' with the term 𝔻 could not dataize (#1401). The+# firing of 'L_dataized' stands in the 'morph' operand of 'L_entry', which+# gets stuck right there and is parked as a whole, instead of handing on the+# residue its operand parked on (#2014). symbolic: |   - λ: L_dataized     dataize:@@ -20,11 +20,7 @@       𝛿1: $.n     morph:       𝑛1: $.v.φ-    𝑛: Φ.l🌵.root( 𝑛1 )-  - λ: L_root-    dataize:-      𝛿1: $.v-    𝑛: ⟦ ⟧+    𝑛: 𝑛1 location: 'Q.l🌵.e1' partial: true input: >@@ -35,11 +31,10 @@     block ↦ ⟦ data ↦ ∅, buffer ↦ ∅, read ↦ ⟦ φ ↦ Φ.dataized( target ↦ ξ.ρ.data.size ).as-bytes ⟧ ⟧,     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 ).φ     ⟧   ⟧-result: '⟦ v ↦ Φ.dataized( target ↦ ⊥ ).as-bytes, λ ⤍ L_root ⟧.φ'+result: '⟦ n ↦ 1, v ↦ Φ.block( buffer ↦ Φ.bytes( φ ↦ 𝜎1:λ ) ).read, λ ⤍ L_entry ⟧.φ' protocol: |   𝕄(Φ.l🌵.e1):     𝑛·0·1 := ⟦ n ↦ ∅, v ↦ ∅, λ ⤍ L_entry ⟧( n ↦ 1 )@@ -55,18 +50,6 @@         𝑛·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(⊥)-      stuck(⊥)+          unanswered ⊥  # 𝔻(⊥)+          stuck ⊥+      stuck ⊥
− test-resources/evaluate-packs/parks-a-firing-whose-remint-comes-to-no-symbol.yaml
@@ -1,35 +0,0 @@-# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com-# SPDX-License-Identifier: MIT-----# yamllint disable rule:line-length-# The very firing that fails without '--partial', with it: a 'remint' step-# with no symbol to answer gets the firing stuck, so '--partial' parks the-# site, as it parks a fork whose branches do not join (#1923).-symbolic: |-  - λ: L_shrink-    morph:-      𝑛1: $.x-    rewrite:-      𝑛2:-        of: 𝑛1-        rules:-          - name: small-pair-            pattern: ⟦ !B1, head ↦ 𝑒1, tail ↦ 𝑒2, !B2 ⟧-            result: ⟦ !B1, head ↦ 𝑒3, tail ↦ 𝑒4, !B2 ⟧-            where:-              - meta: 𝑒3-                function: remint-                args: [𝑒1]-              - meta: 𝑒4-                function: remint-                args: [𝑒2]-    𝑛: 𝑛2-location: 'Q.y'-partial: true-input: '⟦ y ↦ ⟦ x ↦ ⟦ head ↦ ⟦ λ ⤍ 𝜎1 ⟧, tail ↦ ⟦ k ↦ ⟦ λ ⤍ 𝜎2 ⟧ ⟧ ⟧, λ ⤍ L_shrink ⟧.tail ⟧'-result: '⟦ x ↦ ⟦ head ↦ 𝜎1:λ, tail ↦ 𝜎2:λ:k ⟧, λ ⤍ L_shrink ⟧.tail'-protocol: |-  𝕄(Φ.y):-    𝔼(L_shrink):-      𝑛1·1 := ⟦ head ↦ 𝜎1:λ, tail ↦ 𝜎2:λ:k ⟧  # 𝕄(ξ.x)-      unreminted(L_shrink)  # 𝜎2:λ:k
− test-resources/evaluate-packs/parks-a-fork-whose-branches-do-not-join.yaml
@@ -1,27 +0,0 @@-# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com-# SPDX-License-Identifier: MIT-----# yamllint disable rule:line-length-# Branches differing anywhere but in a symbol are not joined, and that is the-# whole rule: no fallback and no second alternative. Bringing two branches to-# one shape is the program's business rather than phino's, so a fork whose-# branches differ in structure gets stuck the way a λ function no entry answers-# does and '--partial' parks the site instead of failing the run.-symbolic: |-  - λ: L_fork-    morph:-      𝑛1: $.a-      𝑛2: $.b-    join:-      𝑛3: [𝑛1, 𝑛2]-    𝑛: 𝑛3-location: 'Q.y'-partial: true-input: '⟦ y ↦ ⟦ a ↦ ⟦ l ↦ ⟦ λ ⤍ 𝜎1 ⟧ ⟧, b ↦ ⟦ l ↦ ⟦ λ ⤍ 𝜎2 ⟧, x ↦ ⟦⟧ ⟧, λ ⤍ L_fork ⟧.l ⟧'-result: '⟦ a ↦ 𝜎1:λ:l, b ↦ ⟦ l ↦ 𝜎2:λ, x ↦ ⟦⟧ ⟧, λ ⤍ L_fork ⟧.l'-protocol: |-  𝕄(Φ.y):-    𝔼(L_fork):-      𝑛1·1 := 𝜎1:λ:l  # 𝕄(ξ.a)-      𝑛2·1 := ⟦ l ↦ 𝜎2:λ, x ↦ ⟦⟧ ⟧  # 𝕄(ξ.b)-      unjoined(L_fork)  # 𝜎1:λ:l vs ⟦ l ↦ 𝜎2:λ, x ↦ ⟦⟧ ⟧
test-resources/evaluate-packs/parks-a-lambda-no-entry-answers.yaml view
@@ -15,4 +15,4 @@ result: 'L_none:λ.foo' protocol: |   𝕄(Φ.x):-    unanswered(L_none)  # 𝕄(L_none:λ)+    unanswered L_none  # 𝕄(L_none:λ)
test-resources/evaluate-packs/parks-an-entry-that-answers-with-a-firing-of-itself.yaml view
@@ -18,4 +18,4 @@     𝔼(L_loop):       𝑛·1·1 := L_loop:λ  # 𝑛       𝑛·1·2 := L_loop:λ  # 𝕄(𝑛·1·1)-    looped(L_loop:λ)  # proven+    looped L_loop:λ  # proven
test-resources/evaluate-packs/parks-an-evaluate-line-firing-a-lambda-no-entry-answers.yaml view
@@ -20,5 +20,5 @@   𝕄(Φ.y):     𝔼(L_fork):       𝑛1·1 := L_none:λ  # 𝕄(ξ.a)-      unanswered(L_none)  # 𝔼(L_none:λ)-      stuck(L_none)+      unanswered L_none  # 𝔼(L_none:λ)+      stuck L_none
− test-resources/evaluate-packs/parks-an-evaluate-line-over-a-branch-left-unreduced.yaml
@@ -1,26 +0,0 @@-# 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/parks-an-evaluate-line-over-a-term-that-is-no-formation.yaml view
@@ -0,0 +1,33 @@+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com+# SPDX-License-Identifier: MIT+---+# yamllint disable rule:line-length+# The 'rewrite' line takes 'Φ.k' out of the formation 'a' morphs to, and that+# dispatch is no formation and so nothing 𝔼 can be asked of: the 'evaluate'+# line gets stuck on the fork itself, and '--partial' parks the site the fork+# stands at (#1874). A branch parked under '--partial' never comes this far,+# since its firing gets stuck where its morph does (#2014).+symbolic: |+  - λ: L_fork+    morph:+      𝑛1: $.a+    rewrite:+      𝑛2:+        of: 𝑛1+        rules:+          - name: unwrap+            pattern: ⟦ φ ↦ 𝑒1 ⟧+            result: 𝑒1+    evaluate:+      𝑛3: 𝑛2+    𝑛: 𝑛3+location: 'Q.y'+partial: true+input: '⟦ k ↦ ⟦⟧, y ↦ ⟦ a ↦ ⟦ φ ↦ Φ.k ⟧, λ ⤍ L_fork ⟧.φ ⟧'+result: '⟦ a ↦ ⟦ φ ↦ Φ.k ⟧, λ ⤍ L_fork ⟧.φ'+protocol: |+  𝕄(Φ.y):+    𝔼(L_fork):+      𝑛1·1 := Φ.k:φ  # 𝕄(ξ.a)+      𝑛2·1 := Φ.k  # rewrite(𝑛1)+      stuck L_fork
test-resources/evaluate-packs/parks-the-same-dataization-loop-under-partial.yaml view
@@ -29,5 +29,5 @@         𝕄(Φ.a🌵0):           𝑛·1·1 := Φ.cyc( x ↦ ⟦⟧ )           𝑛·1·2 := Φ.cyc( x ↦ ⟦⟧ )-        looped(𝑛·1·2)  # proven-      stuck(L_outer)+        looped 𝑛·1·2  # proven+      stuck L_outer
+ test-resources/evaluate-packs/parks-two-sides-that-do-not-join.yaml view
@@ -0,0 +1,26 @@+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com+# SPDX-License-Identifier: MIT+---+# yamllint disable rule:line-length+# The sides differ in a datum, so no join makes one term of them and the+# firing writes 'diverged' (#1996). No line of the text named either side,+# since each morphs to itself, so the line writes them as the terms they are.+# Under '--partial' the site the join stands at is parked instead of failing+# the run.+symbolic: |+  - λ: L_fork+    dataize:+      𝛿1: $.c+    join:+      on: 𝛿1+      left: $.a+      right: $.b+location: 'Q.y'+partial: true+input: '⟦ y ↦ ⟦ c ↦ ⟦ λ ⤍ 𝜎1 ⟧, a ↦ ⟦ φ ↦ ⟦ Δ ⤍ 01- ⟧ ⟧, b ↦ ⟦ φ ↦ ⟦ Δ ⤍ 02- ⟧ ⟧, λ ⤍ L_fork ⟧.φ ⟧'+result: '⟦ c ↦ ⟦ λ ⤍ 𝜎1 ⟧, a ↦ ⟦ φ ↦ ⟦ Δ ⤍ 01- ⟧ ⟧, b ↦ ⟦ φ ↦ ⟦ Δ ⤍ 02- ⟧ ⟧, λ ⤍ L_fork ⟧.φ'+protocol: |+  𝕄(Φ.y):+    𝔼(L_fork):+      𝛿1·1 := 𝔻(𝜎1:λ)  # 𝔻(ξ.c)+      diverged 01-:Δ:φ vs 02-:Δ:φ  # they differ at .φ.Δ, and the join names no otherwise
+ test-resources/evaluate-packs/passes-a-side-that-is-no-formation-through-an-evaluate-line-to-the-join.yaml view
@@ -0,0 +1,49 @@+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com+# SPDX-License-Identifier: MIT+---+# yamllint disable rule:line-length+# The 'rewrite' lines take 'Φ.k' and 'Φ.j' out of the formations the two sides+# morph to, and neither dispatch is a formation. An entry that computes gets+# stuck on such a term in an 'evaluate' line, but a join compares whatever its+# sides came to, so an 'evaluate' line of an entry that joins passes the term+# through as it is (#1999). Here the join gets both, finds them apart, and+# writes 'diverged', so the site the join stands at is parked.+symbolic: |+  - λ: L_fork+    dataize:+      𝛿1: $.c+    morph:+      𝑛1: $.a+      𝑛2: $.b+    rewrite:+      𝑛3:+        of: 𝑛1+        rules: &unwrap+          - name: unwrap+            pattern: ⟦ φ ↦ 𝑒1 ⟧+            result: 𝑒1+      𝑛4:+        of: 𝑛2+        rules: *unwrap+    evaluate:+      𝑛5: 𝑛3+      𝑛6: 𝑛4+    join:+      on: 𝛿1+      left: 𝑛5+      right: 𝑛6+location: 'Q.y'+partial: true+input: '⟦ k ↦ ⟦⟧, j ↦ ⟦⟧, y ↦ ⟦ c ↦ ⟦ λ ⤍ 𝜎1 ⟧, a ↦ ⟦ φ ↦ Φ.k ⟧, b ↦ ⟦ φ ↦ Φ.j ⟧, λ ⤍ L_fork ⟧.φ ⟧'+result: '⟦ c ↦ ⟦ λ ⤍ 𝜎1 ⟧, a ↦ ⟦ φ ↦ Φ.k ⟧, b ↦ ⟦ φ ↦ Φ.j ⟧, λ ⤍ L_fork ⟧.φ'+protocol: |+  𝕄(Φ.y):+    𝔼(L_fork):+      𝛿1·1 := 𝔻(𝜎1:λ)  # 𝔻(ξ.c)+      𝑛1·1 := Φ.k:φ  # 𝕄(ξ.a)+      𝑛2·1 := Φ.j:φ  # 𝕄(ξ.b)+      𝑛3·1 := Φ.k  # rewrite(𝑛1)+      𝑛5·1 := Φ.k  # 𝔼(𝑛3)+      𝑛4·1 := Φ.j  # rewrite(𝑛2)+      𝑛6·1 := Φ.j  # 𝔼(𝑛4)+      diverged 𝑛5·1 vs 𝑛6·1  # the join names no otherwise
− test-resources/evaluate-packs/reduces-the-branches-of-a-fork-through-morphing.yaml
@@ -1,115 +0,0 @@-# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com-# SPDX-License-Identifier: MIT-----# 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 𝕄, 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:-      𝛿1: $.ρ-      𝛿2: $.x-    𝑛: Φ.bool( φ ↦ ⟦ λ ⤍ 𝜎 ⟧ )-  - λ: L_number_plus-    dataize:-      𝛿1: $.ρ-      𝛿2: $.x-    𝑛: Φ.number( φ ↦ ⟦ λ ⤍ 𝜎 ⟧ )-  - λ: L_fork-    dataize:-      𝛿1: $.ρ-    morph:-      𝑛1: $.then-      𝑛2: $.else-    evaluate:-      𝑛3: 𝑛1-      𝑛4: 𝑛2-    𝑛: 𝑛3-location: 'Q.@'-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 ) ).φ ⟧-result: '𝜎2:λ'-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.plus( 2 ) )-    𝑛·0·4 := 𝑛·0·3( else ↦ 3.plus( 4 ) )-    𝔼(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·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)-      𝑛·2·6 := 𝑛3·2  # 𝑛-      𝑛·2·7 := 𝑛3·2  # 𝕄(𝑛·2·6)
− test-resources/evaluate-packs/remints-a-call-as-the-symbol-its-firing-answers.yaml
@@ -1,53 +0,0 @@-# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com-# SPDX-License-Identifier: MIT-----# yamllint disable rule:line-length-# Morphing is lazy, so the tail of the pair below is still the call of 'L_inc'-# as the code spells it. A 'remint' step works such a term out the way an-# argument of a deferred copy is worked out since #1916: it morphs the term,-# fires what it comes to and writes every step, and the answer of 'L_inc' is-# a copy of 'Φ.num' whose φ is the fresh symbol '𝜎2', so the tail is '𝜎2'-# (#1923).-symbolic: |-  - λ: L_shrink-    morph:-      𝑛1: $.x-    rewrite:-      𝑛2:-        of: 𝑛1-        rules:-          - name: small-pair-            pattern: ⟦ !B1, head ↦ 𝑒1, tail ↦ 𝑒2, !B2 ⟧-            result: ⟦ !B1, head ↦ 𝑒3, tail ↦ 𝑒4, !B2 ⟧-            where:-              - meta: 𝑒3-                function: remint-                args: [𝑒1]-              - meta: 𝑒4-                function: remint-                args: [𝑒2]-    𝑛: 𝑛2-  - λ: L_inc-    dataize:-      𝛿1: $.x-    𝑛: Φ.num( φ ↦ ⟦ λ ⤍ 𝜎 ⟧ )-location: 'Q.y'-input: '⟦ num(φ) ↦ ⟦⟧, inc(x) ↦ ⟦ λ ⤍ L_inc ⟧, y ↦ ⟦ x ↦ ⟦ head ↦ ⟦ λ ⤍ 𝜎1 ⟧, tail ↦ Φ.inc( ⟦ Δ ⤍ 2A- ⟧ ) ⟧, λ ⤍ L_shrink ⟧.tail ⟧'-result: '𝜎2:λ'-protocol: |-  𝕄(Φ.y):-    𝔼(L_shrink):-      𝑛1·1 := ⟦ head ↦ 𝜎1:λ, tail ↦ Φ.inc( 2A-:Δ ) ⟧  # 𝕄(ξ.x)-      𝕄(Φ.inc( 2A-:Δ )):-        𝑛·1·1 := Φ.inc( x ↦ 2A-:Δ )-      𝔼(L_inc):  # 𝔼(Φ.inc( 2A-:Δ ))-        𝔻(Φ.a🌵1):-          𝛿·2·1 := 2A--        𝛿1·2 := 𝛿·2·1  # 𝔻(ξ.x)-        𝑛·2·1 := Φ.num( φ ↦ 𝜎2:λ )  # 𝑛-        𝕄(𝑛·2·1):-          𝑛·2·2 := Φ.num( φ ↦ 𝜎2:λ )-        𝑛·2·3 := 𝑛·2·2  # 𝕄(𝑛·2·1)-      𝑛2·1 := ⟦ head ↦ 𝜎1:λ, tail ↦ 𝜎2:λ ⟧  # 𝑛1-      𝑛·1·2 := ⟦ head ↦ 𝜎1:λ, tail ↦ 𝜎2:λ ⟧  # 𝑛-      𝑛·1·3 := ⟦ head ↦ 𝜎1:λ, tail ↦ 𝜎2:λ ⟧  # 𝕄(𝑛·1·2)
− test-resources/evaluate-packs/remints-a-copy-of-an-object-of-the-world-as-a-deferred-symbol.yaml
@@ -1,43 +0,0 @@-# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com-# SPDX-License-Identifier: MIT-----# yamllint disable rule:line-length-# The tail of the pair below is a copy of 'Φ.l/.throw', which raises, so firing-# it answers the copy as it is. A 'remint' step answers such a copy of an-# object of the world with a fresh symbol and writes it as a deferred copy,-# naming the object by the term as written and every argument by the symbol it-# comes to: here '𝜎2', the known symbol of the message '01-' (#1923).-symbolic: |-  - λ: L_shrink-    morph:-      𝑛1: $.x-    rewrite:-      𝑛2:-        of: 𝑛1-        rules:-          - name: small-pair-            pattern: ⟦ !B1, head ↦ 𝑒1, tail ↦ 𝑒2, !B2 ⟧-            result: ⟦ !B1, head ↦ 𝑒3, tail ↦ 𝑒4, !B2 ⟧-            where:-              - meta: 𝑒3-                function: remint-                args: [𝑒1]-              - meta: 𝑒4-                function: remint-                args: [𝑒2]-    terminators: [L_throw]-    𝑛: 𝑛2-location: 'Q.y'-input: '⟦ l/ ↦ ⟦ throw(message) ↦ ⟦ λ ⤍ L_throw ⟧ ⟧, y ↦ ⟦ x ↦ ⟦ head ↦ ⟦ λ ⤍ 𝜎1 ⟧, tail ↦ Φ.l/.throw( message ↦ ⟦ Δ ⤍ 01- ⟧ ) ⟧, λ ⤍ L_shrink ⟧.tail ⟧'-result: '𝜎3:λ'-protocol: |-  𝕄(Φ.y):-    𝔼(L_shrink):-      𝑛1·1 := ⟦ head ↦ 𝜎1:λ, tail ↦ Φ.l/.throw( message ↦ 01-:Δ ) ⟧  # 𝕄(ξ.x)-      𝕄(Φ.l/.throw( message ↦ 01-:Δ )):-        𝑛·1·1 := ⟦ message ↦ ∅, λ ⤍ L_throw ⟧( message ↦ 01-:Δ )-      𝔻(𝜎2:λ) == 01--      deferred(𝜎3) := Φ.l/.throw( message ↦ 𝜎2:λ )  # 𝕄(Φ.l/.throw( message ↦ 01-:Δ ))-      𝑛2·1 := ⟦ head ↦ 𝜎1:λ, tail ↦ 𝜎3:λ ⟧  # 𝑛1-      𝑛·1·2 := ⟦ head ↦ 𝜎1:λ, tail ↦ 𝜎3:λ ⟧  # 𝑛-      𝑛·1·3 := ⟦ head ↦ 𝜎1:λ, tail ↦ 𝜎3:λ ⟧  # 𝕄(𝑛·1·2)
− test-resources/evaluate-packs/remints-a-copy-whose-argument-is-a-copy-as-two-deferred-symbols.yaml
@@ -1,47 +0,0 @@-# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com-# SPDX-License-Identifier: MIT-----# yamllint disable rule:line-length-# The tail of the pair below is a copy of 'Φ.pair' whose own tail is a copy of-# 'Φ.l/.throw', the way the tail of a tuple of EO ends in the empty tuple,-# whose tail throws. A 'remint' step works every argument of a copy out as it-# works out the term itself, so the inner copy is deferred first, as '𝜎4', and-# the outer one names it as its argument, as '𝜎5' (#1923).-symbolic: |-  - λ: L_shrink-    morph:-      𝑛1: $.x-    rewrite:-      𝑛2:-        of: 𝑛1-        rules:-          - name: small-pair-            pattern: ⟦ !B1, head ↦ 𝑒1, tail ↦ 𝑒2, !B2 ⟧-            result: ⟦ !B1, head ↦ 𝑒3, tail ↦ 𝑒4, !B2 ⟧-            where:-              - meta: 𝑒3-                function: remint-                args: [𝑒1]-              - meta: 𝑒4-                function: remint-                args: [𝑒2]-    terminators: [L_throw]-    𝑛: 𝑛2-location: 'Q.y'-input: '⟦ l/ ↦ ⟦ throw(message) ↦ ⟦ λ ⤍ L_throw ⟧ ⟧, pair(tail, head) ↦ ⟦⟧, y ↦ ⟦ x ↦ ⟦ head ↦ ⟦ λ ⤍ 𝜎1 ⟧, tail ↦ Φ.pair( tail ↦ Φ.l/.throw( message ↦ ⟦ Δ ⤍ 01- ⟧ ), head ↦ ⟦ φ ↦ ⟦ λ ⤍ 𝜎2 ⟧ ⟧ ) ⟧, λ ⤍ L_shrink ⟧.tail ⟧'-result: '𝜎5:λ'-protocol: |-  𝕄(Φ.y):-    𝔼(L_shrink):-      𝑛1·1 := ⟦ head ↦ 𝜎1:λ, tail ↦ Φ.pair( tail ↦ Φ.l/.throw( message ↦ 01-:Δ ), head ↦ 𝜎2:λ:φ ) ⟧  # 𝕄(ξ.x)-      𝕄(Φ.pair( tail ↦ Φ.l/.throw( message ↦ 01-:Δ ), head ↦ 𝜎2:λ:φ )):-        𝑛·1·1 := Φ.pair( tail ↦ Φ.l/.throw( message ↦ 01-:Δ ) )-        𝑛·1·2 := 𝑛·1·1( head ↦ 𝜎2:λ:φ )  # 𝕄(𝑛·1·1( head ↦ 𝜎2:λ:φ ))-      𝕄(𝑛·1·2.tail):-        𝑛·1·3 := ⟦ message ↦ ∅, λ ⤍ L_throw ⟧( message ↦ 01-:Δ )-      𝔻(𝜎3:λ) == 01--      deferred(𝜎4) := Φ.l/.throw( message ↦ 𝜎3:λ )  # 𝕄(𝑛·1·2.tail)-      deferred(𝜎5) := Φ.pair( tail ↦ 𝜎4:λ, head ↦ 𝜎2:λ )  # 𝕄(𝑛·1·2)-      𝑛2·1 := ⟦ head ↦ 𝜎1:λ, tail ↦ 𝜎5:λ ⟧  # 𝑛1-      𝑛·1·4 := ⟦ head ↦ 𝜎1:λ, tail ↦ 𝜎5:λ ⟧  # 𝑛-      𝑛·1·5 := ⟦ head ↦ 𝜎1:λ, tail ↦ 𝜎5:λ ⟧  # 𝕄(𝑛·1·4)
− test-resources/evaluate-packs/remints-a-datum-as-a-known-symbol.yaml
@@ -1,39 +0,0 @@-# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com-# SPDX-License-Identifier: MIT-----# yamllint disable rule:line-length-# A 'remint' step given a term whose φ chain ends in data answers a fresh-# symbol and writes it as a known one, the way 'symbolize' does, so the head-# of the pair below becomes '𝜎1' standing for '01-' and the tail '𝜎2' standing-# for '02-' (#1923).-symbolic: |-  - λ: L_shrink-    morph:-      𝑛1: $.x-    rewrite:-      𝑛2:-        of: 𝑛1-        rules:-          - name: small-pair-            pattern: ⟦ !B1, head ↦ 𝑒1, tail ↦ 𝑒2, !B2 ⟧-            result: ⟦ !B1, head ↦ 𝑒3, tail ↦ 𝑒4, !B2 ⟧-            where:-              - meta: 𝑒3-                function: remint-                args: [𝑒1]-              - meta: 𝑒4-                function: remint-                args: [𝑒2]-    𝑛: 𝑛2-location: 'Q.y'-input: '⟦ y ↦ ⟦ x ↦ ⟦ head ↦ ⟦ Δ ⤍ 01- ⟧, tail ↦ ⟦ φ ↦ ⟦ Δ ⤍ 02- ⟧ ⟧ ⟧, λ ⤍ L_shrink ⟧.tail ⟧'-result: '𝜎2:λ'-protocol: |-  𝕄(Φ.y):-    𝔼(L_shrink):-      𝑛1·1 := ⟦ head ↦ 01-:Δ, tail ↦ 02-:Δ:φ ⟧  # 𝕄(ξ.x)-      𝔻(𝜎1:λ) == 01--      𝔻(𝜎2:λ) == 02--      𝑛2·1 := ⟦ head ↦ 𝜎1:λ, tail ↦ 𝜎2:λ ⟧  # 𝑛1-      𝑛·1·1 := ⟦ head ↦ 𝜎1:λ, tail ↦ 𝜎2:λ ⟧  # 𝑛-      𝑛·1·2 := ⟦ head ↦ 𝜎1:λ, tail ↦ 𝜎2:λ ⟧  # 𝕄(𝑛·1·1)
− test-resources/evaluate-packs/remints-a-decorator-as-the-symbol-its-phi-comes-to.yaml
@@ -1,44 +0,0 @@-# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com-# SPDX-License-Identifier: MIT-----# yamllint disable rule:line-length-# Morphing stops at a formation, so the tail of the pair below, a call of-# 'as-number' the way EO's 'length.plus( 1 ).as-number' is one, comes to the-# object 'as-number' and not to the number it decorates. A 'remint' step then-# morphs the φ of what the term came to, once per decorator, until a φ chain-# ends in the bare symbol '𝜎2', and answers that symbol (#1923).-symbolic: |-  - λ: L_shrink-    morph:-      𝑛1: $.x-    rewrite:-      𝑛2:-        of: 𝑛1-        rules:-          - name: small-pair-            pattern: ⟦ !B1, head ↦ 𝑒1, tail ↦ 𝑒2, !B2 ⟧-            result: ⟦ !B1, head ↦ 𝑒3, tail ↦ 𝑒4, !B2 ⟧-            where:-              - meta: 𝑒3-                function: remint-                args: [𝑒1]-              - meta: 𝑒4-                function: remint-                args: [𝑒2]-    𝑛: 𝑛2-location: 'Q.y'-input: '⟦ number(φ) ↦ ⟦⟧, bytes(φ) ↦ ⟦ as-number ↦ ⟦ ρ ↦ ∅, φ ↦ Φ.number( φ ↦ ξ.ρ ) ⟧ ⟧, y ↦ ⟦ x ↦ ⟦ head ↦ ⟦ λ ⤍ 𝜎1 ⟧, tail ↦ Φ.bytes( φ ↦ ⟦ λ ⤍ 𝜎2 ⟧ ).as-number ⟧, λ ⤍ L_shrink ⟧.tail ⟧'-result: '𝜎2:λ'-protocol: |-  𝕄(Φ.y):-    𝔼(L_shrink):-      𝑛1·1 := ⟦ head ↦ 𝜎1:λ, tail ↦ Φ.bytes( φ ↦ 𝜎2:λ ).as-number ⟧  # 𝕄(ξ.x)-      𝕄(Φ.bytes( φ ↦ 𝜎2:λ ).as-number):-        𝑛·1·1 := Φ.bytes( φ ↦ 𝜎2:λ )-      𝕄(𝑛·1·1.as-number.φ):-        𝑛·1·2 := Φ.number( φ ↦ 𝑛·1·1 )-      𝕄(𝑛·1·1.as-number.φ.φ):-        𝑛·1·3 := Φ.bytes( φ ↦ 𝜎2:λ )-      𝑛2·1 := ⟦ head ↦ 𝜎1:λ, tail ↦ 𝜎2:λ ⟧  # 𝑛1-      𝑛·1·4 := ⟦ head ↦ 𝜎1:λ, tail ↦ 𝜎2:λ ⟧  # 𝑛-      𝑛·1·5 := ⟦ head ↦ 𝜎1:λ, tail ↦ 𝜎2:λ ⟧  # 𝕄(𝑛·1·4)
− test-resources/evaluate-packs/remints-a-term-as-the-symbol-its-phi-chain-ends-in.yaml
@@ -1,38 +0,0 @@-# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com-# SPDX-License-Identifier: MIT-----# yamllint disable rule:line-length-# A rule of a 'rewrite' block may not write a 𝜎 into its result, but a 'where'-# step may call 'remint', which answers the symbol the term it is given stands-# for. A term whose φ chain ends in a bare symbol is that symbol, so nothing is-# minted and no line is written: the head of the pair below is '𝜎1' and its-# tail '𝜎2', whatever else the tail holds (#1923).-symbolic: |-  - λ: L_shrink-    morph:-      𝑛1: $.x-    rewrite:-      𝑛2:-        of: 𝑛1-        rules:-          - name: small-pair-            pattern: ⟦ !B1, head ↦ 𝑒1, tail ↦ 𝑒2, !B2 ⟧-            result: ⟦ !B1, head ↦ 𝑒3, tail ↦ 𝑒4, !B2 ⟧-            where:-              - meta: 𝑒3-                function: remint-                args: [𝑒1]-              - meta: 𝑒4-                function: remint-                args: [𝑒2]-    𝑛: 𝑛2-location: 'Q.y'-input: '⟦ y ↦ ⟦ x ↦ ⟦ head ↦ ⟦ φ ↦ ⟦ λ ⤍ 𝜎1 ⟧ ⟧, tail ↦ ⟦ φ ↦ ⟦ λ ⤍ 𝜎2 ⟧, k ↦ ⟦ λ ⤍ 𝜎3 ⟧ ⟧ ⟧, λ ⤍ L_shrink ⟧.tail ⟧'-result: '𝜎2:λ'-protocol: |-  𝕄(Φ.y):-    𝔼(L_shrink):-      𝑛1·1 := ⟦ head ↦ 𝜎1:λ:φ, tail ↦ ⟦ φ ↦ 𝜎2:λ, k ↦ 𝜎3:λ ⟧ ⟧  # 𝕄(ξ.x)-      𝑛2·1 := ⟦ head ↦ 𝜎1:λ, tail ↦ 𝜎2:λ ⟧  # 𝑛1-      𝑛·1·1 := ⟦ head ↦ 𝜎1:λ, tail ↦ 𝜎2:λ ⟧  # 𝑛-      𝑛·1·2 := ⟦ head ↦ 𝜎1:λ, tail ↦ 𝜎2:λ ⟧  # 𝕄(𝑛·1·1)
test-resources/evaluate-packs/spends-a-step-on-every-firing-of-an-evaluate-line.yaml view
@@ -26,4 +26,4 @@       𝑛1·1 := ⟦ next ↦ Φ.loop, λ ⤍ L_loop ⟧  # 𝕄(ξ.next)       𝔼(L_loop):  # 𝔼(𝑛1)         𝕄(Φ.a🌵1):-          starved(6)+          starved 6
− test-resources/evaluate-packs/spends-the-step-budget-on-a-branch-whose-phi-leads-back-to-it.yaml
@@ -1,36 +0,0 @@-# 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 of an 'unfold' line (#1885, #1921) 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 line fire the fork forever. This is the-# control the pack asking for the guard stands against.-symbolic: |-  - λ: L_fork-    morph:-      𝑛1: $.a-      𝑛2: $.b-    unfold:-      𝑛3: 𝑛1-    join:-      𝑛4: [𝑛3, 𝑛2]-    𝑛: 𝑛4-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)-      𝑛3·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)-        𝑛3·2 := ⟦ λ ⤍ L_fork, a ↦ Φ.z, b ↦ 𝜎1:λ:φ ⟧  # 𝕄(𝑛1.φ)-        𝔼(L_fork):  # 𝔼(𝑛1)-          𝕄(Φ.a🌵6):-            starved(6)
test-resources/evaluate-packs/spends-the-step-budget-on-an-entry-answering-itself.yaml view
@@ -33,4 +33,4 @@     𝔼(L_loop):       𝑛·6·1 := L_loop:λ  # 𝑛       𝕄(𝑛·6·1):-        starved(6)+        starved 6
− test-resources/evaluate-packs/stands-the-data-of-a-term-into-unknowns.yaml
@@ -1,37 +0,0 @@-# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com-# SPDX-License-Identifier: MIT-----# A 'symbolize' line reduces nothing: it stands the data of a term another line-# of the entry has already bound into unknowns, so a normal form reached from a-# literal is written the way one reached from an unknown is written and the two-# of them compare as expressions. Here 𝑛1 came from a literal and 𝑛2 from a-# value nobody worked out: after the stage both of them carry ⟦ λ ⤍ 𝜎 ⟧ where-# they differ. What is known about each fresh symbol stands on a line of its-# own, since a 𝜎 is the name of a λ function and nothing binds bytes to it, and-# a term that carried no datum at all passes through as it was. The comments of-# the two pairs of lines are what tells them apart: a 'morph' operand is-# commented with the judgment that reduced it and the term it was reduced from,-# '𝕄(ξ.a)', where a 'symbolize' line names the meta it was told to stand and no-# judgment at all, since none ran (#1306).-symbolic: |-  - λ: L_join-    morph:-      𝑛1: $.a-      𝑛2: $.b-    symbolize:-      𝑛3: 𝑛1-      𝑛4: 𝑛2-    𝑛: ⟦ l ↦ 𝑛3 ⟧-location: 'Q.y'-input: '⟦ y ↦ ⟦ a ↦ ⟦ Δ ⤍ 01- ⟧, b ↦ ⟦ λ ⤍ 𝜎7 ⟧, λ ⤍ L_join ⟧.l ⟧'-result: '𝜎8:λ'-protocol: |-  𝕄(Φ.y):-    𝔼(L_join):-      𝑛1·1 := 01-:Δ  # 𝕄(ξ.a)-      𝑛2·1 := 𝜎7:λ  # 𝕄(ξ.b)-      𝔻(𝜎8:λ) == 01--      𝑛3·1 := 𝜎8:λ  # 𝑛1-      𝑛4·1 := 𝑛2·1  # 𝑛2-      𝑛·1·1 := 𝜎8:λ:l  # 𝑛-      𝑛·1·2 := 𝜎8:λ:l  # 𝕄(𝑛·1·1)
− test-resources/evaluate-packs/stops-an-accumulator-recursion-under-plausible.yaml
@@ -1,77 +0,0 @@-# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com-# SPDX-License-Identifier: MIT-----# yamllint disable rule:line-length-# A factorial with an accumulator, under '--acyclic=plausible'. Every round-# 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:-      𝛿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-location: 'Q.x'-acyclic: plausible-partial: true-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: 'Φ.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·2 := 𝑛·0·1( acc ↦ 00-:Δ )-    𝑛·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-      unjoined(L_if)  # 𝜎3:λ vs 𝑛·1·2( acc ↦ Φ.pair( head ↦ 𝜎1:λ, tail ↦ 𝜎4:λ ) ).φ
+ test-resources/evaluate-packs/takes-the-one-term-both-sides-of-a-join-come-to.yaml view
@@ -0,0 +1,86 @@+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com+# SPDX-License-Identifier: MIT+---+# yamllint disable rule:line-length+# A join whose two sides come to the same normal form through 𝕄 has+# nothing to join (#1954): it mints no symbol and answers that one term,+# commented with both sides it came from.+symbolic: |+  - λ: L_number_gt+    dataize:+      𝛿1: $.ρ+      𝛿2: $.x+    𝑛: Φ.bool( φ ↦ ⟦ λ ⤍ 𝜎 ⟧ )+  - λ: L_number_plus+    dataize:+      𝛿1: $.ρ+      𝛿2: $.x+    𝑛: Φ.number( φ ↦ ⟦ λ ⤍ 𝜎 ⟧ )+  - λ: L_fork+    dataize:+      𝛿1: $.ρ+    join:+      on: 𝛿1+      left: $.then+      right: $.else+location: 'Q.@'+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 ), 1.plus( 2 ) ).φ ⟧+result: '𝜎2:λ'+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.plus( 2 ) )+    𝑛·0·4 := 𝑛·0·3( else ↦ 1.plus( 2 ) )+    𝔼(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 )+      𝕄(Φ.a🌵4):+        𝑛·2·4 := 1+        𝑛·2·5 := 𝑛·2·4.plus( x ↦ 2 )+      𝑛·2·6 := 𝑛·2·5  # 𝕄(ξ.then), 𝕄(ξ.else)+    𝔼(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 := 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)
− test-resources/evaluate-packs/tells-a-budget-stall-to-a-firing-no-shallower.yaml
@@ -1,47 +0,0 @@-# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com-# SPDX-License-Identifier: MIT-----# 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. '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-    𝑛: ⟦ λ ⤍ 𝜎 ⟧-  - λ: L_pair-    morph:-      𝑛1: $.x-      𝑛2: $.y-    𝑛: ⟦ φ ↦ ⟦ λ ⤍ 𝜎 ⟧ ⟧-location: 'Q.p'-partial: true-acyclic: plausible-steps: 4-input: >-  ⟦ 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: |-  𝕄(Φ.p):-    𝔼(L_pair):-      𝕄(Φ.a🌵0):-        𝔼(L_outer):-          𝔻(Φ.a🌵1):-            𝔼(L_outer):-              starved(4)  # 𝔻(Φ.a🌵2)-          stuck(L_outer)-      𝑛1·1 := ⟦ λ ⤍ L_outer, arg ↦ ⟦ λ ⤍ L_outer, arg ↦ ⟦ λ ⤍ L_outer, arg ↦ L_none:λ ⟧ ⟧ ⟧.φ  # 𝕄(ξ.x)-      𝕄(Φ.a🌵3):-        𝔼(L_outer):-          stall(L_outer)-      𝑛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-why-a-join-fabricated-nothing-of-a-side-that-is-no-formation.yaml view
@@ -0,0 +1,38 @@+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com+# SPDX-License-Identifier: MIT+---+# yamllint disable rule:line-length+# The 'rewrite' line takes 'Φ.k' out of the formation the left side morphs to,+# and the join compares that formation with the dispatch. They differ as a+# whole, so no attribute tells where. The block names 'L_join' under+# 'otherwise', but the right side is no formation, so nothing is fabricated of+# the two, and the comment of 'diverged' says so (#2043).+symbolic: |+  - λ: L_fork+    dataize:+      𝛿1: $.c+    morph:+      𝑛1: $.a+    rewrite:+      𝑛2:+        of: 𝑛1+        rules:+          - name: unwrap+            pattern: ⟦ φ ↦ 𝑒1 ⟧+            result: 𝑒1+    join:+      on: 𝛿1+      left: 𝑛1+      right: 𝑛2+      otherwise: L_join+location: 'Q.y'+partial: true+input: '⟦ k ↦ ⟦⟧, y ↦ ⟦ c ↦ ⟦ λ ⤍ 𝜎1 ⟧, a ↦ ⟦ φ ↦ Φ.k ⟧, λ ⤍ L_fork ⟧.φ ⟧'+result: '⟦ c ↦ ⟦ λ ⤍ 𝜎1 ⟧, a ↦ ⟦ φ ↦ Φ.k ⟧, λ ⤍ L_fork ⟧.φ'+protocol: |+  𝕄(Φ.y):+    𝔼(L_fork):+      𝛿1·1 := 𝔻(𝜎1:λ)  # 𝔻(ξ.c)+      𝑛1·1 := Φ.k:φ  # 𝕄(ξ.a)+      𝑛2·1 := Φ.k  # rewrite(𝑛1)+      diverged 𝑛1·1 vs 𝑛2·1  # the right side is no formation
− test-resources/evaluate-packs/upcasts-a-bare-symbol-to-the-tuple-of-the-other-branch.yaml
@@ -1,68 +0,0 @@-# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com-# SPDX-License-Identifier: MIT-----# yamllint disable rule:line-length-# The fork of 'bytes.array' from #1914: one branch is a bare symbol, the other-# a tuple holding three symbols, so a lenient join cannot tell which of them-# the bare one pairs with. A 'rewrite' line spells the tuple as the call-# 'Φ.tuple( … )' it was made of, and the 'upcast' block takes '𝜎1' and that-# call as one pair. It casts the smaller of the two, the bare '𝜎1', into a-# copy of the call whose every symbol is read off '𝜎1' by a path, and passes-# the call through as it is (#1921). The 'join' line then pairs the two metas-# the block wrote symbol by symbol, through their arguments. The program reads 'length' off the answer and gets the symbol-# standing for either length.-symbolic: |-  - λ: L_fork-    morph:-      𝑛1: $.a-      𝑛2: $.b-    rewrite:-      𝑛3:-        of: 𝑛2-        rules:-          - name: called-            pattern: ⟦ tail ↦ 𝑒1, head ↦ 𝑒2, length ↦ 𝑒3, !B ⟧-            result: Φ.tuple( tail ↦ 𝑒1, head ↦ 𝑒2, length ↦ 𝑒3 )-    upcast:-      𝑛4: 𝑛1-      𝑛5: 𝑛3-    join:-      𝑛6: [𝑛4, 𝑛5]-    𝑛: 𝑛6-location: 'Q.y'-input: |-  ⟦-    tuple(tail, head, length) ↦ ⟦ empty ↦ ⟦⟧ ⟧,-    y ↦ ⟦-      a ↦ ⟦ λ ⤍ 𝜎1 ⟧,-      b ↦ Φ.tuple( tail ↦ ⟦ λ ⤍ 𝜎2 ⟧, head ↦ ⟦ λ ⤍ 𝜎3 ⟧, length ↦ ⟦ λ ⤍ 𝜎4 ⟧ ),-      λ ⤍ L_fork-    ⟧.length-  ⟧-result: '𝜎10:λ'-protocol: |-  𝕄(Φ.y):-    𝔼(L_fork):-      𝑛1·1 := 𝜎1:λ  # 𝕄(ξ.a)-      𝕄(Φ.a🌵1):-        𝑛·1·1 := Φ.tuple( tail ↦ 𝜎2:λ )-        𝑛·1·2 := 𝑛·1·1( head ↦ 𝜎3:λ )-        𝑛·1·3 := 𝑛·1·2( length ↦ 𝜎4:λ )-      𝑛2·1 := 𝑛·1·3  # 𝕄(ξ.b)-      𝑛3·1 := 𝑛·1·3  # 𝑛2-      upcast(𝜎1) := Φ.tuple( tail ↦ 𝜎5:λ, head ↦ 𝜎6:λ, length ↦ 𝜎7:λ )-        𝜎5 := 𝜎1.tail-        𝜎6 := 𝜎1.head-        𝜎7 := 𝜎1.length-      𝑛4·1 := Φ.tuple( tail ↦ 𝜎5:λ, head ↦ 𝜎6:λ, length ↦ 𝜎7:λ )  # 𝑛1-      𝑛5·1 := 𝑛·1·3  # 𝑛3-      𝔻(𝜎8:λ) ∈ { 𝔻(𝜎5:λ), 𝔻(𝜎2:λ) }-      𝔻(𝜎9:λ) ∈ { 𝔻(𝜎6:λ), 𝔻(𝜎3:λ) }-      𝔻(𝜎10:λ) ∈ { 𝔻(𝜎7:λ), 𝔻(𝜎4:λ) }-      𝑛6·1 := Φ.tuple( tail ↦ 𝜎8:λ, head ↦ 𝜎9:λ, length ↦ 𝜎10:λ )  # [𝑛4, 𝑛5]-      𝑛·1·4 := Φ.tuple( tail ↦ 𝜎8:λ, head ↦ 𝜎9:λ, length ↦ 𝜎10:λ )  # 𝑛-      𝕄(𝑛·1·4):-        𝑛·1·5 := Φ.tuple( tail ↦ 𝜎8:λ )-        𝑛·1·6 := 𝑛·1·5( head ↦ 𝜎9:λ )-        𝑛·1·7 := 𝑛·1·6( length ↦ 𝜎10:λ )-      𝑛·1·8 := 𝑛·1·7  # 𝕄(𝑛·1·4)
− test-resources/evaluate-packs/upcasts-a-cut-branch-to-the-tuple-an-unfolded-branch-was-reminted-into.yaml
@@ -1,129 +0,0 @@-# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com-# SPDX-License-Identifier: MIT-----# yamllint disable rule:line-length-# The fork of 'bytes.array' from #1921, spelled as a pipeline. The left branch-# 'Φ.rec( ξ.n )' leads back to the same fork, so its 'unfold' line, which says-# 'defer: true', has '--acyclic=proven' cut the firing and answers the copy-# with the looped bare symbol '𝜎3'. The right branch is the 'with' object of-# a tuple, whose φ builds a copy of 'Φ.tuple' with three arguments; its-# 'unfold' line morphs that φ into the real tuple. The 'rewrite' line then-# shrinks the tuple into a call of 'Φ.tuple' with every argument reminted:-# the tail 'Φ.items' as a deferred copy, the head as the symbol '𝜎2' it ends-# in and the length as the symbol the firing of 'L_plus' answers (#1923);-# 'stay' and 'symbolize' see the unfolded term, the two lines of the 'upcast'-# block take the two terms as one pair and cast the smaller, the bare symbol,-# into the shape of the larger by the paths 'tail', 'head' and 'length', and-# the 'join' line pairs the two metas the block wrote symbol by symbol. The-# program reads 'length' off the answer.-symbolic: |-  - λ: L_fork-    morph:-      𝑛1: $.a-      𝑛2: $.b-    unfold:-      𝑛3: {of: 𝑛1, defer: true}-      𝑛4: {of: 𝑛2, defer: true}-    rewrite:-      𝑛5:-        of: 𝑛3-        rules: &shape-          - name: shrunk-            pattern: ⟦ tail ↦ 𝑒1, head ↦ 𝑒2, length ↦ 𝑒3, !B ⟧-            result: Φ.tuple( tail ↦ 𝑒4, head ↦ 𝑒5, length ↦ 𝑒6 )-            where:-              - meta: 𝑒4-                function: remint-                args: [𝑒1]-              - meta: 𝑒5-                function: remint-                args: [𝑒2]-              - meta: 𝑒6-                function: remint-                args: [𝑒3]-      𝑛6:-        of: 𝑛4-        rules: *shape-    stay:-      𝑛7: 𝑛5-      𝑛8: 𝑛6-    symbolize:-      𝑛9: 𝑛7-      𝑛10: 𝑛8-    upcast:-      𝑛11: 𝑛9-      𝑛12: 𝑛10-    join:-      𝑛13: [𝑛11, 𝑛12]-    𝑛: ⟦ φ ↦ 𝑛13 ⟧-  - λ: L_plus-    𝑛: Φ.number( φ ↦ ⟦ λ ⤍ 𝜎 ⟧ )-location: 'Q.y'-acyclic: proven-input: |-  ⟦-    fork(a, b) ↦ ⟦ λ ⤍ L_fork ⟧,-    tuple(tail, head, length) ↦ ⟦⟧,-    number(φ) ↦ ⟦ plus(x) ↦ ⟦ λ ⤍ L_plus ⟧ ⟧,-    items ↦ ⟦-      length ↦ Φ.number( ⟦ λ ⤍ 𝜎1 ⟧ ),-      with(ρ, x) ↦ ⟦ φ ↦ Φ.tuple( tail ↦ ξ.ρ, head ↦ ξ.x, length ↦ ξ.ρ.length.plus( 1 ) ) ⟧-    ⟧,-    rec(n) ↦ ⟦ φ ↦ Φ.fork( Φ.rec( ξ.n ), Φ.items.with( ξ.n ) ) ⟧,-    y ↦ Φ.rec( ⟦ φ ↦ ⟦ λ ⤍ 𝜎2 ⟧ ⟧ ).φ.φ.length-  ⟧-result: '𝜎11:λ'-protocol: |-  𝕄(Φ.y):-    𝑛·0·1 := Φ.rec( n ↦ 𝜎2:λ:φ )-    𝑛·0·2 := Φ.fork( a ↦ Φ.rec( 𝜎2:λ:φ ) )-    𝑛·0·3 := 𝑛·0·2( b ↦ Φ.items.with( 𝜎2:λ:φ ) )-    𝔼(L_fork):-      𝕄(Φ.a🌵0):-        𝑛·1·1 := Φ.rec( n ↦ 𝜎2:λ:φ )-      𝑛1·1 := 𝑛·1·1  # 𝕄(ξ.a)-      𝕄(Φ.a🌵1):-        𝑛·1·2 := Φ.items.with( x ↦ 𝜎2:λ:φ )-      𝑛2·1 := 𝑛·1·2  # 𝕄(ξ.b)-      𝕄(Φ.a🌵2):-        𝑛·1·3 := Φ.fork( a ↦ Φ.rec( 𝜎2:λ:φ ) )-        𝑛·1·4 := 𝑛·1·3( b ↦ Φ.items.with( 𝜎2:λ:φ ) )-      𝑛3·1 := 𝑛·1·4  # 𝕄(𝑛1.φ)-      looped(𝑛·1·4) := 𝜎3  # 𝔼(𝑛1), proven-      𝑛3·1 := 𝜎3:λ  # 𝔼(𝑛1)-      𝕄(Φ.a🌵3):-        𝑛·1·5 := Φ.tuple( tail ↦ Φ.items )-        𝑛·1·6 := 𝑛·1·5( head ↦ 𝜎2:λ:φ )-        𝑛·1·7 := 𝑛·1·6( length ↦ Φ.items.length.plus( 1 ) )-      𝑛4·1 := 𝑛·1·7  # 𝕄(𝑛2.φ)-      𝑛4·1 := 𝑛·1·7  # 𝔼(𝑛2)-      𝑛5·1 := 𝑛3·1  # 𝑛3-      deferred(𝜎4) := Φ.items  # 𝕄(Φ.items)-      𝕄(Φ.items.length.plus( 1 )):-        𝑛·1·8 := Φ.number( φ ↦ 𝜎1:λ )-        𝑛·1·9 := ⟦ x ↦ ∅, λ ⤍ L_plus ⟧( x ↦ 1 )-      𝔼(L_plus):  # 𝔼(Φ.items.length.plus( 1 ))-        𝑛·2·1 := Φ.number( φ ↦ 𝜎5:λ )  # 𝑛-        𝕄(𝑛·2·1):-          𝑛·2·2 := Φ.number( φ ↦ 𝜎5:λ )-        𝑛·2·3 := 𝑛·2·2  # 𝕄(𝑛·2·1)-      𝑛6·1 := Φ.tuple( tail ↦ 𝜎4:λ, head ↦ 𝜎2:λ, length ↦ 𝜎5:λ )  # 𝑛4-      𝑛7·1 := 𝑛5·1  # 𝑛5-      𝑛8·1 := Φ.tuple( tail ↦ 𝜎4:λ, head ↦ 𝜎2:λ, length ↦ 𝜎5:λ )  # 𝑛6-      𝑛9·1 := 𝑛7·1  # 𝑛7-      𝑛10·1 := Φ.tuple( tail ↦ 𝜎4:λ, head ↦ 𝜎2:λ, length ↦ 𝜎5:λ )  # 𝑛8-      upcast(𝜎3) := Φ.tuple( tail ↦ 𝜎6:λ, head ↦ 𝜎7:λ, length ↦ 𝜎8:λ )-        𝜎6 := 𝜎3.tail-        𝜎7 := 𝜎3.head-        𝜎8 := 𝜎3.length-      𝑛11·1 := Φ.tuple( tail ↦ 𝜎6:λ, head ↦ 𝜎7:λ, length ↦ 𝜎8:λ )  # 𝑛9-      𝑛12·1 := Φ.tuple( tail ↦ 𝜎4:λ, head ↦ 𝜎2:λ, length ↦ 𝜎5:λ )  # 𝑛10-      𝔻(𝜎9:λ) ∈ { 𝔻(𝜎6:λ), 𝔻(𝜎4:λ) }-      𝔻(𝜎10:λ) ∈ { 𝔻(𝜎7:λ), 𝔻(𝜎2:λ) }-      𝔻(𝜎11:λ) ∈ { 𝔻(𝜎8:λ), 𝔻(𝜎5:λ) }-      𝑛13·1 := Φ.tuple( tail ↦ 𝜎9:λ, head ↦ 𝜎10:λ, length ↦ 𝜎11:λ )  # [𝑛11, 𝑛12]-      𝑛·1·10 := Φ.tuple( tail ↦ 𝜎9:λ, head ↦ 𝜎10:λ, length ↦ 𝜎11:λ ):φ  # 𝑛-      𝑛·1·11 := Φ.tuple( tail ↦ 𝜎9:λ, head ↦ 𝜎10:λ, length ↦ 𝜎11:λ ):φ  # 𝕄(𝑛·1·10)-    𝑛·0·4 := Φ.tuple( tail ↦ 𝜎9:λ )-    𝑛·0·5 := 𝑛·0·4( head ↦ 𝜎10:λ )-    𝑛·0·6 := 𝑛·0·5( length ↦ 𝜎11:λ )
− test-resources/evaluate-packs/writes-a-symbolized-copy-of-a-term-out-in-full.yaml
@@ -1,30 +0,0 @@-# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com-# SPDX-License-Identifier: MIT-----# A name of the protocol stands for the very term it was given to and for no-# other. A 'symbolize' line stands the data of a term into unknowns without-# touching what the term carries first, so the copy it binds and the term it-# was made of open with the same 𝜎 and differ further down — here 𝑛1 carries-# '02-' under φ where 𝑛2 carries the symbol that datum was stood into — under-# φ, since the value of a term is reached where its φ chain ends and a datum-# standing anywhere else is left alone (#1293). The-# line of 𝑛2 writes the term out, since naming it after 𝑛1 would say nothing-# was replaced on the very line that replaced something, while the '𝔻(…) == …'-# above it says something was (#1292).-symbolic: |-  - λ: L_stand-    morph:-      𝑛1: $.b-    symbolize:-      𝑛2: 𝑛1-    𝑛: ⟦ l ↦ 𝑛2 ⟧-location: 'Q.y'-input: '⟦ y ↦ ⟦ b ↦ ⟦ λ ⤍ 𝜎7, φ ↦ ⟦ Δ ⤍ 02- ⟧ ⟧, λ ⤍ L_stand ⟧.l ⟧'-protocol: |-  𝕄(Φ.y):-    𝔼(L_stand):-      𝑛1·1 := ⟦ λ ⤍ 𝜎7, φ ↦ 02-:Δ ⟧  # 𝕄(ξ.b)-      𝔻(𝜎8:λ) == 02--      𝑛2·1 := ⟦ λ ⤍ 𝜎7, φ ↦ 𝜎8:λ ⟧  # 𝑛1-      𝑛·1·1 := ⟦ λ ⤍ 𝜎7, φ ↦ 𝜎8:λ ⟧:l  # 𝑛-      𝑛·1·2 := ⟦ λ ⤍ 𝜎7, φ ↦ 𝜎8:λ ⟧:l  # 𝕄(𝑛·1·1)
+ test-resources/evaluate-packs/writes-no-joined-line-for-a-join-the-memo-answered.yaml view
@@ -0,0 +1,152 @@+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com+# SPDX-License-Identifier: MIT+---+# yamllint disable rule:line-length+# A join the memo of 'plausible' answered writes no 'joined' line (#2011). The+# program reads 'f' twice, so the join of 'L_fork' fires twice over the same+# formation. The first firing brings its condition down to '𝛿1·2', mints 𝜎4+# and writes 'joined 𝜎4 := 𝛿1·2 ? 𝜎2 : 𝜎3'. The second one takes that answer+# from the memo and brings nothing down, so no meta of its own holds the+# condition, and it writes the answer alone, the way a firing the memo+# answered writes no symbol it minted.+symbolic: |+  - λ: L_number_gt+    dataize:+      𝛿1: $.ρ+      𝛿2: $.x+    𝑛: Φ.bool( φ ↦ ⟦ λ ⤍ 𝜎 ⟧ )+  - λ: L_number_plus+    dataize:+      𝛿1: $.ρ+      𝛿2: $.x+    𝑛: Φ.number( φ ↦ ⟦ λ ⤍ 𝜎 ⟧ )+  - λ: L_fork+    dataize:+      𝛿1: $.ρ+    join:+      on: 𝛿1+      left: $.then+      right: $.else+location: 'Q.@'+acyclic: plausible+input: >+  ⟦ bytes(φ) ↦ ⟦⟧,+    bool(φ) ↦ ⟦ if ↦ ⟦ ρ ↦ ∅, then ↦ ∅, else ↦ ∅, λ ⤍ L_fork ⟧ ⟧,+    number(φ) ↦ ⟦+      gt(ρ, x) ↦ ⟦ λ ⤍ L_number_gt ⟧,+      plus(ρ, x) ↦ ⟦ λ ⤍ L_number_plus ⟧ ⟧,+    f ↦ 5.gt( 0 ).if( 1.plus( 2 ).plus( 0 ), 3.plus( 4 ).plus( 0 ) ),+    φ ↦ Φ.f.plus( Φ.f ).φ ⟧+result: '𝜎6:λ'+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.plus( 2 ).plus( 𝑛·1·3 ) )+    𝑛·0·4 := 𝑛·0·3( else ↦ 3.plus( 4 ).plus( 𝑛·1·3 ) )+    𝔼(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 )+        𝔼(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 := 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)+        𝑛·2·4 := 𝑛·3·6.plus( x ↦ 𝑛·1·3 )+      𝕄(Φ.a🌵6):+        𝑛·2·5 := 3+        𝑛·2·6 := 𝑛·2·5.plus( x ↦ 4 )+        𝔼(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 := 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)+        𝑛·2·7 := 𝑛·4·6.plus( x ↦ 𝑛·1·3 )+      joined 𝜎4 := 𝛿1·2 ? 𝜎2 : 𝜎3+      𝑛·2·8 := ⟦ x ↦ 𝑛·1·3, λ ⤍ L_number_plus ⟧  # 𝕄(ξ.then), 𝕄(ξ.else)+    𝔼(L_number_plus):+      𝔻(Φ.a🌵9):+        𝕄(Φ.a🌵9):+          𝑛·5·1 := Φ.number( φ ↦ 𝜎4:λ )+      𝛿1·5 := 𝔻(𝜎4:λ)  # 𝔻(ξ.ρ)+      𝔻(Φ.a🌵10):+        𝕄(Φ.a🌵10):+          𝑛·5·2 := Φ.number( φ ↦ 𝑛·1·4 )+          𝑛·5·3 := Φ.bytes( φ ↦ 00-00-00-00-00-00-00-00:Δ )+        𝛿·5·1 := 00-00-00-00-00-00-00-00  # 0.0+      𝛿2·5 := 𝛿·5·1  # 𝔻(ξ.x)+      𝑛·5·4 := Φ.number( φ ↦ 𝜎5:λ )  # 𝑛+      𝕄(𝑛·5·4):+        𝑛·5·5 := Φ.number( φ ↦ 𝜎5:λ )+      𝑛·5·6 := 𝑛·5·5  # 𝕄(𝑛·5·4)+    𝑛·0·5 := 𝑛·5·5.plus( x ↦ Φ.f )+    𝔼(L_number_plus):+      𝔻(Φ.a🌵11):+        𝕄(Φ.a🌵11):+          𝑛·6·1 := Φ.number( φ ↦ 𝜎5:λ )+      𝛿1·6 := 𝔻(𝜎5:λ)  # 𝔻(ξ.ρ)+      𝔻(Φ.a🌵12):+        𝕄(Φ.a🌵12):+          𝑛·6·2 := Φ.number( φ ↦ 𝑛·1·2 )+          𝑛·6·3 := 𝑛·6·2.gt( x ↦ 𝑛·5·2 )+          𝔼(L_number_gt):+            𝑛·7·1 := 𝑛·2·1  # 𝑛+            𝑛·7·2 := 𝑛·1·7  # 𝕄(𝑛·7·1)+          𝑛·6·4 := 𝑛·2·1.if( then ↦ 𝑛·3·1.plus( 𝑛·3·3 ).plus( 𝑛·5·2 ) )+          𝑛·6·5 := 𝑛·6·4( else ↦ 𝑛·4·1.plus( 𝑛·4·3 ).plus( 𝑛·5·2 ) )+        𝔼(L_fork):+          𝑛·8·1 := ⟦ x ↦ 𝑛·5·2, λ ⤍ L_number_plus ⟧  # 𝕄(ξ.then), 𝕄(ξ.else)+        𝔼(L_number_plus):+          𝑛·9·1 := 𝑛·6·1  # 𝑛+          𝑛·9·2 := 𝑛·5·6  # 𝕄(𝑛·9·1)+      𝛿2·6 := 𝔻(𝜎5:λ)  # 𝔻(ξ.x)+      𝑛·6·6 := Φ.number( φ ↦ 𝜎6:λ )  # 𝑛+      𝕄(𝑛·6·6):+        𝑛·6·7 := Φ.number( φ ↦ 𝜎6:λ )+      𝑛·6·8 := 𝑛·6·7  # 𝕄(𝑛·6·6)
− test-resources/evaluate-packs/writes-the-unfolded-branches-a-join-could-not-pair.yaml
@@ -1,36 +0,0 @@-# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com-# SPDX-License-Identifier: MIT-----# yamllint disable rule:line-length-# An 'unfold' line morphs the φ of the branch that reads its own object and-# fires what it comes to, but a symbol never joins a datum, so the 'join'-# line that reads the answer gets stuck all the same. The protocol says why:-# 'unjoined(…)' closes the firing in place of the generic 'stuck(…)', and it-# carries the two branches as the join saw them, the unfolded one included-# (#1900, #1921).-symbolic: |-  - λ: L_eq-    𝑛: ⟦ φ ↦ ⟦ λ ⤍ 𝜎 ⟧ ⟧-  - λ: L_fork-    morph:-      𝑛1: $.a-      𝑛2: $.b-    unfold:-      𝑛3: 𝑛1-    join:-      𝑛4: [𝑛3, 𝑛2]-    𝑛: 𝑛4-location: 'Q.y'-input: '⟦ y ↦ ⟦ a ↦ ⟦ φ ↦ ξ.eq, eq ↦ ⟦ λ ⤍ L_eq ⟧ ⟧, b ↦ ⟦ φ ↦ ⟦ Δ ⤍ 01- ⟧ ⟧, λ ⤍ 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 := 01-:Δ:φ  # 𝕄(ξ.b)-      𝑛3·1 := L_eq:λ  # 𝕄(𝑛1.φ)-      𝔼(L_eq):  # 𝔼(𝑛1)-        𝑛·2·1 := 𝜎1:λ:φ  # 𝑛-        𝑛·2·2 := 𝜎1:λ:φ  # 𝕄(𝑛·2·1)-      𝑛3·1 := 𝑛·2·2  # 𝔼(𝑛1)-      unjoined(L_fork)  # 𝜎1:λ:φ vs 01-:Δ:φ
test-resources/explain-packs/dataization/box.yaml view
@@ -6,8 +6,7 @@   \begin{phinoDataizationInference}     \phinoName{box}     \phinoCondition{ [ D \char44{} L ] \cap \lparen B_1 \cup B_2 \rparen = \emptyset }-    \phinoPremise{ \phinoContextualize{ e_2 }{ [[ B_1, @ -> e_2, B_2 ]] }{ e_3 } }-    \phinoPremise{ \phinoNormalize{ e_3 }{ n } }+    \phinoPremise{ \phinoNormalize{ [[ B_1, @ -> e_2, B_2 ]] . @ }{ n } }     \phinoPremise{ \phinoDataize{ n }{ e_1 }{ s_1 }{ \delta }{ s_2 } }     \phinoConclusion{ \phinoDataize{ [[ B_1, @ -> e_2, B_2 ]] }{ e_1 }{ s_1 }{ \delta }{ s_2 } }   \end{phinoDataizationInference}
+ test-resources/filter-packs/hide-inside-hide.yaml view
@@ -0,0 +1,8 @@+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com+# SPDX-License-Identifier: MIT+---+# yamllint disable rule:line-length+expression: '[[ org -> [[ eolang -> [[ number -> [[ ]], string -> [[ ]] ]] ]], xyz -> [[ ]] ]]'+shown: []+hidden: ['Q.org.eolang', 'Q.org.eolang.number']+result: '⟦ org ↦ ⟦⟧, xyz ↦ ⟦⟧ ⟧'
+ test-resources/filter-packs/hide-outside-show.yaml view
@@ -0,0 +1,8 @@+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com+# SPDX-License-Identifier: MIT+---+# yamllint disable rule:line-length+expression: '[[ app -> [[ @ -> Q.org.eolang.number ]], org -> [[ eolang -> [[ number -> [[ ]] ]] ]] ]]'+shown: ['Q.app']+hidden: ['Q.org.eolang']+result: '⟦ app ↦ ⟦ φ ↦ Φ.org.eolang.number ⟧ ⟧'
+ test-resources/yaml-typos/where-step-key.yaml view
@@ -0,0 +1,12 @@+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com+# SPDX-License-Identifier: MIT+---+name: where-step-key+pattern: '⟦ a ↦ ⟦ 𝐵1 ⟧, b ↦ ⟦ 𝐵2 ⟧ ⟧'+result: '⟦ c ↦ ⟦ 𝐵3 ⟧ ⟧'+where:+  - meta: 𝐵3+    function: join+    arg:+      - 𝐵1+      - 𝐵2
test/CLISpec.hs view
@@ -320,12 +320,6 @@           , ["it's expected rewriting cycles to be in range [1], but rewriting has already reached 2"]           )         , ("when --in-place is used without input file", "[[ ]]", ["rewrite", "--in-place"], ["--in-place requires an input file"])-        ,-          ( "with --output=xmir on a non-top-level expression"-          , "⟦ x ↦ 1, ρ ↦ 2 ⟧"-          , ["rewrite", "--output=xmir"]-          , ["[ERROR]:", "its top level must be a single binding"]-          )         ]         (\(desc, input, args, expected) -> it desc (withStdin input (testCLIFailed args expected))) @@ -576,6 +570,17 @@           concat saved `shouldNotContain` "Minus"           concat saved `shouldNotContain` "Plus" +    it "gives the saved steps the --focus of the printed ones" $+      withTempDirectory "phino-steps-focused" $ \dir ->+        withStdin "⟦ x ↦ ⟦ k ↦ ⟦ q ↦ ⟦⟧ ⟧ ⟧.k, y ↦ ⟦ z ↦ ⟦⟧ ⟧ ⟧" $ do+          testCLISucceeded+            ["rewrite", "--normalize", "--sweet", "--flat", "--focus=Q.y", "--sequence", "--steps-dir=" ++ dir]+            ["⟦⟧:z"]+          files <- listDirectory dir+          null files `shouldBe` False+          saved <- mapM (\file -> readFile (dir ++ "/" ++ file)) (sort files)+          map (filter (/= '\n')) saved `shouldSatisfy` all (== "⟦⟧:z")+     it "saves dataize steps to dir with --steps-dir" $       withTempDirectory "phino-steps-dataize" $ \dir ->         withStdin "[[ bytes ↦ ⟦ φ ↦ ∅ ⟧, number(φ) -> [[ plus(^, x) -> [[ L> L_number_plus ]] ]], @ -> 5.plus(6).plus(7) ]]" $ do@@ -679,6 +684,12 @@           ["rewrite", "--focus=Φ", "--output=xmir"]           ["<?xml version=\"1.0\" encoding=\"UTF-8\"?>", "<object", "  <o base=\"Φ.y\" name=\"x\"/>"] +    it "rewrites a formation of several bindings at the root as XMIR" $+      withStdin "⟦ x ↦ 1, ρ ↦ 2 ⟧" $+        testCLISucceeded+          ["rewrite", "--output=xmir"]+          ["<o base=\"Φ.number\" name=\"x\">", "<o base=\"Φ.number\" name=\"ρ\">"]+     it "emits a real revision and ms in XMIR" $ do       (output, _) <- withStdin "[[ x -> Q.y ]]" $ withStdout (runCLI ["rewrite", "--output=xmir"])       let attrValue :: String -> String -> String@@ -1219,6 +1230,18 @@           ["rewrite", "--sweet", "--flat", "--show=Q.org", "--hide=Q.org.eolang"]           ["Φ.y:yegor256:org"] +    it "hides an object outside the --show locator" $+      withStdin "[[ org -> [[ eolang -> Q.x ]], x -> 42 ]]" $+        testCLISucceeded+          ["rewrite", "--sweet", "--flat", "--show=Q.x", "--hide=Q.org.eolang"]+          ["42:x"]++    it "hides an object inside an earlier --hide locator" $+      withStdin "[[ org -> [[ eolang -> [[ number -> [[]] ]] ]], x -> 42 ]]" $+        testCLISucceeded+          ["rewrite", "--sweet", "--flat", "--hide=Q.org.eolang", "--hide=Q.org.eolang.number"]+          ["⟦ org ↦ ⟦⟧, x ↦ 42 ⟧"]+     it "fails on a --show locator that matches nothing" $       withStdin "[[ a -> [[ b -> Q, c -> Q ]], d -> Q ]]" $         testCLIFailed@@ -1323,12 +1346,21 @@     withStdin "⟦ x ↦ ⟦ b ↦ ⟦ Δ ⤍ 01- ⟧ ⟧.b ⟧" $       testCLIFailed ["dataize", "--locator=Q.x"] ["expects a normal form: 01-:Δ:b.b"] +  describe "--partial parks a universe that --max-cycles stops" $ do+    let heavy = "⟦ simple ↦ ⟦ Δ ⤍ 01- ⟧, heavy ↦ ⟦ a ↦ ⟦ b ↦ ⟦ c ↦ ⟦ Δ ⤍ 02- ⟧ ⟧ ⟧ ⟧.a.b.c ⟧"+    it "in dataize" $+      withStdin heavy $+        testCLISucceeded ["dataize", "--locator=Q.simple", "--max-cycles=1", "--partial", "--flat"] ["⟦ Δ ⤍ 01- ⟧"]+    it "in morph" $+      withStdin heavy $+        testCLISucceeded ["morph", "--locator=Q.simple", "--max-cycles=1", "--partial", "--flat"] ["⟦ Δ ⤍ 01- ⟧"]+   describe "dataize" $ do     it "prints help" $       testCLISucceeded ["dataize", "--help"] ["Dataize the 𝜑-expression"]      it "names every block of a --symbolic entry in its help" $-      testCLISucceeded ["dataize", "--help"] ["\"dataize\"", "\"morph\"", "\"rewrite\"", "\"symbolize\"", "\"join\"", "\"lenient\"", "\"unfold\"", "\"upcast\"", "\"terminators\""]+      testCLISucceeded ["dataize", "--help"] ["\"dataize\"", "\"morph\"", "\"evaluate\"", "\"defer\"", "\"stay\"", "\"rewrite\"", "\"symbolize\"", "\"join\""]      it "dataizes simple expression" $       withStdin "[[ D> 01- ]]" $@@ -1400,7 +1432,7 @@                        , "  𝕄(Φ.t):"                        , "    𝑛·0·1 := Φ.cyc( x ↦ ⟦⟧ )"                        , "    𝑛·0·2 := Φ.cyc( x ↦ ⟦⟧ )"-                       , "  looped(𝑛·0·2)  # proven"+                       , "  looped 𝑛·0·2  # proven"                        ]        it "writes the cut to the XML protocol with the formation in an element of its own" $@@ -1416,10 +1448,12 @@                        , "<dataize at=\"Φ.t\">"                        , "  <morph at=\"Φ.t\">"                        , "    <formed meta=\"𝑛·0·1\"/>"-                       , "    <applied meta=\"𝑛·0·2\" by=\"morph\" at=\"Φ.t\" of=\"Φ.cyc\"><attr name=\"x\">𝑛·0·1</attr></applied>"-                       , "    <applied meta=\"𝑛·0·3\" by=\"morph\" at=\"Φ.t\" of=\"Φ.cyc\"><attr name=\"x\">𝑛·0·1</attr></applied>"+                       , "    <applied meta=\"𝑛·0·2\" by=\"morph\" at=\"Φ.t\" of=\"Φ.cyc\"><meta attr=\"x\">𝑛·0·1</meta></applied>"+                       , "    <morphed meta=\"𝑛·0·3\">𝑛·0·2</morphed>"+                       , "    <applied meta=\"𝑛·0·4\" by=\"morph\" at=\"Φ.t\" of=\"Φ.cyc\"><meta attr=\"x\">𝑛·0·1</meta></applied>"+                       , "    <morphed meta=\"𝑛·0·5\">𝑛·0·4</morphed>"                        , "  </morph>"-                       , "  <looped by=\"dataize\" match=\"proven\" at=\"Φ.t\" of=\"𝑛·0·3\"/>"+                       , "  <looped by=\"dataize\" match=\"proven\" at=\"Φ.t\" of=\"𝑛·0·4\"/>"                        , "</dataize>"                        ] @@ -1431,7 +1465,7 @@               ["dataize", "--locator=Q.t", "--acyclic=plausible", "--partial", "--protocol=" ++ path, "--sweet", "--hide-rho", "--flat", "--quiet"]               []           records <- readProtocol path-          lines records `shouldContain` ["  looped(𝑛·0·2)  # plausible"]+          lines records `shouldContain` ["  looped 𝑛·0·2  # plausible"]        it "refuses the flag without a mode" $         withStdin "⟦ t ↦ ⟦ Δ ⤍ 01-02 ⟧ ⟧" $@@ -1478,10 +1512,12 @@           [ intercalate               "\n"               [ "\\begin{phiquation}"-              , "[[ D> |01-|, |y| -> ? ]] ( |y| -> [[]] ) : |x| . |x| : @ \\phiContextualize"-              , "  \\phiContextualize [[ D> |01-|, |y| -> ? ]] ( |y| -> [[]] ) : |x| . |x| \\phiNormalize[\\nameref{r:copy}]"-              , "  \\phiNormalize [[ D> |01-|, |y| -> [[]] ]] : |x| . |x| \\phiNormalize[\\nameref{r:dot}]"-              , "  \\phiNormalize [[ D> |01-|, |y| -> [[]] ]] ( \\phiTerminal{\\rho} -> [[ D> |01-|, |y| -> [[]] ]] : |x| ) \\phiNormalize[\\nameref{r:skip}]"+              , "[[ D> |01-|, |y| -> ? ]] ( |y| -> [[]] ) : |x| . |x| : @ \\phiDataize[\\nameref{r:box}]"+              , "  \\phiDataize [[ D> |01-|, |y| -> ? ]] ( |y| -> [[]] ) : |x| . |x| : @ . @ \\phiNormalize[\\nameref{r:copy}]"+              , "  \\phiNormalize [[ D> |01-|, |y| -> [[]] ]] : |x| . |x| : @ . @ \\phiNormalize[\\nameref{r:dot}]"+              , "  \\phiNormalize [[ D> |01-|, |y| -> [[]] ]] ( \\phiTerminal{\\rho} -> [[ D> |01-|, |y| -> [[]] ]] : |x| ) : @ . @ \\phiNormalize[\\nameref{r:skip}]"+              , "  \\phiNormalize [[ D> |01-|, |y| -> [[]] ]] : @ . @ \\phiNormalize[\\nameref{r:dot}]"+              , "  \\phiNormalize [[ D> |01-|, |y| -> [[]] ]] ( \\phiTerminal{\\rho} -> Q ) \\phiNormalize[\\nameref{r:skip}]"               , "  \\phiNormalize [[ D> |01-|, |y| -> [[]] ]] \\phiDataize[\\nameref{r:delta}]"               , "  \\phiDataize |01-|{.}"               , "\\end{phiquation}"@@ -1511,7 +1547,7 @@       withStdin "[[ @ -> [[ @ -> $.c.plus( 32.0 ), c -> 25.0 ]], bytes ↦ ⟦ φ ↦ ∅ ⟧, number(φ) -> [[ plus -> [[ ^ -> ?, x -> ?, L> L_number_plus ]] ]] ]]" $         testCLISucceeded           ["dataize", symbolic, "--output=latex", "--sweet", "--nonumber", "--compress", "--canonize", "--meet-prefix=dataization", "--sequence", "--flat", "--quiet", "--hide=Q.bytes", "--hide=Q.number", "--locator=Q.@", "--focus=Q.@", "--meet-length=5", "--meet-popularity=1"]-          ["\\phinoMeet{dataization:1}{ [[ @ -> |c| . |plus| ( |32| ), |c| -> |25| ]] } \\phiContextualize"]+          ["\\phinoMeet{dataization:1}{ [[ @ -> |c| . |plus| ( |32| ), |c| -> |25| ]] } \\phiDataize[\\nameref{r:box}]"]      it "compresses a canonized whole-expression sequence into a meet" $       withStdin "[[ @ -> [[ @ -> $.c.plus( 32.0 ), c -> 25.0 ]], bytes ↦ ⟦ φ ↦ ∅ ⟧, number(φ) -> [[ plus -> [[ ^ -> ?, x -> ?, L> L_number_plus ]] ]] ]]" $@@ -1540,7 +1576,7 @@           withStdin stuck $             testCLISucceeded ["dataize", "--locator=Q.t", "--partial", "--protocol=" ++ path, "--abridged", "--sweet", "--hide-rho", "--quiet"] []           records <- readProtocol path-          lines records `shouldContain` ["  unanswered(L_weit)  # 𝔻(⟦ λ ⤍ L_weit, +4 ⟧)"]+          lines records `shouldContain` ["  unanswered L_weit  # 𝔻(⟦ λ ⤍ L_weit, +4 ⟧)"]       it "names a long formation in the XML protocol by its path rather than folding it" $         withTempFile "protocolXXXXXX.xml" $ \(path, stream) -> do           hClose stream@@ -1550,7 +1586,7 @@           lines records `shouldContain` ["  <unanswered λ=\"L_weit\" by=\"dataize\">Φ.t</unanswered>"]       forM_         [ ("textXXXXXX.txt", "      𝛿·1·1 := 01-02-..(8b)..-0B-0C")-        , ("XMLXXXXXX.xml", "      <delta meta=\"𝛿·1·1\">01-02-..(8b)..-0B-0C</delta>")+        , ("XMLXXXXXX.xml", "      <dataized meta=\"𝛿·1·1\">01-02-..(8b)..-0B-0C</dataized>")         ]         ( \(template, line) ->             it ("cuts a long datum a firing came down to under --abridged-data, as " ++ line) $@@ -1564,7 +1600,7 @@         )       forM_         [ ("textXXXXXX.txt", "      𝛿·1·1 := 30-31-32-33-34-35-36-37-38-39-41-42-43-44-45-46")-        , ("XMLXXXXXX.xml", "      <delta meta=\"𝛿·1·1\">30-31-32-33-34-35-36-37-38-39-41-42-43-44-45-46</delta>")+        , ("XMLXXXXXX.xml", "      <dataized meta=\"𝛿·1·1\">30-31-32-33-34-35-36-37-38-39-41-42-43-44-45-46</dataized>")         ]         ( \(template, line) ->             it ("keeps a long datum a firing came down to whole without --abridged-data, as " ++ line) $@@ -1582,14 +1618,14 @@           withStdin stuck $             testCLISucceeded ["dataize", "--locator=Q.t", "--partial", "--protocol=" ++ path, "--abridged=64", "--sweet", "--hide-rho", "--quiet"] []           records <- readProtocol path-          lines records `shouldContain` ["  unanswered(L_weit)  # 𝔻(⟦ λ ⤍ L_weit, +4 ⟧)"]+          lines records `shouldContain` ["  unanswered L_weit  # 𝔻(⟦ λ ⤍ L_weit, +4 ⟧)"]       it "keeps a formation whole under a width it fits in" $         withTempFile "protocolXXXXXX.txt" $ \(path, stream) -> do           hClose stream           withStdin stuck $             testCLISucceeded ["dataize", "--locator=Q.t", "--partial", "--protocol=" ++ path, "--abridged=200", "--sweet", "--hide-rho", "--quiet"] []           records <- readProtocol path-          lines records `shouldContain` ["  unanswered(L_weit)  # 𝔻(⟦ λ ⤍ L_weit, anfang ↦ schluss, mitte ↦ anfang, schluss ↦ mitte, rand ↦ schluss ⟧)"]+          lines records `shouldContain` ["  unanswered L_weit  # 𝔻(⟦ λ ⤍ L_weit, anfang ↦ schluss, mitte ↦ anfang, schluss ↦ mitte, rand ↦ schluss ⟧)"]       it "refuses a width that is not a number" $         withStdin wide $           testCLIFailed ["dataize", "--locator=Q.t", "--protocol=breit.txt", "--abridged=breit"] ["cannot parse value `breit'"]@@ -1627,7 +1663,7 @@           withStdin sum' $             testCLISucceeded ["dataize", symbolic, "--protocol=" ++ path, "--quiet"] []           records <- readUtf8 path-          (lines records !! (length (lines records) - 3)) `shouldSatisfy` isPrefixOf "msec("+          (lines records !! (length (lines records) - 3)) `shouldSatisfy` isPrefixOf "msec "        it "closes the protocol with the firings it counted" $         withTempFile "protocolXXXXXX.txt" $ \(path, stream) -> do@@ -1635,7 +1671,7 @@           withStdin sum' $             testCLISucceeded ["dataize", symbolic, "--protocol=" ++ path, "--quiet"] []           records <- readUtf8 path-          lines records `shouldContain` ["firings(1)"]+          lines records `shouldContain` ["firings 1"]        it "closes the protocol with its fps on the last line" $         withTempFile "protocolXXXXXX.txt" $ \(path, stream) -> do@@ -1643,7 +1679,7 @@           withStdin sum' $             testCLISucceeded ["dataize", symbolic, "--protocol=" ++ path, "--quiet"] []           records <- readUtf8 path-          last (lines records) `shouldSatisfy` isPrefixOf "fps("+          last (lines records) `shouldSatisfy` isPrefixOf "fps "        it "closes the protocol with zero firings when the run fires nothing" $         withTempFile "protocolXXXXXX.txt" $ \(path, stream) -> do@@ -1651,7 +1687,7 @@           withStdin "[[ D> 01- ]]" $             testCLISucceeded ["dataize", "--protocol=" ++ path, "--quiet"] []           records <- readUtf8 path-          lines records `shouldContain` ["firings(0)"]+          lines records `shouldContain` ["firings 0"]        it "writes one line per operand and one per answer of a firing" $         withTempFile "protocolXXXXXX.txt" $ \(path, stream) -> do@@ -1821,32 +1857,6 @@                        , "    𝑛·1·7 := 𝑛·1·6  # 𝕄(𝑛·1·5)"                        ] -      it "writes what is known about every symbol a 'symbolize' line minted" $-        withTempFile "protocolXXXXXX.txt" $ \(path, stream) -> do-          hClose stream-          withLambdasOf (T.pack "- λ: L_stand\n  morph:\n    𝑛1: $.x\n  symbolize:\n    𝑛2: 𝑛1\n  𝑛: ⟦ z ↦ 𝑛2 ⟧\n") $ \stands ->-            withStdin "⟦ y ↦ ⟦ x ↦ ⟦ Δ ⤍ 01- ⟧, λ ⤍ L_stand ⟧.z ⟧" $-              testCLISucceeded ["morph", "--symbolic=" ++ stands, "--locator=Q.y", "--protocol=" ++ path, "--quiet", "--sweet", "--hide-rho"] []-          records <- readProtocol path-          lines records-            `shouldBe` [ "𝕄(Φ.y):"-                       , "  𝔼(L_stand):"-                       , "    𝑛1·1 := 01-:Δ  # 𝕄(ξ.x)"-                       , "    𝔻(𝜎1:λ) == 01-"-                       , "    𝑛2·1 := 𝜎1:λ  # 𝑛1"-                       , "    𝑛·1·1 := 𝜎1:λ:z  # 𝑛"-                       , "    𝑛·1·2 := 𝜎1:λ:z  # 𝕄(𝑛·1·1)"-                       ]--      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- λ: 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\"/>"]-       it "writes a stuck firing to the XML protocol" $         withTempFile "protocolXXXXXX.xml" $ \(path, stream) -> do           hClose stream@@ -1856,15 +1866,6 @@           records <- readProtocol path           lines records `shouldContain` ["    <stuck λ=\"L_absent\"/>"] -      it "writes a fork whose branches do not join to the XML protocol" $-        withTempFile "protocolXXXXXX.xml" $ \(path, stream) -> do-          hClose stream-          withLambdasOf (T.pack "- λ: L_fork\n  morph:\n    𝑛1: $.a\n    𝑛2: $.b\n  join:\n    𝑛3: [𝑛1, 𝑛2]\n  𝑛: 𝑛3\n") $ \forks ->-            withStdin "⟦ y ↦ ⟦ a ↦ ⟦ l ↦ ⟦ λ ⤍ 𝜎1 ⟧ ⟧, b ↦ ⟦ l ↦ ⟦ λ ⤍ 𝜎2 ⟧, x ↦ ⟦⟧ ⟧, λ ⤍ L_fork ⟧.l ⟧" $-              testCLISucceeded ["morph", "--symbolic=" ++ forks, "--locator=Q.y", "--partial", "--protocol=" ++ path, "--quiet", "--sweet", "--hide-rho"] []-          records <- readProtocol path-          lines records `shouldContain` ["    <unjoined λ=\"L_fork\">𝑛·1·1 𝑛·1·3</unjoined>"]-       it "writes a starved step budget to the XML protocol" $         withTempFile "protocolXXXXXX.xml" $ \(path, stream) -> do           hClose stream@@ -1875,7 +1876,7 @@           lines records `shouldContain` ["      <starved limit=\"3\" by=\"dataize\" at=\"Φ.a🌵0\"/>"]        forM_-        [("XMLXXXXXX.xml", "<spent limit=\"5\" by="), ("textXXXXXX.txt", "  spent(5)")]+        [("XMLXXXXXX.xml", "<spent limit=\"5\" by="), ("textXXXXXX.txt", "  spent 5")]         ( \(template, record) ->             it ("writes a spent firing budget to the protocol as " ++ record) $               withTempFile template $ \(path, stream) -> do@@ -1917,7 +1918,7 @@                        , "      𝕄(𝑛·1·5):"                        , "        𝑛·1·6 := Φ.number( φ ↦ 𝜎1:λ )"                        , "      𝑛·1·7 := 𝑛·1·6  # 𝕄(𝑛·1·5)"-                       , "  unanswered(L_number_nope)  # 𝔻(L_number_nope:λ)"+                       , "  unanswered L_number_nope  # 𝔻(L_number_nope:λ)"                        ]        it "truncates the lines left over from the previous run" $@@ -1946,41 +1947,48 @@               `shouldBe` [ "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"                          , "<dataize at=\"Φ\">"                          , "  <morph at=\"Φ\">"-                         , "    <applied meta=\"𝑛·0·1\" of=\"Φ.bytes\"><attr name=\"φ\">40-14-00-00-00-00-00-00</attr></applied>"-                         , "    <applied meta=\"𝑛·0·2\" by=\"morph\" at=\"Φ\" of=\"Φ.number\"><attr name=\"φ\">𝑛·0·1</attr></applied>"-                         , "    <applied meta=\"𝑛·0·3\" of=\"Φ.bytes\"><attr name=\"φ\">40-18-00-00-00-00-00-00</attr></applied>"-                         , "    <applied meta=\"𝑛·0·4\" of=\"Φ.number\"><attr name=\"φ\">𝑛·0·3</attr></applied>"-                         , "    <applied meta=\"𝑛·0·5\" by=\"morph\" at=\"Φ\" of=\"𝑛·0·2.plus\"><attr name=\"x\">𝑛·0·4</attr></applied>"+                         , "    <applied meta=\"𝑛·0·1\" of=\"Φ.bytes\"><datum attr=\"φ\">40-14-00-00-00-00-00-00</datum></applied>"+                         , "    <applied meta=\"𝑛·0·2\" by=\"morph\" at=\"Φ\" of=\"Φ.number\"><meta attr=\"φ\">𝑛·0·1</meta></applied>"+                         , "    <applied meta=\"𝑛·0·3\" of=\"Φ.bytes\"><datum attr=\"φ\">40-18-00-00-00-00-00-00</datum></applied>"+                         , "    <applied meta=\"𝑛·0·4\" of=\"Φ.number\"><meta attr=\"φ\">𝑛·0·3</meta></applied>"+                         , "    <applied meta=\"𝑛·0·5\" by=\"morph\" at=\"Φ\" of=\"𝑛·0·2.plus\"><meta attr=\"x\">𝑛·0·4</meta></applied>"+                         , "    <morphed meta=\"𝑛·0·6\">𝑛·0·5</morphed>"                          , "  </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=\"φ\">𝑛·0·1</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>"+                         , "        <applied meta=\"𝑛·1·1\" by=\"morph\" at=\"Φ.a🌵0\" of=\"Φ.number\"><meta attr=\"φ\">𝑛·0·1</meta></applied>"+                         , "        <morphed meta=\"𝑛·1·2\">𝑛·1·1</morphed>"+                         , "        <applied meta=\"𝑛·1·3\" by=\"morph\" at=\"Φ.a🌵0\" of=\"Φ.bytes\"><datum attr=\"φ\">40-14-00-00-00-00-00-00</datum></applied>"+                         , "        <morphed meta=\"𝑛·1·4\">𝑛·1·3</morphed>"                          , "      </morph>"-                         , "      <delta meta=\"𝛿·1·1\" number=\"5.0\">40-14-00-00-00-00-00-00</delta>"+                         , "      <dataized meta=\"𝛿·1·1\" number=\"5.0\">40-14-00-00-00-00-00-00</dataized>"                          , "    </dataize>"-                         , "    <bind meta=\"𝛿1·1\">𝛿·1·1</bind>"+                         , "    <bound meta=\"𝛿1·1\">𝛿·1·1</bound>"                          , "    <dataize at=\"Φ.a🌵1\">"                          , "      <morph at=\"Φ.a🌵1\">"-                         , "        <applied meta=\"𝑛·1·3\" by=\"morph\" at=\"Φ.a🌵1\" of=\"Φ.number\"><attr name=\"φ\">𝑛·0·3</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>"+                         , "        <applied meta=\"𝑛·1·5\" by=\"morph\" at=\"Φ.a🌵1\" of=\"Φ.number\"><meta attr=\"φ\">𝑛·0·3</meta></applied>"+                         , "        <morphed meta=\"𝑛·1·6\">𝑛·1·5</morphed>"+                         , "        <applied meta=\"𝑛·1·7\" by=\"morph\" at=\"Φ.a🌵1\" of=\"Φ.bytes\"><datum attr=\"φ\">40-18-00-00-00-00-00-00</datum></applied>"+                         , "        <morphed meta=\"𝑛·1·8\">𝑛·1·7</morphed>"                          , "      </morph>"-                         , "      <delta meta=\"𝛿·1·2\" number=\"6.0\">40-18-00-00-00-00-00-00</delta>"+                         , "      <dataized meta=\"𝛿·1·2\" number=\"6.0\">40-18-00-00-00-00-00-00</dataized>"                          , "    </dataize>"-                         , "    <bind meta=\"𝛿2·1\">𝛿·1·2</bind>"+                         , "    <bound meta=\"𝛿2·1\">𝛿·1·2</bound>"                          , "    <minted symbol=\"𝜎1\">40-14-00-00-00-00-00-00 40-18-00-00-00-00-00-00</minted>"-                         , "    <applied meta=\"𝑛·1·5\" of=\"Φ.number\"><attr name=\"φ\">𝜎1</attr></applied>"-                         , "    <built meta=\"𝑛·1·6\">𝑛·1·5</built>"-                         , "    <morph at=\"𝑛·1·6\">"-                         , "      <applied meta=\"𝑛·1·7\" by=\"morph\" at=\"𝑛·1·6\" of=\"Φ.number\"><attr name=\"φ\">𝜎1</attr></applied>"+                         , "    <applied meta=\"𝑛·1·9\" of=\"Φ.number\"><symbol attr=\"φ\">𝜎1</symbol></applied>"+                         , "    <built meta=\"𝑛·1·10\">𝑛·1·9</built>"+                         , "    <morph at=\"𝑛·1·10\">"+                         , "      <applied meta=\"𝑛·1·11\" by=\"morph\" at=\"𝑛·1·10\" of=\"Φ.number\"><symbol attr=\"φ\">𝜎1</symbol></applied>"+                         , "      <morphed meta=\"𝑛·1·12\">𝑛·1·11</morphed>"                          , "    </morph>"-                         , "    <answer meta=\"𝑛·1·8\">𝑛·1·7</answer>"+                         , "    <answered meta=\"𝑛·1·13\">𝑛·1·11</answered>"                          , "  </evaluate>"+                         , "  <manufactured meta=\"𝛿·0·1\">𝜎1</manufactured>"                          , "</dataize>"                          ] -        it "nests the judgment one level inside a '<protocol>' root" $+        it "nests the judgment right inside the '<protocol>' root" $           withTempFile "protocolXXXXXX.xml" $ \(path, stream) -> do             hClose stream             withStdin "[[ D> 01- ]]" $@@ -1990,7 +1998,7 @@               `shouldBe` [ "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"                          , "<protocol>"                          , "  <dataize at=\"Φ\">"-                         , "    <delta meta=\"𝛿·0·1\">01-</delta>"+                         , "    <dataized meta=\"𝛿·0·1\">01-</dataized>"                          ]          it "closes the '<protocol>' root with its msec" $@@ -2023,9 +2031,9 @@             withStdin sum' $               testCLISucceeded ["dataize", symbolic, "--protocol=" ++ path, "--quiet", "--sweet", "--hide-rho"] []             records <- readProtocol path-            filter (\line -> not (any (`isInfixOf` line) ["<applied ", "<morph ", "</morph>"])) (lines records)+            filter (\line -> not (any (`isInfixOf` line) ["<applied ", "<morph ", "<morphed ", "</morph>"])) (lines records)               `shouldContain` [ "    <dataize at=\"Φ.a🌵0\">"-                              , "      <delta meta=\"𝛿·1·1\" number=\"5.0\">40-14-00-00-00-00-00-00</delta>"+                              , "      <dataized meta=\"𝛿·1·1\" number=\"5.0\">40-14-00-00-00-00-00-00</dataized>"                               , "    </dataize>"                               ] @@ -2038,7 +2046,7 @@             lines records               `shouldBe` [ "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"                          , "<dataize at=\"Φ\">"-                         , "  <delta meta=\"𝛿·0·1\">01-</delta>"+                         , "  <dataized meta=\"𝛿·0·1\">01-</dataized>"                          , "</dataize>"                          ] @@ -2052,131 +2060,134 @@               `shouldBe` [ "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"                          , "<dataize at=\"Φ\">"                          , "  <morph at=\"Φ\">"-                         , "    <applied meta=\"𝑛·0·1\" of=\"Φ.bytes\"><attr name=\"φ\">40-14-00-00-00-00-00-00</attr></applied>"-                         , "    <applied meta=\"𝑛·0·2\" by=\"morph\" at=\"Φ\" of=\"Φ.number\"><attr name=\"φ\">𝑛·0·1</attr></applied>"-                         , "    <applied meta=\"𝑛·0·3\" of=\"Φ.bytes\"><attr name=\"φ\">40-18-00-00-00-00-00-00</attr></applied>"-                         , "    <applied meta=\"𝑛·0·4\" of=\"Φ.number\"><attr name=\"φ\">𝑛·0·3</attr></applied>"-                         , "    <applied meta=\"𝑛·0·5\" by=\"morph\" at=\"Φ\" of=\"𝑛·0·2.plus\"><attr name=\"x\">𝑛·0·4</attr></applied>"+                         , "    <applied meta=\"𝑛·0·1\" of=\"Φ.bytes\"><datum attr=\"φ\">40-14-00-00-00-00-00-00</datum></applied>"+                         , "    <applied meta=\"𝑛·0·2\" by=\"morph\" at=\"Φ\" of=\"Φ.number\"><meta attr=\"φ\">𝑛·0·1</meta></applied>"+                         , "    <applied meta=\"𝑛·0·3\" of=\"Φ.bytes\"><datum attr=\"φ\">40-18-00-00-00-00-00-00</datum></applied>"+                         , "    <applied meta=\"𝑛·0·4\" of=\"Φ.number\"><meta attr=\"φ\">𝑛·0·3</meta></applied>"+                         , "    <applied meta=\"𝑛·0·5\" by=\"morph\" at=\"Φ\" of=\"𝑛·0·2.plus\"><meta attr=\"x\">𝑛·0·4</meta></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=\"φ\">𝑛·0·1</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>"+                         , "          <applied meta=\"𝑛·1·1\" by=\"morph\" at=\"Φ.a🌵0\" of=\"Φ.number\"><meta attr=\"φ\">𝑛·0·1</meta></applied>"+                         , "          <morphed meta=\"𝑛·1·2\">𝑛·1·1</morphed>"+                         , "          <applied meta=\"𝑛·1·3\" by=\"morph\" at=\"Φ.a🌵0\" of=\"Φ.bytes\"><datum attr=\"φ\">40-14-00-00-00-00-00-00</datum></applied>"+                         , "          <morphed meta=\"𝑛·1·4\">𝑛·1·3</morphed>"                          , "        </morph>"-                         , "        <delta meta=\"𝛿·1·1\" number=\"5.0\">40-14-00-00-00-00-00-00</delta>"+                         , "        <dataized meta=\"𝛿·1·1\" number=\"5.0\">40-14-00-00-00-00-00-00</dataized>"                          , "      </dataize>"-                         , "      <bind meta=\"𝛿1·1\">𝛿·1·1</bind>"+                         , "      <bound meta=\"𝛿1·1\">𝛿·1·1</bound>"                          , "      <dataize at=\"Φ.a🌵1\">"                          , "        <morph at=\"Φ.a🌵1\">"-                         , "          <applied meta=\"𝑛·1·3\" by=\"morph\" at=\"Φ.a🌵1\" of=\"Φ.number\"><attr name=\"φ\">𝑛·0·3</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>"+                         , "          <applied meta=\"𝑛·1·5\" by=\"morph\" at=\"Φ.a🌵1\" of=\"Φ.number\"><meta attr=\"φ\">𝑛·0·3</meta></applied>"+                         , "          <morphed meta=\"𝑛·1·6\">𝑛·1·5</morphed>"+                         , "          <applied meta=\"𝑛·1·7\" by=\"morph\" at=\"Φ.a🌵1\" of=\"Φ.bytes\"><datum attr=\"φ\">40-18-00-00-00-00-00-00</datum></applied>"+                         , "          <morphed meta=\"𝑛·1·8\">𝑛·1·7</morphed>"                          , "        </morph>"-                         , "        <delta meta=\"𝛿·1·2\" number=\"6.0\">40-18-00-00-00-00-00-00</delta>"+                         , "        <dataized meta=\"𝛿·1·2\" number=\"6.0\">40-18-00-00-00-00-00-00</dataized>"                          , "      </dataize>"-                         , "      <bind meta=\"𝛿2·1\">𝛿·1·2</bind>"+                         , "      <bound meta=\"𝛿2·1\">𝛿·1·2</bound>"                          , "      <minted symbol=\"𝜎1\">40-14-00-00-00-00-00-00 40-18-00-00-00-00-00-00</minted>"-                         , "      <applied meta=\"𝑛·1·5\" of=\"Φ.number\"><attr name=\"φ\">𝜎1</attr></applied>"-                         , "      <built meta=\"𝑛·1·6\">𝑛·1·5</built>"-                         , "      <morph at=\"𝑛·1·6\">"-                         , "        <applied meta=\"𝑛·1·7\" by=\"morph\" at=\"𝑛·1·6\" of=\"Φ.number\"><attr name=\"φ\">𝜎1</attr></applied>"+                         , "      <applied meta=\"𝑛·1·9\" of=\"Φ.number\"><symbol attr=\"φ\">𝜎1</symbol></applied>"+                         , "      <built meta=\"𝑛·1·10\">𝑛·1·9</built>"+                         , "      <morph at=\"𝑛·1·10\">"+                         , "        <applied meta=\"𝑛·1·11\" by=\"morph\" at=\"𝑛·1·10\" of=\"Φ.number\"><symbol attr=\"φ\">𝜎1</symbol></applied>"+                         , "        <morphed meta=\"𝑛·1·12\">𝑛·1·11</morphed>"                          , "      </morph>"-                         , "      <answer meta=\"𝑛·1·8\">𝑛·1·7</answer>"+                         , "      <answered meta=\"𝑛·1·13\">𝑛·1·11</answered>"                          , "    </evaluate>"-                         , "    <applied meta=\"𝑛·0·6\" of=\"Φ.bytes\"><attr name=\"φ\">40-1C-00-00-00-00-00-00</attr></applied>"-                         , "    <applied meta=\"𝑛·0·7\" of=\"Φ.number\"><attr name=\"φ\">𝑛·0·6</attr></applied>"-                         , "    <applied meta=\"𝑛·0·8\" by=\"morph\" at=\"Φ\" of=\"𝑛·1·7.plus\"><attr name=\"x\">𝑛·0·7</attr></applied>"+                         , "    <applied meta=\"𝑛·0·6\" of=\"Φ.bytes\"><datum attr=\"φ\">40-1C-00-00-00-00-00-00</datum></applied>"+                         , "    <applied meta=\"𝑛·0·7\" of=\"Φ.number\"><meta attr=\"φ\">𝑛·0·6</meta></applied>"+                         , "    <applied meta=\"𝑛·0·8\" by=\"morph\" at=\"Φ\" of=\"𝑛·1·11.plus\"><meta attr=\"x\">𝑛·0·7</meta></applied>"+                         , "    <morphed meta=\"𝑛·0·9\">𝑛·0·8</morphed>"                          , "  </morph>"                          , "  <evaluate λ=\"L_number_plus\" by=\"dataize\" at=\"Φ\">"                          , "    <dataize at=\"Φ.a🌵2\">"                          , "      <morph at=\"Φ.a🌵2\">"-                         , "        <applied meta=\"𝑛·2·1\" by=\"morph\" at=\"Φ.a🌵2\" of=\"Φ.number\"><attr name=\"φ\">𝜎1</attr></applied>"+                         , "        <applied meta=\"𝑛·2·1\" by=\"morph\" at=\"Φ.a🌵2\" of=\"Φ.number\"><symbol attr=\"φ\">𝜎1</symbol></applied>"+                         , "        <morphed meta=\"𝑛·2·2\">𝑛·2·1</morphed>"                          , "      </morph>"+                         , "      <manufactured meta=\"𝛿·2·1\">𝜎1</manufactured>"                          , "    </dataize>"-                         , "    <dataize meta=\"𝛿1·2\">𝜎1</dataize>"+                         , "    <bound meta=\"𝛿1·2\">𝛿·2·1</bound>"                          , "    <dataize at=\"Φ.a🌵3\">"                          , "      <morph at=\"Φ.a🌵3\">"-                         , "        <applied meta=\"𝑛·2·2\" by=\"morph\" at=\"Φ.a🌵3\" of=\"Φ.number\"><attr name=\"φ\">𝑛·0·6</attr></applied>"-                         , "        <applied meta=\"𝑛·2·3\" by=\"morph\" at=\"Φ.a🌵3\" of=\"Φ.bytes\"><attr name=\"φ\">40-1C-00-00-00-00-00-00</attr></applied>"+                         , "        <applied meta=\"𝑛·2·3\" by=\"morph\" at=\"Φ.a🌵3\" of=\"Φ.number\"><meta attr=\"φ\">𝑛·0·6</meta></applied>"+                         , "        <morphed meta=\"𝑛·2·4\">𝑛·2·3</morphed>"+                         , "        <applied meta=\"𝑛·2·5\" by=\"morph\" at=\"Φ.a🌵3\" of=\"Φ.bytes\"><datum attr=\"φ\">40-1C-00-00-00-00-00-00</datum></applied>"+                         , "        <morphed meta=\"𝑛·2·6\">𝑛·2·5</morphed>"                          , "      </morph>"-                         , "      <delta meta=\"𝛿·2·1\" number=\"7.0\">40-1C-00-00-00-00-00-00</delta>"+                         , "      <dataized meta=\"𝛿·2·2\" number=\"7.0\">40-1C-00-00-00-00-00-00</dataized>"                          , "    </dataize>"-                         , "    <bind meta=\"𝛿2·2\">𝛿·2·1</bind>"+                         , "    <bound meta=\"𝛿2·2\">𝛿·2·2</bound>"                          , "    <minted symbol=\"𝜎2\">𝜎1 40-1C-00-00-00-00-00-00</minted>"-                         , "    <applied meta=\"𝑛·2·4\" of=\"Φ.number\"><attr name=\"φ\">𝜎2</attr></applied>"-                         , "    <built meta=\"𝑛·2·5\">𝑛·2·4</built>"-                         , "    <morph at=\"𝑛·2·5\">"-                         , "      <applied meta=\"𝑛·2·6\" by=\"morph\" at=\"𝑛·2·5\" of=\"Φ.number\"><attr name=\"φ\">𝜎2</attr></applied>"+                         , "    <applied meta=\"𝑛·2·7\" of=\"Φ.number\"><symbol attr=\"φ\">𝜎2</symbol></applied>"+                         , "    <built meta=\"𝑛·2·8\">𝑛·2·7</built>"+                         , "    <morph at=\"𝑛·2·8\">"+                         , "      <applied meta=\"𝑛·2·9\" by=\"morph\" at=\"𝑛·2·8\" of=\"Φ.number\"><symbol attr=\"φ\">𝜎2</symbol></applied>"+                         , "      <morphed meta=\"𝑛·2·10\">𝑛·2·9</morphed>"                          , "    </morph>"-                         , "    <answer meta=\"𝑛·2·7\">𝑛·2·6</answer>"+                         , "    <answered meta=\"𝑛·2·11\">𝑛·2·9</answered>"                          , "  </evaluate>"+                         , "  <manufactured meta=\"𝛿·0·1\">𝜎2</manufactured>"                          , "</dataize>"                          ] -        it "writes what is known about a symbol as an element of its own" $-          withTempFile "protocolXXXXXX.xml" $ \(path, stream) -> do-            hClose stream-            withLambdasOf (T.pack "- λ: L_stand\n  morph:\n    𝑛1: $.x\n  symbolize:\n    𝑛2: 𝑛1\n  𝑛: ⟦ z ↦ 𝑛2 ⟧\n") $ \stands ->-              withStdin "⟦ y ↦ ⟦ x ↦ ⟦ Δ ⤍ 01- ⟧, λ ⤍ L_stand ⟧.z ⟧" $-                testCLISucceeded ["morph", "--symbolic=" ++ stands, "--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\">"-                         , "  <evaluate λ=\"L_stand\" by=\"morph\" at=\"Φ.y\">"-                         , "    <bind meta=\"𝑛1·1\">01-</bind>"-                         , "    <known symbol=\"𝜎1\">01-</known>"-                         , "    <bind meta=\"𝑛2·1\">𝜎1</bind>"-                         , "    <formed meta=\"𝑛·1·1\"><attr name=\"z\">𝜎1</attr></formed>"-                         , "    <built meta=\"𝑛·1·2\">𝑛·1·1</built>"-                         , "    <answer meta=\"𝑛·1·3\">𝑛·1·1</answer>"-                         , "  </evaluate>"-                         , "</morph>"-                         ]--        it "writes what a 'join' line knows as an element of its own" $+        it "writes a firing whose two sides do not join as diverged, naming both sides" $           withTempFile "protocolXXXXXX.xml" $ \(path, stream) -> do             hClose stream-            withLambdasOf (T.pack "- λ: L_fork\n  morph:\n    𝑛1: $.a\n    𝑛2: $.b\n  join:\n    𝑛3: [𝑛1, 𝑛2]\n  𝑛: 𝑛3\n") $ \forks ->-              withStdin "⟦ y ↦ ⟦ a ↦ ⟦ φ ↦ ⟦ λ ⤍ 𝜎1 ⟧ ⟧, b ↦ ⟦ φ ↦ ⟦ λ ⤍ 𝜎2 ⟧ ⟧, λ ⤍ L_fork ⟧.φ ⟧" $-                testCLISucceeded ["morph", "--symbolic=" ++ forks, "--locator=Q.y", "--protocol=" ++ path, "--quiet", "--sweet", "--hide-rho"] []+            withLambdasOf (T.pack "- λ: L_fork\n  dataize:\n    𝛿1: $.c\n  join:\n    on: 𝛿1\n    left: $.a\n    right: $.b\n") $ \joins ->+              withStdin "⟦ y ↦ ⟦ c ↦ ⟦ λ ⤍ 𝜎1 ⟧, a ↦ ⟦ φ ↦ ⟦ Δ ⤍ 01- ⟧ ⟧, b ↦ ⟦ φ ↦ ⟦ Δ ⤍ 02- ⟧ ⟧, λ ⤍ L_fork ⟧.φ ⟧" $+                testCLIFailed ["morph", "--symbolic=" ++ joins, "--locator=Q.y", "--protocol=" ++ path, "--quiet", "--sweet", "--hide-rho"] ["No entry of --symbolic answers the λ function 'L_fork'"]             records <- readProtocol path             lines records               `shouldBe` [ "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"                          , "<morph at=\"Φ.y\">"                          , "  <evaluate λ=\"L_fork\" by=\"morph\" at=\"Φ.y\">"-                         , "    <formed meta=\"𝑛·1·1\"><attr name=\"φ\">𝜎1</attr></formed>"-                         , "    <bind meta=\"𝑛1·1\">𝑛·1·1</bind>"-                         , "    <formed meta=\"𝑛·1·2\"><attr name=\"φ\">𝜎2</attr></formed>"-                         , "    <bind meta=\"𝑛2·1\">𝑛·1·2</bind>"-                         , "    <joined symbol=\"𝜎3\">𝜎1 𝜎2</joined>"-                         , "    <formed meta=\"𝑛·1·3\"><attr name=\"φ\">𝜎3</attr></formed>"-                         , "    <bind meta=\"𝑛3·1\">𝑛·1·3</bind>"-                         , "    <built meta=\"𝑛·1·4\">𝑛·1·3</built>"-                         , "    <answer meta=\"𝑛·1·5\">𝑛·1·3</answer>"+                         , "    <dataize at=\"Φ.a🌵0\">"+                         , "      <manufactured meta=\"𝛿·1·1\">𝜎1</manufactured>"+                         , "    </dataize>"+                         , "    <bound meta=\"𝛿1·1\">𝛿·1·1</bound>"+                         , "    <morph at=\"Φ.a🌵1\">"+                         , "      <formed meta=\"𝑛·1·1\"><datum attr=\"φ\">01-</datum></formed>"+                         , "      <morphed meta=\"𝑛·1·2\">𝑛·1·1</morphed>"+                         , "    </morph>"+                         , "    <morph at=\"Φ.a🌵2\">"+                         , "      <formed meta=\"𝑛·1·3\"><datum attr=\"φ\">02-</datum></formed>"+                         , "      <morphed meta=\"𝑛·1·4\">𝑛·1·3</morphed>"+                         , "    </morph>"+                         , "    <diverged left=\"𝑛·1·1\" right=\"𝑛·1·3\" at=\".φ.Δ\" reason=\"the join names no otherwise\"/>"                          , "  </evaluate>"                          , "</morph>"                          ] -        it "writes on which side of the condition a fork raises" $+        it "writes the symbol a join minted for two sides that differ only in their symbols as an element of its own" $           withTempFile "protocolXXXXXX.xml" $ \(path, stream) -> do             hClose stream-            withLambdasOf (T.pack "- λ: L_fork\n  dataize:\n    𝛿1: $.c\n  morph:\n    𝑛1: $.a\n    𝑛2: $.b\n  join:\n    𝑛3: [𝑛1, 𝑛2]\n  𝑛: 𝑛3\n") $ \forks ->-              withStdin "⟦ y ↦ ⟦ c ↦ ⟦ λ ⤍ 𝜎1 ⟧, a ↦ ⟦ φ ↦ ⟦ λ ⤍ 𝜎2 ⟧ ⟧, b ↦ ⊥, λ ⤍ L_fork ⟧.φ ⟧" $-                testCLISucceeded ["morph", "--symbolic=" ++ forks, "--locator=Q.y", "--protocol=" ++ path, "--quiet", "--sweet", "--hide-rho"] []+            withLambdasOf (T.pack "- λ: L_fork\n  dataize:\n    𝛿1: $.c\n  join:\n    on: 𝛿1\n    left: $.a\n    right: $.b\n") $ \joins ->+              withStdin "⟦ y ↦ ⟦ c ↦ ⟦ λ ⤍ 𝜎1 ⟧, a ↦ ⟦ φ ↦ ⟦ λ ⤍ 𝜎2 ⟧ ⟧, b ↦ ⟦ φ ↦ ⟦ λ ⤍ 𝜎3 ⟧ ⟧, λ ⤍ L_fork ⟧.φ ⟧" $+                testCLISucceeded ["morph", "--symbolic=" ++ joins, "--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\">"                          , "  <evaluate λ=\"L_fork\" by=\"morph\" at=\"Φ.y\">"-                         , "    <dataize meta=\"𝛿1·1\">𝜎1</dataize>"-                         , "    <formed meta=\"𝑛·1·1\"><attr name=\"φ\">𝜎2</attr></formed>"-                         , "    <bind meta=\"𝑛1·1\">𝑛·1·1</bind>"-                         , "    <bind meta=\"𝑛2·1\">⊥</bind>"-                         , "    <terminate symbol=\"𝜎1\" branch=\"right\"/>"-                         , "    <bind meta=\"𝑛3·1\">𝑛·1·1</bind>"-                         , "    <built meta=\"𝑛·1·2\">𝑛·1·1</built>"-                         , "    <answer meta=\"𝑛·1·3\">𝑛·1·1</answer>"+                         , "    <dataize at=\"Φ.a🌵0\">"+                         , "      <manufactured meta=\"𝛿·1·1\">𝜎1</manufactured>"+                         , "    </dataize>"+                         , "    <bound meta=\"𝛿1·1\">𝛿·1·1</bound>"+                         , "    <morph at=\"Φ.a🌵1\">"+                         , "      <formed meta=\"𝑛·1·1\"><symbol attr=\"φ\">𝜎2</symbol></formed>"+                         , "      <morphed meta=\"𝑛·1·2\">𝑛·1·1</morphed>"+                         , "    </morph>"+                         , "    <morph at=\"Φ.a🌵2\">"+                         , "      <formed meta=\"𝑛·1·3\"><symbol attr=\"φ\">𝜎3</symbol></formed>"+                         , "      <morphed meta=\"𝑛·1·4\">𝑛·1·3</morphed>"+                         , "    </morph>"+                         , "    <joined symbol=\"𝜎4\" on=\"𝛿1·1\" left=\"𝜎2\" right=\"𝜎3\"/>"+                         , "    <formed meta=\"𝑛·1·5\"><symbol attr=\"φ\">𝜎4</symbol></formed>"+                         , "    <answered meta=\"𝑛·1·6\">𝑛·1·5</answered>"                          , "  </evaluate>"+                         , "  <morphed meta=\"𝑛·0·1\">𝜎4</morphed>"                          , "</morph>"                          ] @@ -2191,13 +2202,20 @@               `shouldBe` [ "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"                          , "<morph at=\"Φ.y\">"                          , "  <evaluate λ=\"L_pair\" by=\"morph\" at=\"Φ.y\">"-                         , "    <bind meta=\"𝑛1·1\">01-</bind>"+                         , "    <morph at=\"Φ.a🌵0\">"+                         , "      <morphed meta=\"𝑛·1·1\">01-</morphed>"+                         , "    </morph>"+                         , "    <bound meta=\"𝑛1·1\">01-</bound>"                          , "    <minted symbol=\"𝜎1\"/>"                          , "    <minted symbol=\"𝜎2\"/>"-                         , "    <formed meta=\"𝑛·1·1\"><attr name=\"left\">𝜎1</attr><attr name=\"right\">𝜎2</attr></formed>"-                         , "    <built meta=\"𝑛·1·2\">𝑛·1·1</built>"-                         , "    <answer meta=\"𝑛·1·3\">𝑛·1·1</answer>"+                         , "    <formed meta=\"𝑛·1·2\"><symbol attr=\"left\">𝜎1</symbol><symbol attr=\"right\">𝜎2</symbol></formed>"+                         , "    <built meta=\"𝑛·1·3\">𝑛·1·2</built>"+                         , "    <morph at=\"𝑛·1·3\">"+                         , "      <morphed meta=\"𝑛·1·4\">𝑛·1·2</morphed>"+                         , "    </morph>"+                         , "    <answered meta=\"𝑛·1·5\">𝑛·1·2</answered>"                          , "  </evaluate>"+                         , "  <morphed meta=\"𝑛·0·1\">𝜎1</morphed>"                          , "</morph>"                          ] @@ -2212,41 +2230,21 @@               `shouldBe` [ "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"                          , "<morph at=\"Φ.y\">"                          , "  <evaluate λ=\"L_keep\" by=\"morph\" at=\"Φ.y\">"-                         , "    <bind meta=\"𝑛1·1\">01-</bind>"-                         , "    <formed meta=\"𝑛·1·1\"><attr name=\"z\">01-</attr></formed>"-                         , "    <built meta=\"𝑛·1·2\">𝑛·1·1</built>"-                         , "    <answer meta=\"𝑛·1·3\">𝑛·1·1</answer>"+                         , "    <morph at=\"Φ.a🌵0\">"+                         , "      <morphed meta=\"𝑛·1·1\">01-</morphed>"+                         , "    </morph>"+                         , "    <bound meta=\"𝑛1·1\">01-</bound>"+                         , "    <formed meta=\"𝑛·1·2\"><datum attr=\"z\">01-</datum></formed>"+                         , "    <built meta=\"𝑛·1·3\">𝑛·1·2</built>"+                         , "    <morph at=\"𝑛·1·3\">"+                         , "      <morphed meta=\"𝑛·1·4\">𝑛·1·2</morphed>"+                         , "    </morph>"+                         , "    <answered meta=\"𝑛·1·5\">𝑛·1·2</answered>"                          , "  </evaluate>"+                         , "  <morphed meta=\"𝑛·0·1\">01-</morphed>"                          , "</morph>"                          ] -        it "writes the copy of an object of the world a 'remint' step deferred, naming every argument by a symbol" $-          withTempFile "protocolXXXXXX.xml" $ \(path, stream) -> do-            hClose stream-            withLambdasOf (T.pack "- λ: L_shrink\n  morph:\n    𝑛1: $.x\n  rewrite:\n    𝑛2:\n      of: 𝑛1\n      rules:\n        - name: small\n          pattern: ⟦ !B1, tail ↦ 𝑒1, !B2 ⟧\n          result: ⟦ !B1, tail ↦ 𝑒2, !B2 ⟧\n          where:\n            - meta: 𝑒2\n              function: remint\n              args: [𝑒1]\n  terminators: [L_throw]\n  𝑛: 𝑛2\n") $ \shrinks ->-              withStdin "⟦ l/ ↦ ⟦ throw(message) ↦ ⟦ λ ⤍ L_throw ⟧ ⟧, y ↦ ⟦ x ↦ ⟦ tail ↦ Φ.l/.throw( message ↦ ⟦ Δ ⤍ 4B- ⟧ ) ⟧, λ ⤍ L_shrink ⟧.tail ⟧" $-                testCLISucceeded ["morph", "--symbolic=" ++ shrinks, "--locator=Q.y", "--protocol=" ++ path, "--quiet", "--sweet", "--hide-rho"] []-            records <- readProtocol path-            lines records `shouldContain` ["    <deferred symbol=\"𝜎2\" by=\"morph\" at=\"𝑛·1·1\" of=\"Φ.l/.throw\"><with><attr name=\"message\">𝜎1</attr></with></deferred>"]--        it "writes the path of every symbol an upcast block cast a bare symbol into" $-          withTempFile "protocolXXXXXX.xml" $ \(path, stream) -> do-            hClose stream-            withLambdasOf (T.pack "- λ: L_fork\n  morph:\n    𝑛1: $.a\n    𝑛2: $.b\n  upcast:\n    𝑛3: 𝑛2\n    𝑛4: 𝑛1\n  join:\n    𝑛5: [𝑛3, 𝑛4]\n  𝑛: 𝑛5\n") $ \forks ->-              withStdin "⟦ y ↦ ⟦ a ↦ ⟦ λ ⤍ 𝜎1 ⟧, b ↦ ⟦ sh ↦ ⟦ λ ⤍ 𝜎2 ⟧, zj ↦ ⟦ λ ⤍ 𝜎3 ⟧ ⟧, λ ⤍ L_fork ⟧.φ ⟧" $-                testCLISucceeded ["morph", "--symbolic=" ++ forks, "--locator=Q.y", "--protocol=" ++ path, "--quiet", "--sweet", "--hide-rho"] []-            records <- readProtocol path-            lines records `shouldContain` ["    <upcast symbol=\"𝜎1\" of=\"𝑛·1·2\"><path symbol=\"𝜎4\">sh</path><path symbol=\"𝜎5\">zj</path></upcast>"]--        it "binds both metas of an upcast block, the cast under the line that read the bare symbol" $-          withTempFile "protocolXXXXXX.xml" $ \(path, stream) -> do-            hClose stream-            withLambdasOf (T.pack "- λ: L_fork\n  morph:\n    𝑛1: $.a\n    𝑛2: $.b\n  upcast:\n    𝑛3: 𝑛2\n    𝑛4: 𝑛1\n  join:\n    𝑛5: [𝑛3, 𝑛4]\n  𝑛: 𝑛5\n") $ \forks ->-              withStdin "⟦ y ↦ ⟦ a ↦ ⟦ λ ⤍ 𝜎1 ⟧, b ↦ ⟦ sh ↦ ⟦ λ ⤍ 𝜎2 ⟧, zj ↦ ⟦ λ ⤍ 𝜎3 ⟧ ⟧, λ ⤍ L_fork ⟧.φ ⟧" $-                testCLISucceeded ["morph", "--symbolic=" ++ forks, "--locator=Q.y", "--protocol=" ++ path, "--quiet", "--sweet", "--hide-rho"] []-            records <- readProtocol path-            filter (isInfixOf "<bind meta=\"𝑛3·1\"") (lines records) ++ filter (isInfixOf "<bind meta=\"𝑛4·1\"") (lines records) `shouldBe` ["    <bind meta=\"𝑛3·1\">𝑛·1·1</bind>", "    <bind meta=\"𝑛4·1\">𝑛·1·2</bind>"]-         it "nests a firing an operand took inside the firing that asked" $           withTempFile "protocolXXXXXX.xml" $ \(path, stream) -> do             hClose stream@@ -2257,64 +2255,76 @@               `shouldBe` [ "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"                          , "<dataize at=\"Φ\">"                          , "  <morph at=\"Φ\">"-                         , "    <applied meta=\"𝑛·0·1\" of=\"Φ.bytes\"><attr name=\"φ\">40-14-00-00-00-00-00-00</attr></applied>"-                         , "    <applied meta=\"𝑛·0·2\" by=\"morph\" at=\"Φ\" of=\"Φ.number\"><attr name=\"φ\">𝑛·0·1</attr></applied>"-                         , "    <applied meta=\"𝑛·0·3\" of=\"Φ.bytes\"><attr name=\"φ\">40-18-00-00-00-00-00-00</attr></applied>"-                         , "    <applied meta=\"𝑛·0·4\" of=\"Φ.number\"><attr name=\"φ\">𝑛·0·3</attr></applied>"-                         , "    <applied meta=\"𝑛·0·5\" of=\"Φ.bytes\"><attr name=\"φ\">40-1C-00-00-00-00-00-00</attr></applied>"-                         , "    <applied meta=\"𝑛·0·6\" of=\"Φ.number\"><attr name=\"φ\">𝑛·0·5</attr></applied>"-                         , "    <applied meta=\"𝑛·0·7\" of=\"𝑛·0·4.plus\"><attr name=\"α0\">𝑛·0·6</attr></applied>"-                         , "    <applied meta=\"𝑛·0·8\" by=\"morph\" at=\"Φ\" of=\"𝑛·0·2.plus\"><attr name=\"x\">𝑛·0·7</attr></applied>"+                         , "    <applied meta=\"𝑛·0·1\" of=\"Φ.bytes\"><datum attr=\"φ\">40-14-00-00-00-00-00-00</datum></applied>"+                         , "    <applied meta=\"𝑛·0·2\" by=\"morph\" at=\"Φ\" of=\"Φ.number\"><meta attr=\"φ\">𝑛·0·1</meta></applied>"+                         , "    <applied meta=\"𝑛·0·3\" of=\"Φ.bytes\"><datum attr=\"φ\">40-18-00-00-00-00-00-00</datum></applied>"+                         , "    <applied meta=\"𝑛·0·4\" of=\"Φ.number\"><meta attr=\"φ\">𝑛·0·3</meta></applied>"+                         , "    <applied meta=\"𝑛·0·5\" of=\"Φ.bytes\"><datum attr=\"φ\">40-1C-00-00-00-00-00-00</datum></applied>"+                         , "    <applied meta=\"𝑛·0·6\" of=\"Φ.number\"><meta attr=\"φ\">𝑛·0·5</meta></applied>"+                         , "    <applied meta=\"𝑛·0·7\" of=\"𝑛·0·4.plus\"><meta attr=\"α0\">𝑛·0·6</meta></applied>"+                         , "    <applied meta=\"𝑛·0·8\" by=\"morph\" at=\"Φ\" of=\"𝑛·0·2.plus\"><meta attr=\"x\">𝑛·0·7</meta></applied>"+                         , "    <morphed meta=\"𝑛·0·9\">𝑛·0·8</morphed>"                          , "  </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=\"φ\">𝑛·0·1</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>"+                         , "        <applied meta=\"𝑛·1·1\" by=\"morph\" at=\"Φ.a🌵0\" of=\"Φ.number\"><meta attr=\"φ\">𝑛·0·1</meta></applied>"+                         , "        <morphed meta=\"𝑛·1·2\">𝑛·1·1</morphed>"+                         , "        <applied meta=\"𝑛·1·3\" by=\"morph\" at=\"Φ.a🌵0\" of=\"Φ.bytes\"><datum attr=\"φ\">40-14-00-00-00-00-00-00</datum></applied>"+                         , "        <morphed meta=\"𝑛·1·4\">𝑛·1·3</morphed>"                          , "      </morph>"-                         , "      <delta meta=\"𝛿·1·1\" number=\"5.0\">40-14-00-00-00-00-00-00</delta>"+                         , "      <dataized meta=\"𝛿·1·1\" number=\"5.0\">40-14-00-00-00-00-00-00</dataized>"                          , "    </dataize>"-                         , "    <bind meta=\"𝛿1·1\">𝛿·1·1</bind>"+                         , "    <bound meta=\"𝛿1·1\">𝛿·1·1</bound>"                          , "    <dataize at=\"Φ.a🌵1\">"                          , "      <morph at=\"Φ.a🌵1\">"-                         , "        <applied meta=\"𝑛·1·3\" by=\"morph\" at=\"Φ.a🌵1\" of=\"Φ.number\"><attr name=\"φ\">𝑛·0·3</attr></applied>"-                         , "        <applied meta=\"𝑛·1·4\" by=\"morph\" at=\"Φ.a🌵1\" of=\"𝑛·1·3.plus\"><attr name=\"x\">𝑛·0·6</attr></applied>"+                         , "        <applied meta=\"𝑛·1·5\" by=\"morph\" at=\"Φ.a🌵1\" of=\"Φ.number\"><meta attr=\"φ\">𝑛·0·3</meta></applied>"+                         , "        <applied meta=\"𝑛·1·6\" by=\"morph\" at=\"Φ.a🌵1\" of=\"𝑛·1·5.plus\"><meta attr=\"x\">𝑛·0·6</meta></applied>"+                         , "        <morphed meta=\"𝑛·1·7\">𝑛·1·6</morphed>"                          , "      </morph>"                          , "      <evaluate λ=\"L_number_plus\" by=\"dataize\" at=\"Φ.a🌵1\">"                          , "        <dataize at=\"Φ.a🌵2\">"                          , "          <morph at=\"Φ.a🌵2\">"-                         , "            <applied meta=\"𝑛·2·1\" by=\"morph\" at=\"Φ.a🌵2\" of=\"Φ.number\"><attr name=\"φ\">𝑛·0·3</attr></applied>"-                         , "            <applied meta=\"𝑛·2·2\" by=\"morph\" at=\"Φ.a🌵2\" of=\"Φ.bytes\"><attr name=\"φ\">40-18-00-00-00-00-00-00</attr></applied>"+                         , "            <applied meta=\"𝑛·2·1\" by=\"morph\" at=\"Φ.a🌵2\" of=\"Φ.number\"><meta attr=\"φ\">𝑛·0·3</meta></applied>"+                         , "            <morphed meta=\"𝑛·2·2\">𝑛·2·1</morphed>"+                         , "            <applied meta=\"𝑛·2·3\" by=\"morph\" at=\"Φ.a🌵2\" of=\"Φ.bytes\"><datum attr=\"φ\">40-18-00-00-00-00-00-00</datum></applied>"+                         , "            <morphed meta=\"𝑛·2·4\">𝑛·2·3</morphed>"                          , "          </morph>"-                         , "          <delta meta=\"𝛿·2·1\" number=\"6.0\">40-18-00-00-00-00-00-00</delta>"+                         , "          <dataized meta=\"𝛿·2·1\" number=\"6.0\">40-18-00-00-00-00-00-00</dataized>"                          , "        </dataize>"-                         , "        <bind meta=\"𝛿1·2\">𝛿·2·1</bind>"+                         , "        <bound meta=\"𝛿1·2\">𝛿·2·1</bound>"                          , "        <dataize at=\"Φ.a🌵3\">"                          , "          <morph at=\"Φ.a🌵3\">"-                         , "            <applied meta=\"𝑛·2·3\" by=\"morph\" at=\"Φ.a🌵3\" of=\"Φ.number\"><attr name=\"φ\">𝑛·0·5</attr></applied>"-                         , "            <applied meta=\"𝑛·2·4\" by=\"morph\" at=\"Φ.a🌵3\" of=\"Φ.bytes\"><attr name=\"φ\">40-1C-00-00-00-00-00-00</attr></applied>"+                         , "            <applied meta=\"𝑛·2·5\" by=\"morph\" at=\"Φ.a🌵3\" of=\"Φ.number\"><meta attr=\"φ\">𝑛·0·5</meta></applied>"+                         , "            <morphed meta=\"𝑛·2·6\">𝑛·2·5</morphed>"+                         , "            <applied meta=\"𝑛·2·7\" by=\"morph\" at=\"Φ.a🌵3\" of=\"Φ.bytes\"><datum attr=\"φ\">40-1C-00-00-00-00-00-00</datum></applied>"+                         , "            <morphed meta=\"𝑛·2·8\">𝑛·2·7</morphed>"                          , "          </morph>"-                         , "          <delta meta=\"𝛿·2·2\" number=\"7.0\">40-1C-00-00-00-00-00-00</delta>"+                         , "          <dataized meta=\"𝛿·2·2\" number=\"7.0\">40-1C-00-00-00-00-00-00</dataized>"                          , "        </dataize>"-                         , "        <bind meta=\"𝛿2·2\">𝛿·2·2</bind>"+                         , "        <bound meta=\"𝛿2·2\">𝛿·2·2</bound>"                          , "        <minted symbol=\"𝜎1\">40-18-00-00-00-00-00-00 40-1C-00-00-00-00-00-00</minted>"-                         , "        <applied meta=\"𝑛·2·5\" of=\"Φ.number\"><attr name=\"φ\">𝜎1</attr></applied>"-                         , "        <built meta=\"𝑛·2·6\">𝑛·2·5</built>"-                         , "        <morph at=\"𝑛·2·6\">"-                         , "          <applied meta=\"𝑛·2·7\" by=\"morph\" at=\"𝑛·2·6\" of=\"Φ.number\"><attr name=\"φ\">𝜎1</attr></applied>"+                         , "        <applied meta=\"𝑛·2·9\" of=\"Φ.number\"><symbol attr=\"φ\">𝜎1</symbol></applied>"+                         , "        <built meta=\"𝑛·2·10\">𝑛·2·9</built>"+                         , "        <morph at=\"𝑛·2·10\">"+                         , "          <applied meta=\"𝑛·2·11\" by=\"morph\" at=\"𝑛·2·10\" of=\"Φ.number\"><symbol attr=\"φ\">𝜎1</symbol></applied>"+                         , "          <morphed meta=\"𝑛·2·12\">𝑛·2·11</morphed>"                          , "        </morph>"-                         , "        <answer meta=\"𝑛·2·8\">𝑛·2·7</answer>"+                         , "        <answered meta=\"𝑛·2·13\">𝑛·2·11</answered>"                          , "      </evaluate>"+                         , "      <manufactured meta=\"𝛿·1·2\">𝜎1</manufactured>"                          , "    </dataize>"-                         , "    <dataize meta=\"𝛿2·1\">𝜎1</dataize>"+                         , "    <bound meta=\"𝛿2·1\">𝛿·1·2</bound>"                          , "    <minted symbol=\"𝜎2\">40-14-00-00-00-00-00-00 𝜎1</minted>"-                         , "    <applied meta=\"𝑛·1·5\" of=\"Φ.number\"><attr name=\"φ\">𝜎2</attr></applied>"-                         , "    <built meta=\"𝑛·1·6\">𝑛·1·5</built>"-                         , "    <morph at=\"𝑛·1·6\">"-                         , "      <applied meta=\"𝑛·1·7\" by=\"morph\" at=\"𝑛·1·6\" of=\"Φ.number\"><attr name=\"φ\">𝜎2</attr></applied>"+                         , "    <applied meta=\"𝑛·1·8\" of=\"Φ.number\"><symbol attr=\"φ\">𝜎2</symbol></applied>"+                         , "    <built meta=\"𝑛·1·9\">𝑛·1·8</built>"+                         , "    <morph at=\"𝑛·1·9\">"+                         , "      <applied meta=\"𝑛·1·10\" by=\"morph\" at=\"𝑛·1·9\" of=\"Φ.number\"><symbol attr=\"φ\">𝜎2</symbol></applied>"+                         , "      <morphed meta=\"𝑛·1·11\">𝑛·1·10</morphed>"                          , "    </morph>"-                         , "    <answer meta=\"𝑛·1·8\">𝑛·1·7</answer>"+                         , "    <answered meta=\"𝑛·1·12\">𝑛·1·10</answered>"                          , "  </evaluate>"+                         , "  <manufactured meta=\"𝛿·0·1\">𝜎2</manufactured>"                          , "</dataize>"                          ] @@ -2328,38 +2338,44 @@               `shouldBe` [ "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"                          , "<dataize at=\"Φ\">"                          , "  <morph at=\"Φ\">"-                         , "    <applied meta=\"𝑛·0·1\" of=\"Φ.bytes\"><attr name=\"φ\">40-00-00-00-00-00-00-00</attr></applied>"-                         , "    <applied meta=\"𝑛·0·2\" by=\"morph\" at=\"Φ\" of=\"Φ.number\"><attr name=\"φ\">𝑛·0·1</attr></applied>"-                         , "    <applied meta=\"𝑛·0·3\" of=\"Φ.bytes\"><attr name=\"φ\">40-08-00-00-00-00-00-00</attr></applied>"-                         , "    <applied meta=\"𝑛·0·4\" of=\"Φ.number\"><attr name=\"φ\">𝑛·0·3</attr></applied>"-                         , "    <applied meta=\"𝑛·0·5\" by=\"morph\" at=\"Φ\" of=\"𝑛·0·2.times\"><attr name=\"x\">𝑛·0·4</attr></applied>"+                         , "    <applied meta=\"𝑛·0·1\" of=\"Φ.bytes\"><datum attr=\"φ\">40-00-00-00-00-00-00-00</datum></applied>"+                         , "    <applied meta=\"𝑛·0·2\" by=\"morph\" at=\"Φ\" of=\"Φ.number\"><meta attr=\"φ\">𝑛·0·1</meta></applied>"+                         , "    <applied meta=\"𝑛·0·3\" of=\"Φ.bytes\"><datum attr=\"φ\">40-08-00-00-00-00-00-00</datum></applied>"+                         , "    <applied meta=\"𝑛·0·4\" of=\"Φ.number\"><meta attr=\"φ\">𝑛·0·3</meta></applied>"+                         , "    <applied meta=\"𝑛·0·5\" by=\"morph\" at=\"Φ\" of=\"𝑛·0·2.times\"><meta attr=\"x\">𝑛·0·4</meta></applied>"                          , "    <evaluate λ=\"L_number_times\" by=\"morph\" at=\"Φ\">"                          , "      <dataize at=\"Φ.a🌵0\">"                          , "        <morph at=\"Φ.a🌵0\">"-                         , "          <applied meta=\"𝑛·1·1\" by=\"morph\" at=\"Φ.a🌵0\" of=\"Φ.number\"><attr name=\"φ\">𝑛·0·1</attr></applied>"-                         , "          <applied meta=\"𝑛·1·2\" by=\"morph\" at=\"Φ.a🌵0\" of=\"Φ.bytes\"><attr name=\"φ\">40-00-00-00-00-00-00-00</attr></applied>"+                         , "          <applied meta=\"𝑛·1·1\" by=\"morph\" at=\"Φ.a🌵0\" of=\"Φ.number\"><meta attr=\"φ\">𝑛·0·1</meta></applied>"+                         , "          <morphed meta=\"𝑛·1·2\">𝑛·1·1</morphed>"+                         , "          <applied meta=\"𝑛·1·3\" by=\"morph\" at=\"Φ.a🌵0\" of=\"Φ.bytes\"><datum attr=\"φ\">40-00-00-00-00-00-00-00</datum></applied>"+                         , "          <morphed meta=\"𝑛·1·4\">𝑛·1·3</morphed>"                          , "        </morph>"-                         , "        <delta meta=\"𝛿·1·1\" number=\"2.0\">40-00-00-00-00-00-00-00</delta>"+                         , "        <dataized meta=\"𝛿·1·1\" number=\"2.0\">40-00-00-00-00-00-00-00</dataized>"                          , "      </dataize>"-                         , "      <bind meta=\"𝛿1·1\">𝛿·1·1</bind>"+                         , "      <bound meta=\"𝛿1·1\">𝛿·1·1</bound>"                          , "      <dataize at=\"Φ.a🌵1\">"                          , "        <morph at=\"Φ.a🌵1\">"-                         , "          <applied meta=\"𝑛·1·3\" by=\"morph\" at=\"Φ.a🌵1\" of=\"Φ.number\"><attr name=\"φ\">𝑛·0·3</attr></applied>"-                         , "          <applied meta=\"𝑛·1·4\" by=\"morph\" at=\"Φ.a🌵1\" of=\"Φ.bytes\"><attr name=\"φ\">40-08-00-00-00-00-00-00</attr></applied>"+                         , "          <applied meta=\"𝑛·1·5\" by=\"morph\" at=\"Φ.a🌵1\" of=\"Φ.number\"><meta attr=\"φ\">𝑛·0·3</meta></applied>"+                         , "          <morphed meta=\"𝑛·1·6\">𝑛·1·5</morphed>"+                         , "          <applied meta=\"𝑛·1·7\" by=\"morph\" at=\"Φ.a🌵1\" of=\"Φ.bytes\"><datum attr=\"φ\">40-08-00-00-00-00-00-00</datum></applied>"+                         , "          <morphed meta=\"𝑛·1·8\">𝑛·1·7</morphed>"                          , "        </morph>"-                         , "        <delta meta=\"𝛿·1·2\" number=\"3.0\">40-08-00-00-00-00-00-00</delta>"+                         , "        <dataized meta=\"𝛿·1·2\" number=\"3.0\">40-08-00-00-00-00-00-00</dataized>"                          , "      </dataize>"-                         , "      <bind meta=\"𝛿2·1\">𝛿·1·2</bind>"+                         , "      <bound meta=\"𝛿2·1\">𝛿·1·2</bound>"                          , "      <minted symbol=\"𝜎1\">40-00-00-00-00-00-00-00 40-08-00-00-00-00-00-00</minted>"-                         , "      <applied meta=\"𝑛·1·5\" of=\"Φ.number\"><attr name=\"φ\">𝜎1</attr></applied>"-                         , "      <built meta=\"𝑛·1·6\">𝑛·1·5</built>"-                         , "      <morph at=\"𝑛·1·6\">"-                         , "        <applied meta=\"𝑛·1·7\" by=\"morph\" at=\"𝑛·1·6\" of=\"Φ.number\"><attr name=\"φ\">𝜎1</attr></applied>"+                         , "      <applied meta=\"𝑛·1·9\" of=\"Φ.number\"><symbol attr=\"φ\">𝜎1</symbol></applied>"+                         , "      <built meta=\"𝑛·1·10\">𝑛·1·9</built>"+                         , "      <morph at=\"𝑛·1·10\">"+                         , "        <applied meta=\"𝑛·1·11\" by=\"morph\" at=\"𝑛·1·10\" of=\"Φ.number\"><symbol attr=\"φ\">𝜎1</symbol></applied>"+                         , "        <morphed meta=\"𝑛·1·12\">𝑛·1·11</morphed>"                          , "      </morph>"-                         , "      <answer meta=\"𝑛·1·8\">𝑛·1·7</answer>"+                         , "      <answered meta=\"𝑛·1·13\">𝑛·1·11</answered>"                          , "    </evaluate>"+                         , "    <morphed meta=\"𝑛·0·6\">𝑛·1·11.nope</morphed>"                          , "  </morph>"-                         , "  <unanswered λ=\"L_number_nope\" by=\"dataize\">𝑛·1·7.nope</unanswered>"+                         , "  <unanswered λ=\"L_number_nope\" by=\"dataize\">𝑛·1·11.nope</unanswered>"                          , "</dataize>"                          ] @@ -2387,38 +2403,44 @@               `shouldBe` [ "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"                          , "<dataize at=\"Φ\">"                          , "  <morph at=\"Φ\">"-                         , "    <applied meta=\"𝑛·0·1\" of=\"Φ.bytes\"><attr name=\"φ\">40-00-00-00-00-00-00-00</attr></applied>"-                         , "    <applied meta=\"𝑛·0·2\" by=\"morph\" at=\"Φ\" of=\"Φ.number\"><attr name=\"φ\">𝑛·0·1</attr></applied>"-                         , "    <applied meta=\"𝑛·0·3\" of=\"Φ.bytes\"><attr name=\"φ\">40-08-00-00-00-00-00-00</attr></applied>"-                         , "    <applied meta=\"𝑛·0·4\" of=\"Φ.number\"><attr name=\"φ\">𝑛·0·3</attr></applied>"-                         , "    <applied meta=\"𝑛·0·5\" by=\"morph\" at=\"Φ\" of=\"𝑛·0·2.times\"><attr name=\"x\">𝑛·0·4</attr></applied>"+                         , "    <applied meta=\"𝑛·0·1\" of=\"Φ.bytes\"><datum attr=\"φ\">40-00-00-00-00-00-00-00</datum></applied>"+                         , "    <applied meta=\"𝑛·0·2\" by=\"morph\" at=\"Φ\" of=\"Φ.number\"><meta attr=\"φ\">𝑛·0·1</meta></applied>"+                         , "    <applied meta=\"𝑛·0·3\" of=\"Φ.bytes\"><datum attr=\"φ\">40-08-00-00-00-00-00-00</datum></applied>"+                         , "    <applied meta=\"𝑛·0·4\" of=\"Φ.number\"><meta attr=\"φ\">𝑛·0·3</meta></applied>"+                         , "    <applied meta=\"𝑛·0·5\" by=\"morph\" at=\"Φ\" of=\"𝑛·0·2.times\"><meta attr=\"x\">𝑛·0·4</meta></applied>"                          , "    <evaluate λ=\"L_number_times\" by=\"morph\" at=\"Φ\">"                          , "      <dataize at=\"Φ.a🌵0\">"                          , "        <morph at=\"Φ.a🌵0\">"-                         , "          <applied meta=\"𝑛·1·1\" by=\"morph\" at=\"Φ.a🌵0\" of=\"Φ.number\"><attr name=\"φ\">𝑛·0·1</attr></applied>"-                         , "          <applied meta=\"𝑛·1·2\" by=\"morph\" at=\"Φ.a🌵0\" of=\"Φ.bytes\"><attr name=\"φ\">40-00-00-00-00-00-00-00</attr></applied>"+                         , "          <applied meta=\"𝑛·1·1\" by=\"morph\" at=\"Φ.a🌵0\" of=\"Φ.number\"><meta attr=\"φ\">𝑛·0·1</meta></applied>"+                         , "          <morphed meta=\"𝑛·1·2\">𝑛·1·1</morphed>"+                         , "          <applied meta=\"𝑛·1·3\" by=\"morph\" at=\"Φ.a🌵0\" of=\"Φ.bytes\"><datum attr=\"φ\">40-00-00-00-00-00-00-00</datum></applied>"+                         , "          <morphed meta=\"𝑛·1·4\">𝑛·1·3</morphed>"                          , "        </morph>"-                         , "        <delta meta=\"𝛿·1·1\" number=\"2.0\">40-00-00-00-00-00-00-00</delta>"+                         , "        <dataized meta=\"𝛿·1·1\" number=\"2.0\">40-00-00-00-00-00-00-00</dataized>"                          , "      </dataize>"-                         , "      <bind meta=\"𝛿1·1\">𝛿·1·1</bind>"+                         , "      <bound meta=\"𝛿1·1\">𝛿·1·1</bound>"                          , "      <dataize at=\"Φ.a🌵1\">"                          , "        <morph at=\"Φ.a🌵1\">"-                         , "          <applied meta=\"𝑛·1·3\" by=\"morph\" at=\"Φ.a🌵1\" of=\"Φ.number\"><attr name=\"φ\">𝑛·0·3</attr></applied>"-                         , "          <applied meta=\"𝑛·1·4\" by=\"morph\" at=\"Φ.a🌵1\" of=\"Φ.bytes\"><attr name=\"φ\">40-08-00-00-00-00-00-00</attr></applied>"+                         , "          <applied meta=\"𝑛·1·5\" by=\"morph\" at=\"Φ.a🌵1\" of=\"Φ.number\"><meta attr=\"φ\">𝑛·0·3</meta></applied>"+                         , "          <morphed meta=\"𝑛·1·6\">𝑛·1·5</morphed>"+                         , "          <applied meta=\"𝑛·1·7\" by=\"morph\" at=\"Φ.a🌵1\" of=\"Φ.bytes\"><datum attr=\"φ\">40-08-00-00-00-00-00-00</datum></applied>"+                         , "          <morphed meta=\"𝑛·1·8\">𝑛·1·7</morphed>"                          , "        </morph>"-                         , "        <delta meta=\"𝛿·1·2\" number=\"3.0\">40-08-00-00-00-00-00-00</delta>"+                         , "        <dataized meta=\"𝛿·1·2\" number=\"3.0\">40-08-00-00-00-00-00-00</dataized>"                          , "      </dataize>"-                         , "      <bind meta=\"𝛿2·1\">𝛿·1·2</bind>"+                         , "      <bound meta=\"𝛿2·1\">𝛿·1·2</bound>"                          , "      <minted symbol=\"𝜎1\">40-00-00-00-00-00-00-00 40-08-00-00-00-00-00-00</minted>"-                         , "      <applied meta=\"𝑛·1·5\" of=\"Φ.number\"><attr name=\"φ\">𝜎1</attr></applied>"-                         , "      <built meta=\"𝑛·1·6\">𝑛·1·5</built>"-                         , "      <morph at=\"𝑛·1·6\">"-                         , "        <applied meta=\"𝑛·1·7\" by=\"morph\" at=\"𝑛·1·6\" of=\"Φ.number\"><attr name=\"φ\">𝜎1</attr></applied>"+                         , "      <applied meta=\"𝑛·1·9\" of=\"Φ.number\"><symbol attr=\"φ\">𝜎1</symbol></applied>"+                         , "      <built meta=\"𝑛·1·10\">𝑛·1·9</built>"+                         , "      <morph at=\"𝑛·1·10\">"+                         , "        <applied meta=\"𝑛·1·11\" by=\"morph\" at=\"𝑛·1·10\" of=\"Φ.number\"><symbol attr=\"φ\">𝜎1</symbol></applied>"+                         , "        <morphed meta=\"𝑛·1·12\">𝑛·1·11</morphed>"                          , "      </morph>"-                         , "      <answer meta=\"𝑛·1·8\">𝑛·1·7</answer>"+                         , "      <answered meta=\"𝑛·1·13\">𝑛·1·11</answered>"                          , "    </evaluate>"+                         , "    <morphed meta=\"𝑛·0·6\">𝑛·1·11.nope</morphed>"                          , "  </morph>"-                         , "  <unanswered λ=\"L_number_nope\" by=\"dataize\">𝑛·1·7.nope</unanswered>"+                         , "  <unanswered λ=\"L_number_nope\" by=\"dataize\">𝑛·1·11.nope</unanswered>"                          , "</dataize>"                          ] @@ -2433,10 +2455,16 @@               `shouldBe` [ "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"                          , "<morph at=\"Φ.x\">"                          , "  <evaluate λ=\"L_pick\" by=\"morph\" at=\"Φ.x\">"-                         , "    <bind meta=\"𝑛1·1\">⊥</bind>"+                         , "    <morph at=\"Φ.a🌵0\">"+                         , "      <morphed meta=\"𝑛·1·1\">⊥</morphed>"+                         , "    </morph>"+                         , "    <bound meta=\"𝑛1·1\">⊥</bound>"                          , "    <minted symbol=\"𝜎1\"/>"-                         , "    <built meta=\"𝑛·1·1\">𝜎1</built>"-                         , "    <answer meta=\"𝑛·1·2\">𝜎1</answer>"+                         , "    <built meta=\"𝑛·1·2\">𝜎1</built>"+                         , "    <morph at=\"𝑛·1·2\">"+                         , "      <morphed meta=\"𝑛·1·3\">𝜎1</morphed>"+                         , "    </morph>"+                         , "    <answered meta=\"𝑛·1·4\">𝜎1</answered>"                          , "  </evaluate>"                          , "  <unanswered λ=\"𝜎1\" by=\"morph\">𝜎1</unanswered>"                          , "</morph>"@@ -2500,29 +2528,29 @@                        , "      𝕄(𝑛·1·5):"                        , "        𝑛·1·6 := Φ.number( φ ↦ 𝜎1:λ )"                        , "      𝑛·1·7 := 𝑛·1·6  # 𝕄(𝑛·1·5)"-                       , "  unanswered(L_number_nope)  # 𝔻(L_number_nope:λ)"+                       , "  unanswered L_number_nope  # 𝔻(L_number_nope:λ)"                        ]        it "still prints bytes when nothing gets stuck" $         withStdin "[[ bytes ↦ ⟦ φ ↦ ∅ ⟧, number(φ) -> [[ plus(^, x) -> [[ L> L_number_plus ]] ]], @ -> 5.plus(6) ]]" $           testCLISucceeded ["dataize", symbolic, "--partial"] ["40-45-00-00-00-00-00-00"] -      it "prints the residual at --locator, which XMIR has no top level for" $+      it "prints the residual at --locator as XMIR, with ρ hidden" $         withStdin wrapped $-          testCLIFailed+          testCLISucceeded             ["dataize", symbolic, "--partial", "--locator=Q.app", "--output=xmir", "--hide-rho"]-            ["[ERROR]:", "its top level must be a single binding"]+            ["<o name=\"λ\">L_number_nope</o>"]        it "prints the residual at --locator, not the whole program" $         withStdin wrapped $ do           (out, _) <- withStdout (runCLI ["dataize", symbolic, "--partial", "--locator=Q.app", "--hide-rho", "--flat"])           lines out `shouldBe` ["⟦ λ ⤍ L_number_nope ⟧"] -      it "cannot print a residual of several top bindings as XMIR" $+      it "prints a residual of several top bindings as XMIR" $         withStdin dispatched $-          testCLIFailed+          testCLISucceeded             ["dataize", symbolic, "--partial", "--output=xmir"]-            ["[ERROR]:", "its top level must be a single binding"]+            ["<o base=\"Φ.foo\" name=\"ρ\"/>", "<o name=\"λ\">L_number_nope</o>"]        it "prints the chain of steps ending in the residue with --sequence" $         withStdin stuck $@@ -2557,19 +2585,31 @@           withStdin sum' $             testCLIFailed ["dataize", "--symbolic=" ++ path] ["cannot be read"] -    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: []\n  𝑛: 𝑛3\n"-      it "gets stuck on a fork whose second branch decorates the first when the entry is strict" $-        withLambdasOf strict $ \forks ->-          withStdin "⟦ y ↦ ⟦ a ↦ ⟦ φ ↦ ⟦ λ ⤍ 𝜎1 ⟧, neg ↦ ⟦⟧ ⟧, b ↦ ⟦ φ ↦ ⟦ φ ↦ ⟦ λ ⤍ 𝜎2 ⟧, neg ↦ ⟦⟧ ⟧ ⟧, λ ⤍ L_fork ⟧ ⟧" $-            testCLIFailed ["dataize", "--symbolic=" ++ forks, "--locator=Q.y"] ["No entry of --symbolic answers the λ function 'L_fork'"]+    describe "a join on a condition nobody decided" $ do+      let joining = T.pack "- λ: L_fork\n  dataize:\n    𝛿1: $.c\n  join:\n    on: 𝛿1\n    left: $.a\n    right: $.b\n"+          dataized :: String -> IO String+          dataized program =+            withLambdasOf joining $ \joins ->+              withStdin program $+                fst <$> withStdout (runCLI ["dataize", "--symbolic=" ++ joins, "--locator=Q.y"])+      it "gets stuck on two sides that do not join, instead of splitting the run" $+        withLambdasOf joining $ \joins ->+          withStdin "⟦ y ↦ ⟦ c ↦ ⟦ λ ⤍ 𝜎1 ⟧, a ↦ ⟦ Δ ⤍ 01- ⟧, b ↦ ⟦ Δ ⤍ 02- ⟧, λ ⤍ L_fork ⟧ ⟧" $+            testCLIFailed ["dataize", "--symbolic=" ++ joins, "--locator=Q.y"] ["No entry of --symbolic answers the λ function 'L_fork'"] -      it "dataizes a fork whose second branch decorates the first" $-        withLambdasOf lenient $ \forks ->-          withStdin "⟦ y ↦ ⟦ a ↦ ⟦ φ ↦ ⟦ λ ⤍ 𝜎1 ⟧, neg ↦ ⟦⟧ ⟧, b ↦ ⟦ φ ↦ ⟦ φ ↦ ⟦ λ ⤍ 𝜎2 ⟧, neg ↦ ⟦⟧ ⟧ ⟧, λ ⤍ L_fork ⟧ ⟧" $-            testCLISucceeded ["dataize", "--symbolic=" ++ forks, "--locator=Q.y"] ["40-45-00-00-00-00-00-00"]+      it "gets stuck on a side whose morph got stuck" $+        withLambdasOf joining $ \joins ->+          withStdin "⟦ y ↦ ⟦ c ↦ ⟦ λ ⤍ 𝜎1 ⟧, a ↦ ⟦ Δ ⤍ 05- ⟧, b ↦ ⟦ λ ⤍ L_none ⟧.x, λ ⤍ L_fork ⟧ ⟧" $+            testCLIFailed ["dataize", "--symbolic=" ++ joins, "--locator=Q.y"] ["No entry of --symbolic answers the λ function 'L_none'"] +      it "dataizes two sides that come to the same term" $ do+        printed <- dataized "⟦ y ↦ ⟦ c ↦ ⟦ λ ⤍ 𝜎1 ⟧, a ↦ ⟦ Δ ⤍ 07- ⟧, b ↦ ⟦ Δ ⤍ 07- ⟧, λ ⤍ L_fork ⟧ ⟧"+        lines printed `shouldBe` ["07-"]++      it "dataizes two sides that differ only in their symbols" $ do+        printed <- dataized "⟦ y ↦ ⟦ c ↦ ⟦ λ ⤍ 𝜎1 ⟧, a ↦ ⟦ φ ↦ ⟦ λ ⤍ 𝜎2 ⟧ ⟧, b ↦ ⟦ φ ↦ ⟦ λ ⤍ 𝜎3 ⟧ ⟧, λ ⤍ L_fork ⟧ ⟧"+        length (lines printed) `shouldBe` 1+     describe "--inside" $ do       let universe = "[[ bytes ↦ ⟦ φ ↦ ∅ ⟧, number(φ) -> [[ plus(^, x) -> [[ L> L_number_plus ]] ]], @ -> [[ D> 01- ]] ]]"       it "dataizes an expression the input does not contain" $@@ -2828,7 +2868,7 @@           withStdin split $             testCLISucceeded               ["morph", "--symbolic=" ++ table, "--locator=Q.x", "--max-firings=64", "--partial", "--flat", "--hide-rho", "--sweet"]-              ["⊥"]+              ["L_split:λ.foo"]        it "fires no more λ functions than --max-firings allows" $         splitting $ \table ->@@ -2891,7 +2931,7 @@                     (["morph", "--symbolic=" ++ table, "--max-seconds=1", "--protocol=" ++ path, "--quiet"] ++ opts)                     ["--max-seconds=1"]               records <- readProtocol path-              dropWhile (== ' ') (last (lines records)) `shouldBe` "timeout(1)"+              dropWhile (== ' ') (last (lines records)) `shouldBe` "timeout 1"        it "writes the timeout once to the XML protocol" $         ladder $ \table ->@@ -2935,114 +2975,60 @@             ["morph", "--locator=Q.@", "--partial", "--flat", "--hide-rho"]             ["⟦ λ ⤍ Sym_arg_0 ⟧.foo"] -    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: []\n  𝑛: 𝑛3\n"-      it "gets stuck on a fork whose second branch decorates the first when the entry is strict" $-        withLambdasOf strict $ \forks ->-          withStdin "⟦ y ↦ ⟦ a ↦ ⟦ φ ↦ ⟦ λ ⤍ 𝜎1 ⟧, neg ↦ ⟦⟧ ⟧, b ↦ ⟦ φ ↦ ⟦ φ ↦ ⟦ λ ⤍ 𝜎2 ⟧, neg ↦ ⟦⟧ ⟧ ⟧, λ ⤍ L_fork ⟧.φ ⟧" $-            testCLIFailed ["morph", "--symbolic=" ++ forks, "--locator=Q.y"] ["No entry of --symbolic answers the λ function 'L_fork'"]--      it "joins a fork whose second branch decorates the first" $-        withLambdasOf lenient $ \forks ->-          withStdin "⟦ y ↦ ⟦ a ↦ ⟦ φ ↦ ⟦ λ ⤍ 𝜎1 ⟧, neg ↦ ⟦⟧ ⟧, b ↦ ⟦ φ ↦ ⟦ φ ↦ ⟦ λ ⤍ 𝜎2 ⟧, neg ↦ ⟦⟧ ⟧ ⟧, λ ⤍ L_fork ⟧.φ ⟧" $-            testCLISucceeded-              ["morph", "--symbolic=" ++ forks, "--locator=Q.y", "--sweet", "--hide-rho", "--flat"]-              ["⟦ φ ↦ 𝜎3:λ, neg ↦ ⟦⟧ ⟧"]--      it "joins a fork whose first branch decorates the second" $-        withLambdasOf lenient $ \forks ->-          withStdin "⟦ y ↦ ⟦ a ↦ ⟦ φ ↦ ⟦ φ ↦ ⟦ λ ⤍ 𝜎1 ⟧, neg ↦ ⟦⟧ ⟧ ⟧, b ↦ ⟦ φ ↦ ⟦ λ ⤍ 𝜎2 ⟧, neg ↦ ⟦⟧ ⟧, λ ⤍ L_fork ⟧.φ ⟧" $-            testCLISucceeded-              ["morph", "--symbolic=" ++ forks, "--locator=Q.y", "--sweet", "--hide-rho", "--flat"]-              ["⟦ φ ↦ 𝜎3:λ, neg ↦ ⟦⟧ ⟧"]--      it "writes the decorator it joined into the protocol" $-        withTempFile "protocolXXXXXX.txt" $ \(path, stream) -> do-          hClose stream-          withLambdasOf lenient $ \forks ->-            withStdin "⟦ y ↦ ⟦ a ↦ ⟦ φ ↦ ⟦ λ ⤍ 𝜎1 ⟧, neg ↦ ⟦⟧ ⟧, b ↦ ⟦ φ ↦ ⟦ φ ↦ ⟦ λ ⤍ 𝜎2 ⟧, neg ↦ ⟦⟧ ⟧ ⟧, λ ⤍ L_fork ⟧.φ ⟧" $-              testCLISucceeded ["morph", "--symbolic=" ++ forks, "--locator=Q.y", "--protocol=" ++ path, "--quiet", "--sweet", "--hide-rho"] []-          records <- readProtocol path-          lines records `shouldContain` ["    𝑛3·1 := ⟦ φ ↦ 𝜎3:λ, neg ↦ ⟦⟧ ⟧:φ  # [𝑛1, 𝑛2]"]--      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 ↦ ⟦ 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" $-        withLambdasOf lenient $ \forks ->-          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", "--sweet", "--hide-rho", "--flat"]-              ["⟦ if(left, right) ↦ ⟦ λ ⤍ L_fork, φ ↦ 𝜎3:λ ⟧, φ ↦ if( FF-:Δ, 00-:Δ ) ⟧"]--    describe "a deferring unfold" $ do-      let deferring = T.pack "- λ: L_fork\n  morph:\n    𝑛1: $.a\n    𝑛2: $.b\n  unfold:\n    𝑛3: {of: 𝑛2, defer: true}\n  join:\n    𝑛4: [𝑛1, 𝑛3]\n  𝑛: ⟦ φ ↦ 𝑛4 ⟧\n"-          counting = deferring <> T.pack "- λ: L_inc\n  dataize:\n    𝛿1: $.x\n  𝑛: Φ.num( φ ↦ ⟦ λ ⤍ 𝜎 ⟧ )\n"+    describe "a copy a 'defer' line of a join defers" $ do+      let joining = T.pack "- λ: L_fork\n  dataize:\n    𝛿1: $.c\n  morph:\n    𝑛1: $.a\n    𝑛2: $.b\n  defer:\n    𝑛3: 𝑛1\n    𝑛4: 𝑛2\n  join:\n    on: 𝛿1\n    left: 𝑛3\n    right: 𝑛4\n"+          counting = T.pack "- λ: L_inc\n  dataize:\n    𝛿1: $.x\n  𝑛: Φ.num( φ ↦ ⟦ λ ⤍ 𝜎 ⟧ )\n"           recordedWith :: T.Text -> [String] -> String -> IO [String]           recordedWith entries options program =             withTempFile "protocolXXXXXX.xml" $ \(path, stream) -> do               hClose stream-              withLambdasOf entries $ \forks ->+              withLambdasOf entries $ \joins ->                 withStdin program $-                  testCLISucceeded (["morph", "--symbolic=" ++ forks, "--locator=Q.y", "--protocol=" ++ path, "--quiet", "--sweet", "--hide-rho"] ++ options) []+                  testCLIFailed (["dataize", "--symbolic=" ++ joins, "--locator=Q.y", "--protocol=" ++ path, "--quiet", "--sweet", "--hide-rho"] ++ options) ["No entry of --symbolic answers the λ function 'L_fork'"]               lines <$> readProtocol path           deferred :: [String] -> String -> IO [String]-          deferred = recordedWith deferring+          deferred = recordedWith joining       it "writes the copy it deferred to the XML protocol as a call of the object it was made of" $ do-        records <- deferred [] "⟦ step(tup) ↦ ⟦ φ ↦ tup.next ⟧, items ↦ ⟦ tail ↦ ⟦ λ ⤍ 𝜎1 ⟧ ⟧, y ↦ ⟦ a ↦ ⟦ λ ⤍ 𝜎2 ⟧, b ↦ Φ.step( tup ↦ Φ.items.tail ), λ ⤍ L_fork ⟧.φ ⟧"+        records <- deferred [] "⟦ step(tup) ↦ ⟦ φ ↦ tup.next ⟧, items ↦ ⟦ tail ↦ ⟦ λ ⤍ 𝜎1 ⟧ ⟧, y ↦ ⟦ c ↦ ⟦ λ ⤍ 𝜎2 ⟧, a ↦ ⟦ Δ ⤍ 01- ⟧, b ↦ Φ.step( tup ↦ Φ.items.tail ), λ ⤍ L_fork ⟧ ⟧"         records           `shouldBe` [ "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"-                     , "<morph at=\"Φ.y\">"-                     , "  <evaluate λ=\"L_fork\" by=\"morph\" at=\"Φ.y\">"-                     , "    <bind meta=\"𝑛1·1\">𝜎2</bind>"+                     , "<dataize at=\"Φ.y\">"+                     , "  <evaluate λ=\"L_fork\" by=\"dataize\" at=\"Φ.y\">"+                     , "    <dataize at=\"Φ.a🌵0\">"+                     , "      <manufactured meta=\"𝛿·1·1\">𝜎2</manufactured>"+                     , "    </dataize>"+                     , "    <bound meta=\"𝛿1·1\">𝛿·1·1</bound>"                      , "    <morph at=\"Φ.a🌵1\">"-                     , "      <applied meta=\"𝑛·1·1\" by=\"morph\" at=\"Φ.a🌵1\" of=\"Φ.step\"><attr name=\"tup\">Φ.items.tail</attr></applied>"+                     , "      <morphed meta=\"𝑛·1·1\">01-</morphed>"                      , "    </morph>"-                     , "    <bind meta=\"𝑛2·1\">𝑛·1·1</bind>"-                     , "    <deferred symbol=\"𝜎3\" by=\"morph\" at=\"𝑛2.φ\" of=\"Φ.step\"><with><attr name=\"tup\">𝜎1</attr></with></deferred>"-                     , "    <joined symbol=\"𝜎4\">𝜎2 𝜎3</joined>"-                     , "    <bind meta=\"𝑛4·1\">𝜎4</bind>"-                     , "    <formed meta=\"𝑛·1·2\"><attr name=\"φ\">𝜎4</attr></formed>"-                     , "    <built meta=\"𝑛·1·3\">𝑛·1·2</built>"-                     , "    <answer meta=\"𝑛·1·4\">𝑛·1·2</answer>"+                     , "    <bound meta=\"𝑛1·1\">01-</bound>"+                     , "    <morph at=\"Φ.a🌵2\">"+                     , "      <applied meta=\"𝑛·1·2\" by=\"morph\" at=\"Φ.a🌵2\" of=\"Φ.step\"><ref attr=\"tup\">Φ.items.tail</ref></applied>"+                     , "      <morphed meta=\"𝑛·1·3\">𝑛·1·2</morphed>"+                     , "    </morph>"+                     , "    <bound meta=\"𝑛2·1\">𝑛·1·2</bound>"+                     , "    <bound meta=\"𝑛3·1\">01-</bound>"+                     , "    <deferred symbol=\"𝜎3\" by=\"morph\" at=\"𝑛2.φ\" of=\"Φ.step\"><with><symbol attr=\"tup\">𝜎1</symbol></with></deferred>"+                     , "    <diverged left=\"𝑛3·1\" right=\"𝜎3\" at=\".Δ\" reason=\"the join names no otherwise\"/>"                      , "  </evaluate>"-                     , "</morph>"+                     , "</dataize>"                      ]        it "names the object a deferred copy was made of through the formation its ρ holds" $ do-        records <- deferred [] "⟦ joined(items) ↦ ⟦ φ ↦ step( tup ↦ items ), step(ρ, tup) ↦ ⟦ φ ↦ tup.next ⟧ ⟧, items ↦ ⟦ tail ↦ ⟦ λ ⤍ 𝜎1 ⟧ ⟧, y ↦ ⟦ a ↦ ⟦ λ ⤍ 𝜎2 ⟧, b ↦ Φ.joined( items ↦ Φ.items.tail ).φ, λ ⤍ L_fork ⟧.φ ⟧"-        records `shouldContain` ["    <deferred symbol=\"𝜎3\" by=\"morph\" at=\"𝑛2.φ\" of=\"Φ.joined.step\"><with><attr name=\"tup\">𝜎1</attr></with></deferred>"]+        records <- deferred [] "⟦ joined(items) ↦ ⟦ φ ↦ step( tup ↦ items ), step(ρ, tup) ↦ ⟦ φ ↦ tup.next ⟧ ⟧, items ↦ ⟦ tail ↦ ⟦ λ ⤍ 𝜎1 ⟧ ⟧, y ↦ ⟦ c ↦ ⟦ λ ⤍ 𝜎2 ⟧, a ↦ ⟦ Δ ⤍ 01- ⟧, b ↦ Φ.joined( items ↦ Φ.items.tail ).φ, λ ⤍ L_fork ⟧ ⟧"+        records `shouldContain` ["    <deferred symbol=\"𝜎3\" by=\"morph\" at=\"𝑛2.φ\" of=\"Φ.joined.step\"><with><symbol attr=\"tup\">𝜎1</symbol></with></deferred>"]        it "names an argument of a deferred copy that is no bare symbol by the element that formed it" $ do-        records <- deferred [] "⟦ box(x, w) ↦ ⟦ φ ↦ x.next ⟧, items ↦ ⟦ tail ↦ ⟦ λ ⤍ 𝜎1 ⟧ ⟧, y ↦ ⟦ a ↦ ⟦ λ ⤍ 𝜎2 ⟧, b ↦ Φ.box( x ↦ Φ.items.tail, w ↦ ⟦ z ↦ Φ ⟧ ), λ ⤍ L_fork ⟧.φ ⟧"-        records `shouldContain` ["    <deferred symbol=\"𝜎3\" by=\"morph\" at=\"𝑛2.φ\" of=\"Φ.box\"><with><attr name=\"x\">𝜎1</attr><attr name=\"w\">𝑛·1·2</attr></with></deferred>"]+        records <- deferred [] "⟦ box(x, w) ↦ ⟦ φ ↦ x.next ⟧, items ↦ ⟦ tail ↦ ⟦ λ ⤍ 𝜎1 ⟧ ⟧, y ↦ ⟦ c ↦ ⟦ λ ⤍ 𝜎2 ⟧, a ↦ ⟦ Δ ⤍ 01- ⟧, b ↦ Φ.box( x ↦ Φ.items.tail, w ↦ ⟦ z ↦ Φ ⟧ ), λ ⤍ L_fork ⟧ ⟧"+        records `shouldContain` ["    <deferred symbol=\"𝜎3\" by=\"morph\" at=\"𝑛2.φ\" of=\"Φ.box\"><with><symbol attr=\"x\">𝜎1</symbol><meta attr=\"w\">𝑛·1·3</meta></with></deferred>"]        it "names a deferred copy of a formation the world does not declare by the element that formed it" $ do-        records <- deferred [] "⟦ wrap(v) ↦ ⟦⟧, items ↦ ⟦ tail ↦ ⟦ λ ⤍ 𝜎1 ⟧ ⟧, y ↦ ⟦ a ↦ ⟦ λ ⤍ 𝜎2 ⟧, b ↦ Φ.wrap( v ↦ ⟦ inner(x) ↦ ⟦ φ ↦ x.next ⟧ ⟧ ).v.inner( x ↦ Φ.items.tail ), λ ⤍ L_fork ⟧.φ ⟧"-        records `shouldContain` ["    <formed meta=\"𝑛·1·5\"><attr name=\"x\">𝜎1</attr><attr name=\"φ\">x.next</attr></formed>", "    <deferred symbol=\"𝜎3\" by=\"morph\" at=\"𝑛2.φ\" of=\"𝑛·1·5\"/>"]--      it "writes the copy a cut stopped to the XML protocol with its symbol and the call of the object it was made of" $ do-        records <- deferred ["--acyclic=proven"] "⟦ fork(a, b) ↦ ⟦ λ ⤍ L_fork ⟧, rec(n) ↦ ⟦ φ ↦ Φ.fork( ⟦ λ ⤍ 𝜎1 ⟧, Φ.rec( ξ.n ) ) ⟧, y ↦ Φ.rec( ⟦ φ ↦ ⟦ λ ⤍ 𝜎2 ⟧ ⟧ ).φ.φ ⟧"-        records `shouldContain` ["    <looped symbol=\"𝜎3\" by=\"evaluate\" match=\"proven\" at=\"𝑛2\" of=\"Φ.rec\"><with><attr name=\"n\">𝑛·0·1</attr></with></looped>"]--      it "names an argument of a copy a cut stopped by the object its morph answered" $ do-        records <- recordedWith counting ["--acyclic=plausible"] "⟦ fork(a, b) ↦ ⟦ λ ⤍ L_fork ⟧, num(φ) ↦ ⟦⟧, inc(x) ↦ ⟦ λ ⤍ L_inc ⟧, rec(n) ↦ ⟦ φ ↦ Φ.fork( ⟦ λ ⤍ 𝜎1 ⟧, Φ.rec( Φ.inc( ξ.n ) ) ) ⟧, y ↦ Φ.rec( ⟦ φ ↦ ⟦ λ ⤍ 𝜎2 ⟧ ⟧ ).φ.φ ⟧"-        filter (isInfixOf "<looped") records `shouldBe` ["    <looped symbol=\"𝜎4\" by=\"evaluate\" match=\"plausible\" at=\"𝑛2\" of=\"Φ.rec\"><with><attr name=\"n\">𝑛·2·3</attr></with></looped>"]--      it "fires the argument of a copy a cut stopped before it writes the cut" $ do-        records <- recordedWith counting ["--acyclic=plausible"] "⟦ fork(a, b) ↦ ⟦ λ ⤍ L_fork ⟧, num(φ) ↦ ⟦⟧, inc(x) ↦ ⟦ λ ⤍ L_inc ⟧, rec(n) ↦ ⟦ φ ↦ Φ.fork( ⟦ λ ⤍ 𝜎1 ⟧, Φ.rec( Φ.inc( ξ.n ) ) ) ⟧, y ↦ Φ.rec( ⟦ φ ↦ ⟦ λ ⤍ 𝜎2 ⟧ ⟧ ).φ.φ ⟧"-        takeWhile (not . isInfixOf "<looped") records `shouldContain` ["    <evaluate λ=\"L_inc\" by=\"evaluate\" at=\"𝑛2.n\">"]--      it "names an argument of a copy a cut stopped whose firing gets stuck by the application it stands for" $ do-        records <- recordedWith deferring ["--acyclic=plausible"] "⟦ fork(a, b) ↦ ⟦ λ ⤍ L_fork ⟧, dec(x) ↦ ⟦ λ ⤍ L_dec ⟧, rec(n) ↦ ⟦ φ ↦ Φ.fork( ⟦ λ ⤍ 𝜎1 ⟧, Φ.rec( Φ.dec( ξ.n ) ) ) ⟧, y ↦ Φ.rec( ⟦ φ ↦ ⟦ λ ⤍ 𝜎2 ⟧ ⟧ ).φ.φ ⟧"-        filter (isInfixOf "<looped") records `shouldBe` ["    <looped symbol=\"𝜎3\" by=\"evaluate\" match=\"plausible\" at=\"𝑛2\" of=\"Φ.rec\"><with><attr name=\"n\">𝑛·0·4</attr></with></looped>"]+        records <- deferred [] "⟦ wrap(v) ↦ ⟦⟧, items ↦ ⟦ tail ↦ ⟦ λ ⤍ 𝜎1 ⟧ ⟧, y ↦ ⟦ c ↦ ⟦ λ ⤍ 𝜎2 ⟧, a ↦ ⟦ Δ ⤍ 01- ⟧, b ↦ Φ.wrap( v ↦ ⟦ inner(x) ↦ ⟦ φ ↦ x.next ⟧ ⟧ ).v.inner( x ↦ Φ.items.tail ), λ ⤍ L_fork ⟧ ⟧"+        records `shouldContain` ["    <formed meta=\"𝑛·1·7\"><symbol attr=\"x\">𝜎1</symbol><ref attr=\"φ\">x.next</ref></formed>", "    <deferred symbol=\"𝜎3\" by=\"morph\" at=\"𝑛2.φ\" of=\"𝑛·1·7\"/>"]        it "names an argument of a deferred copy by the object its morph answered" $ do-        records <- recordedWith counting [] "⟦ box(x, w) ↦ ⟦ φ ↦ x.next ⟧, num(φ) ↦ ⟦⟧, inc(x) ↦ ⟦ λ ⤍ L_inc ⟧, items ↦ ⟦ tail ↦ ⟦ λ ⤍ 𝜎1 ⟧ ⟧, y ↦ ⟦ a ↦ ⟦ λ ⤍ 𝜎2 ⟧, b ↦ Φ.box( x ↦ Φ.items.tail, w ↦ Φ.inc( ⟦ Δ ⤍ 2A- ⟧ ) ), λ ⤍ L_fork ⟧.φ ⟧"-        filter (isInfixOf "<deferred") records `shouldBe` ["    <deferred symbol=\"𝜎4\" by=\"morph\" at=\"𝑛2.φ\" of=\"Φ.box\"><with><attr name=\"x\">𝜎1</attr><attr name=\"w\">𝑛·2·3</attr></with></deferred>"]+        records <- recordedWith (joining <> counting) [] "⟦ box(x, w) ↦ ⟦ φ ↦ x.next ⟧, num(φ) ↦ ⟦⟧, inc(x) ↦ ⟦ λ ⤍ L_inc ⟧, items ↦ ⟦ tail ↦ ⟦ λ ⤍ 𝜎1 ⟧ ⟧, y ↦ ⟦ c ↦ ⟦ λ ⤍ 𝜎2 ⟧, a ↦ ⟦ Δ ⤍ 01- ⟧, b ↦ Φ.box( x ↦ Φ.items.tail, w ↦ Φ.inc( ⟦ Δ ⤍ 2A- ⟧ ) ), λ ⤍ L_fork ⟧ ⟧"+        filter (isInfixOf "<deferred") records `shouldBe` ["    <deferred symbol=\"𝜎4\" by=\"morph\" at=\"𝑛2.φ\" of=\"Φ.box\"><with><symbol attr=\"x\">𝜎1</symbol><meta attr=\"w\">𝑛·2·3</meta></with></deferred>"]      describe "--acyclic=plausible" $ do       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 ) ) ).φ ]]"@@ -3127,13 +3113,13 @@         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" $-        withStdin "[[ x -> [[ D> 01- ]], y -> [[ D> 02- ]] ]]" $-          testCLIFailed-            ["morph", "--output=xmir"]-            ["[ERROR]:", "its top level must be a single binding"]+    it "prints a top formation of several bindings as XMIR" $+      withStdin "[[ x -> [[ D> 01- ]], y -> [[ D> 02- ]] ]]" $+        testCLISucceeded+          ["morph", "--output=xmir"]+          ["<o name=\"x\">01-</o>", "<o name=\"y\">02-</o>"] +    describe "fails" $ do       it "with --output != latex and --nonumber" $         withStdin "" $           testCLIFailed
test/DataizeSpec.hs view
@@ -33,6 +33,7 @@ import Rewriter (Rewritten) import Rule (RuleContext (RuleContext), matchExpressionWithRule') import System.FilePath (makeRelative)+import Tau (seedTaus) import Test.Hspec import Yaml qualified @@ -72,6 +73,7 @@ partially :: Lambdas -> String -> IO ((Outcome, [Rewritten]), String) partially known src = do   expr <- parseExpressionThrows (primitives src)+  seedTaus expr   recorded $ \record -> do     base <- defaultReduceContext ExRoot     let ctx = (withLambdas known base){_partial = True, _saveEval = record}@@ -221,14 +223,14 @@           , "      𝑛·0·1 := 2"           , "      𝑛·0·2 := 𝑛·0·1.times( x ↦ 3 )"           , "      𝔼(L_number_times):"-          , "        𝔻(Φ.a🌵17):"-          , "          𝕄(Φ.a🌵17):"+          , "        𝔻(Φ.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🌵18):"-          , "          𝕄(Φ.a🌵18):"+          , "        𝔻(Φ.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"@@ -237,12 +239,12 @@           , "        𝕄(𝑛·1·5):"           , "          𝑛·1·6 := Φ.number( φ ↦ 𝜎1:λ )"           , "        𝑛·1·7 := 𝑛·1·6  # 𝕄(𝑛·1·5)"-          , "    unanswered(L_number_nope)  # 𝔻(⟦ ρ ↦ 𝑛·1·6, λ ⤍ L_number_nope ⟧)"+          , "    unanswered L_number_nope  # 𝔻(⟦ ρ ↦ 𝑛·1·6, λ ⤍ L_number_nope ⟧)"           ]     it "leaves an unanswered λ function dataized directly as the whole residue" $ do       ((outcome, chain), protocol) <- partially known "[[ L> Sym_arg_0 ]]"       outcome `shouldBe` Residual placeholder-      protocol `shouldBe` "  𝔻(Φ):\n    unanswered(Sym_arg_0)  # 𝔻(Sym_arg_0:λ)\n"+      protocol `shouldBe` "  𝔻(Φ):\n    unanswered Sym_arg_0  # 𝔻(Sym_arg_0:λ)\n"       map fst chain `shouldEndWith` [placeholder]     it "closes a dataization with the datum the delta rule found" $ do       (_, protocol) <- partially known "[[ @ -> [[ D> 01-02 ]] ]]"@@ -258,7 +260,7 @@       case outcome of         Residual (ExFormation bds) -> bds `shouldContain` [BiLambda (Function "L_number_plus")]         other -> expectationFailure ("expected a residual formation, got " ++ show other)-      protocol `shouldSatisfy` isInfixOf "unanswered(⊥)  # 𝔻(⊥)"+      protocol `shouldSatisfy` isInfixOf "unanswered ⊥  # 𝔻(⊥)"    describe "ReduceContext's --max-depth/--max-cycles reach into the normalization it splices in" $ do     let boxed = "[[ @ -> [[ D> 00- ]] ]]"@@ -283,7 +285,7 @@     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 Nothing 0 Dataization [] Map.empty emptyLambdas (building linked) reduction evaluation dontSaveStep dontSaveEval linked)+      (value, _, _) <- dataize expr emptyState (ReduceContext ExRoot ExRoot Nothing 1 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@@ -317,10 +319,10 @@       loc <- parseExpressionThrows "Q"       (_, chain, _) <- dataize expr emptyState . withLambdas known =<< defaultReduceContext loc       map snd chain `shouldContain` [Just (Evaluation, "evaluate")]-    it "takes the step of a box by contextualization" $ do+    it "takes the step of a box by dataization" $ do       expr <- parseExpressionThrows "[[ @ -> [[ D> 0A- ]] ]]"       (_, chain, _) <- dataize expr emptyState =<< defaultReduceContext ExRoot-      map snd chain `shouldContain` [Just (Contextualization, "contextualize")]+      map snd chain `shouldContain` [Just (Dataization, "box")]     it "takes the step of a delta by dataization" $ do       expr <- parseExpressionThrows "[[ D> 3C- ]]"       (_, chain, _) <- dataize expr emptyState =<< defaultReduceContext ExRoot@@ -348,28 +350,32 @@           "Q"           "[[ bytes ↦ ⟦ φ ↦ ∅ ⟧, number(φ) -> [[ plus(^, x) -> [[ L> L_number_plus ]] ]], @ -> 5.plus(6) ]]"       labels-        `shouldBe` [ "contextualize"-                   , "maa"-                   , "alpha"-                   , "copy"+        `shouldBe` [ "box"+                   , "dot"+                   , "ma"+                   , "stay"                    , "mf"                    , "evaluate"-                   , "contextualize"+                   , "box"+                   , "dot"+                   , "skip"                    , "symbol"                    ]     it "dataizes a located reference through the expected rules" $ do       labels <- labelsOf "Q.foo.bar" "[[ foo -> [[ bar -> [[ @ -> Q.x ]] ]], x -> [[ D> 42- ]] ]]"-      labels `shouldBe` ["contextualize", "md", "dot", "skip", "mf", "delta"]+      labels `shouldBe` ["box", "dot", "ma", "skip", "mf", "delta"]     it "takes every step of 5.plus(6) by the judgment of its rule" $ do       expr <- parseExpressionThrows "[[ bytes ↦ ⟦ φ ↦ ∅ ⟧, number(φ) -> [[ plus(^, x) -> [[ L> L_number_plus ]] ]], @ -> 5.plus(6) ]]"       (_, chain, _) <- dataize expr emptyState . withLambdas known =<< defaultReduceContext ExRoot       [judgment | (_, Just (judgment, _)) <- chain]-        `shouldBe` [ Contextualization-                   , Morphing+        `shouldBe` [ Dataization                    , Normalization+                   , Morphing                    , Normalization                    , Morphing                    , Evaluation-                   , Contextualization+                   , Dataization+                   , Normalization+                   , Normalization                    , Dataization                    ]
test/DepsSpec.hs view
@@ -5,14 +5,15 @@  module DepsSpec where -import AST (Argument (ArTau), Attribute (AtLabel, AtPhi, AtRho), Binding (BiDelta, BiLambda, BiTau, BiVoid), Bytes (BtMany, BtOne), Expression (ExApplication, ExDispatch, ExFormation, ExRoot, ExXi), Function (FnSymbol, Function))+import AST (Argument (ArTau), Attribute (AtDelta, AtLabel, AtLambda, AtPhi, AtRho), Binding (BiDelta, BiLambda, BiTau, BiVoid), Bytes (BtMany, BtOne), Expression (ExApplication, ExBytes, ExDispatch, ExFormation, ExMeta, ExRoot, ExTermination, ExXi), Function (FnSymbol, Function)) 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 (Plausible, Proven), Evaluation (EvAnswer, EvApplied, EvBuilt, EvData, EvDeferred, EvDelta, EvFiring, EvLooped, EvRun, EvStarted, EvStuck, EvStuckOn, EvTerm, EvTerminate, EvUnjoined, EvUnreminted, EvUpcast, EvWorld), Judgment (Dataization, Morphing), Nesting (..), Protocol (..), dontSaveEval, dontSaveStep, emptyNesting, emptyProgress, emptyProtocol, endEval, endEvalXml, perSecond, progressed, resited, saveStep)-import Fixtures (readUtf8, recorded, recordedXml)+import Deps (Acyclic (Proven), Evaluation (EvAnswer, EvApplied, EvBound, EvBuilt, EvData, EvDeferred, EvDelta, EvDiverged, EvFabricated, EvFiring, EvJoined, EvLooped, EvManufactured, EvMorphed, EvReshaped, EvRun, EvStarted, EvStuck, EvStuckOn, EvSymbolized, EvTaken, EvTerm, EvWorld), Judgment (Dataization, Morphing), Nesting (..), Protocol (..), dontSaveEval, dontSaveStep, emptyNesting, emptyProgress, emptyProtocol, endEval, endEvalXml, perSecond, progressed, resited, saveStep)+import Fixtures (readUtf8, recorded, recordedWhole, recordedXml)+import Forks (Refusal (..), Side (..)) import GHC.Clock (getMonotonicTime) import Logger (LogLevel (DEBUG, ERROR, INFO), setLogConfig) import System.Directory@@ -157,6 +158,14 @@     it "spells the datum of an operand by the name the delta rule just gave it" $ do       (_, written) <- recorded (\record -> mapM_ record [EvFiring 1 "L_ж" Dataization ExRoot, EvDelta 3 (BtOne "7F"), EvData 2 "𝛿1" (ExDispatch ExXi (AtLabel "щ")) (Right (BtOne "7F"))])       last (lines written) `shouldBe` "    𝛿1·1 := 𝛿·1·1  # 𝔻(ξ.щ)"+    it "writes no line for the symbol a dataization came down to" $ do+      (_, manufactured) <- recorded (\record -> mapM_ record [EvRun Dataization "Φ", EvFiring 1 "L_ж" Dataization ExRoot, EvManufactured 3 32, EvData 2 "𝛿1" (ExDispatch ExXi (AtLabel "щ")) (Left 32)])+      (_, plain) <- recorded (\record -> mapM_ record [EvRun Dataization "Φ", EvFiring 1 "L_ж" Dataization ExRoot, EvData 2 "𝛿1" (ExDispatch ExXi (AtLabel "щ")) (Left 32)])+      manufactured `shouldBe` plain+    it "writes no line for the term a morphing came to" $ do+      (_, morphed) <- recorded (\record -> mapM_ record [EvRun Morphing "Φ", EvStarted 1 Morphing (ExDispatch ExRoot (AtLabel "жук")), EvMorphed 2 (ExDispatch ExRoot (AtLabel "щука"))])+      (_, plain) <- recorded (\record -> mapM_ record [EvRun Morphing "Φ", EvStarted 1 Morphing (ExDispatch ExRoot (AtLabel "жук"))])+      morphed `shouldBe` plain     it "spells the datum of an operand in full when the line before it found no datum" $ do       (_, written) <- recorded (\record -> mapM_ record [EvFiring 1 "L_ж" Dataization ExRoot, EvDelta 3 (BtOne "7F"), EvFiring 2 "L_з" Dataization ExRoot, EvData 2 "𝛿1" (ExDispatch ExXi (AtLabel "щ")) (Right (BtOne "7F"))])       last (lines written) `shouldBe` "    𝛿1·1 := 7F-  # 𝔻(ξ.щ)"@@ -185,109 +194,175 @@       (_, 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])-      last (lines written) `shouldBe` "  looped(⟦⟧)  # proven"-    it "writes the symbol a cut answered a copy with beside the formation it cut" $ do-      (_, written) <- recorded (\record -> mapM_ record [EvRun Morphing "Φ", EvLooped 1 Morphing Proven (ExFormation [BiTau (AtLabel "ъ") ExRoot]) ExRoot (Just (13, Nothing))])-      last (lines written) `shouldBe` "  looped(Φ:ъ) := 𝜎13  # proven"+      (_, 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 "ю"))])       lines written `shouldContain` ["    𝕄(𝑛·1·1):"]-    it "names the message a fork raises with 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") (ExFormation []))) (ExFormation [BiTau (AtLabel "q") (ExFormation [])]) ExRoot, EvTerminate 2 (Just (Left 7)) "right" "𝑛4" (Just (ExFormation [BiTau (AtLabel "q") (ExFormation [])]))])-      last (lines written) `shouldBe` "    terminate(𝔻(𝜎7:λ), right, 𝑛·1·1)  # 𝑛4"-    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 a deferred copy as the call of the object it was made of" $ do       (_, written) <- recorded (\record -> mapM_ record [EvRun Morphing "Φ", EvDeferred 1 9 Morphing (ExFormation [BiTau (AtLabel "щ") (ExFormation [BiLambda (FnSymbol 4)]), BiTau AtPhi (ExDispatch (ExDispatch ExXi (AtLabel "щ")) (AtLabel "й"))]) (Just (ExApplication (ExDispatch ExRoot (AtLabel "ёж")) (ArTau (AtLabel "щ") (ExFormation [BiLambda (FnSymbol 4)])))) ExRoot])-      last (lines written) `shouldBe` "  deferred(𝜎9) := Φ.ёж( щ ↦ 𝜎4:λ )"+      last (lines written) `shouldBe` "  deferred 𝜎9 := Φ.ёж( щ ↦ 𝜎4:λ )"     it "writes a deferred copy as it stands when no object of the world made it" $ do       (_, written) <- recorded (\record -> mapM_ record [EvRun Morphing "Φ", EvDeferred 1 9 Morphing (ExFormation [BiTau (AtLabel "щ") (ExFormation [BiLambda (FnSymbol 4)]), BiTau AtPhi (ExDispatch (ExDispatch ExXi (AtLabel "щ")) (AtLabel "й"))]) Nothing ExRoot])-      last (lines written) `shouldBe` "  deferred(𝜎9) := ⟦ щ ↦ 𝜎4:λ, φ ↦ щ.й ⟧"-    it "writes a join that failed with the two branches it could not pair" $ do-      (_, written) <- recorded (\record -> mapM_ record [EvRun Morphing "Φ", EvFiring 1 "L_щ" Morphing ExRoot, EvUnjoined 2 "L_щ" (ExFormation [BiLambda (FnSymbol 5)]) (ExFormation [BiTau (AtLabel "ю") (ExFormation [BiLambda (FnSymbol 8)]), BiTau (AtLabel "ц") ExRoot])])-      last (lines written) `shouldBe` "    unjoined(L_щ)  # 𝜎5:λ vs ⟦ ю ↦ 𝜎8:λ, ц ↦ Φ ⟧"-    it "writes a remint that failed with the term it found no symbol for" $ do-      (_, written) <- recorded (\record -> mapM_ record [EvRun Morphing "Φ", EvFiring 1 "L_ёж" Morphing ExRoot, EvUnreminted 2 "L_ёж" (ExFormation [BiTau (AtLabel "жук") (ExFormation [BiLambda (FnSymbol 17)]), BiTau (AtLabel "ф") ExRoot])])-      last (lines written) `shouldBe` "    unreminted(L_ёж)  # ⟦ жук ↦ 𝜎17:λ, ф ↦ Φ ⟧"-    it "writes an upcast as the term a bare symbol was cast to" $ do-      (_, written) <- recorded (\record -> mapM_ record [EvRun Morphing "Φ", EvFiring 1 "L_щ" Morphing ExRoot, EvUpcast 2 41 (ExApplication (ExApplication (ExDispatch ExRoot (AtLabel "кортеж")) (ArTau (AtLabel "хвост") (ExFormation [BiLambda (FnSymbol 43)]))) (ArTau AtPhi (ExFormation [BiLambda (FnSymbol 44)]))) [(43, [AtLabel "хвост"]), (44, [AtPhi])]])-      lines written `shouldContain` ["    upcast(𝜎41) := Φ.кортеж( хвост ↦ 𝜎43:λ, φ ↦ 𝜎44:λ )"]-    it "writes every symbol of an upcast as the path that reads it off the bare symbol" $ do-      (_, written) <- recorded (\record -> mapM_ record [EvRun Morphing "Φ", EvFiring 1 "L_щ" Morphing ExRoot, EvUpcast 2 41 (ExApplication (ExApplication (ExDispatch ExRoot (AtLabel "кортеж")) (ArTau (AtLabel "хвост") (ExFormation [BiLambda (FnSymbol 43)]))) (ArTau AtPhi (ExFormation [BiTau AtRho (ExFormation [BiLambda (FnSymbol 44)])]))) [(43, [AtLabel "хвост"]), (44, [AtPhi, AtRho])]])-      drop 3 (lines written) `shouldBe` ["      𝜎43 := 𝜎41.хвост", "      𝜎44 := 𝜎41.φ.ρ"]-+      last (lines written) `shouldBe` "  deferred 𝜎9 := ⟦ щ ↦ 𝜎4:λ, φ ↦ щ.й ⟧"+    it "writes the term a join came to as the answer of its firing" $ do+      (_, written) <- recorded (\record -> mapM_ record [EvRun Morphing "Φ", EvFiring 1 "L_ф" Morphing ExRoot, EvTaken 2 [ExDispatch ExXi (AtLabel "then")] (ExFormation [BiLambda (FnSymbol 3)])])+      last (lines written) `shouldBe` "    𝑛·1·1 := 𝜎3:λ  # 𝕄(ξ.then)"+    it "writes the term both sides of a join came to beside both of them" $ do+      (_, written) <- recorded (\record -> mapM_ record [EvRun Morphing "Φ", EvFiring 1 "L_ф" Morphing ExRoot, EvTaken 2 [ExDispatch ExXi (AtLabel "then"), ExDispatch ExXi (AtLabel "else")] (ExFormation [BiLambda (FnSymbol 3)])])+      last (lines written) `shouldBe` "    𝑛·1·1 := 𝜎3:λ  # 𝕄(ξ.then), 𝕄(ξ.else)"+    it "writes the side a join read from a meta beside that meta alone" $ do+      (_, written) <- recorded (\record -> mapM_ record [EvRun Morphing "Φ", EvFiring 1 "L_ф" Morphing ExRoot, EvTaken 2 [ExMeta "n3"] (ExFormation [BiLambda (FnSymbol 3)])])+      last (lines written) `shouldBe` "    𝑛·1·1 := 𝜎3:λ  # 𝑛3"+    it "writes the term a path and a meta of a join came to beside the morph of the one and the other alone" $ do+      (_, written) <- recorded (\record -> mapM_ record [EvRun Morphing "Φ", EvFiring 1 "L_ф" Morphing ExRoot, EvTaken 2 [ExDispatch ExXi (AtLabel "then"), ExMeta "n4"] (ExFormation [BiLambda (FnSymbol 3)])])+      last (lines written) `shouldBe` "    𝑛·1·1 := 𝜎3:λ  # 𝕄(ξ.then), 𝑛4"+    it "writes the term a stay line dropped a ρ from beside the stay of the meta it read" $ do+      (_, written) <- recorded (\record -> mapM_ record [EvRun Morphing "Φ", EvFiring 1 "L_ф" Morphing ExRoot, EvReshaped 2 "𝑛3" "stay" (ExMeta "n1") (ExFormation [BiLambda (FnSymbol 3)])])+      last (lines written) `shouldBe` "    𝑛3·1 := 𝜎3:λ  # stay(𝑛1)"+    it "writes the term a symbolize line turned a datum of into a symbol beside the symbolize of the meta it read" $ do+      (_, written) <- recorded (\record -> mapM_ record [EvRun Morphing "Φ", EvFiring 1 "L_ф" Morphing ExRoot, EvReshaped 2 "𝑛4" "symbolize" (ExMeta "n2") (ExFormation [BiLambda (FnSymbol 6)])])+      last (lines written) `shouldBe` "    𝑛4·1 := 𝜎6:λ  # symbolize(𝑛2)"+    it "writes the symbol a symbolize line minted beside the datum it stands for" $ do+      (_, written) <- recorded (\record -> mapM_ record [EvRun Morphing "Φ", EvFiring 1 "L_ф" Morphing ExRoot, EvSymbolized 2 6 (BtOne "4A")])+      last (lines written) `shouldBe` "    symbolized 𝜎6 := 4A-"+    it "writes the double eight bytes hold beside the symbol a symbolize line minted for them" $ do+      (_, written) <- recorded (\record -> mapM_ record [EvRun Morphing "Φ", EvFiring 1 "L_ф" Morphing ExRoot, EvSymbolized 2 6 (BtMany ["C0", "09", "1E", "B8", "51", "EB", "85", "1F"])])+      last (lines written) `shouldBe` "    symbolized 𝜎6 := C0-09-1E-B8-51-EB-85-1F  # -3.14"+    it "writes the symbol a join minted beside the meta its condition was bound to and the two it stands for" $ do+      (_, written) <- recorded (\record -> mapM_ record [EvRun Morphing "Φ", EvFiring 1 "L_ф" Morphing ExRoot, EvJoined 2 (Left "𝛿3") 12 (5, 9)])+      last (lines written) `shouldBe` "    joined 𝜎12 := 𝛿3·1 ? 𝜎5 : 𝜎9"+    it "writes the symbol a join minted beside the path its condition was read off" $ do+      (_, written) <- recorded (\record -> mapM_ record [EvRun Morphing "Φ", EvFiring 1 "L_ф" Morphing ExRoot, EvJoined 2 (Right (ExDispatch ExXi (AtLabel "щит"))) 12 (5, 9)])+      last (lines written) `shouldBe` "    joined 𝜎12 := ξ.щит ? 𝜎5 : 𝜎9"+    it "writes two sides a join could not join by the applications that made them" $ do+      (_, written) <- recorded (\record -> mapM_ record [EvRun Morphing "Φ", EvFiring 1 "L_ф" Morphing ExRoot, EvApplied 2 Morphing (ExApplication (ExDispatch ExRoot (AtLabel "ёж")) (ArTau (AtLabel "q") (ExFormation []))) (ExFormation [BiTau (AtLabel "q") (ExFormation [])]) ExRoot, EvApplied 2 Morphing (ExApplication (ExDispatch ExRoot (AtLabel "жук")) (ArTau (AtLabel "w") (ExFormation []))) (ExFormation [BiTau (AtLabel "w") (ExFormation [])]) ExRoot, EvDiverged 2 (ExDispatch ExXi (AtLabel "then"), ExFormation [BiTau (AtLabel "q") (ExFormation [])]) (ExDispatch ExXi (AtLabel "else"), ExFormation [BiTau (AtLabel "w") (ExFormation [])]) [AtLabel "q"] Unnamed])+      last (lines written) `shouldBe` "    diverged 𝑛·1·1 vs 𝑛·1·2  # they differ at .q, and the join names no otherwise"+    it "writes two sides a join could not join by the metas of the lines that bound them" $ do+      (_, written) <- recorded (\record -> mapM_ record [EvRun Morphing "Φ", EvFiring 1 "L_ф" Morphing ExRoot, EvTerm 2 "𝑛1" Morphing (ExDispatch ExXi (AtLabel "a")) (ExFormation [BiTau AtPhi (ExFormation [BiDelta (BtOne "1F")])]), EvTerm 2 "𝑛2" Morphing (ExDispatch ExXi (AtLabel "b")) (ExFormation [BiTau AtPhi (ExFormation [BiDelta (BtOne "2E")])]), EvDiverged 2 (ExMeta "n1", ExFormation [BiTau AtPhi (ExFormation [BiDelta (BtOne "1F")])]) (ExMeta "n2", ExFormation [BiTau AtPhi (ExFormation [BiDelta (BtOne "2E")])]) [AtPhi, AtDelta] Unnamed])+      last (lines written) `shouldBe` "    diverged 𝑛1·1 vs 𝑛2·1  # they differ at .φ.Δ, and the join names no otherwise"+    it "writes a side of a join no line named as the term it is" $ do+      (_, written) <- recorded (\record -> mapM_ record [EvRun Morphing "Φ", EvFiring 1 "L_ф" Morphing ExRoot, EvDiverged 2 (ExDispatch ExXi (AtLabel "a"), ExDispatch ExRoot (AtLabel "ёж")) (ExDispatch ExXi (AtLabel "b"), ExDispatch ExRoot (AtLabel "щука")) [] (Unformed Lefthand)])+      last (lines written) `shouldBe` "    diverged Φ.ёж vs Φ.щука  # the left side is no formation"+    it "writes a side of a join a defer line bound to a symbol as that symbol" $ do+      (_, written) <- recorded (\record -> mapM_ record [EvRun Morphing "Φ", EvFiring 1 "L_ф" Morphing ExRoot, EvDiverged 2 (ExMeta "n7", ExFormation [BiLambda (FnSymbol 4)]) (ExDispatch ExXi (AtLabel "b"), ExDispatch ExRoot (AtLabel "щука")) [] (Holding Lefthand AtLambda)])+      last (lines written) `shouldBe` "    diverged 𝜎4:λ vs Φ.щука  # the left side has λ"+    it "writes where the two sides of a join differ beside why no formation was fabricated of them" $ do+      (_, written) <- recorded (\record -> mapM_ record [EvRun Morphing "Φ", EvFiring 1 "L_ф" Morphing ExRoot, EvDiverged 2 (ExMeta "n2", ExFormation [BiTau AtPhi (ExFormation [BiTau (AtLabel "ко") (ExDispatch ExRoot (AtLabel "уж"))])]) (ExMeta "n9", ExFormation [BiTau AtPhi (ExFormation [BiTau (AtLabel "ко") (ExDispatch ExRoot (AtLabel "ёрш"))]), BiDelta (BtOne "3C")]) [AtPhi, AtLabel "ко"] (Holding Righthand AtDelta)])+      last (lines written) `shouldBe` "    diverged 𝑛2·1 vs 𝑛9·1  # they differ at .φ.ко, and the right side has Δ"+    it "writes the formation a join fabricated beside the metas of the lines that bound its sides" $ do+      (_, written) <- recorded (\record -> mapM_ record [EvRun Morphing "Φ", EvFiring 1 "L_ф" Morphing ExRoot, EvFabricated 2 (ExMeta "n1", ExFormation [BiTau AtPhi (ExDispatch ExRoot (AtLabel "ёж"))]) (ExMeta "n2", ExFormation [BiTau (AtLabel "к") (ExDispatch ExRoot (AtLabel "щука"))]) (ExFormation [BiTau AtPhi (ExFormation [BiLambda (Function "L_ж"), BiTau (AtLabel "on") (ExFormation [BiDelta (BtOne "05")]), BiTau (AtLabel "left") (ExDispatch (ExFormation [BiTau AtPhi (ExDispatch ExRoot (AtLabel "ёж"))]) AtPhi), BiTau (AtLabel "right") (ExDispatch (ExFormation [BiTau (AtLabel "к") (ExDispatch ExRoot (AtLabel "щука"))]) AtPhi)]), BiTau (AtLabel "к") (ExFormation [BiLambda (Function "L_ж"), BiTau (AtLabel "on") (ExFormation [BiDelta (BtOne "05")]), BiTau (AtLabel "left") (ExDispatch (ExFormation [BiTau AtPhi (ExDispatch ExRoot (AtLabel "ёж"))]) (AtLabel "к")), BiTau (AtLabel "right") (ExDispatch (ExFormation [BiTau (AtLabel "к") (ExDispatch ExRoot (AtLabel "щука"))]) (AtLabel "к"))])])])+      last (lines written) `shouldSatisfy` isPrefixOf "    fabricated 𝑛·1·1 := 𝑛1·1 and 𝑛2·1  # "+    it "writes the formation a join fabricated in the comment of its line" $ do+      (_, written) <- recorded (\record -> mapM_ record [EvRun Morphing "Φ", EvFiring 1 "L_ф" Morphing ExRoot, EvFabricated 2 (ExMeta "n1", ExFormation [BiTau (AtLabel "к") (ExDispatch ExRoot (AtLabel "ёж"))]) (ExMeta "n2", ExFormation [BiTau (AtLabel "к") (ExDispatch ExRoot (AtLabel "щука"))]) (ExFormation [BiTau (AtLabel "к") (ExFormation [BiLambda (Function "L_ж"), BiTau (AtLabel "on") (ExFormation [BiDelta (BtOne "05")]), BiTau (AtLabel "left") (ExDispatch (ExFormation [BiTau (AtLabel "к") (ExDispatch ExRoot (AtLabel "ёж"))]) (AtLabel "к")), BiTau (AtLabel "right") (ExDispatch (ExFormation [BiTau (AtLabel "к") (ExDispatch ExRoot (AtLabel "щука"))]) (AtLabel "к"))])])])+      last (lines written) `shouldBe` "    fabricated 𝑛·1·1 := 𝑛1·1 and 𝑛2·1  # ⟦ λ ⤍ L_ж, on ↦ 05-:Δ, left ↦ Φ.ёж:к.к, right ↦ Φ.щука:к.к ⟧:к"+    it "spells out in the comment of its line a formation a join fabricated once more" $ do+      (_, written) <- recorded (\record -> mapM_ record (EvRun Morphing "Φ" : EvFiring 1 "L_ф" Morphing ExRoot : replicate 2 (EvFabricated 2 (ExMeta "n1", ExFormation [BiTau (AtLabel "ю") (ExDispatch ExRoot (AtLabel "ёж"))]) (ExMeta "n2", ExFormation [BiTau (AtLabel "ю") (ExDispatch ExRoot (AtLabel "щука"))]) (ExFormation [BiTau (AtLabel "ю") (ExFormation [BiLambda (Function "L_ж"), BiTau (AtLabel "on") (ExFormation [BiDelta (BtOne "3F")]), BiTau (AtLabel "left") (ExDispatch ExRoot (AtLabel "ёж")), BiTau (AtLabel "right") (ExDispatch ExRoot (AtLabel "щука"))])]))))+      last (lines written) `shouldBe` "    fabricated 𝑛·1·2 := 𝑛1·1 and 𝑛2·1  # ⟦ λ ⤍ L_ж, on ↦ 3F-:Δ, left ↦ Φ.ёж, right ↦ Φ.щука ⟧:ю"+    it "writes the formation a join fabricated by its name in a later line" $ do+      (_, written) <- recorded (\record -> mapM_ record [EvRun Morphing "Φ", EvFiring 1 "L_ф" Morphing ExRoot, EvFabricated 2 (ExMeta "n1", ExFormation [BiTau AtPhi (ExDispatch ExRoot (AtLabel "ёж"))]) (ExMeta "n2", ExFormation [BiTau (AtLabel "к") (ExDispatch ExRoot (AtLabel "щука"))]) (ExFormation [BiTau AtPhi (ExFormation [BiLambda (Function "L_ж"), BiTau (AtLabel "on") (ExFormation [BiDelta (BtOne "05")]), BiTau (AtLabel "left") (ExDispatch (ExFormation [BiTau AtPhi (ExDispatch ExRoot (AtLabel "ёж"))]) AtPhi), BiTau (AtLabel "right") (ExDispatch (ExFormation [BiTau (AtLabel "к") (ExDispatch ExRoot (AtLabel "щука"))]) AtPhi)]), BiTau (AtLabel "к") (ExFormation [BiLambda (Function "L_ж"), BiTau (AtLabel "on") (ExFormation [BiDelta (BtOne "05")]), BiTau (AtLabel "left") (ExDispatch (ExFormation [BiTau AtPhi (ExDispatch ExRoot (AtLabel "ёж"))]) (AtLabel "к")), BiTau (AtLabel "right") (ExDispatch (ExFormation [BiTau (AtLabel "к") (ExDispatch ExRoot (AtLabel "щука"))]) (AtLabel "к"))])]), EvTaken 2 [ExMeta "n7"] (ExFormation [BiTau AtPhi (ExFormation [BiLambda (Function "L_ж"), BiTau (AtLabel "on") (ExFormation [BiDelta (BtOne "05")]), BiTau (AtLabel "left") (ExDispatch (ExFormation [BiTau AtPhi (ExDispatch ExRoot (AtLabel "ёж"))]) AtPhi), BiTau (AtLabel "right") (ExDispatch (ExFormation [BiTau (AtLabel "к") (ExDispatch ExRoot (AtLabel "щука"))]) AtPhi)]), BiTau (AtLabel "к") (ExFormation [BiLambda (Function "L_ж"), BiTau (AtLabel "on") (ExFormation [BiDelta (BtOne "05")]), BiTau (AtLabel "left") (ExDispatch (ExFormation [BiTau AtPhi (ExDispatch ExRoot (AtLabel "ёж"))]) (AtLabel "к")), BiTau (AtLabel "right") (ExDispatch (ExFormation [BiTau (AtLabel "к") (ExDispatch ExRoot (AtLabel "щука"))]) (AtLabel "к"))])])])+      last (lines written) `shouldBe` "    𝑛·1·2 := 𝑛·1·1  # 𝑛7"+    it "opens a heading of its own for every run of the protocol" $ do+      (_, written) <- recorded (\record -> mapM_ record [EvRun Morphing "Φ", EvDelta 1 (BtOne "0C"), EvRun Morphing "Φ", EvDelta 1 (BtOne "0D")])+      lines written `shouldBe` ["𝕄(Φ):", "  𝛿·0·1 := 0C-", "𝕄(Φ):", "  𝛿·0·2 := 0D-"]   describe "saveEvalXml" $ do-    it "writes an upcast as an element naming the term a bare symbol was cast to" $ do-      (_, written) <- recordedXml (\record -> mapM_ record [EvRun Morphing "Φ", EvFiring 1 "L_щ" Morphing ExRoot, EvUpcast 2 41 (ExApplication (ExApplication (ExDispatch ExRoot (AtLabel "кортеж")) (ArTau (AtLabel "хвост") (ExFormation [BiLambda (FnSymbol 43)]))) (ArTau AtPhi (ExFormation [BiTau AtRho (ExFormation [BiLambda (FnSymbol 44)])]))) [(43, [AtLabel "хвост"]), (44, [AtPhi, AtRho])]])-      lines written `shouldContain` ["    <upcast symbol=\"𝜎41\" of=\"𝑛·1·2\"><path symbol=\"𝜎43\">хвост</path><path symbol=\"𝜎44\">φ.ρ</path></upcast>"]-    it "names the term an upcast was cast to by an element written before it" $ do-      (_, written) <- recordedXml (\record -> mapM_ record [EvRun Morphing "Φ", EvFiring 1 "L_щ" Morphing ExRoot, EvUpcast 2 41 (ExApplication (ExApplication (ExDispatch ExRoot (AtLabel "кортеж")) (ArTau (AtLabel "хвост") (ExFormation [BiLambda (FnSymbol 43)]))) (ArTau AtPhi (ExFormation [BiTau AtRho (ExFormation [BiLambda (FnSymbol 44)])]))) [(43, [AtLabel "хвост"]), (44, [AtPhi, AtRho])]])-      lines written `shouldContain` ["    <applied meta=\"𝑛·1·2\" of=\"Φ.кортеж\"><attr name=\"хвост\">𝜎43</attr><attr name=\"φ\">𝑛·1·1</attr></applied>"]     it "writes an application as an element naming its head and holding its argument" $ do       (_, written) <- recordedXml (\record -> mapM_ record [EvRun Morphing "Φ.w", EvApplied 1 Morphing (ExApplication (ExDispatch ExRoot (AtLabel "ёж")) (ArTau (AtLabel "q") (ExDispatch ExRoot (AtLabel "ю")))) (ExFormation [BiTau (AtLabel "q") (ExDispatch ExRoot (AtLabel "ю"))]) (ExDispatch ExRoot (AtLabel "w"))])-      lines written `shouldContain` ["  <applied meta=\"𝑛·0·1\" by=\"morph\" at=\"Φ.w\" of=\"Φ.ёж\"><attr name=\"q\">Φ.ю</attr></applied>"]+      lines written `shouldContain` ["  <applied meta=\"𝑛·0·1\" by=\"morph\" at=\"Φ.w\" of=\"Φ.ёж\"><ref attr=\"q\">Φ.ю</ref></applied>"]     it "spells an argument an earlier application made by its name" $ 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, EvApplied 1 Morphing (ExApplication (ExDispatch ExRoot (AtLabel "жук")) (ArTau (AtLabel "w") (ExApplication (ExDispatch ExRoot (AtLabel "ёж")) (ArTau (AtLabel "q") (ExDispatch ExRoot (AtLabel "ю")))))) (ExFormation [BiTau (AtLabel "w") (ExApplication (ExDispatch ExRoot (AtLabel "ёж")) (ArTau (AtLabel "q") (ExDispatch ExRoot (AtLabel "ю"))))]) ExRoot])-      lines written `shouldContain` ["  <applied meta=\"𝑛·0·2\" by=\"morph\" at=\"Φ\" of=\"Φ.жук\"><attr name=\"w\">𝑛·0·1</attr></applied>"]+      lines written `shouldContain` ["  <applied meta=\"𝑛·0·2\" by=\"morph\" at=\"Φ\" of=\"Φ.жук\"><meta attr=\"w\">𝑛·0·1</meta></applied>"]     it "spells an argument that is a bare symbol as that symbol" $ do       (_, written) <- recordedXml (\record -> mapM_ record [EvRun Morphing "Φ", EvApplied 1 Morphing (ExApplication (ExDispatch ExRoot (AtLabel "цапля")) (ArTau AtPhi (ExFormation [BiLambda (FnSymbol 7)]))) (ExFormation [BiTau AtPhi (ExFormation [BiLambda (FnSymbol 7)])]) ExRoot])-      lines written `shouldContain` ["  <applied meta=\"𝑛·0·1\" by=\"morph\" at=\"Φ\" of=\"Φ.цапля\"><attr name=\"φ\">𝜎7</attr></applied>"]+      lines written `shouldContain` ["  <applied meta=\"𝑛·0·1\" by=\"morph\" at=\"Φ\" of=\"Φ.цапля\"><symbol attr=\"φ\">𝜎7</symbol></applied>"]     it "spells a datum an application holds with no tag" $ do       (_, written) <- recordedXml (\record -> mapM_ record [EvRun Morphing "Φ", EvApplied 1 Morphing (ExApplication (ExDispatch ExRoot (AtLabel "байты")) (ArTau AtPhi (ExFormation [BiDelta (BtOne "0C")]))) (ExFormation [BiTau AtPhi (ExFormation [BiDelta (BtOne "0C")])]) ExRoot])-      lines written `shouldContain` ["  <applied meta=\"𝑛·0·1\" by=\"morph\" at=\"Φ\" of=\"Φ.байты\"><attr name=\"φ\">0C-</attr></applied>"]+      lines written `shouldContain` ["  <applied meta=\"𝑛·0·1\" by=\"morph\" at=\"Φ\" of=\"Φ.байты\"><datum attr=\"φ\">0C-</datum></applied>"]+    it "spells bytes an application holds as a datum" $ do+      (_, written) <- recordedXml (\record -> mapM_ record [EvRun Morphing "Φ", EvApplied 1 Morphing (ExApplication (ExDispatch ExRoot (AtLabel "щука")) (ArTau (AtLabel "ё") (ExBytes (BtMany ["7E", "A1"])))) (ExFormation [BiTau (AtLabel "ё") (ExBytes (BtMany ["7E", "A1"]))]) ExRoot])+      lines written `shouldContain` ["  <applied meta=\"𝑛·0·1\" by=\"morph\" at=\"Φ\" of=\"Φ.щука\"><datum attr=\"ё\">7E-A1</datum></applied>"]+    it "writes an argument that is a termination as an empty element of its own" $ do+      (_, written) <- recordedXml (\record -> mapM_ record [EvRun Morphing "Φ", EvApplied 1 Morphing (ExApplication (ExDispatch ExRoot (AtLabel "ёрш")) (ArTau (AtLabel "ц") ExTermination)) (ExFormation [BiTau (AtLabel "ц") ExTermination]) ExRoot])+      lines written `shouldContain` ["  <applied meta=\"𝑛·0·1\" by=\"morph\" at=\"Φ\" of=\"Φ.ёрш\"><bottom attr=\"ц\"/></applied>"]+    it "spells an argument dispatched from a named application as a reference, not a meta" $ do+      (_, written) <- recordedXml (\record -> mapM_ record [EvRun Morphing "Φ", EvApplied 1 Morphing (ExApplication (ExDispatch ExRoot (AtLabel "жаба")) (ArTau (AtLabel "у") (ExDispatch (ExApplication (ExDispatch ExRoot (AtLabel "ил")) (ArTau (AtLabel "в") (ExFormation [BiLambda (FnSymbol 6)]))) (AtLabel "дно")))) (ExFormation []) ExRoot])+      lines written `shouldContain` ["  <applied meta=\"𝑛·0·2\" by=\"morph\" at=\"Φ\" of=\"Φ.жаба\"><ref attr=\"у\">𝑛·0·1.дно</ref></applied>"]     it "names an argument that is an application by an element of its own" $ do       (_, written) <- recordedXml (\record -> mapM_ record [EvRun Morphing "Φ", EvApplied 1 Morphing (ExApplication (ExDispatch ExRoot (AtLabel "цапля")) (ArTau AtPhi (ExApplication (ExDispatch ExRoot (AtLabel "байты")) (ArTau AtPhi (ExFormation [BiDelta (BtOne "0C")]))))) (ExFormation [BiTau AtPhi (ExFormation [BiDelta (BtOne "0C")])]) ExRoot])-      lines written `shouldContain` ["  <applied meta=\"𝑛·0·1\" of=\"Φ.байты\"><attr name=\"φ\">0C-</attr></applied>"]+      lines written `shouldContain` ["  <applied meta=\"𝑛·0·1\" of=\"Φ.байты\"><datum attr=\"φ\">0C-</datum></applied>"]     it "writes an application of two arguments nothing reduced as one element" $ do       (_, written) <- recordedXml (\record -> mapM_ record [EvRun Morphing "Φ", EvAnswer 1 (ExApplication (ExApplication (ExDispatch ExRoot (AtLabel "ёж")) (ArTau (AtLabel "а") (ExFormation [BiLambda (FnSymbol 1)]))) (ArTau (AtLabel "б") (ExFormation [BiLambda (FnSymbol 2)])))])-      lines written `shouldContain` ["  <applied meta=\"𝑛·0·1\" of=\"Φ.ёж\"><attr name=\"а\">𝜎1</attr><attr name=\"б\">𝜎2</attr></applied>"]+      lines written `shouldContain` ["  <applied meta=\"𝑛·0·1\" of=\"Φ.ёж\"><symbol attr=\"а\">𝜎1</symbol><symbol attr=\"б\">𝜎2</symbol></applied>"]     it "writes an answer as the name of the term it holds" $ do       (_, written) <- recordedXml (\record -> mapM_ record [EvRun Morphing "Φ", EvAnswer 1 (ExApplication (ExApplication (ExDispatch ExRoot (AtLabel "ёж")) (ArTau (AtLabel "а") (ExFormation [BiLambda (FnSymbol 1)]))) (ArTau (AtLabel "б") (ExFormation [BiLambda (FnSymbol 2)])))])-      lines written `shouldContain` ["  <answer meta=\"𝑛·0·2\">𝑛·0·1</answer>"]+      lines written `shouldContain` ["  <answered meta=\"𝑛·0·2\">𝑛·0·1</answered>"]     it "writes a term a firing built as the name of the term" $ do       (_, written) <- recordedXml (\record -> mapM_ record [EvRun Morphing "Φ", EvBuilt 1 (ExApplication (ExDispatch ExRoot (AtLabel "ёж")) (ArTau (AtLabel "а") (ExFormation [BiLambda (FnSymbol 1)])))])       lines written `shouldContain` ["  <built meta=\"𝑛·0·2\">𝑛·0·1</built>"]     it "spells an object an application made by its name in a later element" $ do       (_, written) <- recordedXml (\record -> mapM_ record [EvRun Morphing "Φ", EvFiring 1 "L_ы" Morphing ExRoot, EvBuilt 2 (ExDispatch ExRoot (AtLabel "ю")), EvApplied 2 Morphing (ExApplication (ExDispatch ExRoot (AtLabel "ёж")) (ArTau (AtLabel "q") (ExDispatch ExRoot (AtLabel "ю")))) (ExFormation [BiTau (AtLabel "q") (ExDispatch ExRoot (AtLabel "ю"))]) ExRoot, EvAnswer 2 (ExFormation [BiTau (AtLabel "q") (ExDispatch ExRoot (AtLabel "ю"))])])-      lines written `shouldContain` ["    <answer meta=\"𝑛·1·3\">𝑛·1·2</answer>"]+      lines written `shouldContain` ["    <answered meta=\"𝑛·1·3\">𝑛·1·2</answered>"]     it "writes a formation no object of the world made as an element holding its bindings" $ do       (_, written) <- recordedXml (\record -> mapM_ record [EvRun Morphing "Φ", EvAnswer 1 (ExFormation [BiLambda (Function "L_вилка"), BiVoid (AtLabel "лево"), BiTau AtPhi (ExFormation [BiLambda (FnSymbol 5)])])])-      lines written `shouldContain` ["  <formed meta=\"𝑛·0·1\" λ=\"L_вилка\"><void name=\"лево\"/><attr name=\"φ\">𝜎5</attr></formed>"]+      lines written `shouldContain` ["  <formed meta=\"𝑛·0·1\" λ=\"L_вилка\"><void attr=\"лево\"/><symbol attr=\"φ\">𝜎5</symbol></formed>"]     it "names a formation that copies an object of the world by the object and the attributes it binds" $ do       (_, written) <- recordedXml (\record -> mapM_ record [EvRun Morphing "Φ", EvWorld (ExFormation [BiTau (AtLabel "ёж") (ExFormation [BiVoid (AtLabel "q"), BiTau (AtLabel "w") ExRoot])]), EvAnswer 1 (ExFormation [BiTau (AtLabel "q") (ExFormation [BiLambda (FnSymbol 3)]), BiTau (AtLabel "w") ExRoot])])-      lines written `shouldContain` ["  <formed meta=\"𝑛·0·1\" of=\"Φ.ёж\"><attr name=\"q\">𝜎3</attr></formed>"]+      lines written `shouldContain` ["  <formed meta=\"𝑛·0·1\" of=\"Φ.ёж\"><symbol attr=\"q\">𝜎3</symbol></formed>"]     it "names a formation that replaced an attribute of an object of the world by the object and that attribute" $ do       (_, written) <- recordedXml (\record -> mapM_ record [EvRun Morphing "Φ", EvWorld (ExFormation [BiTau (AtLabel "ёж") (ExFormation [BiVoid (AtLabel "q"), BiTau AtPhi (ExDispatch ExXi (AtLabel "q")), BiTau (AtLabel "w") ExRoot])]), EvAnswer 1 (ExFormation [BiTau (AtLabel "q") (ExFormation [BiLambda (FnSymbol 3)]), BiTau AtPhi (ExFormation [BiLambda (FnSymbol 5)]), BiTau (AtLabel "w") ExRoot])])-      lines written `shouldContain` ["  <formed meta=\"𝑛·0·1\" of=\"Φ.ёж\"><attr name=\"q\">𝜎3</attr><attr name=\"φ\">𝜎5</attr></formed>"]+      lines written `shouldContain` ["  <formed meta=\"𝑛·0·1\" of=\"Φ.ёж\"><symbol attr=\"q\">𝜎3</symbol><symbol attr=\"φ\">𝜎5</symbol></formed>"]     it "writes a formation that keeps no binding of an object of the world as a formation of its own" $ do       (_, written) <- recordedXml (\record -> mapM_ record [EvRun Morphing "Φ", EvWorld (ExFormation [BiTau (AtLabel "ёж") (ExFormation [BiVoid AtPhi])]), EvAnswer 1 (ExFormation [BiTau AtPhi (ExFormation [BiLambda (FnSymbol 8)])])])-      lines written `shouldContain` ["  <formed meta=\"𝑛·0·1\"><attr name=\"φ\">𝜎8</attr></formed>"]+      lines written `shouldContain` ["  <formed meta=\"𝑛·0·1\"><symbol attr=\"φ\">𝜎8</symbol></formed>"]     it "spells a formation that is an object of the world by its path" $ do       (_, written) <- recordedXml (\record -> mapM_ record [EvRun Morphing "Φ", EvWorld (ExFormation [BiTau (AtLabel "ёж") (ExFormation [BiVoid (AtLabel "q"), BiTau (AtLabel "w") ExRoot])]), EvAnswer 1 (ExFormation [BiVoid (AtLabel "q"), BiTau (AtLabel "w") ExRoot])])-      lines written `shouldContain` ["  <answer meta=\"𝑛·0·1\">Φ.ёж</answer>"]+      lines written `shouldContain` ["  <answered meta=\"𝑛·0·1\">Φ.ёж</answered>"]     it "names a formation that copies an object nested in the world by its path" $ do       (_, written) <- recordedXml (\record -> mapM_ record [EvRun Morphing "Φ", EvWorld (ExFormation [BiTau (AtLabel "лес") (ExFormation [BiTau (AtLabel "ёж") (ExFormation [BiVoid (AtLabel "q"), BiLambda (Function "L_ёж")])])]), EvAnswer 1 (ExFormation [BiTau (AtLabel "q") (ExFormation [BiLambda (FnSymbol 7)]), BiLambda (Function "L_ёж")])])-      lines written `shouldContain` ["  <formed meta=\"𝑛·0·1\" of=\"Φ.лес.ёж\"><attr name=\"q\">𝜎7</attr></formed>"]+      lines written `shouldContain` ["  <formed meta=\"𝑛·0·1\" of=\"Φ.лес.ёж\"><symbol attr=\"q\">𝜎7</symbol></formed>"]     it "names a method of a copy by the copy it is dispatched from" $ do       (_, written) <- recordedXml (\record -> mapM_ record [EvRun Morphing "Φ", EvWorld (ExFormation [BiTau (AtLabel "ёж") (ExFormation [BiVoid (AtLabel "q"), BiTau (AtLabel "м") (ExFormation [BiVoid AtRho, BiVoid (AtLabel "z")])])]), EvAnswer 1 (ExFormation [BiTau AtRho (ExApplication (ExDispatch ExRoot (AtLabel "ёж")) (ArTau (AtLabel "q") (ExFormation [BiLambda (FnSymbol 3)]))), BiTau (AtLabel "z") (ExFormation [BiLambda (FnSymbol 4)])])])-      lines written `shouldContain` ["  <formed meta=\"𝑛·0·2\" of=\"𝑛·0·1.м\"><attr name=\"z\">𝜎4</attr></formed>"]+      lines written `shouldContain` ["  <formed meta=\"𝑛·0·2\" of=\"𝑛·0·1.м\"><symbol attr=\"z\">𝜎4</symbol></formed>"]     it "writes nothing for the world it is told" $ do       (_, written) <- recordedXml (\record -> mapM_ record [EvRun Morphing "Φ", EvWorld (ExFormation [BiTau (AtLabel "ёж") (ExFormation [])])])       filter (isInfixOf "ёж") (lines written) `shouldBe` []     it "writes the datum the delta rule found as an element with its name" $ do       (_, written) <- recordedXml (\record -> mapM_ record [EvRun Dataization "Φ", EvDelta 1 (BtMany ["0C", "D4"])])-      lines written `shouldContain` ["  <delta meta=\"𝛿·0·1\">0C-D4</delta>"]-    it "writes the double a datum of eight bytes holds as the number of its delta element" $ do+      lines written `shouldContain` ["  <dataized meta=\"𝛿·0·1\">0C-D4</dataized>"]+    it "writes the double a datum of eight bytes holds as the number of its dataized element" $ do       (_, written) <- recordedXml (\record -> mapM_ record [EvRun Dataization "Φ", EvDelta 1 (BtMany ["C0", "09", "1E", "B8", "51", "EB", "85", "1F"])])-      lines written `shouldContain` ["  <delta meta=\"𝛿·0·1\" number=\"-3.14\">C0-09-1E-B8-51-EB-85-1F</delta>"]+      lines written `shouldContain` ["  <dataized meta=\"𝛿·0·1\" number=\"-3.14\">C0-09-1E-B8-51-EB-85-1F</dataized>"]     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 the symbol a dataization stood on with no tag" $ do-      (_, written) <- recordedXml (\record -> mapM_ record [EvRun Dataization "Φ", EvFiring 1 "L_ж" Dataization ExRoot, EvData 2 "𝛿1" (ExDispatch ExXi (AtLabel "щ")) (Left 32)])-      lines written `shouldContain` ["    <dataize meta=\"𝛿1·1\">𝜎32</dataize>"]+      lines written `shouldContain` ["    <bound meta=\"𝛿1·1\">𝛿·1·1</bound>"]+    it "binds the term a stay line dropped a ρ from in the markup as it binds a term passed through" $ do+      (_, stayed) <- recordedXml (\record -> mapM_ record [EvRun Morphing "Φ", EvFiring 1 "L_ж" Morphing ExRoot, EvReshaped 2 "𝑛3" "stay" (ExMeta "n1") (ExFormation [BiLambda (FnSymbol 3)])])+      (_, passed) <- recordedXml (\record -> mapM_ record [EvRun Morphing "Φ", EvFiring 1 "L_ж" Morphing ExRoot, EvBound 2 "𝑛3" (ExMeta "n1") (ExFormation [BiLambda (FnSymbol 3)])])+      stayed `shouldBe` passed+    it "writes the term a line bound as a bound element in the markup" $ do+      (_, written) <- recordedXml (\record -> mapM_ record [EvRun Morphing "Φ", EvFiring 1 "L_ж" Morphing ExRoot, EvBound 2 "𝑛3" (ExMeta "n1") (ExDispatch ExRoot (AtLabel "щ"))])+      lines written `shouldContain` ["    <bound meta=\"𝑛3·1\">Φ.щ</bound>"]+    it "writes the term a morphing came to as an element with its name" $ do+      (_, written) <- recordedXml (\record -> mapM_ record [EvRun Morphing "Φ", EvMorphed 1 (ExDispatch ExRoot (AtLabel "щука"))])+      lines written `shouldContain` ["  <morphed meta=\"𝑛·0·1\">Φ.щука</morphed>"]+    it "writes a morphing that came to a termination as an element holding it" $ do+      (_, written) <- recordedXml (\record -> mapM_ record [EvRun Morphing "Φ", EvMorphed 1 ExTermination])+      lines written `shouldContain` ["  <morphed meta=\"𝑛·0·1\">⊥</morphed>"]+    it "closes a morph element with the term the morphing came to" $ do+      (_, written) <- recordedXml (\record -> mapM_ record [EvRun Dataization "Φ", EvStarted 1 Morphing (ExDispatch ExRoot (AtLabel "жук")), EvMorphed 2 (ExDispatch ExRoot (AtLabel "ёрш")), EvDelta 1 (BtOne "0D")])+      lines written `shouldContain` ["    <morphed meta=\"𝑛·0·1\">Φ.ёрш</morphed>", "  </morph>"]+    it "writes the symbol a dataization came down to as an element with its name" $ do+      (_, written) <- recordedXml (\record -> mapM_ record [EvRun Dataization "Φ", EvManufactured 1 12])+      lines written `shouldContain` ["  <manufactured meta=\"𝛿·0·1\">𝜎12</manufactured>"]+    it "counts the symbols a dataization came down to with the data the delta rule found" $ do+      (_, written) <- recordedXml (\record -> mapM_ record [EvRun Dataization "Φ", EvFiring 1 "L_ж" Dataization ExRoot, EvDelta 3 (BtOne "7F"), EvManufactured 3 32])+      lines written `shouldContain` ["      <manufactured meta=\"𝛿·1·2\">𝜎32</manufactured>"]+    it "spells the symbol of an operand by the name the dataization just gave it in the markup" $ do+      (_, written) <- recordedXml (\record -> mapM_ record [EvRun Dataization "Φ", EvFiring 1 "L_ж" Dataization ExRoot, EvManufactured 3 32, EvData 2 "𝛿1" (ExDispatch ExXi (AtLabel "щ")) (Left 32)])+      lines written `shouldContain` ["    <bound meta=\"𝛿1·1\">𝛿·1·1</bound>"]+    it "spells the symbol of an operand in full in the markup when the element before it named another symbol" $ do+      (_, written) <- recordedXml (\record -> mapM_ record [EvRun Dataization "Φ", EvFiring 1 "L_ж" Dataization ExRoot, EvManufactured 3 31, EvData 2 "𝛿1" (ExDispatch ExXi (AtLabel "щ")) (Left 32)])+      lines written `shouldContain` ["    <bound meta=\"𝛿1·1\">𝜎32</bound>"]     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\">"]@@ -296,61 +371,76 @@       filter (isInfixOf "morph") (lines written) `shouldBe` []     it "names the site of a block that is an application by an element written before the block" $ do       (_, written) <- recordedXml (\record -> mapM_ record [EvRun Dataization "Φ", EvStarted 1 Morphing (ExApplication (ExDispatch ExRoot (AtLabel "ж")) (ArTau (AtLabel "q") (ExFormation [BiLambda (FnSymbol 1)]))), EvDelta 2 (BtOne "0C")])-      lines written `shouldContain` ["  <applied meta=\"𝑛·0·1\" of=\"Φ.ж\"><attr name=\"q\">𝜎1</attr></applied>", "  <morph at=\"𝑛·0·1\">"]+      lines written `shouldContain` ["  <applied meta=\"𝑛·0·1\" of=\"Φ.ж\"><symbol attr=\"q\">𝜎1</symbol></applied>", "  <morph at=\"𝑛·0·1\">"]     it "names nothing for the site of a block nothing stands in" $ do       (_, written) <- recordedXml (\record -> mapM_ record [EvRun Dataization "Φ", EvStarted 1 Morphing (ExApplication (ExDispatch ExRoot (AtLabel "ж")) (ArTau (AtLabel "q") (ExFormation [BiLambda (FnSymbol 1)]))), EvDelta 1 (BtOne "0C")])       filter (isInfixOf "applied") (lines written) `shouldBe` []     it "names the site of a firing that is an application by an element written before the firing" $ do       (_, written) <- recordedXml (\record -> mapM_ record [EvRun Morphing "Φ", EvFiring 1 "L_ж" Morphing (ExApplication (ExDispatch ExRoot (AtLabel "ж")) (ArTau (AtLabel "q") (ExFormation [BiLambda (FnSymbol 1)])))])-      lines written `shouldContain` ["  <applied meta=\"𝑛·0·1\" of=\"Φ.ж\"><attr name=\"q\">𝜎1</attr></applied>", "  <evaluate λ=\"L_ж\" by=\"morph\" at=\"𝑛·0·1\">"]+      lines written `shouldContain` ["  <applied meta=\"𝑛·0·1\" of=\"Φ.ж\"><symbol attr=\"q\">𝜎1</symbol></applied>", "  <evaluate λ=\"L_ж\" by=\"morph\" at=\"𝑛·0·1\">"]     it "writes one element for two blocks of one judgment at one site in a row" $ 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")), 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 (isInfixOf "<morph") (lines written)) `shouldBe` 1     it "closes a block element before the element that follows beside 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")), EvDelta 1 (BtOne "0D")])-      lines written `shouldContain` ["  </morph>", "  <delta meta=\"𝛿·0·1\">0D-</delta>"]+      lines written `shouldContain` ["  </morph>", "  <dataized meta=\"𝛿·0·1\">0D-</dataized>"]     it "names the element over an answer by the element that built it" $ do       (_, written) <- recordedXml (\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 "ю"))])       lines written `shouldContain` ["    <morph at=\"𝑛·1·1\">"]-    it "names the message a fork raises with by the name its application gave it in the markup" $ do-      (_, written) <- recordedXml (\record -> mapM_ record [EvRun Morphing "Φ", 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, EvTerminate 2 (Just (Left 7)) "right" "𝑛4" (Just (ExFormation [BiTau (AtLabel "q") (ExDispatch ExRoot (AtLabel "ю"))]))])-      lines written `shouldContain` ["    <terminate symbol=\"𝜎7\" branch=\"right\" message=\"𝑛·1·1\"/>"]-    it "writes the message a fork raises with beside the datum of its condition" $ do-      (_, written) <- recordedXml (\record -> mapM_ record [EvRun Morphing "Φ", EvTerminate 1 (Just (Right (BtOne "0D"))) "left" "𝑛3" (Just (ExDispatch ExRoot (AtLabel "щука")))])-      lines written `shouldContain` ["  <terminate branch=\"left\" message=\"Φ.щука\">0D-</terminate>"]-    it "names the message a fork raises with that is an application by an element of its own" $ do-      (_, written) <- recordedXml (\record -> mapM_ record [EvRun Morphing "Φ", EvTerminate 1 (Just (Right (BtOne "0D"))) "left" "𝑛3" (Just (ExApplication (ExDispatch ExRoot (AtLabel "щука")) (ArTau (AtLabel "q") (ExFormation [BiLambda (FnSymbol 2)]))))])-      lines written `shouldContain` ["  <terminate branch=\"left\" message=\"𝑛·0·1\">0D-</terminate>"]     it "writes a deferred copy as an element naming its object and the symbols of its arguments" $ do       (_, written) <- recordedXml (\record -> mapM_ record [EvRun Morphing "Φ", EvDeferred 1 9 Morphing (ExFormation [BiTau (AtLabel "щ") (ExFormation [BiLambda (FnSymbol 4)]), BiTau AtPhi (ExDispatch (ExDispatch ExXi (AtLabel "щ")) (AtLabel "й"))]) (Just (ExApplication (ExDispatch ExRoot (AtLabel "ёж")) (ArTau (AtLabel "щ") (ExFormation [BiLambda (FnSymbol 4)])))) ExRoot])-      lines written `shouldContain` ["  <deferred symbol=\"𝜎9\" by=\"morph\" at=\"Φ\" of=\"Φ.ёж\"><with><attr name=\"щ\">𝜎4</attr></with></deferred>"]+      lines written `shouldContain` ["  <deferred symbol=\"𝜎9\" by=\"morph\" at=\"Φ\" of=\"Φ.ёж\"><with><symbol attr=\"щ\">𝜎4</symbol></with></deferred>"]     it "writes a deferred copy that names no call as an element naming the copy" $ do       (_, written) <- recordedXml (\record -> mapM_ record [EvRun Morphing "Φ", EvDeferred 1 9 Morphing (ExFormation [BiTau (AtLabel "щ") (ExFormation [BiLambda (FnSymbol 4)])]) Nothing ExRoot])       lines written `shouldContain` ["  <deferred symbol=\"𝜎9\" by=\"morph\" at=\"Φ\" of=\"𝑛·0·1\"/>"]     it "names an argument of a copy nothing worked out by an expression meta" $ do       (_, written) <- recordedXml (\record -> mapM_ record [EvRun Morphing "Φ", EvDeferred 1 9 Morphing (ExFormation []) (Just (ExApplication (ExDispatch ExRoot (AtLabel "ёж")) (ArTau (AtLabel "щ") (ExApplication (ExDispatch ExRoot (AtLabel "ю")) (ArTau (AtLabel "ж") (ExFormation [BiLambda (FnSymbol 2)])))))) ExRoot])-      lines written `shouldSatisfy` any (isInfixOf "<attr name=\"щ\">𝑒1</attr>")+      lines written `shouldSatisfy` any (isInfixOf "<meta attr=\"щ\">𝑒1</meta>")     it "names one argument nothing worked out by one expression meta in two elements" $ do       (_, written) <- recordedXml (\record -> mapM_ record [EvRun Morphing "Φ", EvDeferred 1 9 Morphing (ExFormation []) (Just (ExApplication (ExDispatch ExRoot (AtLabel "ёж")) (ArTau (AtLabel "щ") (ExApplication (ExDispatch ExRoot (AtLabel "ю")) (ArTau (AtLabel "ж") (ExFormation [BiLambda (FnSymbol 2)])))))) ExRoot, EvDeferred 1 10 Morphing (ExFormation []) (Just (ExApplication (ExDispatch ExRoot (AtLabel "ёрш")) (ArTau (AtLabel "ц") (ExApplication (ExDispatch ExRoot (AtLabel "ю")) (ArTau (AtLabel "ж") (ExFormation [BiLambda (FnSymbol 2)])))))) ExRoot])       length (filter (isInfixOf ">𝑒1<") (lines written)) `shouldBe` 2-    it "writes a cut that answered a copy as an element naming its symbol, its object and the symbols of its arguments" $ do-      (_, written) <- recordedXml (\record -> mapM_ record [EvRun Morphing "Φ", EvLooped 1 Morphing Proven (ExFormation [BiTau (AtLabel "ъ") ExRoot]) ExRoot (Just (13, Just (ExApplication (ExDispatch ExRoot (AtLabel "ёрш")) (ArTau (AtLabel "ц") (ExFormation [BiLambda (FnSymbol 6)])))))])-      lines written `shouldContain` ["  <looped symbol=\"𝜎13\" by=\"morph\" match=\"proven\" at=\"Φ\" of=\"Φ.ёрш\"><with><attr name=\"ц\">𝜎6</attr></with></looped>"]     it "writes a cut that answered no copy as an element naming the formation it entered" $ do-      (_, written) <- recordedXml (\record -> mapM_ record [EvRun Morphing "Φ", EvLooped 1 Morphing Proven (ExFormation [BiTau (AtLabel "ъ") ExRoot]) ExRoot Nothing])+      (_, written) <- recordedXml (\record -> mapM_ record [EvRun Morphing "Φ", EvLooped 1 Morphing Proven (ExFormation [BiTau (AtLabel "ъ") ExRoot]) ExRoot])       lines written `shouldContain` ["  <looped by=\"morph\" match=\"proven\" at=\"Φ\" of=\"𝑛·0·1\"/>"]-    it "names an argument of a copy a cut answered by the application that made it" $ do-      (_, written) <- recordedXml (\record -> mapM_ record [EvRun Morphing "Φ", EvApplied 1 Morphing (ExApplication (ExDispatch ExRoot (AtLabel "жаба")) (ArTau AtPhi (ExFormation [BiLambda (FnSymbol 17)]))) (ExFormation [BiTau AtPhi (ExFormation [BiLambda (FnSymbol 17)])]) ExRoot, EvLooped 1 Morphing Plausible (ExFormation [BiTau (AtLabel "ъ") ExRoot]) ExRoot (Just (23, Just (ExApplication (ExDispatch ExRoot (AtLabel "ёрш")) (ArTau (AtLabel "ц") (ExFormation [BiTau AtPhi (ExFormation [BiLambda (FnSymbol 17)])])))))])-      lines written `shouldContain` ["  <looped symbol=\"𝜎23\" by=\"morph\" match=\"plausible\" at=\"Φ\" of=\"Φ.ёрш\"><with><attr name=\"ц\">𝑛·0·1</attr></with></looped>"]     it "names an argument of a deferred copy by the application that made it" $ do       (_, written) <- recordedXml (\record -> mapM_ record [EvRun Morphing "Φ", EvApplied 1 Morphing (ExApplication (ExDispatch ExRoot (AtLabel "кулик")) (ArTau (AtLabel "ш") ExRoot)) (ExFormation [BiTau (AtLabel "ш") ExRoot]) ExRoot, EvDeferred 1 31 Morphing (ExFormation [BiTau (AtLabel "щ") (ExFormation [BiTau (AtLabel "ш") ExRoot]), BiTau AtPhi (ExDispatch ExXi (AtLabel "щ"))]) (Just (ExApplication (ExDispatch ExRoot (AtLabel "ёж")) (ArTau (AtLabel "щ") (ExFormation [BiTau (AtLabel "ш") ExRoot])))) ExRoot])-      lines written `shouldContain` ["  <deferred symbol=\"𝜎31\" by=\"morph\" at=\"Φ\" of=\"Φ.ёж\"><with><attr name=\"щ\">𝑛·0·1</attr></with></deferred>"]-    it "writes a join that failed as an element naming the two branches it could not pair" $ do-      (_, written) <- recordedXml (\record -> mapM_ record [EvRun Morphing "Φ", EvFiring 1 "L_щ" Morphing ExRoot, EvUnjoined 2 "L_щ" (ExFormation [BiLambda (FnSymbol 5)]) (ExFormation [BiTau (AtLabel "ю") (ExFormation [BiLambda (FnSymbol 8)]), BiTau (AtLabel "ц") ExRoot])])-      lines written `shouldContain` ["    <unjoined λ=\"L_щ\">𝜎5 𝑛·1·1</unjoined>"]-    it "writes a remint that failed as an element naming the term it found no symbol for" $ do-      (_, written) <- recordedXml (\record -> mapM_ record [EvRun Morphing "Φ", EvFiring 1 "L_ёж" Morphing ExRoot, EvUnreminted 2 "L_ёж" (ExFormation [BiTau (AtLabel "жук") (ExFormation [BiLambda (FnSymbol 17)]), BiTau (AtLabel "ф") ExRoot])])-      lines written `shouldContain` ["    <unreminted λ=\"L_ёж\">𝑛·1·1</unreminted>"]+      lines written `shouldContain` ["  <deferred symbol=\"𝜎31\" by=\"morph\" at=\"Φ\" of=\"Φ.ёж\"><with><meta attr=\"щ\">𝑛·0·1</meta></with></deferred>"]+    it "writes the term a join came to as the answer of its firing in the markup" $ do+      (_, written) <- recordedXml (\record -> mapM_ record [EvRun Morphing "Φ", EvFiring 1 "L_ф" Morphing ExRoot, EvTaken 2 [ExDispatch ExXi (AtLabel "then")] (ExFormation [BiLambda (FnSymbol 3)])])+      lines written `shouldContain` ["    <answered meta=\"𝑛·1·1\">𝜎3</answered>"]+    it "writes the symbol a join minted as an element naming the meta its condition was bound to and the two it stands for" $ do+      (_, written) <- recordedXml (\record -> mapM_ record [EvRun Morphing "Φ", EvFiring 1 "L_ф" Morphing ExRoot, EvJoined 2 (Left "𝛿3") 12 (5, 9)])+      lines written `shouldContain` ["    <joined symbol=\"𝜎12\" on=\"𝛿3·1\" left=\"𝜎5\" right=\"𝜎9\"/>"]+    it "writes the symbol a join minted as an element naming the path its condition was read off" $ do+      (_, written) <- recordedXml (\record -> mapM_ record [EvRun Morphing "Φ", EvFiring 1 "L_ф" Morphing ExRoot, EvJoined 2 (Right (ExDispatch ExXi (AtLabel "щит"))) 12 (5, 9)])+      lines written `shouldContain` ["    <joined symbol=\"𝜎12\" on=\"ξ.щит\" left=\"𝜎5\" right=\"𝜎9\"/>"]+    it "writes two sides a join could not join as an element naming both" $ do+      (_, written) <- recordedXml (\record -> mapM_ record [EvRun Morphing "Φ", 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, EvApplied 2 Morphing (ExApplication (ExDispatch ExRoot (AtLabel "жук")) (ArTau (AtLabel "w") (ExDispatch ExRoot (AtLabel "ю")))) (ExFormation [BiTau (AtLabel "w") (ExDispatch ExRoot (AtLabel "ю"))]) ExRoot, EvDiverged 2 (ExDispatch ExXi (AtLabel "then"), ExFormation [BiTau (AtLabel "q") (ExDispatch ExRoot (AtLabel "ю"))]) (ExDispatch ExXi (AtLabel "else"), ExFormation [BiTau (AtLabel "w") (ExDispatch ExRoot (AtLabel "ю"))]) [AtLabel "q"] Unnamed])+      lines written `shouldContain` ["    <diverged left=\"𝑛·1·1\" right=\"𝑛·1·2\" at=\".q\" reason=\"the join names no otherwise\"/>"]+    it "writes a side of a join that is a path as that path in the markup" $ do+      (_, written) <- recordedXml (\record -> mapM_ record [EvRun Morphing "Φ", EvFiring 1 "L_ф" Morphing ExRoot, EvDiverged 2 (ExDispatch ExXi (AtLabel "a"), ExDispatch ExRoot (AtLabel "ёж")) (ExDispatch ExXi (AtLabel "b"), ExDispatch ExRoot (AtLabel "щука")) [] (Unformed Righthand)])+      lines written `shouldContain` ["    <diverged left=\"Φ.ёж\" right=\"Φ.щука\" reason=\"the right side is no formation\"/>"]+    it "writes a side of a join read from a meta by the meta of its line in the markup" $ do+      (_, written) <- recordedXml (\record -> mapM_ record [EvRun Morphing "Φ", EvFiring 1 "L_ф" Morphing ExRoot, EvDiverged 2 (ExMeta "n3", ExFormation [BiTau AtPhi (ExFormation [BiDelta (BtOne "1F")])]) (ExMeta "n4", ExFormation [BiTau AtPhi (ExFormation [BiDelta (BtOne "2E")])]) [AtPhi, AtDelta] Unnamed])+      lines written `shouldContain` ["    <diverged left=\"𝑛3·1\" right=\"𝑛4·1\" at=\".φ.Δ\" reason=\"the join names no otherwise\"/>"]+    it "writes where the two sides of a join differ and why no formation was fabricated of them in the markup" $ do+      (_, written) <- recordedXml (\record -> mapM_ record [EvRun Morphing "Φ", EvFiring 1 "L_ф" Morphing ExRoot, EvDiverged 2 (ExMeta "n5", ExFormation [BiLambda (FnSymbol 8)]) (ExMeta "n6", ExFormation [BiTau AtPhi (ExDispatch ExRoot (AtLabel "ёрш"))]) [AtLambda] (Holding Lefthand AtLambda)])+      lines written `shouldContain` ["    <diverged left=\"𝜎8\" right=\"𝑛6·1\" at=\".λ\" reason=\"the left side has λ\"/>"]+    it "writes the formation a join fabricated as an element naming both sides in the markup" $ do+      (_, written) <- recordedXml (\record -> mapM_ record [EvRun Morphing "Φ", EvFiring 1 "L_ф" Morphing ExRoot, EvFabricated 2 (ExMeta "n3", ExFormation [BiTau AtPhi (ExDispatch ExRoot (AtLabel "ёж"))]) (ExMeta "n4", ExFormation [BiTau (AtLabel "к") (ExDispatch ExRoot (AtLabel "щука"))]) (ExFormation [BiTau AtPhi (ExFormation [BiLambda (Function "L_ж"), BiTau (AtLabel "on") (ExFormation [BiDelta (BtOne "05")]), BiTau (AtLabel "left") (ExDispatch (ExFormation [BiTau AtPhi (ExDispatch ExRoot (AtLabel "ёж"))]) AtPhi), BiTau (AtLabel "right") (ExDispatch (ExFormation [BiTau (AtLabel "к") (ExDispatch ExRoot (AtLabel "щука"))]) AtPhi)]), BiTau (AtLabel "к") (ExFormation [BiLambda (Function "L_ж"), BiTau (AtLabel "on") (ExFormation [BiDelta (BtOne "05")]), BiTau (AtLabel "left") (ExDispatch (ExFormation [BiTau AtPhi (ExDispatch ExRoot (AtLabel "ёж"))]) (AtLabel "к")), BiTau (AtLabel "right") (ExDispatch (ExFormation [BiTau (AtLabel "к") (ExDispatch ExRoot (AtLabel "щука"))]) (AtLabel "к"))])])])+      lines written `shouldContain` ["    <fabricated meta=\"𝑛·1·6\" left=\"𝑛3·1\" right=\"𝑛4·1\">𝑛·1·5</fabricated>"]+    it "writes the symbol a symbolize line minted as an element holding the datum it stands for" $ do+      (_, written) <- recordedXml (\record -> mapM_ record [EvRun Morphing "Φ", EvFiring 1 "L_ф" Morphing ExRoot, EvSymbolized 2 6 (BtOne "4A")])+      lines written `shouldContain` ["    <symbolized symbol=\"𝜎6\">4A-</symbolized>"]+    it "writes the double eight bytes hold as the number of the element of the symbol a symbolize line minted" $ do+      (_, written) <- recordedXml (\record -> mapM_ record [EvRun Morphing "Φ", EvFiring 1 "L_ф" Morphing ExRoot, EvSymbolized 2 6 (BtMany ["C0", "09", "1E", "B8", "51", "EB", "85", "1F"])])+      lines written `shouldContain` ["    <symbolized symbol=\"𝜎6\" number=\"-3.14\">C0-09-1E-B8-51-EB-85-1F</symbolized>"]+    it "wraps the run in no path element" $ do+      (_, written) <- recordedWhole (\record -> mapM_ record [EvRun Morphing "Φ", EvDelta 1 (BtOne "0C")])+      filter (isInfixOf "path>") (lines written) `shouldBe` []+    it "indents the elements of a run inside the root" $ do+      (_, written) <- recordedWhole (\record -> mapM_ record [EvRun Morphing "Φ", EvDelta 1 (BtOne "0C")])+      filter (isInfixOf "morph") (lines written) `shouldBe` ["  <morph at=\"Φ\">", "  </morph>"]     it "writes a firing no entry answered as an element naming the term it got stuck on" $ do       (_, written) <- recordedXml (\record -> mapM_ record [EvRun Morphing "Φ", EvFiring 1 "L_щ" Morphing ExRoot, EvStuck 2 "L_щ" Morphing (ExFormation [BiLambda (FnSymbol 120)])])       lines written `shouldContain` ["    <unanswered λ=\"L_щ\" by=\"morph\">𝜎120</unanswered>"]@@ -381,7 +471,7 @@       began <- getMonotonicTime       withFile path WriteMode (\handle -> endEval handle cursor began)       content <- readUtf8 path-      map (takeWhile (/= '(')) (lines content) `shouldBe` ["msec", "firings", "fps"]+      map (takeWhile (/= ' ')) (lines content) `shouldBe` ["msec", "firings", "fps"]      it "names the firings of the run it closes" $ withScratchDir $ \dir -> do       let path = dir </> "protocol.txt"@@ -390,7 +480,7 @@       began <- getMonotonicTime       withFile path WriteMode (\handle -> endEval handle cursor began)       content <- readUtf8 path-      lines content `shouldSatisfy` elem "firings(5)"+      lines content `shouldSatisfy` elem "firings 5"    describe "endEvalXml" $ do     it "writes nothing once a run that never opened the protocol closes" $ withScratchDir $ \dir -> do
test/EvaluateSpec.hs view
@@ -133,7 +133,7 @@         let stuck = ctx{_saveEval = record}             fire = execBuildTerm univ stuck "evaluate" [ArgExpression (ExFormation [BiLambda (FnSymbol 1)]), ArgExpression univ] substEmpty         fire `shouldThrow` (\e -> "No entry of --symbolic answers the λ function '𝜎1'" `isInfixOf` show (e :: SomeException))-      written `shouldBe` "  unanswered(𝜎1)  # 𝕄(𝜎1:λ)\n"+      written `shouldBe` "  unanswered 𝜎1  # 𝕄(𝜎1:λ)\n"      it "throws when the formation carries more than one λ binding" $       runEvaluate [ArgExpression (ExFormation [BiLambda (Function "L_one"), BiLambda (Function "L_two")]), ArgExpression univ]
test/FilterSpec.hs view
@@ -46,7 +46,7 @@           included <- traverse (parseExpressionThrows' MONOFORM) shown           excluded <- traverse (parseExpressionThrows' MONOFORM) hidden           res <- parseExpressionThrows' MONOFORM result-          [(expr', _)] <- F.include [(expr, Nothing)] included >>= (`F.exclude` excluded)+          [(expr', _)] <- F.exclude [(expr, Nothing)] excluded >>= (`F.include` included)           expr' `shouldBe` res       ) 
test/Fixtures.hs view
@@ -16,6 +16,7 @@   , readUtf8   , recorded   , recorded'+  , recordedWhole   , recordedXml   , withLambdas   , withLambdasOf@@ -122,6 +123,13 @@     written <- readProtocol path     pure (answer, written) +recordedWhole :: (SaveEvalFunc -> IO a) -> IO (a, String)+recordedWhole action =+  withTemp "phino-protocol-.xml" BS.empty $ \path -> do+    answer <- withEvalFunc (Just path) (printing False) action+    written <- readUtf8 path+    pure (answer, written)+ printing :: Bool -> PrintContext printing hidden =   PrintCtx@@ -174,9 +182,9 @@ withoutTotals :: String -> String withoutTotals text   | [msec, firings, fps] <- drop (length ls - 3) ls-  , "msec(" `isPrefixOf` msec-  , "firings(" `isPrefixOf` firings-  , "fps(" `isPrefixOf` fps =+  , "msec " `isPrefixOf` msec+  , "firings " `isPrefixOf` firings+  , "fps " `isPrefixOf` fps =       unlines (take (length ls - 3) ls)   | otherwise = text   where
+ test/ForksSpec.hs view
@@ -0,0 +1,119 @@+{-# LANGUAGE OverloadedStrings #-}++-- SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com+-- SPDX-License-Identifier: MIT++module ForksSpec (spec) where++import AST+import Data.Text (Text)+import Forks (Refusal (..), Side (..), forked, joined)+import Parser (parseExpressionThrows)+import Test.Hspec (Spec, describe, it, shouldBe)++spec :: Spec+spec = do+  describe "joined" $ do+    let application :: [Int] -> Expression+        application idxs = ExApplication (ExDispatch ExRoot (AtLabel "f")) (ArTau (AtLabel "x") (ExFormation (map (\idx -> BiTau (AtLabel "k") (ExFormation [BiLambda (FnSymbol idx)])) idxs)))+    it "joins two terms that differ in one symbol under a fresh symbol past the count" $+      joined (application [3]) (application [8]) 11 `shouldBe` Right (application [12], [(12, (3, 8))], 12)+    it "names a pair of symbols met twice by one fresh symbol" $+      joined (application [4, 4]) (application [9, 9]) 17 `shouldBe` Right (application [18, 18], [(18, (4, 9))], 18)+    it "names two pairs sharing a symbol by two fresh symbols" $+      joined (application [5, 5]) (application [6, 7]) 20 `shouldBe` Right (application [21, 22], [(21, (5, 6)), (22, (5, 7))], 22)+    it "mints nothing for two terms that are the same" $+      joined (application [2, 6]) (application [2, 6]) 13 `shouldBe` Right (application [2, 6], [], 13)+    it "tells the datum two formations differ in" $+      joined (ExFormation [BiDelta (BtOne "01"), BiLambda (FnSymbol 4)]) (ExFormation [BiDelta (BtOne "02"), BiLambda (FnSymbol 9)]) 10 `shouldBe` Left [AtDelta]+    it "tells the λ of two formations naming two functions" $+      joined (ExFormation [BiLambda (Function "L_one")]) (ExFormation [BiLambda (Function "L_two")]) 7 `shouldBe` Left [AtLambda]+    it "tells the λ of two formations naming a symbol and a function" $+      joined (ExFormation [BiLambda (FnSymbol 3)]) (ExFormation [BiLambda (Function "L_three")]) 5 `shouldBe` Left [AtLambda]+    it "tells no attribute of two dispatches of two attributes" $+      joined (ExDispatch (application [3]) (AtLabel "a")) (ExDispatch (application [8]) (AtLabel "b")) 9 `shouldBe` Left []+    it "tells no attribute inside an application whose arguments differ" $+      joined (application [3]) (application [3, 4]) 6 `shouldBe` Left []+    let apart :: String -> String -> IO (Either [Attribute] (Expression, [(Int, (Int, Int))], Int))+        apart left right = (\one two -> joined one two 41) <$> parseExpressionThrows left <*> parseExpressionThrows right+    it "tells every attribute down to the datum two formations differ in" $+      apart "⟦ φ ↦ ⟦ ko ↦ ⟦ Δ ⤍ 01- ⟧ ⟧ ⟧" "⟦ φ ↦ ⟦ ko ↦ ⟦ Δ ⤍ 02- ⟧ ⟧ ⟧" >>= (`shouldBe` Left [AtPhi, AtLabel "ko", AtDelta])+    it "tells the attribute only the right formation binds among the others" $+      apart "⟦ ub ↦ Φ.ka, xe ↦ Φ.mo ⟧" "⟦ ub ↦ Φ.ka, ρ ↦ Φ.zi, xe ↦ Φ.mo ⟧" >>= (`shouldBe` Left [AtRho])+    it "tells the attribute only the left formation binds among the others" $+      apart "⟦ ub ↦ Φ.ka, ρ ↦ Φ.zi, xe ↦ Φ.mo ⟧" "⟦ ub ↦ Φ.ka, xe ↦ Φ.mo ⟧" >>= (`shouldBe` Left [AtRho])+    it "tells the attribute the left formation binds past the end of the right one" $+      apart "⟦ ub ↦ Φ.ka, ρ ↦ Φ ⟧" "⟦ ub ↦ Φ.ka ⟧" >>= (`shouldBe` Left [AtRho])+    it "tells the first attribute of the left formation when both bind the same ones in other orders" $+      apart "⟦ ub ↦ Φ.ka, xe ↦ Φ.mo ⟧" "⟦ xe ↦ Φ.mo, ub ↦ Φ.ka ⟧" >>= (`shouldBe` Left [AtLabel "ub"])+    it "tells the attribute one formation leaves void and the other binds" $+      apart "⟦ ws ↦ ∅ ⟧" "⟦ ws ↦ Φ.ug ⟧" >>= (`shouldBe` Left [AtLabel "ws"])+    it "tells the attribute bound to an application whose arguments differ and nothing inside it" $+      apart "⟦ φ ↦ Φ.fe( ix ↦ ⟦ ke ↦ Φ.ab ⟧ ) ⟧" "⟦ φ ↦ Φ.fe( ix ↦ ⟦ ke ↦ Φ.yo ⟧ ) ⟧" >>= (`shouldBe` Left [AtPhi])+    it "tells no attribute of a dispatch and a formation" $+      apart "Φ.ab.cd" "⟦ φ ↦ Φ.ab ⟧" >>= (`shouldBe` Left [])++  describe "forked" $ do+    let fork :: Text -> Either Int Bytes -> String -> String -> IO (Either Refusal Expression)+        fork func condition left right = forked (Function func) condition <$> parseExpressionThrows left <*> parseExpressionThrows right+    it "asks the copies of both sides for an attribute the λ it is given picks between" $ do+      expected <- parseExpressionThrows "⟦ left🌵 ↦ ⟦ φ ↦ Φ.hx ⟧, right🌵 ↦ ⟦ φ ↦ Φ.zu( ke ↦ Φ.ty ) ⟧, φ ↦ ⟦ ρ ↦ ∅, λ ⤍ L_qw, on🌵 ↦ ⟦ Δ ⤍ 1C- ⟧, left🌵 ↦ ξ.ρ.left🌵.φ, right🌵 ↦ ξ.ρ.right🌵.φ ⟧ ⟧"+      fork "L_qw" (Right (BtOne "1C")) "⟦ φ ↦ Φ.hx ⟧" "⟦ φ ↦ Φ.zu( ke ↦ Φ.ty ) ⟧" >>= (`shouldBe` Right expected)+    it "asks the copies of both sides for an attribute only one of them binds" $ do+      expected <- parseExpressionThrows "⟦ left🌵 ↦ ⟦ gv ↦ Φ.ob ⟧, right🌵 ↦ ⟦ mu ↦ Φ.ra ⟧, gv ↦ ⟦ ρ ↦ ∅, λ ⤍ L_ny, on🌵 ↦ ⟦ Δ ⤍ 2B-31-70-0F ⟧, left🌵 ↦ ξ.ρ.left🌵.gv, right🌵 ↦ ξ.ρ.right🌵.gv ⟧, mu ↦ ⟦ ρ ↦ ∅, λ ⤍ L_ny, on🌵 ↦ ⟦ Δ ⤍ 2B-31-70-0F ⟧, left🌵 ↦ ξ.ρ.left🌵.mu, right🌵 ↦ ξ.ρ.right🌵.mu ⟧ ⟧"+      fork "L_ny" (Right (BtMany ["2B", "31", "70", "0F"])) "⟦ gv ↦ Φ.ob ⟧" "⟦ mu ↦ Φ.ra ⟧" >>= (`shouldBe` Right expected)+    it "keeps the ρ of both sides in their copies and asks neither for it" $ do+      expected <- parseExpressionThrows "⟦ left🌵 ↦ ⟦ ρ ↦ Φ.ji, φ ↦ Φ.wo ⟧, right🌵 ↦ ⟦ ρ ↦ Φ.sa, φ ↦ Φ.pl ⟧, φ ↦ ⟦ ρ ↦ ∅, λ ⤍ L_ef, on🌵 ↦ ⟦ Δ ⤍ 0A- ⟧, left🌵 ↦ ξ.ρ.left🌵.φ, right🌵 ↦ ξ.ρ.right🌵.φ ⟧ ⟧"+      fork "L_ef" (Right (BtOne "0A")) "⟦ ρ ↦ Φ.ji, φ ↦ Φ.wo ⟧" "⟦ ρ ↦ Φ.sa, φ ↦ Φ.pl ⟧" >>= (`shouldBe` Right expected)+    it "takes every void of both sides as a void of its own and applies each side to its voids" $ do+      expected <- parseExpressionThrows "⟦ tu ↦ ∅, os ↦ ∅, left🌵 ↦ ⟦ φ ↦ Φ.km( ξ.tu ), tu ↦ ∅ ⟧( tu ↦ ξ.tu ), right🌵 ↦ ⟦ φ ↦ Φ.wb( ξ.os ), os ↦ ∅ ⟧( os ↦ ξ.os ), φ ↦ ⟦ ρ ↦ ∅, λ ⤍ L_gi, on🌵 ↦ ⟦ Δ ⤍ 6D- ⟧, left🌵 ↦ ξ.ρ.left🌵.φ, right🌵 ↦ ξ.ρ.right🌵.φ ⟧ ⟧"+      fork "L_gi" (Right (BtOne "6D")) "⟦ φ ↦ Φ.km( ξ.tu ), tu ↦ ∅ ⟧" "⟦ φ ↦ Φ.wb( ξ.os ), os ↦ ∅ ⟧" >>= (`shouldBe` Right expected)+    it "applies a side binding its ρ to its voids and keeps that ρ" $ do+      expected <- parseExpressionThrows "⟦ pe ↦ ∅, left🌵 ↦ ⟦ ρ ↦ Φ.ov, φ ↦ Φ.ha( ξ.pe ), pe ↦ ∅ ⟧( pe ↦ ξ.pe ), right🌵 ↦ ⟦ φ ↦ Φ.ix ⟧, φ ↦ ⟦ ρ ↦ ∅, λ ⤍ L_dz, on🌵 ↦ ⟦ Δ ⤍ 44- ⟧, left🌵 ↦ ξ.ρ.left🌵.φ, right🌵 ↦ ξ.ρ.right🌵.φ ⟧ ⟧"+      fork "L_dz" (Right (BtOne "44")) "⟦ ρ ↦ Φ.ov, φ ↦ Φ.ha( ξ.pe ), pe ↦ ∅ ⟧" "⟦ φ ↦ Φ.ix ⟧" >>= (`shouldBe` Right expected)+    it "keeps one void for an attribute both sides leave void" $ do+      expected <- parseExpressionThrows "⟦ dk ↦ ∅, left🌵 ↦ ⟦ dk ↦ ∅, φ ↦ Φ.ic ⟧( dk ↦ ξ.dk ), right🌵 ↦ ⟦ φ ↦ Φ.yo, dk ↦ ∅ ⟧( dk ↦ ξ.dk ), φ ↦ ⟦ ρ ↦ ∅, λ ⤍ L_vb, on🌵 ↦ ⟦ Δ ⤍ 3E- ⟧, left🌵 ↦ ξ.ρ.left🌵.φ, right🌵 ↦ ξ.ρ.right🌵.φ ⟧ ⟧"+      fork "L_vb" (Right (BtOne "3E")) "⟦ dk ↦ ∅, φ ↦ Φ.ic ⟧" "⟦ φ ↦ Φ.yo, dk ↦ ∅ ⟧" >>= (`shouldBe` Right expected)+    it "takes as its own void an attribute one side leaves void and the other binds" $ do+      expected <- parseExpressionThrows "⟦ ws ↦ ∅, left🌵 ↦ ⟦ ws ↦ ∅ ⟧( ws ↦ ξ.ws ), right🌵 ↦ ⟦ ws ↦ Φ.ug ⟧ ⟧"+      fork "L_oh" (Right (BtOne "52")) "⟦ ws ↦ ∅ ⟧" "⟦ ws ↦ Φ.ug ⟧" >>= (`shouldBe` Right expected)+    it "skips an attribute of a side whose name ends with the 🌵 mark" $ do+      expected <- parseExpressionThrows "⟦ left🌵 ↦ ⟦ left🌵 ↦ Φ.ub, rq🌵 ↦ ∅, φ ↦ Φ.ar ⟧, right🌵 ↦ ⟦ φ ↦ Φ.se ⟧, φ ↦ ⟦ ρ ↦ ∅, λ ⤍ L_hn, on🌵 ↦ ⟦ Δ ⤍ 19- ⟧, left🌵 ↦ ξ.ρ.left🌵.φ, right🌵 ↦ ξ.ρ.right🌵.φ ⟧ ⟧"+      fork "L_hn" (Right (BtOne "19")) "⟦ left🌵 ↦ Φ.ub, rq🌵 ↦ ∅, φ ↦ Φ.ar ⟧" "⟦ φ ↦ Φ.se ⟧" >>= (`shouldBe` Right expected)+    it "binds the condition to the symbol it came down to, not to the bytes of that symbol" $ do+      expected <- parseExpressionThrows "⟦ left🌵 ↦ ⟦ φ ↦ Φ.re ⟧, right🌵 ↦ ⟦ φ ↦ Φ.dy( Φ.ka ) ⟧, φ ↦ ⟦ ρ ↦ ∅, λ ⤍ L_uo, on🌵 ↦ ⟦ λ ⤍ 𝜎37 ⟧, left🌵 ↦ ξ.ρ.left🌵.φ, right🌵 ↦ ξ.ρ.right🌵.φ ⟧ ⟧"+      fork "L_uo" (Left 37) "⟦ φ ↦ Φ.re ⟧" "⟦ φ ↦ Φ.dy( Φ.ka ) ⟧" >>= (`shouldBe` Right expected)+    it "takes as voids of a copy of the function the voids of the formations both sides bind its attribute to" $ do+      expected <- parseExpressionThrows "⟦ left🌵 ↦ ⟦ ke ↦ ⟦ ix ↦ ∅, φ ↦ Φ.qa ⟧ ⟧, right🌵 ↦ ⟦ ke ↦ ⟦ φ ↦ Φ.vu, ow ↦ ∅ ⟧ ⟧, ke ↦ ⟦ ρ ↦ ∅, λ ⤍ L_bj, ix ↦ ∅, ow ↦ ∅, on🌵 ↦ ⟦ Δ ⤍ 5E- ⟧, left🌵 ↦ ξ.ρ.left🌵.ke( ix ↦ ξ.ix ), right🌵 ↦ ξ.ρ.right🌵.ke( ow ↦ ξ.ow ) ⟧ ⟧"+      fork "L_bj" (Right (BtOne "5E")) "⟦ ke ↦ ⟦ ix ↦ ∅, φ ↦ Φ.qa ⟧ ⟧" "⟦ ke ↦ ⟦ φ ↦ Φ.vu, ow ↦ ∅ ⟧ ⟧" >>= (`shouldBe` Right expected)+    it "keeps one void of a copy of the function for a void both sides leave in its attribute" $ do+      expected <- parseExpressionThrows "⟦ left🌵 ↦ ⟦ ke ↦ ⟦ ix ↦ ∅ ⟧, φ ↦ Φ.ne ⟧, right🌵 ↦ ⟦ ke ↦ ⟦ ix ↦ ∅, φ ↦ Φ.ag ⟧ ⟧, ke ↦ ⟦ ρ ↦ ∅, λ ⤍ L_wy, ix ↦ ∅, on🌵 ↦ ⟦ Δ ⤍ 2F- ⟧, left🌵 ↦ ξ.ρ.left🌵.ke( ix ↦ ξ.ix ), right🌵 ↦ ξ.ρ.right🌵.ke( ix ↦ ξ.ix ) ⟧, φ ↦ ⟦ ρ ↦ ∅, λ ⤍ L_wy, on🌵 ↦ ⟦ Δ ⤍ 2F- ⟧, left🌵 ↦ ξ.ρ.left🌵.φ, right🌵 ↦ ξ.ρ.right🌵.φ ⟧ ⟧"+      fork "L_wy" (Right (BtOne "2F")) "⟦ ke ↦ ⟦ ix ↦ ∅ ⟧, φ ↦ Φ.ne ⟧" "⟦ ke ↦ ⟦ ix ↦ ∅, φ ↦ Φ.ag ⟧ ⟧" >>= (`shouldBe` Right expected)+    it "orders the voids of a copy of the function by the left side first" $ do+      expected <- parseExpressionThrows "⟦ left🌵 ↦ ⟦ ke ↦ ⟦ zo ↦ ∅, ab ↦ ∅ ⟧ ⟧, right🌵 ↦ ⟦ ke ↦ ⟦ mi ↦ ∅, ab ↦ ∅ ⟧ ⟧, ke ↦ ⟦ ρ ↦ ∅, λ ⤍ L_rc, zo ↦ ∅, ab ↦ ∅, mi ↦ ∅, on🌵 ↦ ⟦ λ ⤍ 𝜎61 ⟧, left🌵 ↦ ξ.ρ.left🌵.ke( zo ↦ ξ.zo, ab ↦ ξ.ab ), right🌵 ↦ ξ.ρ.right🌵.ke( mi ↦ ξ.mi, ab ↦ ξ.ab ) ⟧ ⟧"+      fork "L_rc" (Left 61) "⟦ ke ↦ ⟦ zo ↦ ∅, ab ↦ ∅ ⟧ ⟧" "⟦ ke ↦ ⟦ mi ↦ ∅, ab ↦ ∅ ⟧ ⟧" >>= (`shouldBe` Right expected)+    it "takes no ρ of the formation a side binds an attribute to as a void of a copy of the function" $ do+      expected <- parseExpressionThrows "⟦ left🌵 ↦ ⟦ ke ↦ ⟦ ρ ↦ ∅, ux ↦ ∅ ⟧ ⟧, right🌵 ↦ ⟦ ke ↦ ⟦ ρ ↦ ∅, ux ↦ ∅, φ ↦ Φ.sl ⟧ ⟧, ke ↦ ⟦ ρ ↦ ∅, λ ⤍ L_tp, ux ↦ ∅, on🌵 ↦ ⟦ Δ ⤍ 6B- ⟧, left🌵 ↦ ξ.ρ.left🌵.ke( ux ↦ ξ.ux ), right🌵 ↦ ξ.ρ.right🌵.ke( ux ↦ ξ.ux ) ⟧ ⟧"+      fork "L_tp" (Right (BtOne "6B")) "⟦ ke ↦ ⟦ ρ ↦ ∅, ux ↦ ∅ ⟧ ⟧" "⟦ ke ↦ ⟦ ρ ↦ ∅, ux ↦ ∅, φ ↦ Φ.sl ⟧ ⟧" >>= (`shouldBe` Right expected)+    it "hands the voids of a copy of the function to a side binding its attribute to no formation" $ do+      expected <- parseExpressionThrows "⟦ left🌵 ↦ ⟦ ke ↦ Φ.ob ⟧, right🌵 ↦ ⟦ ke ↦ ⟦ ux ↦ ∅, φ ↦ ξ.ux ⟧ ⟧, ke ↦ ⟦ ρ ↦ ∅, λ ⤍ L_ga, ux ↦ ∅, on🌵 ↦ ⟦ Δ ⤍ 0C- ⟧, left🌵 ↦ ξ.ρ.left🌵.ke( ux ↦ ξ.ux ), right🌵 ↦ ξ.ρ.right🌵.ke( ux ↦ ξ.ux ) ⟧ ⟧"+      fork "L_ga" (Right (BtOne "0C")) "⟦ ke ↦ Φ.ob ⟧" "⟦ ke ↦ ⟦ ux ↦ ∅, φ ↦ ξ.ux ⟧ ⟧" >>= (`shouldBe` Right expected)+    it "takes no void of an attribute of a side whose name ends with the 🌵 mark" $ do+      expected <- parseExpressionThrows "⟦ left🌵 ↦ ⟦ ke ↦ ⟦ left🌵 ↦ ∅, ux ↦ ∅ ⟧ ⟧, right🌵 ↦ ⟦ ke ↦ Φ.ob ⟧, ke ↦ ⟦ ρ ↦ ∅, λ ⤍ L_md, ux ↦ ∅, on🌵 ↦ ⟦ Δ ⤍ 71- ⟧, left🌵 ↦ ξ.ρ.left🌵.ke( ux ↦ ξ.ux ), right🌵 ↦ ξ.ρ.right🌵.ke( ux ↦ ξ.ux ) ⟧ ⟧"+      fork "L_md" (Right (BtOne "71")) "⟦ ke ↦ ⟦ left🌵 ↦ ∅, ux ↦ ∅ ⟧ ⟧" "⟦ ke ↦ Φ.ob ⟧" >>= (`shouldBe` Right expected)+    it "applies nothing to a side binding the attribute of a copy of the function to a formation with no void" $ do+      expected <- parseExpressionThrows "⟦ left🌵 ↦ ⟦ ke ↦ ⟦ φ ↦ Φ.lo ⟧ ⟧, right🌵 ↦ ⟦ ke ↦ ⟦ ev ↦ ∅ ⟧ ⟧, ke ↦ ⟦ ρ ↦ ∅, λ ⤍ L_fz, ev ↦ ∅, on🌵 ↦ ⟦ Δ ⤍ 3A- ⟧, left🌵 ↦ ξ.ρ.left🌵.ke, right🌵 ↦ ξ.ρ.right🌵.ke( ev ↦ ξ.ev ) ⟧ ⟧"+      fork "L_fz" (Right (BtOne "3A")) "⟦ ke ↦ ⟦ φ ↦ Φ.lo ⟧ ⟧" "⟦ ke ↦ ⟦ ev ↦ ∅ ⟧ ⟧" >>= (`shouldBe` Right expected)+    it "hands the voids of a copy of the function to a side that does not bind its attribute" $ do+      expected <- parseExpressionThrows "⟦ left🌵 ↦ ⟦ ke ↦ ⟦ ev ↦ ∅, jo ↦ ∅ ⟧ ⟧, right🌵 ↦ ⟦ φ ↦ Φ.ty ⟧, ke ↦ ⟦ ρ ↦ ∅, λ ⤍ L_pu, ev ↦ ∅, jo ↦ ∅, on🌵 ↦ ⟦ Δ ⤍ 5C- ⟧, left🌵 ↦ ξ.ρ.left🌵.ke( ev ↦ ξ.ev, jo ↦ ξ.jo ), right🌵 ↦ ξ.ρ.right🌵.ke( ev ↦ ξ.ev, jo ↦ ξ.jo ) ⟧, φ ↦ ⟦ ρ ↦ ∅, λ ⤍ L_pu, on🌵 ↦ ⟦ Δ ⤍ 5C- ⟧, left🌵 ↦ ξ.ρ.left🌵.φ, right🌵 ↦ ξ.ρ.right🌵.φ ⟧ ⟧"+      fork "L_pu" (Right (BtOne "5C")) "⟦ ke ↦ ⟦ ev ↦ ∅, jo ↦ ∅ ⟧ ⟧" "⟦ φ ↦ Φ.ty ⟧" >>= (`shouldBe` Right expected)+    it "tells that the left side has a datum" $+      fork "L_ax" (Right (BtOne "11")) "⟦ Δ ⤍ 4F- ⟧" "⟦ φ ↦ Φ.bu ⟧" >>= (`shouldBe` Left (Holding Lefthand AtDelta))+    it "tells that the left side has a λ function" $+      fork "L_cz" (Right (BtOne "07")) "⟦ λ ⤍ L_kp, φ ↦ Φ.ev ⟧" "⟦ φ ↦ Φ.ni ⟧" >>= (`shouldBe` Left (Holding Lefthand AtLambda))+    it "tells that the right side has a λ when it is a bare symbol" $+      fork "L_wq" (Left 83) "⟦ φ ↦ Φ.ju( ξ.zi ), zi ↦ Φ.ko ⟧" "⟦ λ ⤍ 𝜎329 ⟧" >>= (`shouldBe` Left (Holding Righthand AtLambda))+    it "tells that the right side is no formation" $+      fork "L_rt" (Right (BtOne "2D")) "⟦ φ ↦ Φ.mo ⟧" "Φ.gl.fy" >>= (`shouldBe` Left (Unformed Righthand))+    it "tells about the left side before the right one" $+      fork "L_ib" (Left 5) "Φ.vy" "⟦ Δ ⤍ 1A- ⟧" >>= (`shouldBe` Left (Unformed Lefthand))
+ test/Grown.hs view
@@ -0,0 +1,89 @@+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE OverloadedStrings #-}++-- SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com+-- SPDX-License-Identifier: MIT++module Grown (Grown (..)) where++import AST+import Bytes (numToBts, strToBts)+import Data.List (nub)+import Data.Text qualified as T+import Test.QuickCheck (Arbitrary (..), Gen, chooseInt, elements, frequency, shrinkList, shuffle, sized, suchThat, vectorOf)++newtype Grown = Grown Expression+  deriving (Show)++instance Arbitrary Grown where+  arbitrary = Grown <$> sized (expression . (`div` 20))+    where+      expression :: Int -> Gen Expression+      expression depth =+        frequency+          ( [ (2, pure ExXi)+            , (2, pure ExRoot)+            , (1, pure ExTermination)+            , (1, ExMeta <$> meta "enk")+            , (1, DataNumber . numToBts <$> arbitrary)+            , (1, DataString . strToBts <$> arbitrary)+            , (2, ExFormation <$> bindings 0)+            ]+              ++ [ (weight, grown)+                 | depth > 0+                 , (weight, grown) <-+                     [ (4, ExFormation <$> bindings depth)+                     , (3, ExDispatch <$> expression (depth - 1) <*> attribute)+                     , (3, (ExApplication <$> expression (depth - 1) <*> argument depth) `suchThat` spelled)+                     ]+                 ]+          )+      spelled :: Expression -> Bool+      spelled (ExApplication ExXi _) = False+      spelled (ExApplication ExRoot _) = False+      spelled (ExApplication (DataNumber _) (ArTau AtRho _)) = False+      spelled (ExApplication (DataString _) (ArTau AtRho _)) = False+      spelled _ = True+      argument :: Int -> Gen Argument+      argument depth = frequency [(3, ArTau <$> attribute <*> expression (depth - 1)), (2, ArAlpha <$> alpha <*> expression (depth - 1))]+      alpha :: Gen Alpha+      alpha = frequency [(4, Alpha <$> chooseInt (0, 3)), (1, AlMeta <$> meta "i")]+      bindings :: Int -> Gen [Binding]+      bindings depth = do+        attrs <- nub <$> (chooseInt (0, 4) >>= (`vectorOf` attribute))+        named <- mapM (\attr -> frequency [(1, pure (BiVoid attr)), (3, BiTau attr <$> expression (depth - 1))]) attrs+        wildcard <- frequency [(3, pure []), (1, pure . BiMeta <$> meta "B")]+        delta <- frequency [(2, pure []), (1, pure . BiDelta <$> bytes)]+        lambda <- frequency [(2, pure []), (1, pure . BiLambda <$> function)]+        (++ delta ++ lambda) <$> shuffle (named ++ wildcard)+      attribute :: Gen Attribute+      attribute = frequency [(6, AtLabel <$> label), (1, pure AtPhi), (1, pure AtRho), (1, AtMeta <$> meta "t")]+      label :: Gen T.Text+      label = T.pack <$> spelt ['a' .. 'z'] (['a' .. 'z'] ++ ['0' .. '9'] ++ "-_")+      function :: Gen Function+      function = frequency [(4, Function . T.pack <$> spelt ['A' .. 'Z'] (['a' .. 'z'] ++ ['0' .. '9'] ++ "_")), (1, FnMeta <$> meta "F"), (2, FnSymbol <$> chooseInt (1, 99))]+      bytes :: Gen Bytes+      bytes = frequency [(1, pure BtEmpty), (2, BtOne <$> byte), (3, BtMany <$> (chooseInt (2, 9) >>= (`vectorOf` byte))), (1, BtMeta <$> meta "d")]+      byte :: Gen String+      byte = vectorOf 2 (elements (['0' .. '9'] ++ ['A' .. 'F']))+      meta :: String -> Gen T.Text+      meta heads = T.pack <$> ((:) <$> elements heads <*> (chooseInt (1, 3) >>= (`vectorOf` elements (['1' .. '9'] ++ ['a' .. 'z']))))+      spelt :: String -> String -> Gen String+      spelt heads rest = (:) <$> elements heads <*> (chooseInt (0, 5) >>= (`vectorOf` elements rest))+  shrink (Grown expr) = Grown <$> smaller expr+    where+      smaller :: Expression -> [Expression]+      smaller = \case+        ExFormation bds -> [inner | BiTau _ inner <- bds] ++ (ExFormation <$> shrinkList binding bds)+        ExDispatch inner attr -> inner : (ExDispatch <$> smaller inner <*> pure attr)+        ExApplication inner arg -> inner : held arg : [ExApplication less arg | less <- smaller inner, less `notElem` [ExXi, ExRoot]] ++ (ExApplication inner <$> argued arg)+        _ -> []+      binding :: Binding -> [Binding]+      binding (BiTau attr inner) = BiTau attr <$> smaller inner+      binding _ = []+      held :: Argument -> Expression+      held (ArTau _ inner) = inner+      held (ArAlpha _ inner) = inner+      argued :: Argument -> [Argument]+      argued (ArTau attr inner) = ArTau attr <$> smaller inner+      argued (ArAlpha idx inner) = ArAlpha idx <$> smaller inner
test/InferenceSpec.hs view
@@ -39,17 +39,17 @@   describe "dataizationSpine" $ do     it "answers with the data the rule 'delta' finds" $       snd <$> dataizationSpine (dataizationRule "delta") `shouldBe` Right (Answered (Dataization, "delta") (BtMeta "d1"))-    it "labels the step of the rule 'box' by its 'contextualize'" $-      snd <$> dataizationSpine (dataizationRule "box") `shouldBe` Right (Onward (Normalized (Contextualization, "contextualize")) (ExMeta "e3") (ExMeta "e1"))+    it "labels the step of the rule 'box' by its own name" $+      snd <$> dataizationSpine (dataizationRule "box") `shouldBe` Right (Onward (Normalized (Dataization, "box")) (ExDispatch (ExFormation [BiMeta "B1", BiTau AtPhi (ExMeta "e2"), BiMeta "B2"]) AtPhi) (ExMeta "e1"))     it "labels the step of the rule 'fire' by its 'evaluate'" $       snd <$> dataizationSpine (dataizationRule "fire") `shouldBe` Right (Onward (Taken (Evaluation, "evaluate")) (ExMeta "n1") (ExMeta "e1"))     it "labels the step of the rule 'none' by its 'dataize'" $       snd <$> dataizationSpine (dataizationRule "none") `shouldBe` Right (Onward (Taken (Dataization, "dataize")) ExTermination (ExMeta "e1"))     it "stages the morphing the rule 'norm' asks for" $       snd <$> dataizationSpine (dataizationRule "norm") `shouldBe` Right (Onward (Staged (ExMeta "e1")) (ExMeta "n1") (ExMeta "e1"))-    it "labels blank a step normalizing with nothing beside it" $+    it "labels a step normalizing with nothing beside it by its rule" $       snd <$> dataizationSpine (Y.DataizeRule "pz" Nothing ExXi (ExMeta "e1") (BtMeta "d4") Nothing [Y.Premise "n5" (Y.OpNormalize ExXi), Y.Premise "d4" (Y.OpDataize (ExMeta "n5") (ExMeta "e1"))])-        `shouldBe` Right (Onward (Normalized (Dataization, "")) ExXi (ExMeta "e1"))+        `shouldBe` Right (Onward (Normalized (Dataization, "pz")) ExXi (ExMeta "e1"))     it "refuses a rule whose data no 'dataize' produces" $       dataizationSpine (Y.DataizeRule "jr" Nothing ExXi (ExMeta "e1") (BtMeta "d2") Nothing [Y.Premise "d2" (Y.OpNormalize ExXi)])         `shouldBe` Left "it concludes with no 'dataize' premise"@@ -77,9 +77,9 @@     it "hands the rule 'fire' the formation to evaluate" $ do       Just (Evaluates form world _) <- dataizationOf (dataizationRule "fire") context (ExFormation [BiLambda (Function "Kq")]) (ExFormation [BiVoid (AtLabel "rb")])       (form, world) `shouldBe` (ExFormation [BiLambda (Function "Kq")], ExFormation [BiVoid (AtLabel "rb")])-    it "hands the rule 'box' the body to contextualize in its formation" $ do-      Just (Contextualizes body formation _) <- dataizationOf (dataizationRule "box") context (ExFormation [BiTau AtPhi (ExDispatch ExXi (AtLabel "zu"))]) (ExFormation [])-      (body, formation) `shouldBe` (ExDispatch ExXi (AtLabel "zu"), ExFormation [BiTau AtPhi (ExDispatch ExXi (AtLabel "zu"))])+    it "hands the rule 'box' the φ of its formation to normalize" $ do+      Just (Concludes conclusion) <- dataizationOf (dataizationRule "box") context (ExFormation [BiTau AtPhi (ExDispatch ExXi (AtLabel "zu"))]) (ExFormation [])+      conclusion `shouldBe` Onward (Normalized (Dataization, "box")) (ExDispatch (ExFormation [BiTau AtPhi (ExDispatch ExXi (AtLabel "zu"))]) AtPhi) (ExFormation [])   describe "direct" $ do     it "takes the first way a compiled rule matches" $ do       Just (Concludes conclusion) <- direct (\_ _ -> [Concludes (Answered (Morphing, "w1") ExXi), Concludes (Answered (Morphing, "w2") ExXi)]) context ExXi ExXi
test/LambdasSpec.hs view
@@ -12,9 +12,12 @@ import Data.List (isInfixOf) import Data.Text qualified as T import Fixtures (withLambdasOf)-import Lambdas (Lambda (..), Lambdas, Line (..), Meta (..), emptyLambdas, joined, matched, message, minted, readLambdas, stayed, symbolized, taken, thrown, upcasted)+import Functions (buildTerm)+import Lambdas (Ending (..), Lambda (..), Lambdas, Line (..), Meta (..), emptyLambdas, matched, minted, readLambdas, rewritten, stayed, symbolized, taken) import Parser (parseExpressionThrows)+import Rule (RuleContext (..), normal) import Test.Hspec+import Yaml qualified as Y  lambdasOf :: T.Text -> IO Lambdas lambdasOf text = withLambdasOf text readLambdas@@ -22,6 +25,9 @@ entry :: T.Text -> T.Text entry key = "- λ: " <> key <> "\n  dataize:\n    𝛿1: $.ρ\n  𝑛: ⟦ λ ⤍ 𝜎 ⟧\n" +joint :: T.Text+joint = "- λ: L_fork\n  dataize:\n    𝛿1: $.c\n  join:\n    on: 𝛿1\n    left: $.a\n    right: $.b\n"+ answering :: Lambdas -> T.Text -> Maybe T.Text answering known func = _key <$> matched known func @@ -29,44 +35,62 @@ lined known func = maybe [] _lines (matched known func)  order :: Lambdas -> T.Text -> [T.Text]-order known func = concatMap bound (lined known func)+order known func = map bound (lined known func)   where-    bound :: Line -> [T.Text]-    bound (Evaluated meta _) = [_spelling meta]-    bound (Unfolded meta _ _) = [_spelling meta]-    bound (Rewritten meta _ _) = [_spelling meta]-    bound (Stayed meta _) = [_spelling meta]-    bound (Symbolized meta _) = [_spelling meta]-    bound (Upcasted one _ two _) = [_spelling one, _spelling two]-    bound (Paired meta _ _) = [_spelling meta]+    bound :: Line -> T.Text+    bound (Morphed meta _) = _spelling meta+    bound (Evaluated meta _) = _spelling meta+    bound (Deferred meta _) = _spelling meta+    bound (Stayed meta _) = _spelling meta+    bound (Symbolized meta _) = _spelling meta+    bound (Rewritten meta _ _) = _spelling meta -joins :: Lambdas -> T.Text -> [(T.Text, (T.Text, T.Text))]-joins known func = [(_spelling meta, (_spelling left, _spelling right)) | Paired meta left right <- lined known func]+morphs :: Lambdas -> T.Text -> [(T.Text, T.Text)]+morphs known func = [(_spelling meta, _spelling source) | Morphed meta source <- lined known func]  fired :: Lambdas -> T.Text -> [(T.Text, T.Text)] fired known func = [(_spelling meta, _spelling source) | Evaluated meta source <- lined known func] -unfolds :: Lambdas -> T.Text -> [(T.Text, T.Text, Bool)]-unfolds known func = [(_spelling meta, _spelling source, deferring) | Unfolded meta source deferring <- lined known func]--casts :: Lambdas -> T.Text -> [((T.Text, T.Text), (T.Text, T.Text))]-casts known func = [((_spelling one, _spelling first), (_spelling two, _spelling second)) | Upcasted one first two second <- lined known func]+defers :: Lambdas -> T.Text -> [(T.Text, T.Text)]+defers known func = [(_spelling meta, _spelling source) | Deferred meta source <- lined known func]  stays :: Lambdas -> T.Text -> [(T.Text, T.Text)] stays known func = [(_spelling meta, _spelling source) | Stayed meta source <- lined known func] -stood :: Lambdas -> T.Text -> [T.Text]-stood known func = [_spelling meta | Symbolized meta _ <- lined known func]+symbolizes :: Lambdas -> T.Text -> [(T.Text, T.Text)]+symbolizes known func = [(_spelling meta, _spelling source) | Symbolized meta source <- lined known func] -rewriting :: T.Text -> T.Text-rewriting lines' = "- λ: L_fork\n  morph:\n    𝑛1: $.a\n  rewrite:\n" <> lines'+rewrites :: Lambdas -> T.Text -> [(T.Text, T.Text, Int)]+rewrites known func = [(_spelling meta, _spelling source, length rules) | Rewritten meta source rules <- lined known func] -rules :: T.Text-rules = "      rules:\n        - name: no-x\n          pattern: ⟦ !B1, x ↦ ⟦⟧, !B2 ⟧\n          result: ⟦ !B1, !B2 ⟧\n"+truth :: T.Text+truth = "      rules:\n        - name: true-literal\n          pattern: Φ.true\n          result: Φ.bool( if ↦ ⟦ Δ ⤍ 01- ⟧ )\n" -rewrote :: Lambdas -> T.Text -> [(T.Text, T.Text, Int)]-rewrote known func = [(_spelling meta, _spelling source, length written) | Rewritten meta source written <- lined known func]+rewrote :: [(String, String)] -> String -> IO Expression+rewrote rules term = do+  parsed <- mapM (\(source, target) -> (\from to -> Y.Rule "kq" Nothing Nothing from to Nothing Nothing Nothing) <$> parseExpressionThrows source <*> parseExpressionThrows target) rules+  parseExpressionThrows term >>= rewritten (RuleContext buildTerm Nothing normal) parsed +answers :: Lambdas -> T.Text -> Maybe Expression+answers known func = case _ending <$> matched known func of+  Just (Answering term) -> Just term+  _ -> Nothing++joining :: Lambdas -> T.Text -> Maybe (Either T.Text Expression)+joining known func = case _ending <$> matched known func of+  Just (Joining on _ _ _) -> Just (either (Left . _spelling) Right on)+  _ -> Nothing++sides :: Lambdas -> T.Text -> Maybe (Expression, Expression)+sides known func = case _ending <$> matched known func of+  Just (Joining _ left right _) -> Just (left, right)+  _ -> Nothing++fallback :: Lambdas -> T.Text -> Maybe Function+fallback known func = case _ending <$> matched known func of+  Just (Joining _ _ _ name) -> name+  _ -> Nothing+ spec :: Spec spec = do   describe "readLambdas" $ do@@ -102,26 +126,30 @@       known <- lambdasOf "- λ: L_pair\n  dataize:\n    𝛿10: $.b\n    𝛿9: $.a\n    𝛿1: $.ρ\n  𝑛: ⟦ λ ⤍ 𝜎 ⟧\n"       map (_spelling . fst) (maybe [] _dataized (matched known "L_pair")) `shouldBe` ["𝛿1", "𝛿9", "𝛿10"] -    it "reads a 'symbolize' line that stands the term of line nine as line ten" $ do-      known <- lambdasOf "- λ: L_pair\n  morph:\n    𝑛1: $.x\n  symbolize:\n    𝑛9: 𝑛1\n    𝑛10: 𝑛9\n  𝑛: 𝑛10\n"-      stood known "L_pair" `shouldBe` ["𝑛9", "𝑛10"]-     it "reads a meta under both the name it is spelled with and the name it binds" $ do       known <- lambdasOf "- λ: L_pair\n  dataize:\n    𝛿1: $.ρ\n  𝑛: ⟦ λ ⤍ 𝜎 ⟧\n"       map (_name . fst) (maybe [] _dataized (matched known "L_pair")) `shouldBe` ["d1"]      it "reads the operands of 'morph' under expression metas" $ do-      known <- lambdasOf "- λ: L_fork\n  morph:\n    𝑛1: $.then\n    𝑛2: $.else\n  𝑛: 𝑛1\n"-      map (_spelling . fst) (maybe [] _morphed (matched known "L_fork")) `shouldBe` ["𝑛1", "𝑛2"]+      known <- lambdasOf "- λ: L_pick\n  morph:\n    𝑛1: $.then\n    𝑛2: $.else\n  𝑛: 𝑛1\n"+      map (_spelling . fst) (maybe [] _morphed (matched known "L_pick")) `shouldBe` ["𝑛1", "𝑛2"] -    it "reads the operands of 'symbolize' under expression metas" $ do-      known <- lambdasOf "- λ: L_fork\n  morph:\n    𝑛1: $.then\n  symbolize:\n    𝑛2: 𝑛1\n  𝑛: 𝑛2\n"-      stood known "L_fork" `shouldBe` ["𝑛2"]+    it "keeps under 'morph' only the operands reading '$'" $ do+      known <- lambdasOf "- λ: L_hold\n  morph:\n    𝑛1: $.a\n    𝑛3: 𝑛2\n  stay:\n    𝑛2: 𝑛1\n  𝑛: 𝑛3\n"+      map (_spelling . fst) (maybe [] _morphed (matched known "L_hold")) `shouldBe` ["𝑛1"] -    it "reads a 'symbolize' line standing the term the line above it made" $ do-      known <- lambdasOf "- λ: L_fork\n  morph:\n    𝑛1: $.then\n  symbolize:\n    𝑛2: 𝑛1\n    𝑛3: 𝑛2\n  𝑛: 𝑛3\n"-      stood known "L_fork" `shouldBe` ["𝑛2", "𝑛3"]+    it "reads an operand of 'morph' naming a meta as a line reading that meta" $ do+      known <- lambdasOf "- λ: L_hold\n  morph:\n    𝑛1: $.a\n    𝑛3: 𝑛2\n  stay:\n    𝑛2: 𝑛1\n  𝑛: 𝑛3\n"+      morphs known "L_hold" `shouldBe` [("𝑛3", "𝑛2")] +    it "runs a 'morph' line after the 'rewrite' line binding the meta it reads" $ do+      known <- lambdasOf ("- λ: L_hold\n  morph:\n    𝑛1: $.a\n    𝑛3: 𝑛2\n  rewrite:\n    𝑛2:\n      of: 𝑛1\n" <> truth <> "  𝑛: 𝑛3\n")+      order known "L_hold" `shouldBe` ["𝑛2", "𝑛3"]++    it "runs a 'morph' line before an 'evaluate' line no meta orders" $ do+      known <- lambdasOf "- λ: L_hold\n  morph:\n    𝑛1: $.a\n    𝑛4: 𝑛2\n  stay:\n    𝑛2: 𝑛1\n  evaluate:\n    𝑛3: 𝑛2\n  𝑛: 𝑛3\n"+      order known "L_hold" `shouldBe` ["𝑛2", "𝑛4", "𝑛3"]+     it "reads the term an operand is reduced from" $ do       known <- lambdasOf "- λ: L_pair\n  dataize:\n    𝛿1: $.x\n  𝑛: ⟦ λ ⤍ 𝜎 ⟧\n"       operand <- parseExpressionThrows "$.x"@@ -130,136 +158,167 @@     it "reads the term the firing answers with" $ do       known <- lambdasOf "- λ: L_pair\n  𝑛: Φ.number( φ ↦ ⟦ λ ⤍ 𝜎 ⟧ )\n"       term <- parseExpressionThrows "Φ.number( φ ↦ ⟦ λ ⤍ 𝜎 ⟧ )"-      fmap _answer (matched known "L_pair") `shouldBe` Just term+      answers known "L_pair" `shouldBe` Just term -    it "reads the two metas a 'join' line joins" $ do-      known <- lambdasOf "- λ: L_fork\n  morph:\n    𝑛1: $.then\n    𝑛2: $.else\n  join:\n    𝑛3: [𝑛1, 𝑛2]\n  𝑛: 𝑛3\n"-      joins known "L_fork" `shouldBe` [("𝑛3", ("𝑛1", "𝑛2"))]+    it "reads a 'join' block joining on a meta 'dataize' bound" $ do+      known <- lambdasOf joint+      joining known "L_fork" `shouldBe` Just (Left "𝛿1") -    it "reads the two metas of a 'join' line in the order it lists them" $ do-      known <- lambdasOf "- λ: L_fork\n  morph:\n    𝑛1: $.then\n    𝑛2: $.else\n  join:\n    𝑛3: [𝑛2, 𝑛1]\n  𝑛: 𝑛3\n"-      joins known "L_fork" `shouldBe` [("𝑛3", ("𝑛2", "𝑛1"))]+    it "reads a 'join' block joining on a path from '$' as one that dataizes nothing" $ do+      known <- lambdasOf "- λ: L_join\n  join:\n    on: $.on\n    left: $.left\n    right: $.right\n"+      path <- parseExpressionThrows "$.on"+      joining known "L_join" `shouldBe` Just (Right path) -    it "reads a 'join' line joining the terms a 'symbolize' line stood" $ do-      known <- lambdasOf "- λ: L_fork\n  morph:\n    𝑛1: $.then\n    𝑛2: $.else\n  symbolize:\n    𝑛3: 𝑛1\n    𝑛4: 𝑛2\n  join:\n    𝑛5: [𝑛3, 𝑛4]\n  𝑛: 𝑛5\n"-      joins known "L_fork" `shouldBe` [("𝑛5", ("𝑛3", "𝑛4"))]+    it "reads the two sides of a 'join' block as terms" $ do+      known <- lambdasOf joint+      left <- parseExpressionThrows "$.a"+      right <- parseExpressionThrows "$.b"+      sides known "L_fork" `shouldBe` Just (left, right) -    it "reads a 'join' line joining what a line above it joined" $ do-      known <- lambdasOf "- λ: L_fork\n  morph:\n    𝑛1: $.a\n    𝑛2: $.b\n    𝑛3: $.c\n  join:\n    𝑛4: [𝑛1, 𝑛2]\n    𝑛5: [𝑛4, 𝑛3]\n  𝑛: 𝑛5\n"-      joins known "L_fork" `shouldBe` [("𝑛4", ("𝑛1", "𝑛2")), ("𝑛5", ("𝑛4", "𝑛3"))]+    it "reads a 'join' block whose sides are metas 'morph' and 'stay' bound" $ do+      known <- lambdasOf "- λ: L_fork\n  dataize:\n    𝛿1: $.c\n  morph:\n    𝑛1: $.a\n    𝑛2: $.b\n  stay:\n    𝑛3: 𝑛1\n  join:\n    on: 𝛿1\n    left: 𝑛3\n    right: 𝑛2\n"+      left <- parseExpressionThrows "𝑛3"+      right <- parseExpressionThrows "𝑛2"+      sides known "L_fork" `shouldBe` Just (left, right) -    it "reads an entry whose 'join' lines are lenient with no option" $ do-      known <- lambdasOf "- λ: L_fork\n  morph:\n    𝑛1: $.then\n    𝑛2: $.else\n  join:\n    𝑛3: [𝑛1, 𝑛2]\n  lenient: []\n  𝑛: 𝑛3\n"-      fmap _lenient (matched known "L_fork") `shouldBe` Just True+    it "reads a 'join' block whose sides are metas 'symbolize' bound" $ do+      known <- lambdasOf "- λ: L_fork\n  dataize:\n    𝛿1: $.c\n  morph:\n    𝑛1: $.a\n    𝑛2: $.b\n  symbolize:\n    𝑛3: 𝑛1\n    𝑛4: 𝑛2\n  join:\n    on: 𝛿1\n    left: 𝑛3\n    right: 𝑛4\n"+      left <- parseExpressionThrows "𝑛3"+      right <- parseExpressionThrows "𝑛4"+      sides known "L_fork" `shouldBe` Just (left, right) -    it "reads an entry saying nothing of leniency as strict" $ do-      known <- lambdasOf "- λ: L_fork\n  morph:\n    𝑛1: $.then\n    𝑛2: $.else\n  join:\n    𝑛3: [𝑛1, 𝑛2]\n  𝑛: 𝑛3\n"-      fmap _lenient (matched known "L_fork") `shouldBe` Just False+    it "reads a 'join' block whose sides are metas 'defer' bound" $ do+      known <- lambdasOf "- λ: L_fork\n  dataize:\n    𝛿1: $.c\n  morph:\n    𝑛1: $.a\n    𝑛2: $.b\n  defer:\n    𝑛3: 𝑛1\n    𝑛4: 𝑛2\n  join:\n    on: 𝛿1\n    left: 𝑛3\n    right: 𝑛4\n"+      left <- parseExpressionThrows "𝑛3"+      right <- parseExpressionThrows "𝑛4"+      sides known "L_fork" `shouldBe` Just (left, right) -    it "reads the lines of 'unfold' with the metas they read and whether they defer" $ do-      known <- lambdasOf "- λ: L_fork\n  morph:\n    𝑛1: $.a\n  unfold:\n    𝑛2: 𝑛1\n    𝑛3: {of: 𝑛2, defer: true}\n  𝑛: 𝑛3\n"-      unfolds known "L_fork" `shouldBe` [("𝑛2", "𝑛1", False), ("𝑛3", "𝑛2", True)]+    it "reads a 'join' block whose sides are metas 'evaluate' bound" $ do+      known <- lambdasOf "- λ: L_fork\n  dataize:\n    𝛿1: $.c\n  morph:\n    𝑛1: $.a\n    𝑛2: $.b\n  evaluate:\n    𝑛3: 𝑛1\n    𝑛4: 𝑛2\n  join:\n    on: 𝛿1\n    left: 𝑛3\n    right: 𝑛4\n"+      left <- parseExpressionThrows "𝑛3"+      right <- parseExpressionThrows "𝑛4"+      sides known "L_fork" `shouldBe` Just (left, right) -    it "reads an 'unfold' line saying 'of' alone as one that does not defer" $ do-      known <- lambdasOf "- λ: L_fork\n  morph:\n    𝑛1: $.a\n  unfold:\n    𝑛2: {of: 𝑛1}\n  𝑛: 𝑛2\n"-      unfolds known "L_fork" `shouldBe` [("𝑛2", "𝑛1", False)]+    it "runs each 'evaluate' line of a join right after the 'stay' line it reads" $ do+      known <- lambdasOf "- λ: L_fork\n  dataize:\n    𝛿1: $.φ\n  morph:\n    𝑛1: $.left\n    𝑛2: $.right\n  stay:\n    𝑛3: 𝑛1\n    𝑛4: 𝑛2\n  evaluate:\n    𝑛5: 𝑛3\n    𝑛6: 𝑛4\n  symbolize:\n    𝑛7: 𝑛5\n    𝑛8: 𝑛6\n  defer:\n    𝑛9: 𝑛7\n    𝑛10: 𝑛8\n  join:\n    on: 𝛿1\n    left: 𝑛9\n    right: 𝑛10\n"+      order known "L_fork" `shouldBe` ["𝑛3", "𝑛5", "𝑛4", "𝑛6", "𝑛7", "𝑛9", "𝑛8", "𝑛10"] -    it "reads the two lines of an 'upcast' block as one pair" $ do-      known <- lambdasOf "- λ: L_fork\n  morph:\n    𝑛1: $.a\n    𝑛2: $.b\n  upcast:\n    𝑛4: 𝑛2\n    𝑛3: 𝑛1\n  𝑛: 𝑛3\n"-      casts known "L_fork" `shouldBe` [(("𝑛3", "𝑛1"), ("𝑛4", "𝑛2"))]+    it "runs each 'defer' line of a join right after the 'symbolize' line it reads" $ do+      known <- lambdasOf "- λ: L_fork\n  dataize:\n    𝛿1: $.c\n  morph:\n    𝑛1: $.a\n    𝑛2: $.b\n  defer:\n    𝑛7: 𝑛5\n    𝑛8: 𝑛6\n  symbolize:\n    𝑛5: 𝑛3\n    𝑛6: 𝑛4\n  stay:\n    𝑛3: 𝑛1\n    𝑛4: 𝑛2\n  join:\n    on: 𝛿1\n    left: 𝑛7\n    right: 𝑛8\n"+      order known "L_fork" `shouldBe` ["𝑛3", "𝑛4", "𝑛5", "𝑛7", "𝑛6", "𝑛8"] -    it "reads the terminators an entry names beside its 'join' block" $ do-      known <- lambdasOf "- λ: L_fork\n  morph:\n    𝑛1: $.then\n    𝑛2: $.else\n  join:\n    𝑛3: [𝑛1, 𝑛2]\n  terminators: [L_throw, L_halt]\n  𝑛: 𝑛3\n"-      fmap _terminators (matched known "L_fork") `shouldBe` Just ["L_throw", "L_halt"]+    it "reads a 'join' block with one side a path and the other a meta" $ do+      known <- lambdasOf "- λ: L_fork\n  dataize:\n    𝛿1: $.c\n  morph:\n    𝑛1: $.b\n  join:\n    on: 𝛿1\n    left: $.a\n    right: 𝑛1\n"+      left <- parseExpressionThrows "$.a"+      right <- parseExpressionThrows "𝑛1"+      sides known "L_fork" `shouldBe` Just (left, right) -    it "reads an entry saying nothing of terminators as one naming none" $ do-      known <- lambdasOf "- λ: L_fork\n  morph:\n    𝑛1: $.then\n    𝑛2: $.else\n  join:\n    𝑛3: [𝑛1, 𝑛2]\n  𝑛: 𝑛3\n"-      fmap _terminators (matched known "L_fork") `shouldBe` Just []+    it "reads the λ function the 'otherwise' of a 'join' block names" $ do+      known <- lambdasOf (joint <> "    otherwise: L_join\n")+      fallback known "L_fork" `shouldBe` Just (Function "L_join") -    it "reads a terminator that no key of the file matches" $ do-      known <- lambdasOf ("- λ: L_fork\n  morph:\n    𝑛1: $.a\n  terminators: [L_throw]\n  𝑛: 𝑛1\n" <> entry "L_throw_[0-9]+")-      fmap _terminators (matched known "L_fork") `shouldBe` Just ["L_throw"]+    it "reads a 'join' block with no 'otherwise' as one fabricating nothing" $ do+      known <- lambdasOf joint+      fallback known "L_fork" `shouldBe` Nothing -    it "reads the lines of 'rewrite' with the metas they read and their rules" $ do-      known <- lambdasOf (rewriting "    𝑛2:\n      of: 𝑛1\n" <> rules <> "    𝑛3:\n      of: 𝑛2\n" <> rules <> "  𝑛: 𝑛3\n")-      rewrote known "L_fork" `shouldBe` [("𝑛2", "𝑛1", 1), ("𝑛3", "𝑛2", 1)]+    it "reads an entry with a 'join' block as one answering no term of its own" $ do+      known <- lambdasOf joint+      answers known "L_fork" `shouldBe` Nothing -    it "reads a 'symbolize' line standing the term a 'rewrite' line made" $ do-      known <- lambdasOf (rewriting "    𝑛2:\n      of: 𝑛1\n" <> rules <> "  symbolize:\n    𝑛3: 𝑛2\n  𝑛: 𝑛3\n")-      stood known "L_fork" `shouldBe` ["𝑛3"]+    it "reads an entry with an '𝑛' key as one joining nowhere" $ do+      known <- lambdasOf (entry "L_pair")+      joining known "L_pair" `shouldBe` Nothing +    it "reads the lines of 'defer' with the metas they read" $ do+      known <- lambdasOf "- λ: L_hold\n  morph:\n    𝑛1: $.a\n    𝑛2: $.b\n  defer:\n    𝑛4: 𝑛2\n    𝑛3: 𝑛1\n  𝑛: 𝑛3\n"+      defers known "L_hold" `shouldBe` [("𝑛3", "𝑛1"), ("𝑛4", "𝑛2")]+     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"-      fired known "L_fork" `shouldBe` [("𝑛3", "𝑛1"), ("𝑛4", "𝑛2")]+      known <- lambdasOf "- λ: L_hold\n  morph:\n    𝑛1: $.a\n    𝑛2: $.b\n  evaluate:\n    𝑛3: 𝑛1\n    𝑛4: 𝑛2\n  𝑛: 𝑛3\n"+      fired known "L_hold" `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"-      fired known "L_fork" `shouldBe` [("𝑛2", "𝑛1"), ("𝑛3", "𝑛2")]+      known <- lambdasOf "- λ: L_hold\n  morph:\n    𝑛1: $.a\n  evaluate:\n    𝑛2: 𝑛1\n    𝑛3: 𝑛2\n  𝑛: 𝑛3\n"+      fired known "L_hold" `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")-      rewrote known "L_fork" `shouldBe` [("𝑛3", "𝑛2", 1)]+    it "runs an 'evaluate' line before a 'defer' line no meta orders" $ do+      known <- lambdasOf "- λ: L_hold\n  morph:\n    𝑛1: $.a\n    𝑛2: $.b\n  defer:\n    𝑛4: 𝑛2\n  evaluate:\n    𝑛3: 𝑛1\n  𝑛: 𝑛3\n"+      order known "L_hold" `shouldBe` ["𝑛3", "𝑛4"] -    it "reads the metas the lines of 'stay' keep, a line keeping what the line above it kept" $ do-      known <- lambdasOf "- λ: L_fork\n  morph:\n    𝑛1: $.a\n  stay:\n    𝑛2: 𝑛1\n    𝑛3: 𝑛2\n  𝑛: 𝑛3\n"-      stays known "L_fork" `shouldBe` [("𝑛2", "𝑛1"), ("𝑛3", "𝑛2")]+    it "runs a line before the line of its own block reading what it made" $ do+      known <- lambdasOf "- λ: L_hold\n  morph:\n    𝑛1: $.a\n  evaluate:\n    𝑛2: 𝑛3\n    𝑛3: 𝑛1\n  𝑛: 𝑛2\n"+      order known "L_hold" `shouldBe` ["𝑛3", "𝑛2"] -    it "reads a 'stay' line keeping what a 'rewrite' line made" $ do-      known <- lambdasOf (rewriting "    𝑛2:\n      of: 𝑛1\n" <> rules <> "  stay:\n    𝑛3: 𝑛2\n  𝑛: 𝑛3\n")-      stays known "L_fork" `shouldBe` [("𝑛3", "𝑛2")]+    it "runs the lines in the order of the metas they read, whatever the order of their blocks" $ do+      known <- lambdasOf "- λ: L_hold\n  morph:\n    𝑛1: $.a\n  evaluate:\n    𝑛3: 𝑛2\n  defer:\n    𝑛2: 𝑛1\n  𝑛: 𝑛3\n"+      order known "L_hold" `shouldBe` ["𝑛2", "𝑛3"] -    it "reads a 'symbolize' line standing what a 'stay' line kept" $ do-      known <- lambdasOf "- λ: L_fork\n  morph:\n    𝑛1: $.a\n  stay:\n    𝑛2: 𝑛1\n  symbolize:\n    𝑛3: 𝑛2\n  𝑛: 𝑛3\n"-      stood known "L_fork" `shouldBe` ["𝑛3"]+    it "reads an entry naming no 'evaluate' block at all" $ do+      known <- lambdasOf "- λ: L_hold\n  morph:\n    𝑛1: $.a\n  𝑛: 𝑛1\n"+      fired known "L_hold" `shouldBe` [] -    it "runs a 'rewrite' line before the 'evaluate' line firing what it made" $ do-      known <- lambdasOf (rewriting "    𝑛2:\n      of: 𝑛1\n" <> rules <> "  evaluate:\n    𝑛3: 𝑛2\n  𝑛: 𝑛3\n")-      order known "L_fork" `shouldBe` ["𝑛2", "𝑛3"]+    it "reads the lines of 'stay' with the metas they read" $ do+      known <- lambdasOf "- λ: L_hold\n  morph:\n    𝑛1: $.a\n    𝑛2: $.b\n  stay:\n    𝑛4: 𝑛2\n    𝑛3: 𝑛1\n  𝑛: 𝑛3\n"+      stays known "L_hold" `shouldBe` [("𝑛3", "𝑛1"), ("𝑛4", "𝑛2")] -    it "runs a line before the line of its own block reading what it made" $ do-      known <- lambdasOf "- λ: L_fork\n  morph:\n    𝑛1: $.a\n  evaluate:\n    𝑛2: 𝑛3\n    𝑛3: 𝑛1\n  𝑛: 𝑛2\n"-      order known "L_fork" `shouldBe` ["𝑛3", "𝑛2"]+    it "runs a 'stay' line after the line binding the meta it reads" $ do+      known <- lambdasOf "- λ: L_hold\n  morph:\n    𝑛1: $.a\n  stay:\n    𝑛3: 𝑛2\n  evaluate:\n    𝑛2: 𝑛1\n  𝑛: 𝑛3\n"+      order known "L_hold" `shouldBe` ["𝑛2", "𝑛3"] -    it "runs a 'symbolize' line before the 'rewrite' line rewriting what it stood" $ do-      known <- lambdasOf (rewriting "    𝑛3:\n      of: 𝑛2\n" <> rules <> "  symbolize:\n    𝑛2: 𝑛1\n  𝑛: 𝑛3\n")-      order known "L_fork" `shouldBe` ["𝑛2", "𝑛3"]+    it "runs a 'defer' line before a 'stay' line no meta orders" $ do+      known <- lambdasOf "- λ: L_hold\n  morph:\n    𝑛1: $.a\n    𝑛2: $.b\n  stay:\n    𝑛3: 𝑛1\n  defer:\n    𝑛4: 𝑛2\n  𝑛: 𝑛3\n"+      order known "L_hold" `shouldBe` ["𝑛4", "𝑛3"] -    it "runs a 'symbolize' line before the 'stay' line keeping what it stood" $ do-      known <- lambdasOf "- λ: L_fork\n  morph:\n    𝑛1: $.a\n  stay:\n    𝑛3: 𝑛2\n  symbolize:\n    𝑛2: 𝑛1\n  𝑛: 𝑛3\n"-      order known "L_fork" `shouldBe` ["𝑛2", "𝑛3"]+    it "reads an entry naming no 'stay' block at all" $ do+      known <- lambdasOf "- λ: L_hold\n  morph:\n    𝑛1: $.a\n  𝑛: 𝑛1\n"+      stays known "L_hold" `shouldBe` [] -    it "runs a 'join' line before the 'symbolize' line standing what it joined" $ do-      known <- lambdasOf "- λ: L_fork\n  morph:\n    𝑛1: $.a\n    𝑛2: $.b\n  symbolize:\n    𝑛4: 𝑛3\n  join:\n    𝑛3: [𝑛1, 𝑛2]\n  𝑛: 𝑛4\n"-      order known "L_fork" `shouldBe` ["𝑛3", "𝑛4"]+    it "reads the lines of 'symbolize' with the metas they read" $ do+      known <- lambdasOf "- λ: L_hold\n  morph:\n    𝑛1: $.a\n    𝑛2: $.b\n  symbolize:\n    𝑛4: 𝑛2\n    𝑛3: 𝑛1\n  𝑛: 𝑛3\n"+      symbolizes known "L_hold" `shouldBe` [("𝑛3", "𝑛1"), ("𝑛4", "𝑛2")] -    it "runs an 'upcast' block once the line binding either meta it reads has run" $ do-      known <- lambdasOf "- λ: L_fork\n  morph:\n    𝑛1: $.a\n    𝑛2: $.b\n  upcast:\n    𝑛3: 𝑛1\n    𝑛5: 𝑛4\n  unfold:\n    𝑛4: 𝑛2\n  𝑛: 𝑛3\n"-      order known "L_fork" `shouldBe` ["𝑛4", "𝑛3", "𝑛5"]+    it "runs a 'symbolize' line after the 'stay' line binding the meta it reads" $ do+      known <- lambdasOf "- λ: L_hold\n  morph:\n    𝑛1: $.a\n  symbolize:\n    𝑛3: 𝑛2\n  stay:\n    𝑛2: 𝑛1\n  𝑛: 𝑛3\n"+      order known "L_hold" `shouldBe` ["𝑛2", "𝑛3"] -    it "runs a line reading either meta an 'upcast' block binds after that block" $ do-      known <- lambdasOf "- λ: L_fork\n  morph:\n    𝑛1: $.a\n    𝑛2: $.b\n  evaluate:\n    𝑛3: 𝑛5\n  upcast:\n    𝑛4: 𝑛1\n    𝑛5: 𝑛2\n  𝑛: 𝑛3\n"-      order known "L_fork" `shouldBe` ["𝑛4", "𝑛5", "𝑛3"]+    it "runs a 'stay' line before a 'symbolize' line no meta orders" $ do+      known <- lambdasOf "- λ: L_hold\n  morph:\n    𝑛1: $.a\n    𝑛2: $.b\n  symbolize:\n    𝑛3: 𝑛1\n  stay:\n    𝑛4: 𝑛2\n  𝑛: 𝑛3\n"+      order known "L_hold" `shouldBe` ["𝑛4", "𝑛3"] -    it "runs the lines of a fork in the order of the metas they read, whatever the order of their blocks" $ do-      known <- lambdasOf (T.concat ["- λ: L_fork\n  morph:\n    𝑛1: $.a\n    𝑛2: $.b\n  join:\n    𝑛15: [𝑛13, 𝑛14]\n  upcast:\n    𝑛13: 𝑛11\n    𝑛14: 𝑛12\n  symbolize:\n    𝑛11: 𝑛9\n    𝑛12: 𝑛10\n  stay:\n    𝑛9: 𝑛7\n    𝑛10: 𝑛8\n  rewrite:\n    𝑛7:\n      of: 𝑛5\n", rules, "    𝑛8:\n      of: 𝑛6\n", rules, "  unfold:\n    𝑛5: {of: 𝑛3, defer: true}\n    𝑛6: {of: 𝑛4, defer: true}\n  evaluate:\n    𝑛3: 𝑛1\n    𝑛4: 𝑛2\n  𝑛: 𝑛15\n"])-      order known "L_fork" `shouldBe` ["𝑛3", "𝑛4", "𝑛5", "𝑛6", "𝑛7", "𝑛8", "𝑛9", "𝑛10", "𝑛11", "𝑛12", "𝑛13", "𝑛14", "𝑛15"]+    it "reads an entry naming no 'symbolize' block at all" $ do+      known <- lambdasOf "- λ: L_hold\n  morph:\n    𝑛1: $.a\n  𝑛: 𝑛1\n"+      symbolizes known "L_hold" `shouldBe` [] -    it "runs the lines no meta orders in the order of their blocks" $ do-      known <- lambdasOf "- λ: L_fork\n  morph:\n    𝑛1: $.a\n    𝑛2: $.b\n  join:\n    𝑛3: [𝑛1, 𝑛2]\n  upcast:\n    𝑛7: 𝑛1\n    𝑛9: 𝑛2\n  symbolize:\n    𝑛4: 𝑛1\n  stay:\n    𝑛5: 𝑛2\n  unfold:\n    𝑛8: 𝑛2\n  evaluate:\n    𝑛6: 𝑛1\n  𝑛: 𝑛3\n"-      order known "L_fork" `shouldBe` ["𝑛6", "𝑛8", "𝑛5", "𝑛4", "𝑛7", "𝑛9", "𝑛3"]+    it "reads the lines of 'rewrite' with the metas they read and the count of their rules" $ do+      known <- lambdasOf ("- λ: L_hold\n  morph:\n    𝑛1: $.a\n    𝑛2: $.b\n  rewrite:\n    𝑛4:\n      of: 𝑛2\n" <> truth <> "        - name: false-literal\n          pattern: Φ.false\n          result: Φ.bool( if ↦ ⟦ Δ ⤍ 00- ⟧ )\n    𝑛3:\n      of: 𝑛1\n" <> truth <> "  𝑛: 𝑛3\n")+      rewrites known "L_hold" `shouldBe` [("𝑛3", "𝑛1", 1), ("𝑛4", "𝑛2", 2)] -    it "reads an entry naming no 'stay' block at all" $ do-      known <- lambdasOf "- λ: L_fork\n  morph:\n    𝑛1: $.a\n  𝑛: 𝑛1\n"-      stays known "L_fork" `shouldBe` []+    it "reads a line of 'rewrite' sharing its rules with another one through a YAML anchor" $ do+      known <- lambdasOf "- λ: L_hold\n  morph:\n    𝑛1: $.a\n    𝑛2: $.b\n  rewrite:\n    𝑛3:\n      of: 𝑛1\n      rules: &bool\n        - name: true-literal\n          pattern: Φ.true\n          result: Φ.bool( if ↦ ⟦ Δ ⤍ 01- ⟧ )\n    𝑛4:\n      of: 𝑛2\n      rules: *bool\n  𝑛: 𝑛3\n"+      rewrites known "L_hold" `shouldBe` [("𝑛3", "𝑛1", 1), ("𝑛4", "𝑛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"-      fired known "L_fork" `shouldBe` []+    it "runs a 'rewrite' line after the 'evaluate' line binding the meta it reads" $ do+      known <- lambdasOf ("- λ: L_hold\n  morph:\n    𝑛1: $.a\n  rewrite:\n    𝑛3:\n      of: 𝑛2\n" <> truth <> "  evaluate:\n    𝑛2: 𝑛1\n  𝑛: 𝑛3\n")+      order known "L_hold" `shouldBe` ["𝑛2", "𝑛3"] -    it "reads an entry naming no 'join' block at all" $ do-      known <- lambdasOf "- λ: L_pair\n  𝑛: ⟦ λ ⤍ 𝜎 ⟧\n"-      joins known "L_pair" `shouldBe` []+    it "runs a 'rewrite' line before a 'symbolize' line no meta orders" $ do+      known <- lambdasOf ("- λ: L_hold\n  morph:\n    𝑛1: $.a\n    𝑛2: $.b\n  symbolize:\n    𝑛3: 𝑛1\n  rewrite:\n    𝑛4:\n      of: 𝑛2\n" <> truth <> "  𝑛: 𝑛3\n")+      order known "L_hold" `shouldBe` ["𝑛4", "𝑛3"] +    it "reads a 'join' block whose sides are metas 'rewrite' bound" $ do+      known <- lambdasOf ("- λ: L_fork\n  dataize:\n    𝛿1: $.c\n  morph:\n    𝑛1: $.a\n    𝑛2: $.b\n  rewrite:\n    𝑛3:\n      of: 𝑛1\n" <> truth <> "    𝑛4:\n      of: 𝑛2\n" <> truth <> "  join:\n    on: 𝛿1\n    left: 𝑛3\n    right: 𝑛4\n")+      left <- parseExpressionThrows "𝑛3"+      right <- parseExpressionThrows "𝑛4"+      sides known "L_fork" `shouldBe` Just (left, right)++    it "reads an entry naming no 'rewrite' block at all" $ do+      known <- lambdasOf "- λ: L_hold\n  morph:\n    𝑛1: $.a\n  𝑛: 𝑛1\n"+      rewrites known "L_hold" `shouldBe` []++    it "reads an entry naming no 'defer' block at all" $ do+      known <- lambdasOf "- λ: L_hold\n  morph:\n    𝑛1: $.a\n  𝑛: 𝑛1\n"+      defers known "L_hold" `shouldBe` []+     it "reads an entry naming neither operand block" $ do       known <- lambdasOf "- λ: L_pair\n  𝑛: ⟦ λ ⤍ 𝜎 ⟧\n"       map (_spelling . fst) (maybe [] _dataized (matched known "L_pair")) `shouldBe` []@@ -267,7 +326,7 @@     forM_       [ ("a file which is no list of entries" :: String, "λ: L_pair\n" :: T.Text, "cannot be read" :: String)       , ("an entry with no λ key", "- 𝑛: ⟦ λ ⤍ 𝜎 ⟧\n", "no 'λ' key")-      , ("an entry with no answer at all", "- λ: L_pair\n", "no '𝑛' key")+      , ("an entry with no answer at all", "- λ: L_pair\n", "The entry of λ function 'L_pair' has neither an '𝑛' key nor a 'join' block")       , ("an entry whose answer is no term of the calculus", "- λ: L_pair\n  𝑛: ⟦ λ ⤍\n", "cannot be read")       , ("two entries under one key", entry "L_pair" <> entry "L_pair", "is used by more than one entry")       ,@@ -284,463 +343,252 @@       , ("a key which is no regular expression", entry "L_[pair", "is not a regular expression")       , ("an operand of 'dataize' which is no bytes meta", "- λ: L_pair\n  dataize:\n    𝑛1: $.x\n  𝑛: ⟦ λ ⤍ 𝜎 ⟧\n", "is not a bytes meta")       , ("an operand of 'morph' which is no expression meta", "- λ: L_pair\n  morph:\n    𝛿1: $.x\n  𝑛: ⟦ λ ⤍ 𝜎 ⟧\n", "is not an expression meta")-      , ("an operand of 'symbolize' which is no expression meta", "- λ: L_pair\n  morph:\n    𝑛1: $.x\n  symbolize:\n    𝛿1: 𝑛1\n  𝑛: 𝑛1\n", "is not an expression meta")-      , ("an operand of 'symbolize' the entry never bound", "- λ: L_pair\n  symbolize:\n    𝑛2: 𝑛1\n  𝑛: 𝑛2\n", "names no meta")-      , ("an operand of 'symbolize' which is no meta at all", "- λ: L_pair\n  morph:\n    𝑛1: $.x\n  symbolize:\n    𝑛2: $.x\n  𝑛: 𝑛2\n", "is not an expression meta")       , ("an operand referencing a meta the entry never matched", "- λ: L_pair\n  dataize:\n    𝛿1: '!n'\n  𝑛: ⟦ λ ⤍ 𝜎 ⟧\n", "cannot be referenced")       , ("an answer reading the data its operands came down to", "- λ: L_pair\n  dataize:\n    𝛿1: $.ρ\n  𝑛: ⟦ Δ ⤍ 𝛿1 ⟧\n", "reads data")-      , ("a meta bound by 'morph' and again by 'symbolize'", "- λ: L_pair\n  morph:\n    𝑛1: $.x\n  symbolize:\n    𝑛1: 𝑛1\n  𝑛: 𝑛1\n", "The meta '𝑛1' of λ function 'L_pair' is bound by more than one line")       , ("an answer writing a numbered symbol", "- λ: L_pair\n  𝑛: '⟦ a ↦ ⟦ λ ⤍ 𝜎1 ⟧, b ↦ ⟦ λ ⤍ 𝜎 ⟧ ⟧'\n", "writes the numbered symbol '𝜎1'")       , ("an answer carrying an anonymous meta of another kind", "- λ: L_pair\n  𝑛: '⟦ φ ↦ !n ⟧'\n", "cannot be referenced")-      , ("a 'join' line joining one meta alone", "- λ: L_fork\n  morph:\n    𝑛1: $.then\n  join:\n    𝑛2: [𝑛1]\n  𝑛: 𝑛2\n", "must join exactly two metas")-      , ("a 'join' line joining three metas", "- λ: L_fork\n  morph:\n    𝑛1: $.a\n    𝑛2: $.b\n    𝑛3: $.c\n  join:\n    𝑛4: [𝑛1, 𝑛2, 𝑛3]\n  𝑛: 𝑛4\n", "must join exactly two metas")-      , ("a 'join' line joining what is no expression meta", "- λ: L_fork\n  morph:\n    𝑛1: $.then\n  join:\n    𝑛2: [𝑛1, $.else]\n  𝑛: 𝑛2\n", "is not an expression meta")-      , ("a 'join' line bound to what is no expression meta", "- λ: L_fork\n  morph:\n    𝑛1: $.a\n    𝑛2: $.b\n  join:\n    𝛿1: [𝑛1, 𝑛2]\n  𝑛: 𝑛1\n", "is not an expression meta")-      , ("a 'join' line joining a meta the entry never bound", "- λ: L_fork\n  morph:\n    𝑛1: $.then\n  join:\n    𝑛3: [𝑛1, 𝑛2]\n  𝑛: 𝑛3\n", "names no meta bound by 'morph' or by any line")-      , ("a 'join' line joining a meta that came down to data", "- λ: L_fork\n  dataize:\n    𝛿1: $.ρ\n  morph:\n    𝑛1: $.then\n  join:\n    𝑛2: [𝑛1, 𝛿1]\n  𝑛: 𝑛2\n", "is not an expression meta")-      , ("a 'rewrite' line rewriting a meta the entry never bound", rewriting "    𝑛2:\n      of: 𝑛5\n" <> rules <> "  𝑛: 𝑛2\n", "names no meta bound by 'morph' or by any line")-      , ("a 'rewrite' line bound to what is no expression meta", rewriting "    𝛿1:\n      of: 𝑛1\n" <> rules <> "  𝑛: 𝑛1\n", "is not an expression meta")-      , ("a 'rewrite' line with no rules at all", rewriting "    𝑛2:\n      of: 𝑛1\n  𝑛: 𝑛2\n", "cannot be read")-      , ("a rule of 'rewrite' minting a fresh symbol", rewriting "    𝑛2:\n      of: 𝑛1\n      rules:\n        - name: minting\n          pattern: ⟦ φ ↦ 𝑒1 ⟧\n          result: ⟦ λ ⤍ 𝜎 ⟧\n  𝑛: 𝑛2\n", "writes a symbol 𝜎 into its result")-      , ("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 any line")-      , ("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")-      , ("a 'stay' line keeping a meta the entry never bound", "- λ: L_fork\n  morph:\n    𝑛1: $.a\n  stay:\n    𝑛3: 𝑛2\n  𝑛: 𝑛3\n", "names no meta bound by 'morph' or by any line")-      , ("a 'stay' line bound to what is no expression meta", "- λ: L_fork\n  morph:\n    𝑛1: $.a\n  stay:\n    𝛿1: 𝑛1\n  𝑛: 𝑛1\n", "is not an expression meta")-      , ("a 'stay' line keeping a term instead of a meta", "- λ: L_fork\n  morph:\n    𝑛1: $.a\n  stay:\n    𝑛2: $.a\n  𝑛: 𝑛2\n", "is not an expression meta")-      , ("a meta bound by 'morph' and again by 'stay'", "- λ: L_fork\n  morph:\n    𝑛1: $.a\n  stay:\n    𝑛1: 𝑛1\n  𝑛: 𝑛1\n", "The meta '𝑛1' of λ function 'L_fork' is bound by more than one line")-      , ("a line reading the meta it binds", "- λ: L_fork\n  morph:\n    𝑛1: $.a\n  evaluate:\n    𝑛2: 𝑛2\n  𝑛: 𝑛2\n", "The line binding '𝑛2' of λ function 'L_fork' reads the meta it binds")-      , ("two lines reading what each other made", "- λ: L_fork\n  morph:\n    𝑛1: $.a\n  evaluate:\n    𝑛2: 𝑛3\n  symbolize:\n    𝑛3: 𝑛2\n  𝑛: 𝑛2\n", "The line binding '𝑛2' of λ function 'L_fork' reads the meta it binds")-      , ("a line reading what two lines reading each other made, blaming one of the two", "- λ: L_fork\n  morph:\n    𝑛1: $.a\n  evaluate:\n    𝑛5: 𝑛3\n  symbolize:\n    𝑛3: 𝑛4\n    𝑛4: 𝑛3\n  𝑛: 𝑛5\n", "The line binding '𝑛3' of λ function 'L_fork' reads the meta it binds")+      , ("an 'evaluate' line firing a meta the entry never bound", "- λ: L_hold\n  morph:\n    𝑛1: $.a\n  evaluate:\n    𝑛3: 𝑛2\n  𝑛: 𝑛3\n", "names no meta bound by 'morph' or by any line")+      , ("an 'evaluate' line bound to what is no expression meta", "- λ: L_hold\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_hold\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_hold\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_hold\n  morph:\n    𝑛1: $.a\n  evaluate:\n    𝑛1: 𝑛1\n  𝑛: 𝑛1\n", "The meta '𝑛1' of λ function 'L_hold' is bound by more than one line")+      , ("a line reading the meta it binds", "- λ: L_hold\n  morph:\n    𝑛1: $.a\n  evaluate:\n    𝑛2: 𝑛2\n  𝑛: 𝑛2\n", "The line binding '𝑛2' of λ function 'L_hold' reads the meta it binds")+      , ("two lines reading what each other made", "- λ: L_hold\n  morph:\n    𝑛1: $.a\n  evaluate:\n    𝑛2: 𝑛3\n  defer:\n    𝑛3: 𝑛2\n  𝑛: 𝑛2\n", "The line binding '𝑛2' of λ function 'L_hold' reads the meta it binds")+      , ("a line reading what two lines reading each other made, blaming one of the two", "- λ: L_hold\n  morph:\n    𝑛1: $.a\n  evaluate:\n    𝑛5: 𝑛3\n  defer:\n    𝑛3: 𝑛4\n    𝑛4: 𝑛3\n  𝑛: 𝑛5\n", "The line binding '𝑛3' of λ function 'L_hold' reads the meta it binds")       , ("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'")-      , ("a lenient entry with no 'join' line to be lenient in", "- λ: L_pair\n  dataize:\n    𝛿1: $.ρ\n  lenient: []\n  𝑛: ⟦ λ ⤍ 𝜎 ⟧\n", "is lenient but has no 'join' line")-      , ("a 'lenient' key which is no list", "- λ: L_fork\n  morph:\n    𝑛1: $.a\n    𝑛2: $.b\n  join:\n    𝑛3: [𝑛1, 𝑛2]\n  lenient: true\n  𝑛: 𝑛3\n", "lenient: parsing [] failed, expected Array")-      , ("a 'lenient' key naming an option phino does not know", "- λ: L_fork\n  morph:\n    𝑛1: $.a\n    𝑛2: $.b\n  join:\n    𝑛3: [𝑛1, 𝑛2]\n  lenient: [defr]\n  𝑛: 𝑛3\n", "The option 'defr' of 'lenient' of λ function 'L_fork' is unknown")-      , ("a 'lenient' key naming 'defer', which an 'unfold' line took over", "- λ: L_fork\n  morph:\n    𝑛1: $.a\n    𝑛2: $.b\n  join:\n    𝑛3: [𝑛1, 𝑛2]\n  lenient: [defer]\n  𝑛: 𝑛3\n", "The option 'defer' of 'lenient' of λ function 'L_fork' is gone")-      , ("a 'lenient' key naming 'upcast', which an 'upcast' line took over", "- λ: L_fork\n  morph:\n    𝑛1: $.a\n    𝑛2: $.b\n  join:\n    𝑛3: [𝑛1, 𝑛2]\n  lenient: [upcast, defer]\n  𝑛: 𝑛3\n", "The option 'upcast' of 'lenient' of λ function 'L_fork' is gone")-      , ("an 'unfold' line unfolding a meta the entry never bound", "- λ: L_fork\n  morph:\n    𝑛1: $.a\n  unfold:\n    𝑛3: 𝑛2\n  𝑛: 𝑛3\n", "names no meta bound by 'morph' or by any line")-      , ("an 'unfold' line naming a list", "- λ: L_fork\n  morph:\n    𝑛1: $.a\n  unfold:\n    𝑛2: [𝑛1]\n  𝑛: 𝑛2\n", "The line '𝑛2' of 'unfold' of λ function 'L_fork' names neither a meta nor a mapping with 'of'")-      , ("an 'unfold' line with no 'of'", "- λ: L_fork\n  morph:\n    𝑛1: $.a\n  unfold:\n    𝑛2: {defer: true}\n  𝑛: 𝑛2\n", "key \"of\" not found")-      , ("an 'unfold' line whose 'defer' is no flag", "- λ: L_fork\n  morph:\n    𝑛1: $.a\n  unfold:\n    𝑛2: {of: 𝑛1, defer: often}\n  𝑛: 𝑛2\n", "expected Bool")-      , ("an 'upcast' block holding one line", "- λ: L_fork\n  morph:\n    𝑛1: $.a\n  upcast:\n    𝑛2: 𝑛1\n  𝑛: 𝑛2\n", "The 'upcast' block of λ function 'L_fork' holds 1 line, while it casts one pair and takes exactly two")-      , ("an 'upcast' block holding three lines", "- λ: L_fork\n  morph:\n    𝑛1: $.a\n    𝑛2: $.b\n    𝑛3: $.c\n  upcast:\n    𝑛4: 𝑛1\n    𝑛5: 𝑛2\n    𝑛6: 𝑛3\n  𝑛: 𝑛4\n", "The 'upcast' block of λ function 'L_fork' holds 3 lines, while it casts one pair and takes exactly two")-      , ("an 'upcast' line written as the gone mapping with 'of' and 'to'", "- λ: L_fork\n  morph:\n    𝑛1: $.a\n    𝑛2: $.b\n  upcast:\n    𝑛3: {of: 𝑛1, to: 𝑛2}\n    𝑛4: 𝑛2\n  𝑛: 𝑛3\n", "The line '𝑛3' of 'upcast' of λ function 'L_fork' names no meta")-      , ("an 'upcast' line reading a meta the entry never bound", "- λ: L_fork\n  morph:\n    𝑛1: $.a\n  upcast:\n    𝑛3: 𝑛1\n    𝑛4: 𝑛2\n  𝑛: 𝑛3\n", "names no meta bound by 'morph' or by any line")-      , ("a meta bound by 'unfold' and again by 'upcast'", "- λ: L_fork\n  morph:\n    𝑛1: $.a\n  unfold:\n    𝑛2: 𝑛1\n  upcast:\n    𝑛2: 𝑛1\n    𝑛3: 𝑛1\n  𝑛: 𝑛2\n", "The meta '𝑛2' of λ function 'L_fork' is bound by more than one line")-      , ("an 'upcast' block reading a meta it binds itself", "- λ: L_fork\n  morph:\n    𝑛1: $.a\n  upcast:\n    𝑛2: 𝑛1\n    𝑛3: 𝑛2\n  𝑛: 𝑛2\n", "reads the meta it binds")-      , ("a misspelled 'lenient' key", "- λ: L_fork\n  morph:\n    𝑛1: $.a\n    𝑛2: $.b\n  join:\n    𝑛3: [𝑛1, 𝑛2]\n  lenent: []\n  𝑛: 𝑛3\n", "The key 'lenent' of λ function 'L_fork' is none of the keys an entry reads")-      , ("a 'defer' key beside 'lenient', which now lists it", "- λ: L_fork\n  morph:\n    𝑛1: $.a\n    𝑛2: $.b\n  join:\n    𝑛3: [𝑛1, 𝑛2]\n  lenient: []\n  defer: true\n  𝑛: 𝑛3\n", "The key 'defer' of λ function 'L_fork' is none of the keys an entry reads")+      , ("an operand of 'morph' reading a meta inside a term", "- λ: L_hold\n  morph:\n    𝑛1: $.a\n    𝑛2: 𝑛1.x\n  𝑛: 𝑛2\n", "The operand '𝑛2' of 'morph' of λ function 'L_hold' reads the meta 'n1', while only a path from '$' or a meta alone can be reduced there")+      , ("an operand of 'morph' naming a meta nothing binds", "- λ: L_hold\n  morph:\n    𝑛1: $.a\n    𝑛2: 𝑛5\n  𝑛: 𝑛2\n", "The operand '𝑛5' of λ function 'L_hold' names no meta bound by 'morph' or by any line")+      , ("an operand of 'morph' which is no term of the calculus", "- λ: L_hold\n  morph:\n    𝑛1: ⟦ λ ⤍\n  𝑛: 𝑛1\n", "Error in $[0].morph.𝑛1: expression:1:6")+      , ("an operand of 'morph' naming the meta it binds", "- λ: L_hold\n  morph:\n    𝑛1: 𝑛1\n  𝑛: 𝑛1\n", "The line binding '𝑛1' of λ function 'L_hold' reads the meta it binds")+      , ("a 'defer' line deferring a meta the entry never bound", "- λ: L_hold\n  morph:\n    𝑛1: $.a\n  defer:\n    𝑛3: 𝑛2\n  𝑛: 𝑛3\n", "names no meta bound by 'morph' or by any line")+      , ("a 'defer' line bound to what is no expression meta", "- λ: L_hold\n  morph:\n    𝑛1: $.a\n  defer:\n    𝛿1: 𝑛1\n  𝑛: 𝑛1\n", "is not an expression meta")+      , ("a meta bound by 'defer' and again by 'evaluate'", "- λ: L_hold\n  morph:\n    𝑛1: $.a\n  defer:\n    𝑛2: 𝑛1\n  evaluate:\n    𝑛2: 𝑛1\n  𝑛: 𝑛2\n", "The meta '𝑛2' of λ function 'L_hold' is bound by more than one line")+      , ("a meta bound by 'evaluate' and again by 'defer' in its other spelling", "- λ: L_hold\n  morph:\n    𝑛1: $.a\n  evaluate:\n    𝑛3: 𝑛1\n  defer:\n    \"!n3\": 𝑛1\n  𝑛: 𝑛3\n", "The meta '!n3' of λ function 'L_hold' is bound by more than one line")+      , ("a meta bound twice by 'defer' in its two spellings", "- λ: L_hold\n  morph:\n    𝑛1: $.a\n    𝑛2: $.b\n  defer:\n    𝑛3: 𝑛1\n    \"!n3\": 𝑛2\n  𝑛: 𝑛3\n", "is bound by more than one line")+      , ("a 'stay' line keeping a meta the entry never bound", "- λ: L_hold\n  morph:\n    𝑛1: $.a\n  stay:\n    𝑛3: 𝑛2\n  𝑛: 𝑛3\n", "names no meta bound by 'morph' or by any line")+      , ("a 'stay' line bound to what is no expression meta", "- λ: L_hold\n  morph:\n    𝑛1: $.a\n  stay:\n    𝛿1: 𝑛1\n  𝑛: 𝑛1\n", "is not an expression meta")+      , ("a 'stay' line keeping a term instead of a meta", "- λ: L_hold\n  morph:\n    𝑛1: $.a\n  stay:\n    𝑛2: $.a\n  𝑛: 𝑛2\n", "is not an expression meta")+      , ("a 'symbolize' line reading a meta the entry never bound", "- λ: L_hold\n  morph:\n    𝑛1: $.a\n  symbolize:\n    𝑛3: 𝑛2\n  𝑛: 𝑛3\n", "names no meta bound by 'morph' or by any line")+      , ("a 'symbolize' line reading a term instead of a meta", "- λ: L_hold\n  morph:\n    𝑛1: $.a\n  symbolize:\n    𝑛2: $.a\n  𝑛: 𝑛2\n", "is not an expression meta")+      , ("a meta bound by 'stay' and again by 'symbolize'", "- λ: L_hold\n  morph:\n    𝑛1: $.a\n  stay:\n    𝑛2: 𝑛1\n  symbolize:\n    𝑛2: 𝑛1\n  𝑛: 𝑛2\n", "The meta '𝑛2' of λ function 'L_hold' is bound by more than one line")+      , ("a 'rewrite' line reading a meta the entry never bound", "- λ: L_hold\n  morph:\n    𝑛1: $.a\n  rewrite:\n    𝑛3:\n      of: 𝑛2\n" <> truth <> "  𝑛: 𝑛3\n", "names no meta bound by 'morph' or by any line")+      , ("a 'rewrite' line bound to what is no expression meta", "- λ: L_hold\n  morph:\n    𝑛1: $.a\n  rewrite:\n    𝛿1:\n      of: 𝑛1\n" <> truth <> "  𝑛: 𝑛1\n", "is not an expression meta")+      , ("a 'rewrite' line reading a term instead of a meta", "- λ: L_hold\n  morph:\n    𝑛1: $.a\n  rewrite:\n    𝑛2:\n      of: $.a\n" <> truth <> "  𝑛: 𝑛2\n", "is not an expression meta")+      , ("a 'rewrite' line naming no meta it reads", "- λ: L_hold\n  morph:\n    𝑛1: $.a\n  rewrite:\n    𝑛2:\n" <> truth <> "  𝑛: 𝑛2\n", "The line '𝑛2' of the 'rewrite' block of λ function 'L_hold' has no 'of' key")+      , ("a 'rewrite' line holding no rules", "- λ: L_hold\n  morph:\n    𝑛1: $.a\n  rewrite:\n    𝑛2:\n      of: 𝑛1\n  𝑛: 𝑛2\n", "The line '𝑛2' of the 'rewrite' block of λ function 'L_hold' has no 'rules' key")+      , ("a 'rewrite' line with a key it does not read", "- λ: L_hold\n  morph:\n    𝑛1: $.a\n  rewrite:\n    𝑛2:\n      of: 𝑛1\n      once: true\n" <> truth <> "  𝑛: 𝑛2\n", "The key 'once' of the line '𝑛2' of the 'rewrite' block of λ function 'L_hold' is none of its keys: of, rules")+      , ("a 'rewrite' line naming its meta alone, as a 'stay' line does", "- λ: L_hold\n  morph:\n    𝑛1: $.a\n  rewrite:\n    𝑛2: 𝑛1\n  𝑛: 𝑛2\n", "rewrite.𝑛2: parsing KeyMap failed, expected Object, but encountered String")+      , ("a rule of 'rewrite' minting a fresh symbol", "- λ: L_hold\n  morph:\n    𝑛1: $.a\n  rewrite:\n    𝑛2:\n      of: 𝑛1\n      rules:\n        - name: minting\n          pattern: Φ.true\n          result: ⟦ λ ⤍ 𝜎 ⟧\n  𝑛: 𝑛2\n", "anonymous meta '!S' cannot be referenced in 'result' of rule 'minting'")+      , ("a rule of 'rewrite' writing a symbol nobody minted", "- λ: L_hold\n  morph:\n    𝑛1: $.a\n  rewrite:\n    𝑛2:\n      of: 𝑛1\n      rules:\n        - name: naming\n          pattern: Φ.true\n          result: ⟦ λ ⤍ 𝜎4 ⟧\n  𝑛: 𝑛2\n", "The rule 'naming' of the 'rewrite' block of λ function 'L_hold' writes the symbol '𝜎4' into its result, while only a firing mints a symbol")+      , ("a rule of 'rewrite' reading a meta it never binds", "- λ: L_hold\n  morph:\n    𝑛1: $.a\n  rewrite:\n    𝑛2:\n      of: 𝑛1\n      rules:\n        - name: unbound\n          pattern: ⟦ φ ↦ 𝑒1 ⟧\n          result: ⟦ φ ↦ 𝑒2 ⟧\n  𝑛: 𝑛2\n", "The rule 'unbound' reads the meta 'e2' it never binds")+      , ("a rule of 'rewrite' with no result", "- λ: L_hold\n  morph:\n    𝑛1: $.a\n  rewrite:\n    𝑛2:\n      of: 𝑛1\n      rules:\n        - name: empty\n          pattern: Φ.true\n  𝑛: 𝑛2\n", "key \"result\" not found")+      , ("a meta bound by 'morph' and again by 'stay'", "- λ: L_hold\n  morph:\n    𝑛1: $.a\n  stay:\n    𝑛1: 𝑛1\n  𝑛: 𝑛1\n", "The meta '𝑛1' of λ function 'L_hold' is bound by more than one line")+      , ("a meta bound by 'morph' and again by 'defer'", "- λ: L_hold\n  morph:\n    𝑛1: $.a\n  defer:\n    𝑛1: 𝑛1\n  𝑛: 𝑛1\n", "The meta '𝑛1' of λ function 'L_hold' is bound by more than one line")+      , ("an entry with both an '𝑛' key and a 'join' block", joint <> "  𝑛: ⟦ λ ⤍ 𝜎 ⟧\n", "The entry of λ function 'L_fork' has both an '𝑛' key and a 'join' block, while a join answers the term it joins")+      , ("a 'join' block joining on an expression meta", "- λ: L_fork\n  join:\n    on: 𝑛1\n    left: $.a\n    right: $.b\n", "The 'on' of the 'join' block of λ function 'L_fork' is '𝑛1', while it must name a bytes meta of 'dataize', such as '𝛿1', or be a path from '$', such as '$.on'")+      , ("a 'join' block joining on a bytes meta 'dataize' never bound", "- λ: L_fork\n  dataize:\n    𝛿1: $.c\n  join:\n    on: 𝛿2\n    left: $.a\n    right: $.b\n", "The 'on' of the 'join' block of λ function 'L_fork' is '𝛿2', while it must name a bytes meta of 'dataize'")+      , ("a 'join' block joining on a path off 'Φ'", "- λ: L_fork\n  join:\n    on: Φ.c\n    left: $.a\n    right: $.b\n", "The 'on' of the 'join' block of λ function 'L_fork' is 'Φ.c'")+      , ("a 'join' block joining on an application", "- λ: L_fork\n  join:\n    on: $.c(x ↦ $.d)\n    left: $.a\n    right: $.b\n", "The 'on' of the 'join' block of λ function 'L_fork' is '$.c(x ↦ $.d)'")+      , ("a 'join' block with no 'on' key", "- λ: L_fork\n  dataize:\n    𝛿1: $.c\n  join:\n    left: $.a\n    right: $.b\n", "The 'join' block of λ function 'L_fork' has no 'on' key")+      , ("a 'join' block with no 'left' key", "- λ: L_fork\n  dataize:\n    𝛿1: $.c\n  join:\n    on: 𝛿1\n    right: $.b\n", "The 'join' block of λ function 'L_fork' has no 'left' key")+      , ("a 'join' block with no 'right' key", "- λ: L_fork\n  dataize:\n    𝛿1: $.c\n  join:\n    on: 𝛿1\n    left: $.a\n", "The 'join' block of λ function 'L_fork' has no 'right' key")+      , ("a 'join' block with a key it does not read", joint <> "    middle: $.m\n", "The key 'middle' of the 'join' block of λ function 'L_fork' is none of its keys: on, left, right, otherwise")+      , ("a 'join' block whose 'otherwise' is no λ name", joint <> "    otherwise: l_join\n", "The 'otherwise' of the 'join' block of λ function 'L_fork' is 'l_join', while it must name a λ function, such as 'L_join'")+      , ("a 'join' block whose 'otherwise' names a symbol", joint <> "    otherwise: 𝜎3\n", "The 'otherwise' of the 'join' block of λ function 'L_fork' is '𝜎3', while it must name a λ function, such as 'L_join'")+      , ("a 'join' block which is no mapping", "- λ: L_fork\n  dataize:\n    𝛿1: $.c\n  join: [𝛿1, $.a, $.b]\n", "join: parsing")+      , ("a side of a 'join' block naming a meta nothing binds", "- λ: L_fork\n  dataize:\n    𝛿1: $.c\n  join:\n    on: 𝛿1\n    left: 𝑛1\n    right: $.b\n", "The side 'left' of the 'join' block of λ function 'L_fork' reads the meta 'n1' that none of 'morph', 'evaluate', 'stay', 'rewrite', 'symbolize' and 'defer' binds")+      , ("a side of a 'join' block reading a meta inside a term", "- λ: L_fork\n  dataize:\n    𝛿1: $.c\n  morph:\n    𝑛1: $.a\n  join:\n    on: 𝛿1\n    left: 𝑛1.x\n    right: $.b\n", "The side 'left' of the 'join' block of λ function 'L_fork' reads the meta 'n1' inside a term, while a side is a path from '$' or a meta alone")+      , ("a side of a 'join' block which is no term of the calculus", "- λ: L_fork\n  dataize:\n    𝛿1: $.c\n  join:\n    on: 𝛿1\n    left: ⟦ λ ⤍\n    right: $.b\n", "cannot be read")+      , ("a 'fork' block, which is gone", "- λ: L_fork\n  dataize:\n    𝛿1: $.c\n  fork:\n    on: 𝛿1\n    left: $.a\n    right: $.b\n", "The key 'fork' of λ function 'L_fork' is none of the keys an entry reads")+      , ("an 'unfold' block, which is gone", "- λ: L_fork\n  morph:\n    𝑛1: $.a\n  unfold:\n    𝑛2: 𝑛1\n  𝑛: 𝑛2\n", "The key 'unfold' of λ function 'L_fork' is none of the keys an entry reads")+      , ("an 'unbox' block, which is gone", "- λ: L_fork\n  morph:\n    𝑛1: $.a\n  unbox:\n    𝑛2: 𝑛1\n  𝑛: 𝑛2\n", "The key 'unbox' of λ function 'L_fork' is none of the keys an entry reads")+      , ("an 'upcast' block, which is gone", "- λ: L_fork\n  morph:\n    𝑛1: $.a\n    𝑛2: $.b\n  upcast:\n    𝑛3: 𝑛1\n    𝑛4: 𝑛2\n  𝑛: 𝑛3\n", "The key 'upcast' of λ function 'L_fork' is none of the keys an entry reads")+      , ("a 'terminators' key, which is gone", "- λ: L_fork\n  morph:\n    𝑛1: $.a\n  terminators: [L_throw]\n  𝑛: 𝑛1\n", "The key 'terminators' of λ function 'L_fork' is none of the keys an entry reads")+      , ("a 'lenient' key, which is gone", "- λ: L_fork\n  morph:\n    𝑛1: $.a\n  lenient: []\n  𝑛: 𝑛1\n", "The key 'lenient' of λ function 'L_fork' is none of the keys an entry reads")       , ("a misspelled 'dataize' block", "- λ: L_pair\n  datize:\n    𝛿1: $.ρ\n  𝑛: ⟦ λ ⤍ 𝜎 ⟧\n", "The key 'datize' of λ function 'L_pair' is none of the keys an entry reads")-      , ("a 'terminators' key which is no list", "- λ: L_fork\n  morph:\n    𝑛1: $.a\n  terminators: L_throw\n  𝑛: 𝑛1\n", "expected Array")-      ,-        ( "a terminator an entry of the file answers"-        , "- λ: L_fork\n  morph:\n    𝑛1: $.a\n  terminators: [L_throw]\n  𝑛: 𝑛1\n" <> entry "L_th[a-z]+"-        , "the terminator 'L_throw' of the key 'L_fork' is answered by the key 'L_th[a-z]+'"-        )+      , ("a 'defer' key saying a flag instead of holding lines", "- λ: L_hold\n  morph:\n    𝑛1: $.a\n  defer: true\n  𝑛: 𝑛1\n", "defer: parsing")       ]       ( \(desc, text, reason) ->           it ("cannot read " ++ desc) $             lambdasOf text `shouldThrow` (\err -> reason `isInfixOf` show (err :: SomeException))       ) -  describe "emptyLambdas" $-    it "cannot read a λ function without the file naming one" $-      answering emptyLambdas "L_number_plus" `shouldBe` Nothing--  describe "minted" $ do-    it "mints one fresh symbol per bare 𝜎 the answer carries" $ do-      answer <- parseExpressionThrows "⟦ a ↦ ⟦ λ ⤍ 𝜎 ⟧, b ↦ ⟦ λ ⤍ 𝜎 ⟧ ⟧"-      map snd (fst (minted answer 4)) `shouldBe` [FnSymbol 5, FnSymbol 6]--    it "counts every symbol it minted into the state" $ do-      answer <- parseExpressionThrows "⟦ a ↦ ⟦ λ ⤍ 𝜎 ⟧, b ↦ ⟦ λ ⤍ 𝜎 ⟧ ⟧"-      snd (minted answer 4) `shouldBe` 6--    it "mints nothing for a symbol the answer already numbers" $ do-      answer <- parseExpressionThrows "⟦ λ ⤍ 𝜎1 ⟧"-      fst (minted answer 4) `shouldBe` []--    it "mints nothing for an answer carrying no symbol at all" $ do-      answer <- parseExpressionThrows "⟦ Δ ⤍ 00- ⟧"-      snd (minted answer 4) `shouldBe` 4--  describe "symbolized" $ do-    it "stands every datum of a term into an unknown" $ do-      term <- parseExpressionThrows "⟦ φ ↦ Φ.f( φ ↦ ⟦ Δ ⤍ 00- ⟧ )( t ↦ ⟦ Δ ⤍ FF- ⟧ ) ⟧"-      unknown <- parseExpressionThrows "⟦ φ ↦ Φ.f( φ ↦ ⟦ λ ⤍ 𝜎5 ⟧ )( t ↦ ⟦ λ ⤍ 𝜎6 ⟧ ) ⟧"-      let (masked, _, _) = symbolized term 4-      masked `shouldBe` unknown--    it "tells the data every symbol it minted stands for" $ do-      term <- parseExpressionThrows "⟦ φ ↦ Φ.f( φ ↦ ⟦ Δ ⤍ 00- ⟧ )( t ↦ ⟦ Δ ⤍ FF- ⟧ ) ⟧"-      let (_, known, _) = symbolized term 4-      known `shouldBe` [(5, BtOne "00"), (6, BtOne "FF")]--    it "counts every symbol it minted into the state" $ do-      term <- parseExpressionThrows "⟦ φ ↦ Φ.f( φ ↦ ⟦ Δ ⤍ 00- ⟧ )( t ↦ ⟦ Δ ⤍ FF- ⟧ ) ⟧"-      let (_, _, spent) = symbolized term 4-      spent `shouldBe` 6--    it "leaves a datum standing outside the φ chain alone" $ do-      term <- parseExpressionThrows "⟦ φ ↦ ⟦ Δ ⤍ 00- ⟧, neg ↦ ⟦ φ ↦ ⟦ Δ ⤍ FF- ⟧ ⟧ ⟧"-      unknown <- parseExpressionThrows "⟦ φ ↦ ⟦ λ ⤍ 𝜎5 ⟧, neg ↦ ⟦ φ ↦ ⟦ Δ ⤍ FF- ⟧ ⟧ ⟧"-      let (masked, known, spent) = symbolized term 4-      masked `shouldBe` unknown-      known `shouldBe` [(5, BtOne "00")]-      spent `shouldBe` 5--    it "leaves a term carrying no datum as it was written" $ do-      term <- parseExpressionThrows "Φ.number( φ ↦ ⟦ λ ⤍ 𝜎1 ⟧ )"-      let (masked, _, _) = symbolized term 4-      masked `shouldBe` term--    it "stands a datum standing as the argument of an application" $ do-      term <- parseExpressionThrows "Φ.number( φ ↦ Φ.bytes( φ ↦ ⟦ Δ ⤍ 00- ⟧ ) )"-      unknown <- parseExpressionThrows "Φ.number( φ ↦ Φ.bytes( φ ↦ ⟦ λ ⤍ 𝜎5 ⟧ ) )"-      let (masked, _, _) = symbolized term 4-      masked `shouldBe` unknown--    it "keeps what the formation of a datum carries besides the datum" $ do-      term <- parseExpressionThrows "⟦ Δ ⤍ 00-, ρ ↦ ⟦⟧ ⟧"-      unknown <- parseExpressionThrows "⟦ λ ⤍ 𝜎5, ρ ↦ ⟦⟧ ⟧"-      let (masked, _, _) = symbolized term 4-      masked `shouldBe` unknown--    it "leaves the data a ρ carries alone" $ do-      term <- parseExpressionThrows "⟦ φ ↦ ⟦ Δ ⤍ 00- ⟧, ρ ↦ ⟦ x ↦ ⟦ Δ ⤍ FF- ⟧ ⟧ ⟧"-      unknown <- parseExpressionThrows "⟦ φ ↦ ⟦ λ ⤍ 𝜎5 ⟧, ρ ↦ ⟦ x ↦ ⟦ Δ ⤍ FF- ⟧ ⟧ ⟧"-      let (masked, _, _) = symbolized term 4-      masked `shouldBe` unknown--    it "mints nothing for a term carrying no datum at all" $ do-      term <- parseExpressionThrows "Φ.number( φ ↦ ⟦ λ ⤍ 𝜎1 ⟧ )"-      let (_, _, spent) = symbolized term 4-      spent `shouldBe` 4-   describe "stayed" $ do-    let keeping :: String -> String -> IO (Expression, Expression)-        keeping written expected = do-          term <- parseExpressionThrows written-          kept <- parseExpressionThrows expected-          pure (stayed term, kept)-     it "drops the ρ a dispatch is applied to" $ do-      (term, kept) <- keeping "Φ.bytes( ρ ↦ ⟦ x ↦ ∅ ⟧ )" "Φ.bytes"-      term `shouldBe` kept+      term <- parseExpressionThrows "Φ.bytes( ρ ↦ ⟦ x ↦ ∅ ⟧ )"+      kept <- parseExpressionThrows "Φ.bytes"+      stayed term `shouldBe` kept      it "drops the ρ a dispatch is applied to after an argument binding no ρ" $ do-      (term, kept) <- keeping "Φ.bytes( φ ↦ ⟦ λ ⤍ 𝜎1 ⟧ )( ρ ↦ ⟦ x ↦ ∅ ⟧ )" "Φ.bytes( φ ↦ ⟦ λ ⤍ 𝜎1 ⟧ )"-      term `shouldBe` kept+      term <- parseExpressionThrows "Φ.bytes( φ ↦ ⟦ λ ⤍ 𝜎1 ⟧ )( ρ ↦ ⟦ x ↦ ∅ ⟧ )"+      kept <- parseExpressionThrows "Φ.bytes( φ ↦ ⟦ λ ⤍ 𝜎1 ⟧ )"+      stayed term `shouldBe` kept      it "drops every ρ a chain of applications gives a dispatch" $ do-      (term, kept) <- keeping "ξ.f( ρ ↦ ⟦ a ↦ ∅ ⟧ )( α0 ↦ ⟦⟧ )( ρ ↦ ⟦ b ↦ ∅ ⟧ )" "ξ.f( α0 ↦ ⟦⟧ )"-      term `shouldBe` kept+      term <- parseExpressionThrows "ξ.f( ρ ↦ ⟦ a ↦ ∅ ⟧ )( α0 ↦ ⟦⟧ )( ρ ↦ ⟦ b ↦ ∅ ⟧ )"+      kept <- parseExpressionThrows "ξ.f( α0 ↦ ⟦⟧ )"+      stayed term `shouldBe` kept      it "keeps the ρ a formation is applied to" $ do-      (term, kept) <- keeping "⟦ ρ ↦ ∅, t ↦ ∅ ⟧( t ↦ ⟦⟧ )( ρ ↦ ⟦ x ↦ ∅ ⟧ )" "⟦ ρ ↦ ∅, t ↦ ∅ ⟧( t ↦ ⟦⟧ )( ρ ↦ ⟦ x ↦ ∅ ⟧ )"-      term `shouldBe` kept+      term <- parseExpressionThrows "⟦ ρ ↦ ∅, t ↦ ∅ ⟧( t ↦ ⟦⟧ )( ρ ↦ ⟦ x ↦ ∅ ⟧ )"+      kept <- parseExpressionThrows "⟦ ρ ↦ ∅, t ↦ ∅ ⟧( t ↦ ⟦⟧ )( ρ ↦ ⟦ x ↦ ∅ ⟧ )"+      stayed term `shouldBe` kept      it "drops a ρ standing deep inside the argument of an application" $ do-      (term, kept) <- keeping "Φ.number( φ ↦ Φ.bytes( φ ↦ ⟦ λ ⤍ 𝜎1 ⟧ )( ρ ↦ ⟦ x ↦ ∅ ⟧ ) )" "Φ.number( φ ↦ Φ.bytes( φ ↦ ⟦ λ ⤍ 𝜎1 ⟧ ) )"-      term `shouldBe` kept+      term <- parseExpressionThrows "Φ.number( φ ↦ Φ.bytes( φ ↦ ⟦ λ ⤍ 𝜎1 ⟧ )( ρ ↦ ⟦ x ↦ ∅ ⟧ ) )"+      kept <- parseExpressionThrows "Φ.number( φ ↦ Φ.bytes( φ ↦ ⟦ λ ⤍ 𝜎1 ⟧ ) )"+      stayed term `shouldBe` kept      it "drops a ρ standing in a method of a formation and not only in its φ" $ do-      (term, kept) <- keeping "⟦ φ ↦ ⟦⟧, m ↦ ⟦ φ ↦ Φ.f( ρ ↦ ⟦ x ↦ ∅ ⟧ ) ⟧ ⟧" "⟦ φ ↦ ⟦⟧, m ↦ ⟦ φ ↦ Φ.f ⟧ ⟧"-      term `shouldBe` kept+      term <- parseExpressionThrows "⟦ φ ↦ ⟦⟧, m ↦ ⟦ φ ↦ Φ.f( ρ ↦ ⟦ x ↦ ∅ ⟧ ) ⟧ ⟧"+      kept <- parseExpressionThrows "⟦ φ ↦ ⟦⟧, m ↦ ⟦ φ ↦ Φ.f ⟧ ⟧"+      stayed term `shouldBe` kept      it "drops a ρ standing inside a ρ the term keeps" $ do-      (term, kept) <- keeping "⟦ x ↦ ∅ ⟧( ρ ↦ Φ.f( ρ ↦ ⟦ y ↦ ∅ ⟧ ) )" "⟦ x ↦ ∅ ⟧( ρ ↦ Φ.f )"-      term `shouldBe` kept--  describe "joined" $ do-    let joining :: Bool -> String -> String -> Int -> IO (Maybe (Expression, [(Int, (Int, Int))], Int))-        joining lenient left right spent = do-          one <- parseExpressionThrows left-          two <- parseExpressionThrows right-          pure (joined lenient one two spent)--    it "joins two branches differing in one symbol into a fresh one" $ do-      term <- parseExpressionThrows "⟦ φ ↦ ⟦ λ ⤍ 𝜎5 ⟧ ⟧"-      made <- joining False "⟦ φ ↦ ⟦ λ ⤍ 𝜎1 ⟧ ⟧" "⟦ φ ↦ ⟦ λ ⤍ 𝜎2 ⟧ ⟧" 4-      fmap (\(joint, _, _) -> joint) made `shouldBe` Just term--    it "tells the two symbols every fresh one stands for" $ do-      made <- joining False "⟦ φ ↦ ⟦ λ ⤍ 𝜎1 ⟧ ⟧" "⟦ φ ↦ ⟦ λ ⤍ 𝜎2 ⟧ ⟧" 4-      fmap (\(_, facts, _) -> facts) made `shouldBe` Just [(5, (1, 2))]--    it "counts every symbol it minted into the state" $ do-      made <- joining False "⟦ φ ↦ Φ.f( φ ↦ ⟦ λ ⤍ 𝜎1 ⟧ )( t ↦ ⟦ λ ⤍ 𝜎2 ⟧ ) ⟧" "⟦ φ ↦ Φ.f( φ ↦ ⟦ λ ⤍ 𝜎3 ⟧ )( t ↦ ⟦ λ ⤍ 𝜎4 ⟧ ) ⟧" 4-      fmap (\(_, _, spent) -> spent) made `shouldBe` Just 6--    it "mints one symbol per pair of differing symbols" $ do-      made <- joining False "⟦ φ ↦ Φ.f( φ ↦ ⟦ λ ⤍ 𝜎1 ⟧ )( t ↦ ⟦ λ ⤍ 𝜎2 ⟧ ) ⟧" "⟦ φ ↦ Φ.f( φ ↦ ⟦ λ ⤍ 𝜎3 ⟧ )( t ↦ ⟦ λ ⤍ 𝜎4 ⟧ ) ⟧" 4-      fmap (\(_, facts, _) -> facts) made `shouldBe` Just [(5, (1, 3)), (6, (2, 4))]--    it "mints one symbol for the pair it meets twice" $ do-      term <- parseExpressionThrows "⟦ φ ↦ Φ.f( φ ↦ ⟦ λ ⤍ 𝜎5 ⟧ )( t ↦ ⟦ λ ⤍ 𝜎5 ⟧ ) ⟧"-      made <- joining False "⟦ φ ↦ Φ.f( φ ↦ ⟦ λ ⤍ 𝜎1 ⟧ )( t ↦ ⟦ λ ⤍ 𝜎1 ⟧ ) ⟧" "⟦ φ ↦ Φ.f( φ ↦ ⟦ λ ⤍ 𝜎2 ⟧ )( t ↦ ⟦ λ ⤍ 𝜎2 ⟧ ) ⟧" 4-      made `shouldBe` Just (term, [(5, (1, 2))], 5)--    it "carries a method from the first branch and mints nothing for it" $ do-      term <- parseExpressionThrows "⟦ φ ↦ ⟦ λ ⤍ 𝜎5 ⟧, neg ↦ ⟦ φ ↦ ⟦ λ ⤍ 𝜎7 ⟧ ⟧ ⟧"-      made <- joining False "⟦ φ ↦ ⟦ λ ⤍ 𝜎1 ⟧, neg ↦ ⟦ φ ↦ ⟦ λ ⤍ 𝜎7 ⟧ ⟧ ⟧" "⟦ φ ↦ ⟦ λ ⤍ 𝜎2 ⟧, neg ↦ ⟦ φ ↦ ⟦ λ ⤍ 𝜎8 ⟧ ⟧ ⟧" 4-      made `shouldBe` Just (term, [(5, (1, 2))], 5)--    it "refuses two branches whose bindings are named differently" $ do-      made <- joining False "⟦ φ ↦ ⟦ λ ⤍ 𝜎1 ⟧, m ↦ ⟦ x ↦ ∅ ⟧ ⟧" "⟦ φ ↦ ⟦ λ ⤍ 𝜎2 ⟧, other ↦ ⟦ x ↦ ∅ ⟧ ⟧" 4-      made `shouldBe` Nothing--    it "joins two branches that are one term into that very term" $ do-      term <- parseExpressionThrows "Φ.number( φ ↦ ⟦ λ ⤍ 𝜎1 ⟧ )"-      made <- joining False "Φ.number( φ ↦ ⟦ λ ⤍ 𝜎1 ⟧ )" "Φ.number( φ ↦ ⟦ λ ⤍ 𝜎1 ⟧ )" 4-      made `shouldBe` Just (term, [], 4)--    it "joins two branches through the argument of an application" $ do-      term <- parseExpressionThrows "Φ.number( φ ↦ ⟦ λ ⤍ 𝜎5 ⟧ )"-      made <- joining False "Φ.number( φ ↦ ⟦ λ ⤍ 𝜎1 ⟧ )" "Φ.number( φ ↦ ⟦ λ ⤍ 𝜎2 ⟧ )" 4-      fmap (\(joint, _, _) -> joint) made `shouldBe` Just term--    it "leaves what a ρ carries alone" $ do-      term <- parseExpressionThrows "⟦ φ ↦ ⟦ λ ⤍ 𝜎5 ⟧, ρ ↦ ⟦ x ↦ ⟦ Δ ⤍ 00- ⟧ ⟧ ⟧"-      made <- joining False "⟦ φ ↦ ⟦ λ ⤍ 𝜎1 ⟧, ρ ↦ ⟦ x ↦ ⟦ Δ ⤍ 00- ⟧ ⟧ ⟧" "⟦ φ ↦ ⟦ λ ⤍ 𝜎2 ⟧, ρ ↦ ⟦ y ↦ ⟦ Δ ⤍ FF- ⟧ ⟧ ⟧" 4-      made `shouldBe` Just (term, [(5, (1, 2))], 5)--    it "joins a bare symbol with the symbol the φ chain of a formation ends in" $ do-      term <- parseExpressionThrows "⟦ λ ⤍ 𝜎5 ⟧"-      made <- joining False "⟦ φ ↦ ⟦ φ ↦ ⟦ λ ⤍ 𝜎1 ⟧ ⟧, eq ↦ ⟦ b ↦ ∅ ⟧ ⟧" "⟦ λ ⤍ 𝜎2 ⟧" 4-      made `shouldBe` Just (term, [(5, (1, 2))], 5)--    it "joins a formation with a bare symbol standing first" $ do-      term <- parseExpressionThrows "⟦ λ ⤍ 𝜎8 ⟧"-      made <- joining False "⟦ λ ⤍ 𝜎3 ⟧" "⟦ φ ↦ ⟦ λ ⤍ 𝜎6 ⟧, neg ↦ ⟦⟧ ⟧" 7-      made `shouldBe` Just (term, [(8, (3, 6))], 8)--    it "mints nothing for a bare symbol the φ chain of a formation ends in" $ do-      term <- parseExpressionThrows "⟦ λ ⤍ 𝜎2 ⟧"-      made <- joining False "⟦ φ ↦ ⟦ λ ⤍ 𝜎2 ⟧, m ↦ ⟦⟧ ⟧" "⟦ λ ⤍ 𝜎2 ⟧" 4-      made `shouldBe` Just (term, [], 4)--    it "joins two branches differing in their ρ alone" $ do-      term <- parseExpressionThrows "⟦ φ ↦ ⟦ λ ⤍ 𝜎1 ⟧, ρ ↦ ⟦ x ↦ ⟦ Δ ⤍ 00- ⟧ ⟧ ⟧"-      made <- joining False "⟦ φ ↦ ⟦ λ ⤍ 𝜎1 ⟧, ρ ↦ ⟦ x ↦ ⟦ Δ ⤍ 00- ⟧ ⟧ ⟧" "⟦ φ ↦ ⟦ λ ⤍ 𝜎1 ⟧, ρ ↦ ⟦ y ↦ ⟦ Δ ⤍ FF- ⟧ ⟧ ⟧" 4-      made `shouldBe` Just (term, [], 4)--    it "cannot join a term with a decorator of it unless the join is lenient" $ do-      made <- joining False "Φ.number( φ ↦ ⟦ λ ⤍ 𝜎1 ⟧ )" "⟦ φ ↦ Φ.number( φ ↦ ⟦ λ ⤍ 𝜎2 ⟧ ) ⟧" 4-      made `shouldBe` Nothing--    it "joins a term with a decorator of it into the decorator when lenient" $ do-      term <- parseExpressionThrows "⟦ φ ↦ Φ.number( φ ↦ ⟦ λ ⤍ 𝜎5 ⟧ ) ⟧"-      made <- joining True "Φ.number( φ ↦ ⟦ λ ⤍ 𝜎1 ⟧ )" "⟦ φ ↦ Φ.number( φ ↦ ⟦ λ ⤍ 𝜎2 ⟧ ) ⟧" 4-      made `shouldBe` Just (term, [(5, (1, 2))], 5)--    it "joins a decorator with the term it decorates standing second when lenient" $ do-      term <- parseExpressionThrows "⟦ φ ↦ Φ.number( φ ↦ ⟦ λ ⤍ 𝜎8 ⟧ ) ⟧"-      made <- joining True "⟦ φ ↦ Φ.number( φ ↦ ⟦ λ ⤍ 𝜎3 ⟧ ) ⟧" "Φ.number( φ ↦ ⟦ λ ⤍ 𝜎6 ⟧ )" 7-      made `shouldBe` Just (term, [(8, (3, 6))], 8)--    it "drops every binding of the decorator but its φ when lenient" $ do-      term <- parseExpressionThrows "⟦ φ ↦ Φ.number( φ ↦ ⟦ λ ⤍ 𝜎5 ⟧ ) ⟧"-      made <- joining True "Φ.number( φ ↦ ⟦ λ ⤍ 𝜎1 ⟧ )" "⟦ φ ↦ Φ.number( φ ↦ ⟦ λ ⤍ 𝜎2 ⟧ ), ρ ↦ ⟦ x ↦ ⟦⟧ ⟧, neg ↦ ⟦⟧ ⟧" 4-      fmap (\(joint, _, _) -> joint) made `shouldBe` Just term--    it "joins a copy with a decorator of another copy into the decorator when lenient" $ do-      term <- parseExpressionThrows "⟦ φ ↦ ⟦ φ ↦ ⟦ λ ⤍ 𝜎5 ⟧, neg ↦ ⟦⟧ ⟧ ⟧"-      made <- joining True "⟦ φ ↦ ⟦ λ ⤍ 𝜎1 ⟧, neg ↦ ⟦⟧ ⟧" "⟦ φ ↦ ⟦ φ ↦ ⟦ λ ⤍ 𝜎2 ⟧, neg ↦ ⟦⟧ ⟧ ⟧" 4-      fmap (\(joint, _, _) -> joint) made `shouldBe` Just term+      term <- parseExpressionThrows "⟦ x ↦ ∅ ⟧( ρ ↦ Φ.f( ρ ↦ ⟦ y ↦ ∅ ⟧ ) )"+      kept <- parseExpressionThrows "⟦ x ↦ ∅ ⟧( ρ ↦ Φ.f )"+      stayed term `shouldBe` kept -    it "joins a decorator of a copy with another copy standing second into the decorator when lenient" $ do-      term <- parseExpressionThrows "⟦ φ ↦ ⟦ φ ↦ ⟦ λ ⤍ 𝜎5 ⟧, neg ↦ ⟦⟧ ⟧ ⟧"-      made <- joining True "⟦ φ ↦ ⟦ φ ↦ ⟦ λ ⤍ 𝜎2 ⟧, neg ↦ ⟦⟧ ⟧ ⟧" "⟦ φ ↦ ⟦ λ ⤍ 𝜎1 ⟧, neg ↦ ⟦⟧ ⟧" 4-      fmap (\(joint, _, _) -> joint) made `shouldBe` Just term+    it "keeps a term carrying no ρ at all" $ do+      term <- parseExpressionThrows "Φ.number( φ ↦ Φ.bytes( φ ↦ ⟦ λ ⤍ 𝜎2 ⟧ ) )"+      kept <- parseExpressionThrows "Φ.number( φ ↦ Φ.bytes( φ ↦ ⟦ λ ⤍ 𝜎2 ⟧ ) )"+      stayed term `shouldBe` kept -    it "joins a term with a decorator of a decorator of it when lenient" $ do-      term <- parseExpressionThrows "⟦ φ ↦ ⟦ φ ↦ Φ.number( φ ↦ ⟦ λ ⤍ 𝜎5 ⟧ ) ⟧ ⟧"-      made <- joining True "Φ.number( φ ↦ ⟦ λ ⤍ 𝜎1 ⟧ )" "⟦ φ ↦ ⟦ φ ↦ Φ.number( φ ↦ ⟦ λ ⤍ 𝜎2 ⟧ ) ⟧ ⟧" 4-      fmap (\(joint, _, _) -> joint) made `shouldBe` Just term+  describe "rewritten" $ do+    it "rewrites the term a rule matches as a whole" $ do+      made <- rewrote [("Φ.true", "Φ.bool( if ↦ ⟦ Δ ⤍ 01- ⟧ )")] "Φ.true"+      expected <- parseExpressionThrows "Φ.bool( if ↦ ⟦ Δ ⤍ 01- ⟧ )"+      made `shouldBe` expected -    it "joins a term with a decorator of it inside an argument when lenient" $ do-      term <- parseExpressionThrows "Φ.f( x ↦ ⟦ φ ↦ Φ.number( φ ↦ ⟦ λ ⤍ 𝜎5 ⟧ ) ⟧ )"-      made <- joining True "Φ.f( x ↦ Φ.number( φ ↦ ⟦ λ ⤍ 𝜎1 ⟧ ) )" "Φ.f( x ↦ ⟦ φ ↦ Φ.number( φ ↦ ⟦ λ ⤍ 𝜎2 ⟧ ) ⟧ )" 4-      fmap (\(joint, _, _) -> joint) made `shouldBe` Just term+    it "rewrites a term a rule matches inside the argument of an application" $ do+      made <- rewrote [("Φ.true", "Φ.bool( if ↦ ⟦ Δ ⤍ 01- ⟧ )")] "Φ.jq( α0 ↦ Φ.true )"+      expected <- parseExpressionThrows "Φ.jq( α0 ↦ Φ.bool( if ↦ ⟦ Δ ⤍ 01- ⟧ ) )"+      made `shouldBe` expected -    it "joins a bare symbol with a term holding one symbol into a copy of that term when lenient" $ do-      term <- parseExpressionThrows "⟦ φ ↦ Φ.bool( if ↦ ⟦ λ ⤍ L_fork, φ ↦ ⟦ λ ⤍ 𝜎5 ⟧ ⟧ ) ⟧"-      made <- joining True "⟦ λ ⤍ 𝜎1 ⟧" "⟦ φ ↦ Φ.bool( if ↦ ⟦ λ ⤍ L_fork, φ ↦ ⟦ λ ⤍ 𝜎2 ⟧ ⟧ ) ⟧" 4-      made `shouldBe` Just (term, [(5, (1, 2))], 5)+    it "rewrites every place a rule matches inside a formation" $ do+      made <- rewrote [("Φ.true", "Φ.vw")] "⟦ kq ↦ Φ.true, φ ↦ ⟦ zt ↦ Φ.true ⟧ ⟧"+      expected <- parseExpressionThrows "⟦ kq ↦ Φ.vw, φ ↦ ⟦ zt ↦ Φ.vw ⟧ ⟧"+      made `shouldBe` expected -    it "joins a term holding one symbol with a bare symbol standing second when lenient" $ do-      term <- parseExpressionThrows "⟦ φ ↦ Φ.bool( if ↦ ⟦ λ ⤍ L_fork, φ ↦ ⟦ λ ⤍ 𝜎8 ⟧ ⟧ ) ⟧"-      made <- joining True "⟦ φ ↦ Φ.bool( if ↦ ⟦ λ ⤍ L_fork, φ ↦ ⟦ λ ⤍ 𝜎6 ⟧ ⟧ ) ⟧" "⟦ λ ⤍ 𝜎3 ⟧" 7-      made `shouldBe` Just (term, [(8, (6, 3))], 8)+    it "binds the metas of the pattern in the result" $ do+      made <- rewrote [("Φ.flag( α0 ↦ 𝑒1 )", "Φ.bool( if ↦ 𝑒1 )")] "Φ.flag( α0 ↦ ⟦ λ ⤍ 𝜎6 ⟧ )"+      expected <- parseExpressionThrows "Φ.bool( if ↦ ⟦ λ ⤍ 𝜎6 ⟧ )"+      made `shouldBe` expected -    it "keeps every binding of the term holding one symbol when lenient" $ do-      term <- parseExpressionThrows "⟦ φ ↦ Φ.number( φ ↦ ⟦ λ ⤍ 𝜎5 ⟧ ), ρ ↦ ⟦ x ↦ ⟦⟧ ⟧, neg ↦ ⟦⟧ ⟧"-      made <- joining True "⟦ λ ⤍ 𝜎1 ⟧" "⟦ φ ↦ Φ.number( φ ↦ ⟦ λ ⤍ 𝜎2 ⟧ ), ρ ↦ ⟦ x ↦ ⟦⟧ ⟧, neg ↦ ⟦⟧ ⟧" 4-      fmap (\(joint, _, _) -> joint) made `shouldBe` Just term+    it "takes the first rule matching a term" $ do+      made <- rewrote [("Φ.true", "Φ.yq"), ("Φ.𝜏1", "Φ.nx")] "Φ.true"+      expected <- parseExpressionThrows "Φ.yq"+      made `shouldBe` expected -    it "replaces every place the one symbol of a term stands at when lenient" $ do-      term <- parseExpressionThrows "Φ.f( a ↦ ⟦ λ ⤍ 𝜎5 ⟧ )( b ↦ ⟦ λ ⤍ 𝜎5 ⟧ )"-      made <- joining True "⟦ λ ⤍ 𝜎1 ⟧" "Φ.f( a ↦ ⟦ λ ⤍ 𝜎2 ⟧ )( b ↦ ⟦ λ ⤍ 𝜎2 ⟧ )" 4-      made `shouldBe` Just (term, [(5, (1, 2))], 5)+    it "tries a later rule where an earlier one does not match" $ do+      made <- rewrote [("Φ.false", "Φ.yq"), ("Φ.true", "Φ.nx")] "Φ.true"+      expected <- parseExpressionThrows "Φ.nx"+      made `shouldBe` expected -    it "still joins a bare symbol with the symbol a φ chain ends in into a bare one when lenient" $ do-      term <- parseExpressionThrows "⟦ λ ⤍ 𝜎8 ⟧"-      made <- joining True "⟦ λ ⤍ 𝜎3 ⟧" "⟦ φ ↦ ⟦ λ ⤍ 𝜎6 ⟧, neg ↦ ⟦⟧ ⟧" 7-      made `shouldBe` Just (term, [(8, (3, 6))], 8)+    it "rewrites the outermost term a rule matches and nothing inside what it made" $ do+      made <- rewrote [("⟦ φ ↦ 𝑒1 ⟧", "𝑒1")] "⟦ φ ↦ ⟦ φ ↦ ξ.ρ.kq ⟧ ⟧"+      expected <- parseExpressionThrows "⟦ φ ↦ ξ.ρ.kq ⟧"+      made `shouldBe` expected -    it "joins the cast of a bare symbol with the term it was cast to symbol by symbol" $ do-      made <- joining False "Φ.tuple( tail ↦ ⟦ λ ⤍ 𝜎5 ⟧, head ↦ ⟦ λ ⤍ 𝜎6 ⟧, length ↦ ⟦ λ ⤍ 𝜎7 ⟧ )" "Φ.tuple( tail ↦ ⟦ λ ⤍ 𝜎2 ⟧, head ↦ ⟦ λ ⤍ 𝜎3 ⟧, length ↦ ⟦ λ ⤍ 𝜎4 ⟧ )" 7-      fmap (\(_, facts, _) -> facts) made `shouldBe` Just [(8, (5, 2)), (9, (6, 3)), (10, (7, 4))]+    it "leaves what a rule made as it is, not normalized" $ do+      made <- rewrote [("Φ.true", "⟦ hv ↦ ⟦⟧ ⟧.hv")] "Φ.true"+      expected <- parseExpressionThrows "⟦ hv ↦ ⟦⟧ ⟧.hv"+      made `shouldBe` expected -    it "cannot join a bare symbol with a term holding three symbols even when lenient" $ do-      made <- joining True "⟦ λ ⤍ 𝜎1 ⟧" "Φ.tuple( tail ↦ ⟦ λ ⤍ 𝜎2 ⟧, head ↦ ⟦ λ ⤍ 𝜎3 ⟧, length ↦ ⟦ λ ⤍ 𝜎4 ⟧ )" 4-      made `shouldBe` Nothing+    it "keeps a term no rule matches" $ do+      term <- parseExpressionThrows "Φ.false( α0 ↦ ⟦ Δ ⤍ 2E- ⟧ )"+      made <- rewrote [("Φ.true", "Φ.vw")] "Φ.false( α0 ↦ ⟦ Δ ⤍ 2E- ⟧ )"+      made `shouldBe` term -    forM_-      [ ("a term with a formation carrying no φ" :: String, "Φ.number( φ ↦ ⟦ λ ⤍ 𝜎1 ⟧ )" :: String, "⟦ neg ↦ Φ.number( φ ↦ ⟦ λ ⤍ 𝜎2 ⟧ ) ⟧" :: String)-      , ("a term with a decorator of a term of another forma", "Φ.number( φ ↦ ⟦ λ ⤍ 𝜎1 ⟧ )", "⟦ φ ↦ Φ.bool( φ ↦ ⟦ λ ⤍ 𝜎2 ⟧ ) ⟧")-      , ("two decorators one of which carries a binding more", "⟦ φ ↦ ⟦ λ ⤍ 𝜎1 ⟧ ⟧", "⟦ φ ↦ ⟦ λ ⤍ 𝜎2 ⟧, x ↦ ⟦⟧ ⟧")-      , ("a bare symbol with a term holding two symbols", "⟦ λ ⤍ 𝜎1 ⟧", "Φ.f( a ↦ ⟦ λ ⤍ 𝜎2 ⟧ )( b ↦ ⟦ λ ⤍ 𝜎3 ⟧ )")-      , ("a bare symbol with a term holding a second symbol in its ρ", "⟦ λ ⤍ 𝜎1 ⟧", "⟦ φ ↦ Φ.bool( φ ↦ ⟦ λ ⤍ 𝜎2 ⟧ ), ρ ↦ ⟦ x ↦ ⟦ λ ⤍ 𝜎3 ⟧ ⟧ ⟧")-      , ("a bare symbol with a term holding no symbol", "⟦ λ ⤍ 𝜎1 ⟧", "⟦ φ ↦ ⟦ Δ ⤍ 00- ⟧, x ↦ ⟦⟧ ⟧")-      ]-      ( \(desc, left, right) ->-          it ("cannot leniently join " ++ desc) $ do-            made <- joining True left right 4-            fmap (\(joint, _, _) -> joint) made `shouldBe` Nothing-      )+  describe "symbolized" $ do+    it "turns the datum of a formation into a fresh symbol" $ do+      term <- parseExpressionThrows "⟦ Δ ⤍ 4A-2C ⟧"+      masked <- parseExpressionThrows "⟦ λ ⤍ 𝜎8 ⟧"+      (\(made, _, _) -> made) (symbolized term 7) `shouldBe` masked -    forM_-      [ ("a datum with a symbol" :: String, "⟦ φ ↦ ⟦ Δ ⤍ 00- ⟧ ⟧" :: String, "⟦ φ ↦ ⟦ λ ⤍ 𝜎1 ⟧ ⟧" :: String)-      , ("two different data", "⟦ φ ↦ ⟦ Δ ⤍ 00- ⟧ ⟧", "⟦ φ ↦ ⟦ Δ ⤍ FF- ⟧ ⟧")-      , ("a symbol with a λ function nothing else names", "⟦ φ ↦ ⟦ λ ⤍ 𝜎1 ⟧ ⟧", "⟦ φ ↦ ⟦ λ ⤍ L_number_plus ⟧ ⟧")-      , ("two branches one of which carries a binding more", "⟦ φ ↦ ⟦ λ ⤍ 𝜎1 ⟧ ⟧", "⟦ φ ↦ ⟦ λ ⤍ 𝜎2 ⟧, x ↦ ⟦⟧ ⟧")-      , ("two branches binding their symbols under different attributes", "⟦ a ↦ ⟦ λ ⤍ 𝜎1 ⟧ ⟧", "⟦ b ↦ ⟦ λ ⤍ 𝜎2 ⟧ ⟧")-      , ("two branches of different forma", "Φ.number( φ ↦ ⟦ λ ⤍ 𝜎1 ⟧ )", "Φ.bool( φ ↦ ⟦ λ ⤍ 𝜎2 ⟧ )")-      , ("a bare symbol with a formation whose φ chain ends in a datum", "⟦ λ ⤍ 𝜎1 ⟧", "⟦ φ ↦ ⟦ Δ ⤍ 00- ⟧, x ↦ ⟦⟧ ⟧")-      , ("a bare symbol with a formation carrying no φ", "⟦ a ↦ ⟦ λ ⤍ 𝜎2 ⟧ ⟧", "⟦ λ ⤍ 𝜎1 ⟧")-      , ("a bare symbol with an application", "⟦ λ ⤍ 𝜎1 ⟧", "Φ.number( φ ↦ ⟦ λ ⤍ 𝜎2 ⟧ )")-      , ("a bare symbol with a decorator of an application", "⟦ λ ⤍ 𝜎1 ⟧", "⟦ φ ↦ Φ.bool( if ↦ ⟦ λ ⤍ L_fork, φ ↦ ⟦ λ ⤍ 𝜎2 ⟧ ⟧ ) ⟧")-      ]-      ( \(desc, left, right) ->-          it ("cannot join " ++ desc) $ do-            made <- joining False left right 4-            fmap (\(joint, _, _) -> joint) made `shouldBe` Nothing-      )+    it "tells the datum every fresh symbol stands for" $ do+      term <- parseExpressionThrows "⟦ Δ ⤍ 4A-2C ⟧"+      (\(_, known, _) -> known) (symbolized term 7) `shouldBe` [(8, BtMany ["4A", "2C"])] -  describe "upcasted" $ do-    let casting :: String -> String -> Int -> IO (Maybe (Int, Expression, [(Int, [Attribute])], Int))-        casting source shape spent = do-          one <- parseExpressionThrows source-          two <- parseExpressionThrows shape-          pure (upcasted one two spent)+    it "counts every symbol it minted" $ do+      term <- parseExpressionThrows "Φ.number( φ ↦ ⟦ Δ ⤍ 0E- ⟧ )"+      (\(_, _, spent) -> spent) (symbolized term 11) `shouldBe` 12 -    it "casts a bare symbol into a copy of the other term made of fresh symbols" $ do-      cast <- parseExpressionThrows "Φ.tuple( tail ↦ ⟦ λ ⤍ 𝜎5 ⟧, head ↦ ⟦ λ ⤍ 𝜎6 ⟧, length ↦ ⟦ λ ⤍ 𝜎7 ⟧ )"-      made <- casting "⟦ λ ⤍ 𝜎1 ⟧" "Φ.tuple( tail ↦ ⟦ λ ⤍ 𝜎2 ⟧, head ↦ ⟦ λ ⤍ 𝜎3 ⟧, length ↦ ⟦ λ ⤍ 𝜎4 ⟧ )" 4-      made `shouldBe` Just (1, cast, [(5, [AtLabel "tail"]), (6, [AtLabel "head"]), (7, [AtLabel "length"])], 7)+    it "walks the φ an application binds down to the datum" $ do+      term <- parseExpressionThrows "Φ.number( φ ↦ Φ.bytes( φ ↦ ⟦ Δ ⤍ 3F-F0-00-00-00-00-00-00 ⟧ ) )"+      masked <- parseExpressionThrows "Φ.number( φ ↦ Φ.bytes( φ ↦ ⟦ λ ⤍ 𝜎3 ⟧ ) )"+      (\(made, _, _) -> made) (symbolized term 2) `shouldBe` masked -    it "counts the symbols of the cast into the state" $ do-      made <- casting "⟦ λ ⤍ 𝜎9 ⟧" "Φ.pair( left ↦ ⟦ λ ⤍ 𝜎2 ⟧, right ↦ ⟦ λ ⤍ 𝜎3 ⟧ )" 12-      fmap (\(_, _, _, spent) -> spent) made `shouldBe` Just 14+    it "walks the head of an application binding no φ" $ do+      term <- parseExpressionThrows "Φ.bytes( φ ↦ ⟦ Δ ⤍ 61- ⟧ )( ρ ↦ ⟦ w ↦ ∅ ⟧ )"+      masked <- parseExpressionThrows "Φ.bytes( φ ↦ ⟦ λ ⤍ 𝜎20 ⟧ )( ρ ↦ ⟦ w ↦ ∅ ⟧ )"+      (\(made, _, _) -> made) (symbolized term 19) `shouldBe` masked -    it "reads a symbol in the φ chain of a data object by a path through every φ" $ do-      made <- casting "⟦ λ ⤍ 𝜎1 ⟧" "Φ.number( φ ↦ Φ.bytes( φ ↦ ⟦ λ ⤍ 𝜎2 ⟧ ) )" 6-      fmap (\(_, _, paths, _) -> paths) made `shouldBe` Just [(7, [AtPhi, AtPhi])]+    it "walks the φ of a formation down to the datum" $ do+      term <- parseExpressionThrows "⟦ φ ↦ ⟦ φ ↦ ⟦ Δ ⤍ 07- ⟧ ⟧ ⟧"+      masked <- parseExpressionThrows "⟦ φ ↦ ⟦ φ ↦ ⟦ λ ⤍ 𝜎5 ⟧ ⟧ ⟧"+      (\(made, _, _) -> made) (symbolized term 4) `shouldBe` masked -    it "reads the one symbol of a decorated term by its path" $ do-      cast <- parseExpressionThrows "⟦ φ ↦ Φ.bool( if ↦ ⟦ λ ⤍ L_fork, φ ↦ ⟦ λ ⤍ 𝜎5 ⟧ ⟧ ) ⟧"-      made <- casting "⟦ λ ⤍ 𝜎1 ⟧" "⟦ φ ↦ Φ.bool( if ↦ ⟦ λ ⤍ L_fork, φ ↦ ⟦ λ ⤍ 𝜎2 ⟧ ⟧ ) ⟧" 4-      made `shouldBe` Just (1, cast, [(5, [AtPhi, AtLabel "if", AtPhi])], 5)+    it "turns the datum a ρ of a formation holds into a fresh symbol" $ do+      term <- parseExpressionThrows "⟦ ρ ↦ ⟦ Δ ⤍ 02- ⟧, q ↦ ∅ ⟧"+      masked <- parseExpressionThrows "⟦ ρ ↦ ⟦ λ ⤍ 𝜎10 ⟧, q ↦ ∅ ⟧"+      (\(made, _, _) -> made) (symbolized term 9) `shouldBe` masked -    it "casts every symbol of a formation carrying no φ" $ do-      cast <- parseExpressionThrows "⟦ kj ↦ ⟦ λ ⤍ 𝜎12 ⟧, vy ↦ ⟦ λ ⤍ 𝜎13 ⟧, ρ ↦ ⟦⟧ ⟧"-      made <- casting "⟦ λ ⤍ 𝜎9 ⟧" "⟦ kj ↦ ⟦ λ ⤍ 𝜎3 ⟧, vy ↦ ⟦ λ ⤍ 𝜎5 ⟧, ρ ↦ ⟦⟧ ⟧" 11-      fmap (\(_, term, _, _) -> term) made `shouldBe` Just cast+    it "turns the datum a method of a formation holds into a fresh symbol" $ do+      term <- parseExpressionThrows "⟦ φ ↦ ⟦⟧, neg ↦ ⟦ Δ ⤍ BF-F0-00-00-00-00-00-00 ⟧ ⟧"+      masked <- parseExpressionThrows "⟦ φ ↦ ⟦⟧, neg ↦ ⟦ λ ⤍ 𝜎10 ⟧ ⟧"+      (\(made, _, _) -> made) (symbolized term 9) `shouldBe` masked -    it "reads the symbol a formation carries by the path of that formation" $ do-      made <- casting "⟦ λ ⤍ 𝜎1 ⟧" "⟦ x ↦ ⟦ λ ⤍ 𝜎2, q ↦ ⟦⟧ ⟧ ⟧" 4-      fmap (\(_, _, paths, _) -> paths) made `shouldBe` Just [(5, [AtLabel "x"])]+    it "turns the datum a named argument other than φ holds into a fresh symbol" $ do+      term <- parseExpressionThrows "Φ.f( q ↦ ⟦ Δ ⤍ 1D- ⟧ )"+      masked <- parseExpressionThrows "Φ.f( q ↦ ⟦ λ ⤍ 𝜎4 ⟧ )"+      (\(made, _, _) -> made) (symbolized term 3) `shouldBe` masked -    it "mints a fresh symbol for every place a symbol of the other term stands at" $ do-      made <- casting "⟦ λ ⤍ 𝜎1 ⟧" "Φ.f( a ↦ ⟦ λ ⤍ 𝜎2 ⟧ )( b ↦ ⟦ λ ⤍ 𝜎2 ⟧ )" 4-      fmap (\(_, _, paths, _) -> paths) made `shouldBe` Just [(5, [AtLabel "a"]), (6, [AtLabel "b"])]+    it "turns the datum a positional argument holds into a fresh symbol" $ do+      term <- parseExpressionThrows "Φ.vk( α0 ↦ ⟦ Δ ⤍ 5B- ⟧ )"+      masked <- parseExpressionThrows "Φ.vk( α0 ↦ ⟦ λ ⤍ 𝜎14 ⟧ )"+      (\(made, _, _) -> made) (symbolized term 13) `shouldBe` masked -    it "casts a bare symbol into the φ chain of a formation ending in a symbol" $ do-      cast <- parseExpressionThrows "⟦ φ ↦ ⟦ λ ⤍ 𝜎8 ⟧, neg ↦ ⟦⟧ ⟧"-      made <- casting "⟦ λ ⤍ 𝜎3 ⟧" "⟦ φ ↦ ⟦ λ ⤍ 𝜎6 ⟧, neg ↦ ⟦⟧ ⟧" 7-      fmap (\(_, term, _, _) -> term) made `shouldBe` Just cast+    it "walks the target of a dispatch down to the datum" $ do+      term <- parseExpressionThrows "⟦ Δ ⤍ 6E- ⟧.kx"+      masked <- parseExpressionThrows "⟦ λ ⤍ 𝜎8 ⟧.kx"+      (\(made, _, _) -> made) (symbolized term 7) `shouldBe` masked -    it "cannot cast a term that is no bare symbol" $ do-      made <- casting "Φ.pair( a ↦ ⟦ λ ⤍ 𝜎1 ⟧, b ↦ ⟦ λ ⤍ 𝜎2 ⟧ )" "Φ.pair( a ↦ Φ.t( x ↦ ⟦ λ ⤍ 𝜎3 ⟧ ), b ↦ ⟦ λ ⤍ 𝜎5 ⟧ )" 5-      made `shouldBe` Nothing+    it "walks a formation bound to an argument down to the datum it holds in φ" $ do+      term <- parseExpressionThrows "Φ.bool( if ↦ ⟦ λ ⤍ L_fork, φ ↦ ⟦ Δ ⤍ FF- ⟧ ⟧ )"+      masked <- parseExpressionThrows "Φ.bool( if ↦ ⟦ λ ⤍ L_fork, φ ↦ ⟦ λ ⤍ 𝜎147 ⟧ ⟧ )"+      (\(made, _, _) -> made) (symbolized term 146) `shouldBe` masked -    forM_-      [ ("a call holding a symbol no path reaches" :: String, "⟦ k ↦ ⟦⟧ ⟧.slice( start ↦ ⟦ λ ⤍ 𝜎2 ⟧ )" :: String)-      , ("a call holding symbols in its arguments", "⟦ k ↦ ⟦⟧ ⟧.slice( start ↦ ⟦ λ ⤍ 𝜎2 ⟧, len ↦ ⟦ λ ⤍ 𝜎3 ⟧ )")-      , ("a term holding symbols in a dispatch", "Φ.f( a ↦ ⟦ λ ⤍ 𝜎2 ⟧.plus( x ↦ ⟦ λ ⤍ 𝜎3 ⟧ ) )")-      , ("a term holding symbols in positional arguments", "Φ.f( α0 ↦ ⟦ λ ⤍ 𝜎2 ⟧, α1 ↦ ⟦ λ ⤍ 𝜎3 ⟧ )")-      , ("a term holding no symbol", "Φ.tuple.empty")-      , ("another bare symbol", "⟦ λ ⤍ 𝜎2 ⟧")-      ]-      ( \(desc, shape) ->-          it ("cannot cast a bare symbol to " ++ desc) $ do-            made <- casting "⟦ λ ⤍ 𝜎1 ⟧" shape 4-            made `shouldBe` Nothing-      )+    it "mints the symbols in the order the term spells its data" $ do+      term <- parseExpressionThrows "Φ.ws( φ ↦ ⟦ Δ ⤍ 3C- ⟧ )( tz ↦ ⟦ Δ ⤍ 9D- ⟧ )"+      (\(_, known, _) -> known) (symbolized term 5) `shouldBe` [(6, BtOne "3C"), (7, BtOne "9D")] -  describe "thrown" $ do-    it "finds a terminator in a formation whose λ it names" $ do-      term <- parseExpressionThrows "⟦ message ↦ Φ.number( φ ↦ ⟦ λ ⤍ 𝜎4 ⟧ ), λ ⤍ L_throw ⟧"-      thrown ["L_halt", "L_throw"] term `shouldBe` True+    it "mints nothing for a term holding no datum" $ do+      term <- parseExpressionThrows "Φ.number( φ ↦ Φ.bytes( φ ↦ ⟦ λ ⤍ 𝜎2 ⟧ ) )"+      (\(_, _, spent) -> spent) (symbolized term 6) `shouldBe` 6 -    it "finds a terminator in an application of a formation whose λ it names" $ do-      term <- parseExpressionThrows "⟦ message ↦ ∅, code ↦ ∅, λ ⤍ L_throw ⟧( message ↦ ξ.x.printf( ξ.y ) )( code ↦ Φ.z )"-      thrown ["L_throw"] term `shouldBe` True+  describe "emptyLambdas" $+    it "cannot read a λ function without the file naming one" $+      answering emptyLambdas "L_number_plus" `shouldBe` Nothing -    forM_-      [ ("a formation whose λ it does not name" :: String, "⟦ message ↦ ⟦⟧, λ ⤍ L_halt ⟧" :: String)-      , ("a formation whose φ throws", "⟦ φ ↦ ⟦ message ↦ ⟦⟧, λ ⤍ L_throw ⟧ ⟧")-      , ("a dispatch from a formation whose λ it names", "⟦ message ↦ ⟦⟧, λ ⤍ L_throw ⟧.message")-      , ("an application whose argument throws", "Φ.f( x ↦ ⟦ message ↦ ⟦⟧, λ ⤍ L_throw ⟧ )")-      , ("the terminator ⊥", "⊥")-      ]-      ( \(desc, text) ->-          it ("finds no terminator in " ++ desc) $ do-            term <- parseExpressionThrows text-            thrown ["L_throw"] term `shouldBe` False-      )+  describe "minted" $ do+    it "mints one fresh symbol per bare 𝜎 the answer carries" $ do+      answer <- parseExpressionThrows "⟦ a ↦ ⟦ λ ⤍ 𝜎 ⟧, b ↦ ⟦ λ ⤍ 𝜎 ⟧ ⟧"+      map snd (fst (minted answer 4)) `shouldBe` [FnSymbol 5, FnSymbol 6] -  describe "message" $ do-    it "finds the message a formation that throws carries" $ do-      said <- parseExpressionThrows "Φ.pike( φ ↦ ⟦ λ ⤍ 𝜎9 ⟧ )"-      term <- parseExpressionThrows "⟦ message ↦ Φ.pike( φ ↦ ⟦ λ ⤍ 𝜎9 ⟧ ), λ ⤍ L_throw ⟧"-      message term `shouldBe` Just said+    it "counts every symbol it minted into the state" $ do+      answer <- parseExpressionThrows "⟦ a ↦ ⟦ λ ⤍ 𝜎 ⟧, b ↦ ⟦ λ ⤍ 𝜎 ⟧ ⟧"+      snd (minted answer 4) `shouldBe` 6 -    it "finds the message an application gives a formation that throws" $ do-      said <- parseExpressionThrows "ξ.hog.printf( ξ.y )"-      term <- parseExpressionThrows "⟦ message ↦ ∅, code ↦ ∅, λ ⤍ L_throw ⟧( message ↦ ξ.hog.printf( ξ.y ) )( code ↦ Φ.z )"-      message term `shouldBe` Just said+    it "mints nothing for a symbol the answer already numbers" $ do+      answer <- parseExpressionThrows "⟦ λ ⤍ 𝜎1 ⟧"+      fst (minted answer 4) `shouldBe` [] -    forM_-      [ ("the terminator ⊥" :: String, "⊥" :: String)-      , ("a formation whose message is void", "⟦ message ↦ ∅, λ ⤍ L_throw ⟧")-      , ("a formation that carries no message", "⟦ code ↦ Φ.cat, λ ⤍ L_throw ⟧")-      , ("an application that gives no message", "⟦ message ↦ ∅, code ↦ ∅, λ ⤍ L_throw ⟧( code ↦ Φ.cat )")-      ]-      ( \(desc, text) ->-          it ("finds no message in " ++ desc) $ do-            term <- parseExpressionThrows text-            message term `shouldBe` Nothing-      )+    it "mints nothing for an answer carrying no symbol at all" $ do+      answer <- parseExpressionThrows "⟦ Δ ⤍ 00- ⟧"+      snd (minted answer 4) `shouldBe` 4    describe "taken" $ do     it "takes the last symbol the program already carries" $ do
test/MorphSpec.hs view
@@ -20,7 +20,7 @@ import Data.Maybe (fromMaybe) import Data.Yaml qualified as Decode import Dataize (Outcome (..), dataize)-import Deps (Acyclic (..), Evaluation (EvWorld), Judgment (..), State, Term (TeExpression))+import Deps (Acyclic (..), Evaluation (EvMorphed, EvWorld), Judgment (..), State, Term (TeExpression)) import Engine (Engine (_normal)) import Files (allPathsIn) import Fixtures (defaultReduceContext, fixtureLambdas, linked, overdue, primitives)@@ -110,6 +110,24 @@       _ <- morph expr emptyState ctx{_saveEval = \record -> modifyIORef' told (record :)}       records <- readIORef told       [world | EvWorld world <- records] `shouldBe` [expr]++    it "tells the protocol the term it came to as its last record" $ do+      expr <- parseExpressionThrows "[[ wz -> [[ kq -> [[ D> 1F- ]] ]], y -> Q.wz.kq ]]"+      loc <- parseExpressionThrows "Q.y"+      told <- newIORef []+      ctx <- defaultReduceContext loc+      (morphed, _, _) <- morph expr emptyState ctx{_saveEval = \record -> modifyIORef' told (record :)}+      records <- readIORef told+      [term | EvMorphed _ term <- take 1 records] `shouldBe` [morphed]++    it "tells the protocol nothing of the premises it morphed inside its own block" $ do+      expr <- parseExpressionThrows "[[ ox -> [[ ty -> [[ gu -> [[ D> 2A- ]] ]] ]], y -> Q.ox.ty.gu ]]"+      loc <- parseExpressionThrows "Q.y"+      told <- newIORef []+      ctx <- defaultReduceContext loc+      _ <- morph expr emptyState ctx{_saveEval = \record -> modifyIORef' told (record :)}+      records <- readIORef told+      length [term | EvMorphed _ term <- records] `shouldBe` 1    describe "morph'" $     test'
test/PrinterSpec.hs view
@@ -9,6 +9,7 @@ import Control.Monad (forM_) import Data.Map.Strict qualified as Map import Encoding (Encoding (..))+import Grown (Grown (..)) import Lining (LineFormat (..)) import Margin (defaultMargin) import Matcher (Meta (Named), MetaValue (..), Subst (Subst))@@ -16,6 +17,7 @@ import Printer import Sugar (SugarType (..)) import Test.Hspec (Spec, describe, expectationFailure, it, shouldBe, shouldContain, shouldNotContain, shouldSatisfy)+import Test.Hspec.QuickCheck (prop) import Yaml (ExtraArgument (..))  spec :: Spec@@ -402,6 +404,8 @@       , "⟦ x ↦ Φ.number(Φ.bytes(⟦ Δ ⤍ 7F-F8-00-00-00-00-00-01 ⟧)) ⟧"       , "⟦ x ↦ Φ.y(⟦ φ ↦ ξ.z ⟧, ⟦ λ ⤍ Fn ⟧) ⟧"       , "⟦ x ↦ ⟦ λ ⤍ Fn, ρ ↦ ⟦ φ ↦ ξ ⟧ ⟧, y ↦ ∅ ⟧"+      , "⟦ x ↦ ⟦ 𝜏1 ↦ ∅, a ↦ ∅ ⟧ ⟧"+      , "⟦ x ↦ ⟦ a ↦ ∅, 𝜏1 ↦ ∅ ⟧ ⟧"       , "⟦ aVeryLongAttributeNameThatWontFitOnOneLine ↦ Φ.x(y ↦ ⟦ aVeryLongAttributeNameThatWontFitOnOneLine ↦ Φ ⟧) ⟧"       ]       ( \src -> it src $ do@@ -416,6 +420,18 @@             )       ) +  describe "printExpression' spells any expression the way the parser reads it back" $+    forM_+      [ (sugar, encoding, line)+      | sugar <- [SWEET, SALTY]+      , encoding <- [UNICODE, ASCII]+      , line <- [SINGLELINE, MULTILINE]+      ]+      ( \(sugar, encoding, line) ->+          prop (unwords [show sugar, show encoding, show line]) $ \(Grown expr) ->+            parseExpression (printExpression' expr (sugar, encoding, line, 20)) `shouldBe` Right expr+      )+   describe "printExpression' writes the one-binding sugar in the sweet syntax only" $     forM_       [ ("⟦ x ↦ ξ.a ⟧", SWEET, UNICODE, "a:x")@@ -429,6 +445,8 @@       , ("⟦ x ↦ ⟦ φ ↦ ξ.a ⟧.b ⟧", SWEET, UNICODE, "a:φ.b:x")       , ("⟦ x(a) ↦ ⟦ φ ↦ a ⟧ ⟧", SWEET, UNICODE, "⟦ x(a) ↦ a:φ ⟧")       , ("⟦ x ↦ ξ.a, y ↦ ∅ ⟧", SWEET, UNICODE, "⟦ x ↦ a, y ↦ ∅ ⟧")+      , ("⟦ x ↦ ⟦ a ↦ ∅, 𝜏1 ↦ ∅ ⟧ ⟧", SWEET, UNICODE, "⟦ x(a) ↦ ∅:𝜏1 ⟧")+      , ("⟦ x ↦ ⟦ 𝜏1 ↦ ∅, a ↦ ∅ ⟧ ⟧", SWEET, UNICODE, "⟦ 𝜏1 ↦ ∅, a ↦ ∅ ⟧:x")       , ("⟦ x ↦ ξ.a ⟧", SALTY, UNICODE, "⟦ x ↦ ξ.a ⟧")       , ("⟦ Δ ⤍ FF-AA ⟧", SALTY, ASCII, "[[ D> FF-AA ]]")       ]
test/SugarSpec.hs view
@@ -665,6 +665,8 @@   describe "full pipeline round trips, SWEET vs SALTY" $ do     let config :: SugarType -> (SugarType, Encoding, LineFormat, Int)         config sugar = (sugar, UNICODE, SINGLELINE, defaultMargin)+        applied :: Argument -> Argument -> Expression+        applied first = ExApplication (ExApplication (DataNumber (BtMany ["40", "45", "00", "00", "00", "00", "00", "00"])) first)     it       "a sweet numeric literal expands into Q.number(Q.bytes(...)) when salted"       $ do@@ -695,3 +697,11 @@         let meetChain = ExFormation [BiTau (AtLabel "x") (ExPhiMeet Nothing 2 (ExPhiAgain (Just "a") 1 (ExDispatch ExXi (AtLabel "y"))))]         printExpression' meetChain (config SWEET) `shouldBe` "\\phinoMeet{2}{ \\phinoAgain{a:1} }:x"         printExpression' meetChain (config SALTY) `shouldBe` "⟦ x ↦ \\phinoMeet{2}{ \\phinoAgain{a:1} } ⟧"+    it "a ρ applied to a number literal after another argument keeps its place when sweet" $+      printExpression' (applied (ArTau (AtLabel "x") ExXi) (ArTau AtRho ExTermination)) (config SWEET) `shouldBe` "42( x ↦ ξ, ρ ↦ ⊥ )"+    it "a ρ applied to a number literal after another argument keeps its place when salted" $+      printExpression' (applied (ArTau (AtLabel "x") ExXi) (ArTau AtRho ExTermination)) (config SALTY) `shouldBe` "Φ.number( φ ↦ Φ.bytes( φ ↦ ⟦ Δ ⤍ 40-45-00-00-00-00-00-00 ⟧ ) )( x ↦ ξ )( ρ ↦ ⊥ )"+    it "a ρ applied straight to a number literal is dropped when sweet" $+      printExpression' (applied (ArTau AtRho ExTermination) (ArTau (AtLabel "x") ExXi)) (config SWEET) `shouldBe` "42( x ↦ ξ )"+    it "a ρ applied straight to a number literal keeps its place when salted" $+      printExpression' (applied (ArTau AtRho ExTermination) (ArTau (AtLabel "x") ExXi)) (config SALTY) `shouldBe` "Φ.number( φ ↦ Φ.bytes( φ ↦ ⟦ Δ ⤍ 40-45-00-00-00-00-00-00 ⟧ ) )( ρ ↦ ⊥ )( x ↦ ξ )"
test/YamlSpec.hs view
@@ -43,6 +43,12 @@         (decodeYaml' "name: foo\npattern: '⟦ 𝜏1 ↦ 𝑒1 ⟧'\nresult: '𝑒1'\nunexpected: true" :: Either Yaml.ParseException Rule)         `shouldBe` True +    it "rejects unknown keys of a where step" $+      failsWith+        "Unknown key"+        (decodeYaml' "name: foo\npattern: '⟦ 𝜏1 ↦ 𝑒1, 𝐵1 ⟧'\nresult: '⟦ 𝜏1 ↦ 𝑒2, 𝐵1 ⟧'\nwhere:\n  - meta: 𝑒2\n    function: contextualize\n    arg: ['𝑒1', '⟦ 𝐵1 ⟧']" :: Either Yaml.ParseException Rule)+        `shouldBe` True+   describe "parses yaml rule" $ do     let resources = "test-resources/yaml-packs"     packs <- runIO (allPathsIn resources)@@ -272,6 +278,22 @@       , ("'disjoint' with a single argument", "disjoint: [[]]", "'disjoint' expects exactly two arguments")       ]       (\(desc, yaml, message) -> it desc ((decodeYaml' yaml :: Either Yaml.ParseException Condition) `shouldSatisfy` failsWith message))++  describe "tells why an argument of a condition does not parse" $+    forM_+      [ ("bytes missing a dash in 'eq'", "eq: ['𝑒1', '⟦ Δ ⤍ 01 ⟧']", "expecting '-'")+      , ("bytes missing a dash in 'gt'", "gt: ['⟦ Δ ⤍ 7F ⟧', 3]", "expecting '-'")+      , ("a list in 'gt'", "gt: [[1], 2]", "is a number, an attribute, an expression, or an object of 'length' or 'domain'")+      , ("a boolean in 'eq'", "eq: [true, 'x']", "is a number, an attribute, an expression, or an object of 'length' or 'domain'")+      ]+      (\(desc, yaml, message) -> it desc ((decodeYaml' yaml :: Either Yaml.ParseException Condition) `shouldSatisfy` failsWith message))++  describe "tells why an argument of a 'where' function does not parse" $+    forM_+      [ ("bytes missing a dash", "args: ['⟦ Δ ⤍ 0C ⟧']", "expecting '-'")+      , ("a number", "args: [42]", "is a string holding an attribute, a binding, bytes or an expression")+      ]+      (\(desc, args, message) -> it desc ((decodeYaml' ("name: w\npattern: '⟦ x ↦ 𝑒1 ⟧'\nresult: '⟦ x ↦ 𝑒2 ⟧'\nwhere:\n  - meta: '𝑒2'\n    function: concat\n    " ++ args) :: Either Yaml.ParseException Rule) `shouldSatisfy` failsWith message))    describe "rejects a malformed premise" $     forM_