bricks-0.0.0.4: src/Bricks/BuiltinFunctions.hs
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
module Bricks.BuiltinFunctions where
import Bricks.Term
import Bricks.Type
-- Bricks internal
import Bricks.Internal.Prelude
import Bricks.Internal.Text (Text)
import qualified Bricks.Internal.Text as Text
-- Containers
import qualified Data.Map as Map
import Data.Set (Set)
-- Base
import Data.Dynamic (fromDynamic, toDyn)
import Data.Typeable (Typeable)
import Prelude (Integer, Num (..))
import Text.Read (readMaybe)
term'data :: forall a. Typeable a => Type a -> a -> Term
term'data (Type n) = Term'Data n . toDyn @a
fn'pure'parametric'arity1 :: (Term -> Term) -> Term
fn'pure'parametric'arity1 f =
Term'Function $ \x -> pure $
f x
fn'pure'parametric'arity2 :: (Term -> Term -> Term) -> Term
fn'pure'parametric'arity2 f =
Term'Function $ \x -> pure $
fn'pure'parametric'arity1 $ f x
fn'pure'parametric'arity3 :: (Term -> Term -> Term -> Term) -> Term
fn'pure'parametric'arity3 f =
Term'Function $ \x -> pure $
fn'pure'parametric'arity2 $ f x
fn'id :: Term
fn'id = fn'pure'parametric'arity1 id
fn'const :: Term
fn'const = fn'pure'parametric'arity2 const
{- | Function composition, in the traditional "backwards" order.
Read @f `fn'comp` g@ as "/f/ after /g/." -}
fn'comp :: Term
fn'comp = fn'pure'parametric'arity3 $ \f g x -> f /@\ (g /@\ x)
fn'flip :: Term
fn'flip = fn'pure'parametric'arity3 $ \f x y -> f /@@\ (y, x)
fn'dict'lookup :: Term
fn'dict'lookup =
Term'Function $ \x -> do
map <- reduce'dict'keys x
pure $ Term'Function $ \y -> do
key <- cast'data type'string y
case Map.lookup key map of
Nothing -> bottom . Bottom $ "Key " <> key <> " not found in dict"
Just a -> pure a
fn'or :: Term
fn'or =
Term'Function $ \x -> cast'data type'boolean x <&>
\case
True -> fn'const /@\ term'data type'boolean True
False -> assert'type type'boolean
fn'and :: Term
fn'and =
Term'Function $ \x -> cast'data type'boolean x <&>
\case
False -> fn'const /@\ term'data type'boolean False
True -> assert'type type'boolean
fn'string'append :: Term
fn'string'append =
Term'Function $ \x -> cast'data type'string x <&> \x' ->
Term'Function $ \y -> cast'data type'string y <&> \y' ->
term'data type'string (Text.append x' y')
fn'dict'disallowExtraKeys :: Set Text -> Term
fn'dict'disallowExtraKeys _allowedKeys =
Term'Function $ undefined
fn'dict'merge'preferLeft :: Term
fn'dict'merge'preferLeft =
Term'Function $ \x -> reduce'dict'keys x <&> \x' ->
Term'Function $ \y -> reduce'dict'keys y <&> \y' ->
Term'Dict'ReducedKeys $ Map.union x' y'
fn'dict'merge'preferRight :: Term
fn'dict'merge'preferRight = fn'flip /@\ fn'dict'merge'preferLeft
cast'data :: (MonadEval m, Typeable a) => Type a -> Term -> m a
cast'data = req fst
-- | Like 'fn'id', but fails if the argument is not of the given type.
assert'type :: Typeable a => Type a -> Term
assert'type t = Term'Function $ req snd t
req :: forall a b m. (MonadEval m, Typeable a)
=> ((a, Term) -> b)
-> Type a
-> Term
-> m b
req s (Type n) = reduce'term >=> \case
t@(Term'Data n' x) ->
case fromDynamic @a x of
Nothing -> bottom . Bottom $ "Expected " <> n <> ", got " <> n'
Just a -> pure (s (a, t))
x ->
termTypeName x >>= \n' ->
bottom . Bottom $ "Expected " <> n <> ", got " <> n'
fn'int'add :: Term
fn'int'add =
Term'Function $ \x -> cast'data type'integer x <&> \x' ->
Term'Function $ \y -> cast'data type'integer y <&> \y' ->
term'data type'integer (x' + y')
fn'int'constructor :: Term
fn'int'constructor =
Term'Function $ cast'data type'string >=> (
Text.unpack >>> readMaybe @Integer >>>
maybe (bottom . Bottom $ "invalid integer")
(pure . term'data type'integer)
)
standard'library :: Term
standard'library =
Term'Dict'ReducedKeys . Map.fromList $
[ ("add", fn'int'add)
, ("integer", fn'int'constructor)
, ("and", fn'and)
, ("or", fn'or)
, ("id", fn'id)
, ("const", fn'const)
]