phino-0.0.134: 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, Meta (..), emptyLambdas, joined, matched, minted, readLambdas, symbolized, taken)
import Parser (parseExpressionThrows)
import Test.Hspec
-- The Ξ» functions the given text spells, read out of a file of its own, which
-- is how '--symbolic' reads them and the only way they are ever read
lambdasOf :: T.Text -> IO Lambdas
lambdasOf text = withLambdasOf text readLambdas
-- One entry with the given key, one 'dataize' operand and an answer standing
-- for the unknown it came down to, which is the shape most cases start from
entry :: T.Text -> T.Text
entry key = "- Ξ»: " <> key <> "\n dataize:\n πΏ1: $.Ο\n π: β¦ Ξ» β€ π β§\n"
-- The key the entry answering the given Ξ» name is registered under, or nothing
-- where no entry answers it. Lookups go through this rather than through the
-- entry itself, since an entry is no value and nothing compares two of them.
answering :: Lambdas -> T.Text -> Maybe T.Text
answering known func = _key <$> matched known func
-- Every 'join' line of the entry answering the given Ξ» name, spelled the way
-- the file spells it: the meta it binds and the two it joins.
joins :: Lambdas -> T.Text -> [(T.Text, (T.Text, T.Text))]
joins known func = map spelled (maybe [] _paired (matched known func))
where
spelled :: (Meta, (Meta, Meta)) -> (T.Text, (T.Text, T.Text))
spelled (meta, (left, right)) = (_spelling meta, (_spelling left, _spelling right))
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"
-- A key is a regular expression over Ξ» names, so one entry stands for the
-- whole family of them a box numbers its functions with
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"
-- The expression matches the whole name and not a part of it, so a plain
-- name keeps meaning that one Ξ» function
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 "cannot read a Ξ» function no entry answers" $ do
known <- lambdasOf (entry "L_number_plus")
answering known "L_bytes_not" `shouldBe` Nothing
-- A YAML mapping keeps no order of its own, so the metas are what orders
-- the operands: πΏ1 comes down before πΏ2 however the file lists them
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"]
-- The protocol spells a meta the way the file does, while a substitution
-- keeps it under the name π-calculus gives it, and the two differ
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"
map (_spelling . fst) (maybe [] _symbolized (matched known "L_fork")) `shouldBe` ["π2"]
-- A line of 'symbolize' stands data into unknowns, and the term it stands
-- may already be one a line above it made, so the block reads top to
-- bottom the way the metas number it
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"
map (_spelling . fst) (maybe [] _symbolized (matched 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
-- A fork answers neither of its branches but the join of the two, which a
-- 'join' line binds a meta of its own to, so the answer names that meta
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"))]
-- The two are joined in the order the line lists them, which is the order
-- the protocol writes the two symbols a fresh one stands for in
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"))]
-- 'join' runs after 'symbolize', so a line of it may join what that stage
-- stood, and a line of it may join what a line above it made
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 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` []
-- Everything a file may be wrong about fails where it is read, before any
-- reduction starts, so a run never gets half-way through a derivation to
-- discover that one of its Ξ» functions cannot be read at all
forM_
[ ("a file which is no list of entries" :: String, "Ξ»: L_pair\n" :: T.Text, "cannot be read" :: String)
, ("an entry with no answer at all", "- Ξ»: L_pair\n", "cannot be read")
, ("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")
, ("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", "names no meta")
, ("an operand of 'symbolize' standing the term of a line below it", "- Ξ»: L_pair\n morph:\n π1: $.x\n symbolize:\n π2: π3\n π3: π1\n π: π2\n", "names no 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")
, ("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 a line above it")
, ("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 'join' line joining what a line below it made", "- Ξ»: L_fork\n morph:\n π1: $.a\n π2: $.b\n join:\n π3: [π1, π4]\n π4: [π1, π2]\n π: π3\n", "names no meta bound by 'morph' or by a line above it")
, ("a 'symbolize' line standing what a 'join' line made", "- Ξ»: L_fork\n morph:\n π1: $.a\n π2: $.b\n symbolize:\n π5: π3\n join:\n π3: [π1, π2]\n π: π5\n", "names no meta bound by 'morph' or by a line above it")
]
( \(desc, text, message) ->
it ("cannot read " ++ desc) $
lambdasOf text `shouldThrow` (\err -> message `isInfixOf` show (err :: SomeException))
)
describe "emptyLambdas" $
-- A run without '--symbolic' fires against no Ξ» function at all, which is
-- what every name getting stuck means and what '--partial' parks on
it "cannot read a Ξ» function without the file naming one" $
answering emptyLambdas "L_number_plus" `shouldBe` Nothing
-- Which symbol a fresh π becomes is the state's business and not the file's:
-- the count of symbols the run has minted so far goes in and the names taken
-- come back out, so no two unknowns of one run are ever spelled alike.
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
-- A symbol the answer names is one the entry means, not one it asks for,
-- so nothing is minted for it
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
-- A datum a term carries is a value somebody worked out, and a normal form
-- reached from an unknown carries none, so the two compare as expressions
-- only once the data of the one are unknowns too. Standing them is what a
-- 'symbolize' line of an entry asks for.
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
-- A π is the name of a Ξ» function and no term, so the bytes are not bound
-- to it: what is known is that dataizing the formation it names answers
-- them
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
-- A term carries the value it stands for where its Ο chain ends, so a
-- datum anywhere else is not that value: the literal of a method is the
-- body of something nobody has called, and standing it would write an
-- unknown nobody reads. The method comes back exactly as it was written
-- (#1293).
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
-- A term nobody worked a value out in is an unknown already, and standing
-- it changes nothing
it "leaves a term carrying no datum as it was written" $ do
term <- parseExpressionThrows "Ξ¦.number( Ο β¦ β¦ Ξ» β€ π1 β§ )"
let (masked, _, _) = symbolized term 4
masked `shouldBe` term
-- A literal is sugar for a datum sitting three levels down inside a
-- formation, which is the very place a computed value keeps its unknown
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 is the Ξ binding that becomes an unknown and not the formation around
-- it, since a datum carries a Ο of its own and so does the unknown it is
-- put beside
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
-- A term carries the value it stands for where its Ο chain ends, and a
-- datum sitting under Ο belongs to the object around this one: a normal
-- form drags the whole 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
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
-- Neither branch of a fork is the value the fork answers with, since nobody
-- has picked between the two: what stands for either of them is the shape
-- both of them have, with a fresh symbol wherever they differ
describe "joined" $ do
let joining :: String -> String -> Int -> IO (Maybe (Expression, [(Int, (Int, Int))], Int))
joining left right spent = do
one <- parseExpressionThrows left
two <- parseExpressionThrows right
pure (joined one two spent)
it "joins two branches differing in one symbol into a fresh one" $ do
term <- parseExpressionThrows "β¦ Ο β¦ β¦ Ξ» β€ π5 β§ β§"
made <- joining "β¦ Ο β¦ β¦ Ξ» β€ π1 β§ β§" "β¦ Ο β¦ β¦ Ξ» β€ π2 β§ β§" 4
fmap (\(joint, _, _) -> joint) made `shouldBe` Just term
-- A π is the name of a Ξ» function and nothing is assigned to it, so what
-- comes back beside the term is which two symbols the fresh one stands for
it "tells the two symbols every fresh one stands for" $ do
made <- joining "β¦ Ο β¦ β¦ Ξ» β€ π1 β§ β§" "β¦ Ο β¦ β¦ Ξ» β€ π2 β§ β§" 4
fmap (\(_, facts, _) -> facts) made `shouldBe` Just [(5, (1, 2))]
it "counts every symbol it minted into the state" $ do
made <- joining "β¦ Ο β¦ Ξ¦.f( Ο β¦ β¦ Ξ» β€ π1 β§ )( t β¦ β¦ Ξ» β€ π2 β§ ) β§" "β¦ Ο β¦ Ξ¦.f( Ο β¦ β¦ Ξ» β€ π3 β§ )( t β¦ β¦ Ξ» β€ π4 β§ ) β§" 4
fmap (\(_, _, spent) -> spent) made `shouldBe` Just 6
-- Two pairs are two choices and get two names of their own
it "mints one symbol per pair of differing symbols" $ do
made <- joining "β¦ Ο β¦ Ξ¦.f( Ο β¦ β¦ Ξ» β€ π1 β§ )( t β¦ β¦ Ξ» β€ π2 β§ ) β§" "β¦ Ο β¦ Ξ¦.f( Ο β¦ β¦ Ξ» β€ π3 β§ )( t β¦ β¦ Ξ» β€ π4 β§ ) β§" 4
fmap (\(_, facts, _) -> facts) made `shouldBe` Just [(5, (1, 3)), (6, (2, 4))]
-- One pair met twice is one choice however often the two terms differ by
-- it, so it keeps the symbol it was given the first time
it "mints one symbol for the pair it meets twice" $ do
term <- parseExpressionThrows "β¦ Ο β¦ Ξ¦.f( Ο β¦ β¦ Ξ» β€ π5 β§ )( t β¦ β¦ Ξ» β€ π5 β§ ) β§"
made <- joining "β¦ Ο β¦ Ξ¦.f( Ο β¦ β¦ Ξ» β€ π1 β§ )( t β¦ β¦ Ξ» β€ π1 β§ ) β§" "β¦ Ο β¦ Ξ¦.f( Ο β¦ β¦ Ξ» β€ π2 β§ )( t β¦ β¦ Ξ» β€ π2 β§ ) β§" 4
made `shouldBe` Just (term, [(5, (1, 2))], 5)
-- Only the Ο chain is compared, the value of a branch being where that
-- chain ends. Two branches differing inside a method are not two values:
-- the method is code nobody has called, the first branch's copy of it is
-- what the answer keeps, and nothing is minted for the difference (#1293).
it "carries a method from the first branch and mints nothing for it" $ do
term <- parseExpressionThrows "β¦ Ο β¦ β¦ Ξ» β€ π5 β§, neg β¦ β¦ Ο β¦ β¦ Ξ» β€ π7 β§ β§ β§"
made <- joining "β¦ Ο β¦ β¦ Ξ» β€ π1 β§, neg β¦ β¦ Ο β¦ β¦ Ξ» β€ π7 β§ β§ β§" "β¦ Ο β¦ β¦ Ξ» β€ π2 β§, neg β¦ β¦ Ο β¦ β¦ Ξ» β€ π8 β§ β§ β§" 4
made `shouldBe` Just (term, [(5, (1, 2))], 5)
-- What the two branches are is still read off their shape: a binding one
-- of them carries under a name the other does not is no fork at all, and
-- carrying the first branch's bindings never papers over that
it "refuses two branches whose bindings are named differently" $ do
made <- joining "β¦ Ο β¦ β¦ Ξ» β€ π1 β§, m β¦ β¦ x β¦ β
β§ β§" "β¦ Ο β¦ β¦ Ξ» β€ π2 β§, other β¦ β¦ x β¦ β
β§ β§" 4
made `shouldBe` Nothing
-- Two branches nothing tells apart are the answer themselves: there is
-- nothing to pick between and no unknown to stand for the pick
it "joins two branches that are one term into that very term" $ do
term <- parseExpressionThrows "Ξ¦.number( Ο β¦ β¦ Ξ» β€ π1 β§ )"
made <- joining "Ξ¦.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 "Ξ¦.number( Ο β¦ β¦ Ξ» β€ π1 β§ )" "Ξ¦.number( Ο β¦ β¦ Ξ» β€ π2 β§ )" 4
fmap (\(joint, _, _) -> joint) made `shouldBe` Just term
-- A term carries the value it stands for where its Ο chain ends, and what
-- sits under Ο belongs to the object around this one: the two branches of
-- a fork are reduced in scopes of their own, so their Ο differ wherever
-- that reduction left a trace and comparing them would refuse the join
-- over something saying nothing about either branch
it "leaves what a Ο carries alone" $ do
term <- parseExpressionThrows "β¦ Ο β¦ β¦ Ξ» β€ π5 β§, Ο β¦ β¦ x β¦ β¦ Ξ β€ 00- β§ β§ β§"
made <- joining "β¦ Ο β¦ β¦ Ξ» β€ π1 β§, Ο β¦ β¦ x β¦ β¦ Ξ β€ 00- β§ β§ β§" "β¦ Ο β¦ β¦ Ξ» β€ π2 β§, Ο β¦ β¦ y β¦ β¦ Ξ β€ FF- β§ β§ β§" 4
made `shouldBe` Just (term, [(5, (1, 2))], 5)
-- Two branches nothing but their Ο tells apart are one value, so nothing
-- is minted for what stands under it
it "joins two branches differing in their Ο alone" $ do
term <- parseExpressionThrows "β¦ Ο β¦ β¦ Ξ» β€ π1 β§, Ο β¦ β¦ x β¦ β¦ Ξ β€ 00- β§ β§ β§"
made <- joining "β¦ Ο β¦ β¦ Ξ» β€ π1 β§, Ο β¦ β¦ x β¦ β¦ Ξ β€ 00- β§ β§ β§" "β¦ Ο β¦ β¦ Ξ» β€ π1 β§, Ο β¦ β¦ y β¦ β¦ Ξ β€ FF- β§ β§ β§" 4
made `shouldBe` Just (term, [], 4)
-- The join is strict and a datum is never joined with anything, which is
-- why a branch carrying one goes through 'symbolized' first
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 β§ )")
]
( \(desc, left, right) ->
it ("cannot join " ++ desc) $ do
made <- joining left right 4
fmap (\(joint, _, _) -> joint) made `shouldBe` Nothing
)
-- A program written by an earlier run holds symbols of its own, and a fresh
-- one must never be spelled like one of them
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