packages feed

phino-0.0.141: test/ASTSpec.hs

{-# LANGUAGE OverloadedStrings #-}
{-# OPTIONS_GHC -Wno-incomplete-uni-patterns #-}

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

{- | Tests for the AST module that defines the abstract syntax tree
for phi-calculus expressions: bindings, attributes, and bytes.
Attention! Most of the tests are generated by LLM. Consider that when refactoring
-}
module ASTSpec where

import AST
import Control.Monad (forM_)
import Data.List (nub, sort)
import Data.Text qualified as T
import Test.Hspec (Spec, describe, it, shouldBe, shouldNotBe, shouldSatisfy)

spec :: Spec
spec = do
  describe "Attribute Show instance renders AtLabel" $
    forM_
      [ ("simple label", AtLabel "x", "x")
      , ("unicode label", AtLabel "日本語", "日本語")
      , ("long label", AtLabel "myAttribute", "myAttribute")
      ]
      ( \(desc, attr, expected) ->
          it desc $ show attr `shouldBe` expected
      )

  describe "Attribute Show instance renders AtAlpha" $
    forM_
      [ ("zero index", Alpha 0, "α0")
      , ("positive index", Alpha 42, "α42")
      , ("large index", Alpha 999, "α999")
      ]
      ( \(desc, attr, expected) ->
          it desc $ show attr `shouldBe` expected
      )

  describe "Alpha Eq instance compares same constructors" $
    forM_
      [ ("alphas equal", Alpha 1, Alpha 1, True)
      , ("alphas differ", Alpha 1, Alpha 2, False)
      ]
      ( \(desc, lhs, rhs, expected) ->
          it desc $ (lhs == rhs) `shouldBe` expected
      )

  describe "Attribute Show instance renders special attributes" $
    forM_
      [ ("rho", AtRho, "ρ")
      , ("phi", AtPhi, "φ")
      , ("delta", AtDelta, "Δ")
      , ("lambda", AtLambda, "λ")
      ]
      ( \(desc, attr, expected) ->
          it desc $ show attr `shouldBe` expected
      )

  describe "Attribute Show instance renders AtMeta" $
    forM_
      [ ("simple meta", AtMeta "t", "!t")
      , ("long meta", AtMeta "tttribute", "!tttribute")
      , ("unicode meta", AtMeta "メタ", "!メタ")
      ]
      ( \(desc, attr, expected) ->
          it desc $ show attr `shouldBe` expected
      )

  describe "Attribute Eq instance compares same constructors" $
    forM_
      [ ("labels equal", AtLabel "x", AtLabel "x", True)
      , ("labels differ", AtLabel "x", AtLabel "y", False)
      , ("metas equal", AtMeta "t", AtMeta "t", True)
      , ("metas differ", AtMeta "t", AtMeta "b", False)
      , ("rho equals rho", AtRho, AtRho, True)
      , ("phi equals phi", AtPhi, AtPhi, True)
      , ("delta equals delta", AtDelta, AtDelta, True)
      , ("lambda equals lambda", AtLambda, AtLambda, True)
      ]
      ( \(desc, lhs, rhs, expected) ->
          it desc $ (lhs == rhs) `shouldBe` expected
      )

  describe "Attribute Eq instance compares different constructors" $
    forM_
      [ ("rho vs phi", AtRho, AtPhi, False)
      , ("delta vs lambda", AtDelta, AtLambda, False)
      , ("meta vs label", AtMeta "x", AtLabel "x", False)
      ]
      ( \(desc, lhs, rhs, expected) ->
          it desc $ (lhs == rhs) `shouldBe` expected
      )

  describe "Attribute Ord instance orders correctly" $
    it "sorts attributes by constructor order" $
      let attrs = [AtMeta "z", AtDelta, AtLambda, AtRho, AtPhi, AtLabel "a"]
          first : _ = sort attrs
          isLabel (AtLabel _) = True
          isLabel _ = False
       in first `shouldSatisfy` isLabel

  describe "Bytes Eq instance compares same constructors" $
    forM_
      [ ("empty equals empty", BtEmpty, BtEmpty, True)
      , ("one equals one", BtOne "FF", BtOne "FF", True)
      , ("one differs", BtOne "FF", BtOne "00", False)
      , ("many equals many", BtMany ["00", "01"], BtMany ["00", "01"], True)
      , ("many differs", BtMany ["00"], BtMany ["01"], False)
      , ("meta equals meta", BtMeta "b", BtMeta "b", True)
      , ("meta differs", BtMeta "b", BtMeta "c", False)
      ]
      ( \(desc, lhs, rhs, expected) ->
          it desc $ (lhs == rhs) `shouldBe` expected
      )

  describe "Bytes Eq instance compares different constructors" $
    forM_
      [ ("empty vs one", BtEmpty, BtOne "00", False)
      , ("one vs many", BtOne "00", BtMany ["00"], False)
      , ("many vs meta", BtMany ["00"], BtMeta "b", False)
      ]
      ( \(desc, lhs, rhs, expected) ->
          it desc $ (lhs == rhs) `shouldBe` expected
      )

  describe "Bytes Ord instance orders correctly" $
    it "sorts bytes by constructor order" $
      let bytes = [BtMeta "z", BtMany ["00"], BtOne "FF", BtEmpty]
          first : _ = sort bytes
       in first `shouldBe` BtEmpty

  describe "Binding Eq instance compares same constructors" $
    forM_
      [ ("tau equals tau", BiTau AtRho ExRoot, BiTau AtRho ExRoot, True)
      , ("tau differs by attr", BiTau AtRho ExRoot, BiTau AtPhi ExRoot, False)
      , ("tau differs by expr", BiTau AtRho ExRoot, BiTau AtRho ExXi, False)
      , ("meta equals meta", BiMeta "B", BiMeta "B", True)
      , ("meta differs", BiMeta "B", BiMeta "C", False)
      , ("delta equals delta", BiDelta BtEmpty, BiDelta BtEmpty, True)
      , ("delta differs", BiDelta BtEmpty, BiDelta (BtOne "00"), False)
      , ("void equals void", BiVoid AtRho, BiVoid AtRho, True)
      , ("void differs", BiVoid AtRho, BiVoid AtPhi, False)
      , ("lambda equals lambda", BiLambda (Function "Func"), BiLambda (Function "Func"), True)
      , ("lambda differs", BiLambda (Function "Func"), BiLambda (Function "Other"), False)
      , ("metalambda equals", BiLambda (FnMeta "F"), BiLambda (FnMeta "F"), True)
      , ("metalambda differs", BiLambda (FnMeta "F"), BiLambda (FnMeta "G"), False)
      ]
      ( \(desc, lhs, rhs, expected) ->
          it desc $ (lhs == rhs) `shouldBe` expected
      )

  describe "Binding Eq instance compares different constructors" $
    forM_
      [ ("tau vs meta", BiTau AtRho ExRoot, BiMeta "B", False)
      , ("delta vs void", BiDelta BtEmpty, BiVoid AtDelta, False)
      , ("lambda vs metalambda", BiLambda (Function "F"), BiLambda (FnMeta "F"), False)
      ]
      ( \(desc, lhs, rhs, expected) ->
          it desc $ (lhs == rhs) `shouldBe` expected
      )

  describe "Binding Ord instance orders correctly" $
    it "sorts bindings by constructor order" $
      let bindings = [BiLambda (FnMeta "Z"), BiLambda (Function "A"), BiVoid AtRho, BiDelta BtEmpty, BiMeta "B", BiTau AtRho ExRoot]
          first : _ = sort bindings
          isTau (BiTau _ _) = True
          isTau _ = False
       in first `shouldSatisfy` isTau

  describe "Expression Eq instance compares same constructors" $
    forM_
      [ ("formation equals", ExFormation [], ExFormation [], True)
      , ("formation differs", ExFormation [], ExFormation [BiVoid AtRho], False)
      , ("this equals this", ExXi, ExXi, True)
      , ("global equals global", ExRoot, ExRoot, True)
      , ("termination equals", ExTermination, ExTermination, True)
      , ("meta equals meta", ExMeta "e", ExMeta "e", True)
      , ("meta differs", ExMeta "e", ExMeta "f", False)
      , ("application equals", ExApplication ExRoot (ArTau AtRho ExXi), ExApplication ExRoot (ArTau AtRho ExXi), True)
      , ("dispatch equals", ExDispatch ExRoot AtRho, ExDispatch ExRoot AtRho, True)
      , ("dispatch differs", ExDispatch ExRoot AtRho, ExDispatch ExRoot AtPhi, False)
      ]
      ( \(desc, lhs, rhs, expected) ->
          it desc $ (lhs == rhs) `shouldBe` expected
      )

  describe "Expression Eq instance compares different constructors" $
    forM_
      [ ("formation vs this", ExFormation [], ExXi, False)
      , ("global vs termination", ExRoot, ExTermination, False)
      , ("meta vs dispatch", ExMeta "e", ExDispatch ExRoot AtRho, False)
      ]
      ( \(desc, lhs, rhs, expected) ->
          it desc $ (lhs == rhs) `shouldBe` expected
      )

  describe "Expression Ord instance orders correctly" $
    it "sorts expressions by constructor order" $
      let exprs = [ExDispatch ExRoot AtRho, ExApplication ExRoot (ArTau AtRho ExRoot), ExMeta "e", ExTermination, ExRoot, ExXi, ExFormation []]
          first : _ = sort exprs
       in first `shouldBe` ExFormation []

  describe "countNodes counts ExFormation with non-tau bindings" $
    forM_
      [ ("Q", ExRoot, 1)
      , ("T", ExTermination, 1)
      , ("$", ExXi, 1)
      , ("dispatch on global", ExDispatch ExRoot (AtLabel "x"), 3)
      , ("application with globals", ExApplication ExRoot (ArTau AtRho ExRoot), 6)
      , ("nested expressions", ExFormation [BiTau AtRho ExRoot, BiTau AtPhi ExRoot], 9)
      , ("empty formation", ExFormation [], 1)
      , ("void binding", ExFormation [BiVoid AtRho], 5)
      , ("delta binding", ExFormation [BiDelta BtEmpty], 5)
      , ("lambda binding", ExFormation [BiLambda (Function "Func")], 5)
      , ("meta binding", ExFormation [BiMeta "B"], 3)
      , ("metalambda binding", ExFormation [BiLambda (FnMeta "F")], 5)
      , ("meta expression", ExMeta "e", 1)
      , ("deeply nested dispatch", ExDispatch (ExDispatch ExRoot (AtLabel "a")) (AtLabel "b"), 5)
      , ("formation with dispatch inside", ExFormation [BiTau AtRho (ExDispatch ExRoot (AtLabel "x"))], 7)
      , ("application with tau argument", ExApplication ExRoot (ArTau AtRho ExRoot), 6)
      , ("application with alpha argument", ExApplication ExRoot (ArAlpha (Alpha 0) ExRoot), 6)
      , ("phi-meet wraps its inner count", ExPhiMeet Nothing 1 (ExDispatch ExRoot (AtLabel "x")), 3)
      , ("phi-meet with label wraps its inner count", ExPhiMeet (Just "m") 2 ExRoot, 1)
      , ("phi-again wraps its inner count", ExPhiAgain Nothing 1 (ExDispatch ExRoot (AtLabel "x")), 3)
      , ("phi-again with label wraps its inner count", ExPhiAgain (Just "m") 2 ExRoot, 1)
      , ("bare bytes falls into the catch-all", ExBytes BtEmpty, 1)
      ]
      ( \(desc, expr, expected) ->
          it desc $ countNodes expr `shouldBe` expected
      )

  describe "hashExpression" $ do
    it "hashes every Expression/Binding/Bytes/Attribute/Argument/Alpha/Function constructor without collisions" $
      let exprs =
            [ ExFormation []
            , ExFormation [BiTau AtRho ExRoot]
            , ExFormation [BiVoid AtPhi]
            , ExFormation [BiDelta BtEmpty]
            , ExFormation [BiDelta (BtOne "FF")]
            , ExFormation [BiDelta (BtMany ["00", "01"])]
            , ExFormation [BiDelta (BtMeta "b")]
            , ExFormation [BiLambda (Function "Func")]
            , ExFormation [BiLambda (FnMeta "F")]
            , ExFormation [BiMeta "M"]
            , ExXi
            , ExRoot
            , ExTermination
            , ExApplication ExRoot (ArTau AtRho ExXi)
            , ExApplication ExRoot (ArAlpha (Alpha 3) ExXi)
            , ExApplication ExRoot (ArAlpha (AlMeta "A") ExXi)
            , ExDispatch ExRoot (AtLabel "x")
            , ExDispatch ExRoot AtPhi
            , ExDispatch ExRoot AtRho
            , ExDispatch ExRoot AtLambda
            , ExDispatch ExRoot AtDelta
            , ExDispatch ExRoot (AtMeta "m")
            , ExMeta "e"
            , ExMeta "f"
            , ExPhiMeet (Just "m") 2 ExRoot
            , ExPhiMeet Nothing 2 ExRoot
            , ExPhiMeet Nothing 3 ExRoot
            , ExPhiAgain (Just "m") 2 ExRoot
            , ExPhiAgain Nothing 2 ExRoot
            , ExPhiAgain Nothing 3 ExRoot
            , ExBytes BtEmpty
            , ExBytes (BtOne "FF")
            , ExBytes (BtMany ["00", "FF"])
            , ExBytes (BtMeta "b")
            ]
          hashes = map hashExpression exprs
       in nub hashes `shouldBe` hashes

    it "produces different hashes for a couple of hand-picked distinct expressions" $ do
      hashExpression ExRoot `shouldNotBe` hashExpression ExXi
      hashExpression (ExDispatch ExRoot (AtLabel "x")) `shouldNotBe` hashExpression (ExDispatch ExRoot (AtLabel "y"))

  describe "alike" $ do
    let application :: [Int] -> Expression
        application idxs = ExApplication (ExDispatch ExRoot (AtLabel "f")) (ArTau (AtLabel "x") (ExFormation (map (BiLambda . FnSymbol) idxs)))
    it "does not tell apart two terms that differ by a bijective renaming of symbols" $
      alike (application [1, 2]) (application [7, 3]) `shouldBe` True
    it "does not take two terms that differ by a datum for the same" $
      alike (ExFormation [BiDelta (BtOne "01"), BiLambda (FnSymbol 4)]) (ExFormation [BiDelta (BtOne "02"), BiLambda (FnSymbol 9)]) `shouldBe` False
    it "does not map one symbol to two others" $
      alike (application [1, 1]) (application [1, 2]) `shouldBe` False
    it "does not map two symbols to one other" $
      alike (application [5, 6]) (application [8, 8]) `shouldBe` False
    it "does not take two terms that differ by a λ name for the same" $
      alike (ExFormation [BiLambda (Function "L_one")]) (ExFormation [BiLambda (Function "L_two")]) `shouldBe` False
    it "does not take two terms that differ by an attribute for the same" $
      alike (ExDispatch (application [3]) (AtLabel "a")) (ExDispatch (application [3]) (AtLabel "b")) `shouldBe` False

  describe "within" $ do
    let pair :: Expression -> Expression
        pair tail' = ExApplication (ExDispatch ExRoot (AtLabel "pair")) (ArTau (AtLabel "tail") tail')
        call :: Int -> Expression -> Expression
        call idx acc = ExFormation [BiTau (AtLabel "n") (ExFormation [BiLambda (FnSymbol idx)]), BiTau (AtLabel "acc") acc, BiLambda (Function "L_fact")]
    it "finds a round inside the next one that wraps its accumulator once more" $
      within (call 3 (ExFormation [BiDelta (BtOne "2A")])) (call 8 (pair (ExFormation [BiDelta (BtOne "2A")]))) `shouldBe` True
    it "does not find a call inside a smaller one nested in it" $
      within (call 4 (pair (pair ExRoot))) (call 4 (pair ExRoot)) `shouldBe` False
    it "does not find a formation inside one of other attributes" $
      within (call 5 ExXi) (ExFormation [BiTau (AtLabel "m") (ExFormation [BiLambda (FnSymbol 5)]), BiTau (AtLabel "acc") ExXi, BiLambda (Function "L_fact")]) `shouldBe` False
    it "does not find a formation inside one naming another λ function" $
      within (ExFormation [BiTau (AtLabel "x") ExXi, BiLambda (Function "L_zero")]) (ExFormation [BiTau (AtLabel "x") ExXi, BiLambda (Function "L_dec")]) `shouldBe` False
    it "does not find a round inside one that differs by a datum" $
      within (call 1 (ExFormation [BiDelta (BtOne "07")])) (call 1 (pair (ExFormation [BiDelta (BtOne "09")]))) `shouldBe` False
    it "does not find a formation inside one that only holds it under an attribute" $
      within (call 2 ExRoot) (ExFormation [BiTau (AtLabel "x") (call 2 ExRoot), BiLambda (Function "L_g")]) `shouldBe` False
    it "does not find a formation under the ρ of the next one" $
      within (call 6 ExRoot) (call 6 (ExFormation [BiTau AtRho (call 6 ExRoot)])) `shouldBe` False

  describe "hashSkeleton" $ do
    it "does not tell apart two formations that bind other terms to the same attributes" $
      hashSkeleton (ExFormation [BiTau (AtLabel "acc") ExRoot, BiLambda (Function "L_f")])
        `shouldBe` hashSkeleton (ExFormation [BiTau (AtLabel "acc") (ExDispatch ExXi (AtLabel "q")), BiLambda (Function "L_f")])
    it "does not hash two formations naming different λ functions alike" $
      hashSkeleton (ExFormation [BiLambda (Function "L_f")]) `shouldNotBe` hashSkeleton (ExFormation [BiLambda (Function "L_g")])

  describe "hashShape" $ do
    it "does not tell apart two terms that differ by symbols alone" $
      hashShape (ExFormation [BiTau (AtLabel "n") (ExFormation [BiLambda (FnSymbol 3)])])
        `shouldBe` hashShape (ExFormation [BiTau (AtLabel "n") (ExFormation [BiLambda (FnSymbol 5)])])
    it "does not hash two terms differing by a datum alike" $
      hashShape (ExFormation [BiDelta (BtOne "0A")]) `shouldNotBe` hashShape (ExFormation [BiDelta (BtOne "0B")])

  describe "BaseObject pattern" $ do
    it "constructs a Q-dispatch expression" $
      BaseObject "bytes" `shouldBe` ExDispatch ExRoot (AtLabel "bytes")

    forM_
      [ ("matches a Q-dispatch expression, extracting the label", ExDispatch ExRoot (AtLabel "number"), Just "number")
      , ("does not match a non-Q-dispatch expression", ExDispatch ExXi (AtLabel "number"), Nothing)
      ]
      ( \(desc, expr, expected) ->
          it desc $
            let matched = case expr of
                  BaseObject label -> Just label
                  _ -> Nothing
             in matched `shouldBe` expected
      )

  describe "dataBytes" $
    it "builds the bytes-object formation carrying the given bytes" $
      dataBytes (BtOne "48")
        `shouldBe` ExApplication
          (ExDispatch ExRoot (AtLabel "bytes"))
          (ArTau AtPhi (ExFormation [BiDelta (BtOne "48")]))

  describe "DataObject/DataString/DataNumber pattern" $ do
    it "constructs the phi-named, unwrapped form" $
      DataString (BtOne "48")
        `shouldBe` ExApplication
          (ExDispatch ExRoot (AtLabel "string"))
          (ArTau AtPhi (dataBytes (BtOne "48")))

    forM_
      [
        ( "matches the phi-named, unwrapped form"
        , ExApplication (ExDispatch ExRoot (AtLabel "string")) (ArTau AtPhi (dataBytes (BtOne "48")))
        , Just (BtOne "48")
        )
      ,
        ( "matches the legacy as-bytes name on the outer binding"
        , ExApplication (ExDispatch ExRoot (AtLabel "string")) (ArTau (AtLabel "as-bytes") (dataBytes (BtOne "48")))
        , Just (BtOne "48")
        )
      ,
        ( "matches the named form with a phi-again-wrapped outer object"
        , ExApplication
            (ExPhiAgain Nothing 1 (ExDispatch ExRoot (AtLabel "string")))
            (ArTau (AtLabel "as-bytes") (dataBytes (BtOne "48")))
        , Just (BtOne "48")
        )
      ,
        ( "matches the named form with a phi-again-wrapped inner bytes formation"
        , ExApplication
            (ExDispatch ExRoot (AtLabel "number"))
            ( ArTau
                (AtLabel "as-bytes")
                ( ExApplication
                    (ExDispatch ExRoot (AtLabel "bytes"))
                    (ArTau (AtLabel "data") (ExPhiAgain Nothing 1 (ExFormation [BiDelta (BtOne "05")])))
                )
            )
        , Just (BtOne "05")
        )
      ,
        ( "matches the legacy positional (alpha0) form on both layers"
        , ExApplication
            (ExDispatch ExRoot (AtLabel "number"))
            ( ArAlpha
                (Alpha 0)
                ( ExApplication
                    (ExDispatch ExRoot (AtLabel "bytes"))
                    (ArAlpha (Alpha 0) (ExFormation [BiDelta (BtOne "05")]))
                )
            )
        , Just (BtOne "05")
        )
      ]
      ( \(desc, expr, expected) ->
          it desc $
            let matched = case expr of
                  DataString bts -> Just bts
                  DataNumber bts -> Just bts
                  _ -> Nothing
             in matched `shouldBe` expected
      )

    forM_
      [
        ( "does not match when the outer object is not a base object (matchOuter fails)"
        , ExApplication ExRoot (ArTau (AtLabel "as-bytes") (dataBytes (BtOne "48")))
        )
      ,
        ( "does not match when the inner object is not an application (matchInner fails)"
        , ExApplication (ExDispatch ExRoot (AtLabel "string")) (ArTau (AtLabel "as-bytes") ExRoot)
        )
      ,
        ( "does not match when the inner base object is not 'bytes' (matchesBytes fails)"
        , ExApplication
            (ExDispatch ExRoot (AtLabel "string"))
            ( ArTau
                (AtLabel "as-bytes")
                ( ExApplication
                    (ExDispatch ExRoot (AtLabel "other"))
                    (ArTau AtPhi (ExFormation [BiDelta (BtOne "48")]))
                )
            )
        )
      ,
        ( "does not match when the bytes formation has the wrong shape (matchFormation fails)"
        , ExApplication
            (ExDispatch ExRoot (AtLabel "string"))
            ( ArTau
                (AtLabel "as-bytes")
                ( ExApplication
                    (ExDispatch ExRoot (AtLabel "bytes"))
                    (ArTau (AtLabel "data") (ExFormation [BiDelta (BtOne "48"), BiVoid AtRho]))
                )
            )
        )
      ]
      ( \(desc, expr) ->
          it desc $
            let matched = case expr of
                  DataObject label bts -> Just (label, bts)
                  _ -> Nothing
             in matched `shouldBe` Nothing
      )

  describe "attributeFromBinding" $
    forM_
      [ ("BiTau yields its attribute", BiTau AtRho ExRoot, Just AtRho)
      , ("BiVoid yields its attribute", BiVoid AtPhi, Just AtPhi)
      , ("BiDelta yields AtDelta", BiDelta BtEmpty, Just AtDelta)
      , ("BiLambda yields AtLambda", BiLambda (Function "F"), Just AtLambda)
      , ("BiMeta yields Nothing", BiMeta "B", Nothing)
      ]
      (\(desc, binding, expected) -> it desc (attributeFromBinding binding `shouldBe` expected))

  describe "inert" $ do
    it "takes a formation of dispatches off ξ and Φ for inert" $
      inert (ExFormation [BiTau (AtLabel "kv") (ExDispatch ExXi (AtLabel "ob")), BiTau (AtLabel "zu") (ExApplication (ExDispatch ExRoot (AtLabel "ny")) (ArTau (AtLabel "q") ExXi)), BiLambda (Function "L_wy")])
        `shouldBe` True
    it "does not take a dispatch on a formation for inert" $
      inert (ExDispatch (ExFormation [BiTau (AtLabel "hm") ExRoot]) (AtLabel "hm")) `shouldBe` False
    it "does not take an application of ⊥ for inert" $
      inert (ExApplication ExTermination (ArAlpha (Alpha 2) ExXi)) `shouldBe` False
    it "does not take a formation holding both λ and Δ for inert" $
      inert (ExFormation [BiDelta (BtOne "3F"), BiVoid AtRho, BiLambda (Function "L_ep")]) `shouldBe` False
    it "does not take a formation holding a redex deep inside for inert" $
      inert (ExFormation [BiTau (AtLabel "ix") (ExFormation [BiTau (AtLabel "gu") (ExDispatch ExTermination (AtLabel "sa"))])]) `shouldBe` False
    it "does not take a formation holding a binding meta for inert" $
      inert (ExFormation [BiVoid (AtLabel "ro"), BiMeta "B4"]) `shouldBe` False

  describe "distinct" $ do
    it "takes a formation of different attributes for distinct" $
      distinct (ExFormation [BiVoid (AtLabel "ka"), BiTau (AtLabel "ak") ExXi, BiLambda (Function "L_ok")]) `shouldBe` True
    it "does not take a formation carrying one attribute twice for distinct" $
      distinct (ExFormation [BiVoid (AtLabel "ka"), BiTau AtPhi ExXi, BiTau (AtLabel "ka") ExRoot]) `shouldBe` False

  describe "repeated" $ do
    it "finds the attribute the bindings carry for the second time" $
      repeated [BiVoid (AtLabel "wz"), BiDelta (BtOne "0C"), BiTau (AtLabel "zw") ExXi, BiDelta BtEmpty] `shouldBe` Just AtDelta
    it "does not find a repeat among hundreds of distinct labels" $
      repeated [BiVoid (AtLabel (T.pack ('q' : show idx))) | idx <- [3 :: Int, 10 .. 2900]] `shouldBe` Nothing

  describe "Expression equality" $ do
    it "does not tell apart two formations built apart of the same parts" $
      ExFormation [BiTau (AtLabel "lu") (ExDispatch ExXi (AtLabel "op")), BiVoid AtRho]
        `shouldBe` ExFormation [BiTau (AtLabel "lu") (ExDispatch ExXi (AtLabel "op")), BiVoid AtRho]
    it "tells apart two formations that differ deep inside" $
      ExFormation [BiTau (AtLabel "lu") (ExDispatch ExXi (AtLabel "op"))]
        `shouldNotBe` ExFormation [BiTau (AtLabel "lu") (ExDispatch ExXi (AtLabel "po"))]

  describe "Expression Show instance" $
    it "does not show what a node carries besides its parts" $
      show (ExApplication (ExDispatch ExXi (AtLabel "yb")) (ArAlpha (Alpha 0) (ExFormation [BiVoid AtRho])))
        `shouldBe` "ExApplication (ExDispatch ExXi yb) (ArAlpha α0 (ExFormation [BiVoid ρ]))"