packages feed

phino-0.0.156: test/LambdasSpec.hs

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}

-- SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
-- SPDX-License-Identifier: MIT

module LambdasSpec (spec) where

import AST
import Control.Exception (SomeException)
import Control.Monad (forM_)
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 Parser (parseExpressionThrows)
import Test.Hspec

lambdasOf :: T.Text -> IO Lambdas
lambdasOf text = withLambdasOf text readLambdas

entry :: T.Text -> T.Text
entry key = "- Ξ»: " <> key <> "\n  dataize:\n    𝛿1: $.ρ\n  𝑛: ⟦ Ξ» ‍ 𝜎 ⟧\n"

answering :: Lambdas -> T.Text -> Maybe T.Text
answering known func = _key <$> matched known func

lined :: Lambdas -> T.Text -> [Line]
lined known func = maybe [] _lines (matched known func)

order :: Lambdas -> T.Text -> [T.Text]
order known func = concatMap 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]

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]

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]

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]

rewriting :: T.Text -> T.Text
rewriting lines' = "- Ξ»: L_fork\n  morph:\n    𝑛1: $.a\n  rewrite:\n" <> lines'

rules :: T.Text
rules = "      rules:\n        - name: no-x\n          pattern: ⟦ !B1, x ↦ ⟦⟧, !B2 ⟧\n          result: ⟦ !B1, !B2 ⟧\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]

spec :: Spec
spec = do
  describe "readLambdas" $ do
    it "reads the Ξ» function its key names" $ do
      known <- lambdasOf (entry "L_number_plus")
      answering known "L_number_plus" `shouldBe` Just "L_number_plus"

    it "reads one Ξ» function for the whole family its key spells" $ do
      known <- lambdasOf (entry "L_box_[0-9]+_number")
      answering known "L_box_42_number" `shouldBe` Just "L_box_[0-9]+_number"

    it "reads two keys whose families share no Ξ» name" $ do
      known <- lambdasOf (entry "L_[a-z]+_plus" <> entry "L_number_[0-9]+")
      answering known "L_number_42" `shouldBe` Just "L_number_[0-9]+"

    it "cannot read a Ξ» function whose name merely starts with a key" $ do
      known <- lambdasOf (entry "L_number_plus")
      answering known "L_number_plus_twice" `shouldBe` Nothing

    it "reads a single key that no other key is compared with, even an anchored one" $ do
      known <- lambdasOf (entry "^L_a$")
      answering known "L_a" `shouldBe` Just "^L_a$"

    it "cannot read a Ξ» function no entry answers" $ do
      known <- lambdasOf (entry "L_number_plus")
      answering known "L_bytes_not" `shouldBe` Nothing

    it "reads the operands of 'dataize' in the order their metas number them" $ do
      known <- lambdasOf "- Ξ»: L_pair\n  dataize:\n    𝛿2: $.x\n    𝛿1: $.ρ\n  𝑛: ⟦ Ξ» ‍ 𝜎 ⟧\n"
      map (_spelling . fst) (maybe [] _dataized (matched known "L_pair")) `shouldBe` ["𝛿1", "𝛿2"]

    it "reads the operands of 'dataize' in numeric order past nine" $ do
      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"]

    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 "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 the term an operand is reduced from" $ do
      known <- lambdasOf "- Ξ»: L_pair\n  dataize:\n    𝛿1: $.x\n  𝑛: ⟦ Ξ» ‍ 𝜎 ⟧\n"
      operand <- parseExpressionThrows "$.x"
      map snd (maybe [] _dataized (matched known "L_pair")) `shouldBe` [operand]

    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

    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 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' 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 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 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 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 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 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 "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 "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 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 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 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 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 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")]

    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")]

    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 "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 "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 "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 "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 "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 '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 '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 "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 "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 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 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 "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 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 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 "reads an entry naming no 'join' block at all" $ do
      known <- lambdasOf "- Ξ»: L_pair\n  𝑛: ⟦ Ξ» ‍ 𝜎 ⟧\n"
      joins known "L_pair" `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` []

    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 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")
      ,
        ( "two entries with overlapping regular expressions"
        , entry "L_(foo|bar)" <> entry "L_foo"
        , "match some of the same lambda names"
        )
      ,
        ( "two keys overlapping on a name neither of them spells"
        , entry "L_[a-z]+_plus" <> entry "L_number_[a-z]+"
        , "such as 'L_number_plus'"
        )
      , ("a key with a back reference phino cannot compare", entry "L_(a)\\1" <> entry "L_b", "cannot be compared")
      , ("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 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")
      , ("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]+'"
        )
      ]
      ( \(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

    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

    it "drops every ρ a chain of applications gives a dispatch" $ do
      (term, kept) <- keeping "ΞΎ.f( ρ ↦ ⟦ a ↦ βˆ… ⟧ )( Ξ±0 ↦ ⟦⟧ )( ρ ↦ ⟦ b ↦ βˆ… ⟧ )" "ΞΎ.f( Ξ±0 ↦ ⟦⟧ )"
      term `shouldBe` kept

    it "keeps the ρ a formation is applied to" $ do
      (term, kept) <- keeping "⟦ ρ ↦ βˆ…, t ↦ βˆ… ⟧( t ↦ ⟦⟧ )( ρ ↦ ⟦ x ↦ βˆ… ⟧ )" "⟦ ρ ↦ βˆ…, t ↦ βˆ… ⟧( t ↦ ⟦⟧ )( ρ ↦ ⟦ x ↦ βˆ… ⟧ )"
      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

    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

    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

    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 "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

    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 "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 "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 "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 "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 "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 "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 "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

    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
      )

    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
      )

  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 "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 "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 "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 "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 "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 "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 "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 "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 "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

    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
      )

  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 "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

    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 "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 "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

    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
      )

  describe "taken" $ do
    it "takes the last symbol the program already carries" $ do
      program <- parseExpressionThrows "⟦ a ↦ ⟦ Ξ» ‍ 𝜎3 ⟧, b ↦ ⟦ Ξ» ‍ 𝜎7 ⟧ ⟧"
      taken program `shouldBe` 7

    it "takes nothing from a program carrying no symbol" $ do
      program <- parseExpressionThrows "⟦ Ξ” ‍ 00- ⟧"
      taken program `shouldBe` 0