phino-0.0.157: src/Lambdas.hs
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE OverloadedRecordDot #-}
{-# LANGUAGE OverloadedStrings #-}
-- SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
-- SPDX-License-Identifier: MIT
module Lambdas
( Ending (..)
, Lambda (..)
, LambdaException (..)
, Lambdas
, Line (..)
, Meta (..)
, emptyLambdas
, matched
, minted
, readLambdas
, rewritten
, stayed
, symbolized
, taken
)
where
import AST
import Builder (buildExpressionThrows)
import Control.Exception (Exception, throwIO)
import Control.Monad.Trans.State.Strict (State, runState, state)
import Data.Aeson (FromJSON (parseJSON), Key, Object, withObject, (.!=), (.:?))
import qualified Data.Aeson.Key as Key
import qualified Data.Aeson.KeyMap as KeyMap
import Data.Aeson.Types (JSONPathElement (Key), (<?>))
import Data.Char (isDigit)
import Data.Either (partitionEithers)
import Data.List (find, sortOn)
import Data.Map.Strict (Map)
import qualified Data.Map.Strict as Map
import Data.Maybe (fromMaybe, listToMaybe)
import Data.Text (Text)
import qualified Data.Text as T
import Data.Text.Encoding (encodeUtf8)
import qualified Data.Yaml as Yaml
import Language (Language, language, shared)
import Logger (logDebug)
import Matcher (substEmpty)
import Metas (Metas (metas))
import Parser (parseBinding, parseBytes, parseExpression)
import Rule (RuleContext, matchExpressionWithRule')
import Slots (Slots (slots))
import Text.Printf (printf)
import Text.Regex.PCRE (matchTest)
import Text.Regex.PCRE.ByteString (Regex, compUTF8, compile, execBlank)
import Yaml (referenceless)
import qualified Yaml as Y
data Meta = Meta
{ _spelling :: Text
, _name :: Text
}
data Line
= Morphed Meta Meta
| Evaluated Meta Meta
| Deferred Meta Meta
| Stayed Meta Meta
| Rewritten Meta Meta [Y.Rule]
| Symbolized Meta Meta
data Ending
= Answering Expression
| Joining (Either Meta Expression) Expression Expression (Maybe Function)
data Lambda = Lambda
{ _key :: Text
, _dataized :: [(Meta, Expression)]
, _morphed :: [(Meta, Expression)]
, _lines :: [Line]
, _ending :: Ending
}
newtype Lambdas = Lambdas [(Regex, Lambda)]
data LambdaException
= BrokenLambdas FilePath String
deriving anyclass (Exception)
instance Show LambdaException where
show (BrokenLambdas file failure) =
printf "The λ functions of '%s' cannot be read: %s" file failure
instance FromJSON Lambda where
parseJSON = withObject "Lambda" $ \entry -> do
key <- entry .:? "λ" >>= maybe (fail "The entry has no 'λ' key") pure
unknown (T.unpack key) entry
dataized <- operands key bytesMeta entry "dataize"
ending <- concluded (T.unpack key) dataized entry
(morphed, reading) <- partitionEithers <$> reductions key entry
lambda <-
Lambda key dataized morphed
<$> (written (T.unpack key) entry >>= ordered (T.unpack key) (map (_name . fst) morphed) . (reading ++))
<*> pure ending
once (T.unpack key) lambda
mapM_ (metaless (T.unpack key) "dataize" "a path from '$'") lambda._dataized
mapM_ (metaless (T.unpack key) "morph" "a path from '$' or a meta alone") lambda._morphed
answered (T.unpack key) lambda
pure lambda
where
unknown :: String -> Object -> Yaml.Parser ()
unknown key entry = case filter (`notElem` known) (map Key.toText (KeyMap.keys entry)) of
[] -> pure ()
name : _ -> fail (printf "The key '%s' of λ function '%s' is none of the keys an entry reads: %s" (T.unpack name) key (T.unpack (T.intercalate ", " known)))
where
known :: [Text]
known = ["λ", "dataize", "morph", "evaluate", "defer", "stay", "rewrite", "symbolize", "join", "𝑛"]
concluded :: String -> [(Meta, Expression)] -> Object -> Yaml.Parser Ending
concluded key dataized entry = do
answer <- entry .:? "𝑛"
block <- entry .:? "join"
case (answer, block) of
(Nothing, Nothing) -> fail (printf "The entry of λ function '%s' has neither an '𝑛' key nor a 'join' block" key)
(Just _, Just _) -> fail (printf "The entry of λ function '%s' has both an '𝑛' key and a 'join' block, while a join answers the term it joins" key)
(Just term, Nothing) -> Answering term <$ sigmas key term <* dataless key term
(Nothing, Just joint) -> joining key dataized joint
joining :: String -> [(Meta, Expression)] -> Object -> Yaml.Parser Ending
joining key dataized block = do
case filter (`notElem` ["on", "left", "right", "otherwise"]) (map Key.toText (KeyMap.keys block)) of
[] -> pure ()
name : _ -> fail (printf "The key '%s' of the 'join' block of λ function '%s' is none of its keys: on, left, right, otherwise" (T.unpack name) key)
on <- required "on" >>= condition
Joining on <$> (required "left" >>= side "left") <*> (required "right" >>= side "right") <*> (block .:? "otherwise" >>= traverse fallback)
where
required :: FromJSON value => Key -> Yaml.Parser value
required name = block .:? name >>= maybe (fail (printf "The 'join' block of λ function '%s' has no '%s' key" key (Key.toString name))) pure
condition :: Text -> Yaml.Parser (Either Meta Expression)
condition spelling = case (parseBytes (T.unpack spelling), parseExpression (T.unpack spelling)) of
(Right (BtMeta name), _) | name `elem` map (_name . fst) dataized -> pure (Left (Meta spelling name))
(_, Right path) | pathed path -> pure (Right path)
_ -> fail (printf "The 'on' of the 'join' block of λ function '%s' is '%s', while it must name a bytes meta of 'dataize', such as '𝛿1', or be a path from '$', such as '$.on'" key (T.unpack spelling))
pathed :: Expression -> Bool
pathed ExXi = True
pathed (ExDispatch inner _) = pathed inner
pathed _ = False
side :: String -> Expression -> Yaml.Parser Expression
side _ term@(ExMeta _) = pure term
side name term = case metas term of
[] -> pure term
meta : _ -> fail (printf "The side '%s' of the 'join' block of λ function '%s' reads the meta '%s' inside a term, while a side is a path from '$' or a meta alone" name key (T.unpack meta))
fallback :: Text -> Yaml.Parser Function
fallback spelling = case parseBinding (printf "λ ⤍ %s" (T.unpack spelling)) of
Right (BiLambda func@(Function _)) -> pure func
_ -> fail (printf "The 'otherwise' of the 'join' block of λ function '%s' is '%s', while it must name a λ function, such as 'L_join'" key (T.unpack spelling))
operands :: Text -> (Text -> Yaml.Parser Meta) -> Object -> Key -> Yaml.Parser [(Meta, Expression)]
operands key kind entry name = do
mapping <- entry .:? name .!= (Map.empty :: Map Text Expression)
mapM bound (numbered mapping)
where
bound :: (Text, Expression) -> Yaml.Parser (Meta, Expression)
bound (meta, term) = do
referenceless (T.unpack key) (T.unpack meta) term
kind meta >>= \named -> pure (named, term)
reductions :: Text -> Object -> Yaml.Parser [Either (Meta, Expression) Line]
reductions key entry = do
mapping <- entry .:? "morph" .!= (Map.empty :: Map Text Text)
mapM reduction (numbered mapping)
where
reduction :: (Text, Text) -> Yaml.Parser (Either (Meta, Expression) Line)
reduction (meta, source) = do
named <- expressionMeta meta
case parseExpression (T.unpack source) of
Right (ExMeta name) -> pure (Right (Morphed named (Meta source name)))
Right term -> Left (named, term) <$ referenceless (T.unpack key) (T.unpack meta) term
Left failure -> fail failure <?> Key (Key.fromText meta) <?> Key "morph"
expressionMeta :: Text -> Yaml.Parser Meta
expressionMeta meta = case parseExpression (T.unpack meta) of
Right (ExMeta name) -> pure (Meta meta name)
_ -> fail (printf "The operand '%s' is not an expression meta, such as '𝑛1'" (T.unpack meta))
bytesMeta :: Text -> Yaml.Parser Meta
bytesMeta meta = case parseBytes (T.unpack meta) of
Right (BtMeta name) -> pure (Meta meta name)
_ -> fail (printf "The operand '%s' is not a bytes meta, such as '𝛿1'" (T.unpack meta))
written :: String -> Object -> Yaml.Parser [Line]
written key entry = concat <$> sequence [links Evaluated entry "evaluate", links Deferred entry "defer", links Stayed entry "stay", rewrites key entry, links Symbolized entry "symbolize"]
links :: (Meta -> Meta -> Line) -> Object -> Key -> Yaml.Parser [Line]
links kind entry name = do
mapping <- entry .:? name .!= (Map.empty :: Map Text Text)
mapM (\(meta, source) -> kind <$> expressionMeta meta <*> expressionMeta source) (numbered mapping)
rewrites :: String -> Object -> Yaml.Parser [Line]
rewrites key entry = do
mapping <- entry .:? "rewrite" .!= (Map.empty :: Map Text Object)
mapM line (numbered mapping)
where
line :: (Text, Object) -> Yaml.Parser Line
line (meta, body) = do
case filter (`notElem` ["of", "rules"]) (map Key.toText (KeyMap.keys body)) of
[] -> pure ()
name : _ -> fail (printf "The key '%s' of the line '%s' of the 'rewrite' block of λ function '%s' is none of its keys: of, rules" (T.unpack name) (T.unpack meta) key)
Rewritten <$> expressionMeta meta <*> (required "of" >>= expressionMeta) <*> (required "rules" >>= mapM symbolless)
where
required :: FromJSON value => Key -> Yaml.Parser value
required name = body .:? name >>= maybe (fail (printf "The line '%s' of the 'rewrite' block of λ function '%s' has no '%s' key" (T.unpack meta) key (Key.toString name))) pure
symbolless :: Y.Rule -> Yaml.Parser Y.Rule
symbolless rule = case symbols rule.result of
[] -> pure rule
idx : _ -> fail (printf "The rule '%s' of the 'rewrite' block of λ function '%s' writes the symbol '𝜎%d' into its result, while only a firing mints a symbol" rule.name key idx)
ordered :: String -> [Text] -> [Line] -> Yaml.Parser [Line]
ordered key reduced waiting = mapM_ (bound . source) waiting >> go reduced waiting
where
bound :: Meta -> Yaml.Parser ()
bound meta
| meta._name `elem` reduced ++ map (_name . binding) waiting = pure ()
| otherwise =
fail
( printf
"The operand '%s' of λ function '%s' names no meta bound by 'morph' or by any line"
(T.unpack meta._spelling)
key
)
go :: [Text] -> [Line] -> Yaml.Parser [Line]
go _ [] = pure []
go known pending@(first : _) = case break ((`elem` known) . _name . source) pending of
(before, line : after) -> (line :) <$> go ((binding line)._name : known) (before ++ after)
(_, []) ->
fail
( printf
"The line binding '%s' of λ function '%s' reads the meta it binds, directly or through other lines"
(T.unpack (goCircle [] (binding first))._spelling)
key
)
where
goCircle :: [Text] -> Meta -> Meta
goCircle seen meta
| meta._name `elem` seen = meta
| otherwise = goCircle (meta._name : seen) (fromMaybe meta (listToMaybe (feeding meta)))
feeding :: Meta -> [Meta]
feeding meta = [source line | line <- pending, meta._name == (binding line)._name, (source line)._name `notElem` known]
source :: Line -> Meta
source (Morphed _ meta) = meta
source (Evaluated _ meta) = meta
source (Deferred _ meta) = meta
source (Stayed _ meta) = meta
source (Rewritten _ meta _) = meta
source (Symbolized _ meta) = meta
binding :: Line -> Meta
binding (Morphed meta _) = meta
binding (Evaluated meta _) = meta
binding (Deferred meta _) = meta
binding (Stayed meta _) = meta
binding (Rewritten meta _ _) = meta
binding (Symbolized meta _) = meta
sigmas :: String -> Expression -> Yaml.Parser ()
sigmas key answer = case ([kind | Slot kind _ <- slots answer, kind /= "S"], symbols answer) of
([], []) -> pure ()
(kind : _, _) -> fail (printf "The anonymous meta '!%s' cannot be referenced in the '𝑛' of λ function '%s'" (T.unpack kind) key)
(_, idx : _) -> fail (printf "The '𝑛' of λ function '%s' writes the numbered symbol '𝜎%d', while only a bare 𝜎 mints a fresh one" key idx)
once :: String -> Lambda -> Yaml.Parser ()
once key lambda = case twice [] bound of
Nothing -> pure ()
Just meta -> fail (printf "The meta '%s' of λ function '%s' is bound by more than one line, while each meta may be bound once" (T.unpack meta._spelling) key)
where
bound :: [Meta]
bound = map fst lambda._morphed ++ map binding lambda._lines
twice :: [Text] -> [Meta] -> Maybe Meta
twice _ [] = Nothing
twice seen (meta : rest)
| meta._name `elem` seen = Just meta
| otherwise = twice (meta._name : seen) rest
metaless :: String -> String -> String -> (Meta, Expression) -> Yaml.Parser ()
metaless key block reducible (meta, term) = case metas term of
[] -> pure ()
name : _ ->
fail
( printf
"The operand '%s' of '%s' of λ function '%s' reads the meta '%s', while only %s can be reduced there"
(T.unpack meta._spelling)
block
key
(T.unpack name)
reducible
)
answered :: String -> Lambda -> Yaml.Parser ()
answered key lambda = case lambda._ending of
Joining _ left right _ -> sided "left" left >> sided "right" right
Answering term -> case filter (`notElem` ("S" : known)) (metas term) of
[] -> pure ()
name : _ -> fail (printf "The '𝑛' of λ function '%s' reads the meta '%s' that no block binds" key (T.unpack name))
where
known :: [Text]
known = map (_name . fst) lambda._dataized ++ reduced
reduced :: [Text]
reduced = map (_name . fst) lambda._morphed ++ map (_name . binding) lambda._lines
sided :: String -> Expression -> Yaml.Parser ()
sided name side = case filter (`notElem` reduced) (metas side) of
[] -> pure ()
meta : _ -> fail (printf "The side '%s' of the 'join' block of λ function '%s' reads the meta '%s' that none of 'morph', 'evaluate', 'stay', 'rewrite', 'symbolize' and 'defer' binds" name key (T.unpack meta))
dataless :: String -> Expression -> Yaml.Parser ()
dataless key answer
| computes answer = fail (printf "The '𝑛' of λ function '%s' reads data, while a symbolic answer may mention nothing but 𝜎" key)
| otherwise = pure ()
numbered :: Map Text a -> [(Text, a)]
numbered = sortOn (order . fst) . Map.toList
where
order :: Text -> (Text, Integer)
order meta = case T.takeWhileEnd isDigit meta of
digits | T.null digits -> (meta, 0)
digits -> (T.dropWhileEnd isDigit meta, read (T.unpack digits))
computes :: Expression -> Bool
computes = goExpr
where
goExpr :: Expression -> Bool
goExpr (ExFormation bds) = any goBinding bds
goExpr (ExApplication expr arg) = goExpr expr || goArgument arg
goExpr (ExDispatch expr _) = goExpr expr
goExpr (ExPhiMeet _ _ expr) = goExpr expr
goExpr (ExPhiAgain _ _ expr) = goExpr expr
goExpr (ExBytes bts) = goBytes bts
goExpr _ = False
goBinding :: Binding -> Bool
goBinding (BiTau _ expr) = goExpr expr
goBinding (BiDelta bts) = goBytes bts
goBinding _ = False
goArgument :: Argument -> Bool
goArgument (ArTau _ expr) = goExpr expr
goArgument (ArAlpha _ expr) = goExpr expr
goBytes :: Bytes -> Bool
goBytes (BtMeta _) = True
goBytes (BtAny _) = True
goBytes _ = False
emptyLambdas :: Lambdas
emptyLambdas = Lambdas []
readLambdas :: FilePath -> IO Lambdas
readLambdas path = do
entries <- Yaml.decodeFileEither path >>= either broken pure
mapM_ (unique entries) entries
registered <- mapM keyed entries
overlaps path registered
logDebug (printf "Loaded %d λ function(s) from '%s'" (length entries) path)
pure (Lambdas registered)
where
broken :: Yaml.ParseException -> IO [Lambda]
broken failure = throwIO (BrokenLambdas path (Yaml.prettyPrintParseException failure))
unique :: [Lambda] -> Lambda -> IO ()
unique entries entry
| length (filter ((== entry._key) . (._key)) entries) == 1 = pure ()
| otherwise = throwIO (BrokenLambdas path (printf "the key '%s' is used by more than one entry" (T.unpack entry._key)))
keyed :: Lambda -> IO (Regex, Lambda)
keyed entry = do
compiled <- compile compUTF8 execBlank (encodeUtf8 ("^(?:" <> entry._key <> ")$"))
either (unreadable entry._key) (\key -> pure (key, entry)) compiled
unreadable :: Text -> (a, String) -> IO b
unreadable key (_, failure) =
throwIO (BrokenLambdas path (printf "the key '%s' is not a regular expression: %s" (T.unpack key) failure))
overlaps :: FilePath -> [(Regex, Lambda)] -> IO ()
overlaps _ [_] = pure ()
overlaps file registered = mapM (spoken . snd) registered >>= check
where
spoken :: Lambda -> IO (Lambda, Language)
spoken entry =
either
(throwIO . BrokenLambdas file . printf "the key '%s' cannot be compared with the other keys: %s" (T.unpack entry._key))
(pure . (,) entry)
(language entry._key)
check :: [(Lambda, Language)] -> IO ()
check [] = pure ()
check (first : rest) = mapM_ (pair first) rest >> check rest
pair :: (Lambda, Language) -> (Lambda, Language) -> IO ()
pair (left, one) (right, other) =
maybe
(pure ())
( throwIO
. BrokenLambdas file
. printf "the keys '%s' and '%s' match some of the same lambda names, such as '%s'" (T.unpack left._key) (T.unpack right._key)
. T.unpack
)
(shared one other)
matched :: Lambdas -> Text -> Maybe Lambda
matched (Lambdas entries) func = snd <$> find (\(key, _) -> matchTest key (encodeUtf8 func)) entries
minted :: Expression -> Int -> ([(Slot, Function)], Int)
minted answer spent = (zip fresh [FnSymbol idx | idx <- [spent + 1 ..]], spent + length fresh)
where
fresh :: [Slot]
fresh = [slot | slot@(Slot kind _) <- slots answer, kind == "S"]
taken :: Expression -> Int
taken program = maximum (0 : symbols program)
stayed :: Expression -> Expression
stayed = goExpr
where
goExpr :: Expression -> Expression
goExpr (ExFormation bds) = ExFormation (map goBinding bds)
goExpr (ExApplication expr arg) = case (goExpr expr, arg) of
(target, ArTau AtRho _) | dispatched target -> target
(target, _) -> ExApplication target (goArgument arg)
goExpr (ExDispatch expr attr) = ExDispatch (goExpr expr) attr
goExpr (ExPhiMeet prefix idx expr) = ExPhiMeet prefix idx (goExpr expr)
goExpr (ExPhiAgain prefix idx expr) = ExPhiAgain prefix idx (goExpr expr)
goExpr expr = expr
goBinding :: Binding -> Binding
goBinding (BiTau attr expr) = BiTau attr (goExpr expr)
goBinding bd = bd
goArgument :: Argument -> Argument
goArgument (ArTau attr expr) = ArTau attr (goExpr expr)
goArgument (ArAlpha alpha expr) = ArAlpha alpha (goExpr expr)
dispatched :: Expression -> Bool
dispatched (ExDispatch _ _) = True
dispatched (ExApplication expr _) = dispatched expr
dispatched _ = False
rewritten :: RuleContext -> [Y.Rule] -> Expression -> IO Expression
rewritten ctx rules = goExpr
where
goExpr :: Expression -> IO Expression
goExpr expr = goRules expr rules
goRules :: Expression -> [Y.Rule] -> IO Expression
goRules expr [] = goInside expr
goRules expr (rule : rest) = do
substs <- matchExpressionWithRule' [substEmpty] expr rule ctx
case substs of
subst : _ -> buildExpressionThrows rule.result subst
[] -> goRules expr rest
goInside :: Expression -> IO Expression
goInside (ExFormation bds) = ExFormation <$> mapM goBinding bds
goInside (ExApplication expr arg) = ExApplication <$> goExpr expr <*> goArgument arg
goInside (ExDispatch expr attr) = (`ExDispatch` attr) <$> goExpr expr
goInside (ExPhiMeet prefix idx expr) = ExPhiMeet prefix idx <$> goExpr expr
goInside (ExPhiAgain prefix idx expr) = ExPhiAgain prefix idx <$> goExpr expr
goInside expr = pure expr
goBinding :: Binding -> IO Binding
goBinding (BiTau attr expr) = BiTau attr <$> goExpr expr
goBinding bd = pure bd
goArgument :: Argument -> IO Argument
goArgument (ArTau attr expr) = ArTau attr <$> goExpr expr
goArgument (ArAlpha alpha expr) = ArAlpha alpha <$> goExpr expr
type Minting = (Int, [(Int, Bytes)])
symbolized :: Expression -> Int -> (Expression, [(Int, Bytes)], Int)
symbolized term spent = case runState (goExpr term) (spent, []) of
(masked, (spent', known)) -> (masked, reverse known, spent')
where
goExpr :: Expression -> State Minting Expression
goExpr (ExFormation bds) = ExFormation <$> mapM goBinding bds
goExpr (ExApplication expr arg) = ExApplication <$> goExpr expr <*> goArgument arg
goExpr (ExDispatch expr attr) = (`ExDispatch` attr) <$> goExpr expr
goExpr (ExPhiMeet prefix idx expr) = ExPhiMeet prefix idx <$> goExpr expr
goExpr (ExPhiAgain prefix idx expr) = ExPhiAgain prefix idx <$> goExpr expr
goExpr expr = pure expr
goBinding :: Binding -> State Minting Binding
goBinding (BiDelta bts) = state (\(count, known) -> (BiLambda (FnSymbol (succ count)), (succ count, (succ count, bts) : known)))
goBinding (BiTau attr expr) = BiTau attr <$> goExpr expr
goBinding bd = pure bd
goArgument :: Argument -> State Minting Argument
goArgument (ArTau attr expr) = ArTau attr <$> goExpr expr
goArgument (ArAlpha alpha expr) = ArAlpha alpha <$> goExpr expr