{-# LANGUAGE RecordWildCards #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE ScopedTypeVariables #-} module Data.Rg ( Rg(..) , RgCoreMethods , rgCoreMethodsBE , RgText(..) , BE(..) , Range , newStartOfRangeFromList , newStartOfRangeFromVector , extractRange ) where import Data.Array import Data.Coerce import qualified Data.HashMap.Strict as HM import Data.Maybe import Data.Possibly import qualified Data.Text as T import qualified Data.Vector as V import Fmt -- | Rg acts a bit like a Bounded Enum, but the size of the enumeration -- can be dynamically determined from each value in the type (see 'sizeRg') class Rg rg where -- | the number of values in the enumeration; sizeRg sz > 0 sizeRg :: rg -> Int sizeRg = _rcm_sizeRg rgCoreMethods -- | the nth item in the enumeration (first is 0) toRg :: rg -> Int -> Maybe rg toRg = _rcm_toRg rgCoreMethods -- | place in the enumation (first is 0) fromRg :: rg -> Int fromRg = _rcm_fromRg rgCoreMethods -- | an alternative way of specifying sizeRg, toRg and fromRg rgCoreMethods :: RgCoreMethods rg rgCoreMethods = RgCoreMethods { _rcm_sizeRg = sizeRg , _rcm_toRg = toRg , _rcm_fromRg = fromRg } {-# MINIMAL sizeRg, toRg, fromRg | rgCoreMethods #-} -- | first item in the enumeration minRg :: rg -> rg minRg = fromMaybe oops . flip toRg 0 where oops = error "minRg: no minimum value in range" -- | last item in the enumeration maxRg :: rg -> rg maxRg rg = fromMaybe oops $ toRg rg n where n = sizeRg rg - 1 oops = error "maxRg: no maximum value in range" -- | next item in the enumeration (Nothing if already last) succRg :: rg -> Maybe rg succRg rg = toRg rg $ fromRg rg + 1 -- | previous item in the enumeration (Nothing if already first) predRg :: rg -> Maybe rg predRg rg = toRg rg $ fromRg rg - 1 -- | list given items in the enumeration allListRg :: rg -> [rg] allListRg rg = listRg rg [0..] -- | list given items in the enumeration, stopping as soon as an index is -- out of range listRg :: rg -> [Int] -> [rg] listRg rg is = catMaybes $ takeWhile isJust [ toRg rg i | i<-is ] -- | list given items in the enumeration as a 'V.Vector' allVectorRg :: rg -> [rg] allVectorRg rg = listRg rg [0..] -- | list the items in the enumeration as a 'V.Vector', stopping as soon as an -- index is out of range vectorRg :: rg -> [Int] -> V.Vector rg vectorRg rg is = V.fromList $ listRg rg is ------------------------------------------------------------------------------- -- RgCoreMethods ------------------------------------------------------------------------------- -- | dynamically encapsulates the core 'Rg' methods data RgCoreMethods rg = RgCoreMethods { _rcm_sizeRg :: rg -> Int , _rcm_toRg :: rg -> Int -> Maybe rg , _rcm_fromRg :: rg -> Int } -- | if you want to create an 'Rg' from a 'Bounded' 'Enum' you can bind -- 'rgCoreMethods' to this function rgCoreMethodsBE :: forall rg . (Bounded rg, Enum rg) => RgCoreMethods rg rgCoreMethodsBE = RgCoreMethods { _rcm_sizeRg = coerce (sizeRg :: BE rg -> Int) , _rcm_toRg = coerce (toRg :: BE rg -> Int -> Maybe (BE rg)) , _rcm_fromRg = coerce (fromRg :: BE rg -> Int) } ------------------------------------------------------------------------------- -- class RgText ------------------------------------------------------------------------------- -- | a class in which we can build things and parse them from 'T.Text' class (Rg e, Buildable e, Eq e, Ord e, Show e) => RgText e where parseRgText :: e -> T.Text -> Possibly e parseRgText e txt = maybe (Left msg) Right $ HM.lookup txt $ hashmap_t e where msg = "parseRgText: enumeration not recognised: "++show txt ------------------------------------------------------------------------------- -- newtype BE ------------------------------------------------------------------------------- -- | a @newtype@ wrapper used for deriving 'Rg' instances from 'Bounded' 'Enum' newtype BE a = BE { _BE :: a } deriving (Eq,Ord,Bounded,Enum,Show) instance (Bounded i,Enum i) => Rg (BE i) where sizeRg be = (1 +) $ fromEnum $ maxBound `asTypeOf` _BE be toRg be i = case 0 <= i && i < sizeRg be of True -> Just $ BE $ toEnum i False -> Nothing fromRg = fromEnum . _BE ------------------------------------------------------------------------------- -- data Range ------------------------------------------------------------------------------- -- | used to generate 'Rg' values from lists of things data Range a = Range { _rg_size :: Int -- ^ number of items in enumeration (derivable from Array) , _rg_elem :: Int -- ^ position in the enumeration of this element , _rg_array :: Array Int a -- ^ all of the elements of the enumeration } deriving (Show) instance Rg (Range a) where sizeRg = _rg_size toRg rg i = case 0 <= i && i < _rg_size rg of False -> Nothing True -> Just rg { _rg_elem = i } fromRg = _rg_elem -- | generating a 'Range' from a list newStartOfRangeFromList :: [a] -> Range a newStartOfRangeFromList xs = Range { _rg_size = sz , _rg_elem = 0 , _rg_array = listArray (0,sz-1) xs } where sz = length xs -- | generating a 'Range' from a 'V.Vector' newStartOfRangeFromVector :: V.Vector a -> Range a newStartOfRangeFromVector v = Range { _rg_size = sz , _rg_elem = 0 , _rg_array = listArray (0,sz-1) $ V.toList v } where sz = V.length v -- | extracting the thing extractRange :: Range a -> a extractRange Range{..} = _rg_array ! _rg_elem ------------------------------------------------------------------------------- -- hashmap_t ------------------------------------------------------------------------------- hashmap_t :: RgText e => e -> HM.HashMap T.Text e hashmap_t x = HM.fromList [ (fmt $ build c,c) | c <- allListRg x ]