phino-0.0.153: 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, message, minted, readLambdas, symbolized, taken, thrown)
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
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))
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 :: (Meta, (Meta, [a])) -> (T.Text, T.Text, Int)
rewrote (meta, (source, written)) = (_spelling meta, _spelling source, length written)
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"
map (_spelling . fst) (maybe [] _symbolized (matched 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"
map (_spelling . fst) (maybe [] _symbolized (matched 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"
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
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" $ do
known <- lambdasOf "- Ξ»: L_fork\n morph:\n π1: $.then\n π2: $.else\n join:\n π3: [π1, π2]\n lenient: true\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 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")
map rewrote (maybe [] _rewritten (matched 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")
map (_spelling . fst) (maybe [] _symbolized (matched known "L_fork")) `shouldBe` ["π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` []
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", "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")
, ("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 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 '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 a line above it")
, ("a 'rewrite' line rewriting what a line below it made", rewriting " π2:\n of: π3\n" <> rules <> " π3:\n of: π1\n" <> rules <> " π: π2\n", "names no meta bound by 'morph' or by a line above it")
, ("a 'rewrite' line rewriting what a 'symbolize' line made", rewriting " π3:\n of: π2\n" <> rules <> " symbolize:\n π2: π1\n π: π3\n", "names no meta bound by 'morph' or by a line above it")
, ("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 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: true\n π: β¦ Ξ» β€ π β§\n", "is lenient but has no 'join' line")
, ("a 'lenient' key which is no boolean", "- Ξ»: L_fork\n morph:\n π1: $.a\n π2: $.b\n join:\n π3: [π1, π2]\n lenient: often\n π: π3\n", "lenient: expected Bool")
, ("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")
, ("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 "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)
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 "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