smh-0.2.1: src/Common.hs
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ViewPatterns #-}
{-# LANGUAGE KindSignatures #-}
module Common(module Common) where
import Control.Applicative (empty, (<|>))
import Control.Lens (Lens', Traversal', lens, (^..))
import Control.Loop (numLoop)
import Control.Monad (forM_)
import Control.Monad.ST.Strict (ST, runST)
import Data.Array.ST (STUArray)
import qualified Data.Array.ST as A
import Data.Data (Data)
import Data.List (nub, sort)
import Data.List.Extra (nubOrd)
import Data.Ratio (denominator, numerator)
import Data.STRef ()
import Data.Text (Text)
import qualified Data.Text as T
import Data.Void (Void)
import Text.Megaparsec (Parsec, label)
import Text.Megaparsec.Char (space1)
import qualified Text.Megaparsec.Char.Lexer as L
import Text.Read (readMaybe)
data Focus
= FText !Text
| FList ![Focus]
deriving Data
instance Show Focus where
show (FText str) = show str
show (FList lst) = show lst
instance Eq Focus where
(FText str1) == (FText str2) = str1 == str2
(FList lst1) == (FList lst2) = lst1 == lst2
_ == _ = False
toTextUnsafe :: Focus -> Text
toTextUnsafe (FText str) = str
toTextUnsafe _ = error "toText called on a non-FText"
toListUnsafe :: Focus -> [Focus]
toListUnsafe (FList lst) = lst
toListUnsafe _ = error "toList called on a non-FText"
_toListUnsafe :: Lens' [Focus] Focus
_toListUnsafe = lens FList $ const toListUnsafe
newtype Focuser = FTrav (Traversal' Focus Focus)
composeFocusers :: Focuser -> Focuser -> Focuser
composeFocusers (FTrav a) (FTrav b) = FTrav (a . b)
foldFocusers :: [Focuser] -> Focuser
foldFocusers = foldr composeFocusers (FTrav id)
type Action = Text -> Focuser -> IO ()
type Mapping = Focus -> Focus
foldMappings :: [Mapping] -> Mapping
foldMappings = foldr (flip (.)) id
type Parser = Parsec Void Text
showRational :: Rational -> Text
showRational n = if denominator n == 1
then T.pack $ show (numerator n)
else T.pack $ show (fromInteger (numerator n) / fromInteger (denominator n))
data Range
= RangeSingle !Int
| RangeRange !(Maybe Int) !(Maybe Int)
getIndexes :: [Range] -> Int -> [Int]
getIndexes ranges len = nubOrd . sort . concatMap (getIndexes' . fixRange) $ ranges
where
getIndexes' (RangeSingle i) = [i]
getIndexes' (RangeRange mstart mend) =
case (mstart, mend) of
(Just start, Just end) -> [start .. end - 1]
(Just start, Nothing) -> [start .. len - 1]
(Nothing, Just end) -> [0 .. end - 1]
(Nothing, Nothing) -> [0 .. len - 1]
fixRange (RangeSingle i) = RangeSingle (fixIndex i)
fixRange (RangeRange mstart mend) = RangeRange
(fixIndex <$> mstart) (fixIndex <$> mend)
fixIndex i
| i < 0 = max (i + len) 0
| otherwise = min i len
ws :: Parser ()
ws = L.space space1 empty empty
symbol :: Text -> Parser Text
symbol = L.symbol ws
lexeme :: Parser a -> Parser a
lexeme = L.lexeme ws
integer :: Parser Int
integer = label "integer" $ lexeme $ L.signed ws L.decimal
rational :: Parser Rational
rational = toRational <$> label "number" (lexeme $ L.signed ws L.scientific)
mapText :: (Char -> a) -> Text -> [a]
mapText f = T.foldr (\c cs -> f c : cs) []
unsort :: [Int] -> Int -> [Int]
unsort is isLen = runST $ do
is' <- A.newListArray (0, isLen - 1) is :: ST s (STUArray s Int Int)
is'' <- A.newArray (0, isLen - 1) 0 :: ST s (STUArray s Int Int)
numLoop 0 (isLen - 1) $ \i -> do
j <- A.readArray is' i
A.writeArray is'' j i
return []
makeFilteredText :: Int -> [Int] -> Text -> Text
makeFilteredText maxLen is str = T.unfoldrN maxLen builder (0, is)
where
builder :: (Int, [Int]) -> Maybe (Char, (Int, [Int]))
builder (_, []) = Nothing
builder (n, i : is) = Just (T.index str i, (n + 1, is))
focusTo :: Mapping -> Focuser
focusTo mapping = FTrav $ lens mapping const
mappingTo :: Focuser -> Mapping
mappingTo (FTrav trav) focus = case (focus, focus ^.. trav) of
(FText _, [FText str]) -> FText str
_ -> focus
fromIndexes :: Int -> Text -> [(Int, Int)] -> ([Text], [Text])
fromIndexes _ str [] = ([str], [])
fromIndexes offset str ((i, j) : is) =
let (nonMatch, T.splitAt j -> (match, str')) = T.splitAt (i - offset) str
(nonMatches, matches) = fromIndexes (i + j) str' is
in (nonMatch : nonMatches, match : matches)
readMaybeRational :: Text -> Maybe Rational
readMaybeRational s = (toRational <$> readMaybeInteger s) <|> (toRational <$> readMaybeDouble s)
readMaybeInteger :: Text -> Maybe Integer
readMaybeInteger = readMaybe . T.unpack
readMaybeDouble :: Text -> Maybe Double
readMaybeDouble = readMaybe . T.unpack