packages feed

express-1.0.14: src/Data/Express/Triexpr.hs

-- |
-- Module      : Data.Express.Triexpr
-- Copyright   : (c) 2019-2024 Rudy Matela
-- License     : 3-Clause BSD  (see the file LICENSE)
-- Maintainer  : Rudy Matela <rudy@matela.com.br>
--
-- This module is part of Express.
--
-- An __experimental__ data structure for matching 'Expr's.
--
-- __Warning (1):__
-- Take care when importing this module,
-- the interface is experimental
-- and may change at every minor version.
--
-- __Warning (2):__
-- YMMV:
-- Do not expect this to be faster than manually matching in a list,
-- provisional experiments show that it can be slower depending
-- on the set of expressions being matched.
--
-- This module should be imported qualified
-- as it exports definitions called
-- 'map', 'lookup', 'toList', 'fromList', 'insert' and 'empty':
--
-- > import Data.Express.Triexpr (Triexpr)
-- > import qualified Data.Express.Triexpr as T
module Data.Express.Triexpr
  ( Triexpr (..)
  , empty
  , unit
  , merge
  , insert
  , toList
  , fromList
  , map
  , lookup
  )
where

import Data.Express.Core
import Data.Express.Match
import Data.Maybe
import Prelude hiding (map, lookup)

-- | A trie of 'Expr's.
--
-- In the representation,
-- 'Nothing' matches an App and 'Just' 'Expr' an expression.
newtype Triexpr a  =  Triexpr [(Maybe Expr, Either (Triexpr a) (Expr,a))]
  deriving (Eq, Ord, Show)

-- | An empty 'Triexpr'.
empty :: Triexpr a
empty  =  Triexpr []

-- | Constructs a 'Triexpr' encoding a single expression.
unit :: Expr -> a -> Triexpr a
unit e x  =  u e (Right (e,x))
  where
  u :: Expr -> (Either (Triexpr a) (Expr,a)) -> Triexpr a
  u (e1 :$ e2) et  =  Triexpr [(Nothing, Left $ u e1 $ Left $ u e2 et)]
  u e          et  =  Triexpr [(Just e,  et)]

-- | Merges two 'Triexpr's.
merge :: Triexpr a -> Triexpr a -> Triexpr a
merge (Triexpr ms1) (Triexpr ms2)  =  Triexpr $ m ms1 ms2
  where
  m [] ms  =  ms
  m ms []  =  ms
  m ((e1,mt1):ms1) ((e2,mt2):ms2)  =  case compare e1 e2 of
    LT -> (e1,mt1) : m ms1 ((e2,mt2):ms2)
    GT -> (e2,mt2) : m ((e1,mt1):ms1) ms2
    EQ -> case (mt1,mt2) of
          (Left t1, Left t2) -> (e1, Left $ t1 `merge` t2) : m ms1 ms2
          (_,_) -> (e1,mt1) : (e2,mt2) : m ms1 ms2

-- | Inserts an 'Expr' into a 'Triexpr'.
insert :: Expr -> a -> Triexpr a -> Triexpr a
insert e x t  =  unit e x `merge` t

-- | List all 'Expr' stored in a 'Triexpr' along with their associated values.
toList :: Triexpr a -> [(Expr, a)]
toList (Triexpr ms)  =  concatMap to ms
  where
  to (_, Right ex)  =  [ex]
  to (_, Left t)  =  toList t

-- | Constructs a 'Triexpr' form a list of key 'Expr's and associated values.
fromList :: [(Expr, a)] -> Triexpr a
fromList  =  foldr (uncurry insert) empty

-- | Maps a function to the stored values in a 'Triexpr'.
map :: (a -> b) -> Triexpr a -> Triexpr b
map f (Triexpr ms)  =  Triexpr [(ex, mapEither (map f) (mapSnd f) eth) | (ex, eth) <- ms]
  where
  mapEither :: (a -> c) -> (b -> d) -> Either a b -> Either c d
  mapEither f g (Left x)   =  Left (f x)
  mapEither f g (Right y)  =  Right (g y)
  mapSnd :: (a -> b) -> (c,a) -> (c,b)
  mapSnd f (x,y)  =  (x, f y)

-- | Performs a lookup in a 'Triexpr'.
lookup :: Expr -> Triexpr a -> [ (Expr, [(Expr,Expr)], a) ]
lookup e t  =  [(e, bs, x) | (bs, Right (e,x)) <- look (Just e) t []]
  where
  look :: Maybe Expr -> Triexpr a -> [(Expr, Expr)] -> [([(Expr,Expr)], Either (Triexpr a) (Expr,a))]
  look Nothing  t@(Triexpr ms) bs  =  [(bs, mt) | (Nothing, mt) <- ms]
  look (Just e) t@(Triexpr ms) bs  =  [(bs', mt) | (Just e', mt) <- ms, bs' <- maybeToList (matchWith bs e e')]
                                   ++ [r | e1 :$ e2 <- [e]
                                         , (bs1, Left t1) <- look Nothing t bs
                                         , (bs2, Left t2) <- look (Just e1) t1 bs1
                                         , r              <- look (Just e2) t2 bs2]