1
0
forked from GitHub/gf-core
Files
gf-core/src-3.0/GF/Data/Operations.hs

659 lines
20 KiB
Haskell

----------------------------------------------------------------------
-- |
-- Module : Operations
-- Maintainer : AR
-- Stability : (stable)
-- Portability : (portable)
--
-- > CVS $Date: 2005/11/11 16:12:41 $
-- > CVS $Author: bringert $
-- > CVS $Revision: 1.22 $
--
-- some auxiliary GF operations. AR 19\/6\/1998 -- 6\/2\/2001
--
-- Copyright (c) Aarne Ranta 1998-2000, under GNU General Public License (see GPL)
-----------------------------------------------------------------------------
module GF.Data.Operations (-- * misc functions
ifNull, onSnd,
-- * the Error monad
Err(..), err, maybeErr, testErr, errVal, errIn, derrIn,
performOps, repeatUntilErr, repeatUntil, okError, isNotError,
showBad, lookupErr, lookupErrMsg, lookupDefault, updateLookupList,
mapPairListM, mapPairsM, pairM, mapErr, mapErrN, foldErr,
(!?), errList, singleton,
-- ** checking
checkUnique, titleIfNeeded, errMsg, errAndMsg,
-- * a three-valued maybe type to express indirections
Perhaps(..), yes, may, nope,
mapP,
unifPerhaps, updatePerhaps, updatePerhapsHard,
-- * binary search trees; now with FiniteMap
BinTree, emptyBinTree, isInBinTree, justLookupTree,
lookupTree, lookupTreeMany, lookupTreeManyAll, updateTree,
buildTree, filterBinTree,
sorted2tree, mapTree, mapMTree, tree2list,
-- * parsing
WParser, wParseResults, paragraphs,
-- * printing
indent, (+++), (++-), (++++), (+++++),
prUpper, prReplicate, prTList, prQuotedString, prParenth, prCurly,
prBracket, prArgList, prSemicList, prCurlyList, restoreEscapes,
numberedParagraphs, prConjList, prIfEmpty, wrapLines,
-- ** LaTeX code producing functions
dollar, mbox, ital, boldf, verbat, mkLatexFile,
begindocument, enddocument,
-- * extra
sortByLongest, combinations, mkTextFile, initFilePath,
-- * topological sorting with test of cyclicity
topoTest, topoSort, cyclesIn,
-- * the generic fix point iterator
iterFix,
-- * association lists
updateAssoc, removeAssoc,
-- * chop into separator-separated parts
chunks, readIntArg, subSequences,
-- * state monad with error; from Agda 6\/11\/2001
STM(..), appSTM, stm, stmr, readSTM, updateSTM, writeSTM, done,
-- * error monad class
ErrorMonad(..), checkAgain, checks, allChecks, doUntil
) where
import Data.Char (isSpace, toUpper, isSpace, isDigit)
import Data.List (nub, sortBy, sort, deleteBy, nubBy)
--import Data.FiniteMap
import Control.Monad (liftM,liftM2, MonadPlus, mzero, mplus)
import GF.Data.ErrM
infixr 5 +++
infixr 5 ++-
infixr 5 ++++
infixr 5 +++++
infixl 9 !?
ifNull :: b -> ([a] -> b) -> [a] -> b
ifNull b f xs = if null xs then b else f xs
onSnd :: (a -> b) -> (c,a) -> (c,b)
onSnd f (x, y) = (x, f y)
-- the Error monad
-- | analogue of @maybe@
err :: (String -> b) -> (a -> b) -> Err a -> b
err d f e = case e of
Ok a -> f a
Bad s -> d s
-- | add msg s to @Maybe@ failures
maybeErr :: String -> Maybe a -> Err a
maybeErr s = maybe (Bad s) Ok
testErr :: Bool -> String -> Err ()
testErr cond msg = if cond then return () else Bad msg
errVal :: a -> Err a -> a
errVal a = err (const a) id
errIn :: String -> Err a -> Err a
errIn msg = err (\s -> Bad (s ++++ "OCCURRED IN" ++++ msg)) return
-- | used for extra error reports when developing GF
derrIn :: String -> Err a -> Err a
derrIn m = errIn m -- id
performOps :: [a -> Err a] -> a -> Err a
performOps ops a = case ops of
f:fs -> f a >>= performOps fs
[] -> return a
repeatUntilErr :: (a -> Bool) -> (a -> Err a) -> a -> Err a
repeatUntilErr cond f a = if cond a then return a else f a >>= repeatUntilErr cond f
repeatUntil :: (a -> Bool) -> (a -> a) -> a -> a
repeatUntil cond f a = if cond a then a else repeatUntil cond f (f a)
okError :: Err a -> a
-- okError = err (error "no result Ok") id
okError = err (error . ("Bad result occurred" ++++)) id
isNotError :: Err a -> Bool
isNotError = err (const False) (const True)
showBad :: Show a => String -> a -> Err b
showBad s a = Bad (s +++ show a)
lookupErr :: (Eq a,Show a) => a -> [(a,b)] -> Err b
lookupErr a abs = maybeErr ("Unknown" +++ show a) (lookup a abs)
lookupErrMsg :: (Eq a,Show a) => String -> a -> [(a,b)] -> Err b
lookupErrMsg m a abs = maybeErr (m +++ "gave unknown" +++ show a) (lookup a abs)
lookupDefault :: Eq a => b -> a -> [(a,b)] -> b
lookupDefault d x l = maybe d id $ lookup x l
updateLookupList :: Eq a => (a,b) -> [(a,b)] -> [(a,b)]
updateLookupList ab abs = insert ab [] abs where
insert c cc [] = cc ++ [c]
insert (a,b) cc ((a',b'):cc') = if a == a'
then cc ++ [(a,b)] ++ cc'
else insert (a,b) (cc ++ [(a',b')]) cc'
mapPairListM :: Monad m => ((a,b) -> m c) -> [(a,b)] -> m [(a,c)]
mapPairListM f xys = mapM (\ p@(x,_) -> liftM ((,) x) (f p)) xys
mapPairsM :: Monad m => (b -> m c) -> [(a,b)] -> m [(a,c)]
mapPairsM f xys = mapM (\ (x,y) -> liftM ((,) x) (f y)) xys
pairM :: Monad a => (b -> a c) -> (b,b) -> a (c,c)
pairM op (t1,t2) = liftM2 (,) (op t1) (op t2)
-- | like @mapM@, but continue instead of halting with 'Err'
mapErr :: (a -> Err b) -> [a] -> Err ([b], String)
mapErr f xs = Ok (ys, unlines ss)
where
(ys,ss) = ([y | Ok y <- fxs], [s | Bad s <- fxs])
fxs = map f xs
-- | alternative variant, peb 9\/6-04
mapErrN :: Int -> (a -> Err b) -> [a] -> Err ([b], String)
mapErrN maxN f xs = Ok (ys, unlines (errHdr : ss2))
where
(ys, ss) = ([y | Ok y <- fxs], [s | Bad s <- fxs])
errHdr = show nss ++ " errors occured" ++
if nss > maxN then ", showing the first " ++ show maxN else ""
ss2 = map ("* "++) $ take maxN ss
nss = length ss
fxs = map f xs
-- | like @foldM@, but also return the latest value if fails
foldErr :: (a -> b -> Err a) -> a -> [b] -> Err (a, Maybe String)
foldErr f s xs = case xs of
[] -> return (s,Nothing)
x:xx -> case f s x of
Ok v -> foldErr f v xx
Bad m -> return $ (s, Just m)
-- @!!@ with the error monad
(!?) :: [a] -> Int -> Err a
xs !? i = foldr (const . return) (Bad "too few elements in list") $ drop i xs
errList :: Err [a] -> [a]
errList = errVal []
singleton :: a -> [a]
singleton = (:[])
-- checking
checkUnique :: (Show a, Eq a) => [a] -> [String]
checkUnique ss = ["overloaded" +++ show s | s <- nub overloads] where
overloads = filter overloaded ss
overloaded s = length (filter (==s) ss) > 1
titleIfNeeded :: a -> [a] -> [a]
titleIfNeeded a [] = []
titleIfNeeded a as = a:as
errMsg :: Err a -> [String]
errMsg (Bad m) = [m]
errMsg _ = []
errAndMsg :: Err a -> Err (a,[String])
errAndMsg (Bad m) = Bad m
errAndMsg (Ok a) = return (a,[])
-- | a three-valued maybe type to express indirections
data Perhaps a b = Yes a | May b | Nope deriving (Show,Read,Eq,Ord)
yes :: a -> Perhaps a b
yes = Yes
may :: b -> Perhaps a b
may = May
nope :: Perhaps a b
nope = Nope
mapP :: (a -> c) -> Perhaps a b -> Perhaps c b
mapP f p = case p of
Yes a -> Yes (f a)
May b -> May b
Nope -> Nope
-- | this is what happens when matching two values in the same module
unifPerhaps :: (Eq a, Eq b, Show a, Show b) =>
Perhaps a b -> Perhaps a b -> Err (Perhaps a b)
unifPerhaps p1 p2 = case (p1,p2) of
(Nope, _) -> return p2
(_, Nope) -> return p1
_ -> if p1==p2 then return p1
else Bad ("update conflict between" ++++ show p1 ++++ show p2)
-- | this is what happens when updating a module extension
updatePerhaps :: (Eq a,Eq b, Show a, Show b) =>
b -> Perhaps a b -> Perhaps a b -> Err (Perhaps a b)
updatePerhaps old p1 p2 = case (p1,p2) of
(Yes a, Nope) -> return $ may old
(May older,Nope) -> return $ may older
(_, May a) -> Bad "strange indirection"
_ -> unifPerhaps p1 p2
-- | here the value is copied instead of referred to; used for oper types
updatePerhapsHard :: (Eq a, Eq b, Show a, Show b) => b ->
Perhaps a b -> Perhaps a b -> Err (Perhaps a b)
updatePerhapsHard old p1 p2 = case (p1,p2) of
(Yes a, Nope) -> return $ yes a
(May older,Nope) -> return $ may older
(_, May a) -> Bad "strange indirection"
_ -> unifPerhaps p1 p2
-- binary search trees
--- FiniteMap implementation is slower in crucial tests
data BinTree a b = NT | BT (a,b) !(BinTree a b) !(BinTree a b) deriving (Show)
-- type BinTree a b = FiniteMap a b
emptyBinTree :: BinTree a b
emptyBinTree = NT
-- emptyBinTree = emptyFM
isInBinTree :: (Ord a) => a -> BinTree a b -> Bool
isInBinTree x = err (const False) (const True) . justLookupTree x
-- isInBinTree = elemFM
justLookupTree :: (Monad m,Ord a) => a -> BinTree a b -> m b
justLookupTree = lookupTree (const [])
lookupTree :: (Monad m,Ord a) => (a -> String) -> a -> BinTree a b -> m b
lookupTree pr x tree = case tree of
NT -> fail ("no occurrence of element" +++ pr x)
BT (a,b) left right
| x < a -> lookupTree pr x left
| x > a -> lookupTree pr x right
| x == a -> return b
--lookupTree pr x tree = case lookupFM tree x of
-- Just y -> return y
-- _ -> fail ("no occurrence of element" +++ pr x)
lookupTreeMany :: Ord a => (a -> String) -> [BinTree a b] -> a -> Err b
lookupTreeMany pr (t:ts) x = case lookupTree pr x t of
Ok v -> return v
_ -> lookupTreeMany pr ts x
lookupTreeMany pr [] x = Bad $ "failed to find" +++ pr x
lookupTreeManyAll :: Ord a => (a -> String) -> [BinTree a b] -> a -> [b]
lookupTreeManyAll pr (t:ts) x = case lookupTree pr x t of
Ok v -> v : lookupTreeManyAll pr ts x
_ -> lookupTreeManyAll pr ts x
lookupTreeManyAll pr [] x = []
-- | destructive update
updateTree :: (Ord a) => (a,b) -> BinTree a b -> BinTree a b
-- updateTree (a,b) tr = addToFM tr a b
updateTree = updateTreeGen True
-- | destructive or not
updateTreeGen :: (Ord a) => Bool -> (a,b) -> BinTree a b -> BinTree a b
updateTreeGen destr z@(x,y) tree = case tree of
NT -> BT z NT NT
BT c@(a,b) left right
| x < a -> let left' = updateTree z left in BT c left' right
| x > a -> let right' = updateTree z right in BT c left right'
| otherwise -> if destr
then BT z left right -- removing the old value of a
else tree -- retaining the old value if one exists
buildTree :: (Ord a) => [(a,b)] -> BinTree a b
buildTree = sorted2tree . sortBy fs where
fs (x,_) (y,_)
| x < y = LT
| x > y = GT
| True = EQ
-- buildTree = listToFM
sorted2tree :: Ord a => [(a,b)] -> BinTree a b
sorted2tree [] = NT
sorted2tree xs = BT x (sorted2tree t1) (sorted2tree t2) where
(t1,(x:t2)) = splitAt (length xs `div` 2) xs
--sorted2tree = listToFM
--- dm less general than orig
mapTree :: ((a,b) -> (a,c)) -> BinTree a b -> BinTree a c
mapTree f NT = NT
mapTree f (BT a left right) = BT (f a) (mapTree f left) (mapTree f right)
--mapTree f = mapFM (\k v -> snd (f (k,v)))
--- fm less efficient than orig?
mapMTree :: (Ord a,Monad m) => ((a,b) -> m (a,c)) -> BinTree a b -> m (BinTree a c)
mapMTree f NT = return NT
mapMTree f (BT a left right) = do
a' <- f a
left' <- mapMTree f left
right' <- mapMTree f right
return $ BT a' left' right'
--mapMTree f t = liftM listToFM $ mapM f $ fmToList t
filterBinTree :: Ord a => (a -> b -> Bool) -> BinTree a b -> BinTree a b
-- filterFM f t
filterBinTree f = sorted2tree . filter (uncurry f) . tree2list
tree2list :: BinTree a b -> [(a,b)] -- inorder
tree2list NT = []
tree2list (BT z left right) = tree2list left ++ [z] ++ tree2list right
--tree2list = fmToList
-- parsing
type WParser a b = [a] -> [(b,[a])] -- old Wadler style parser
wParseResults :: WParser a b -> [a] -> [b]
wParseResults p aa = [b | (b,[]) <- p aa]
paragraphs :: String -> [String]
paragraphs = map unlines . chop . lines where
chop [] = []
chop ss = let (ps,rest) = break empty ss in ps : chop (dropWhile empty rest)
empty = all isSpace
-- printing
indent :: Int -> String -> String
indent i s = replicate i ' ' ++ s
(+++), (++-), (++++), (+++++) :: String -> String -> String
a +++ b = a ++ " " ++ b
a ++- "" = a
a ++- b = a +++ b
a ++++ b = a ++ "\n" ++ b
a +++++ b = a ++ "\n\n" ++ b
prUpper :: String -> String
prUpper s = s1 ++ s2' where
(s1,s2) = span isSpace s
s2' = case s2 of
c:t -> toUpper c : t
_ -> s2
prReplicate :: Int -> String -> String
prReplicate n s = concat (replicate n s)
prTList :: String -> [String] -> String
prTList t ss = case ss of
[] -> ""
[s] -> s
s:ss -> s ++ t ++ prTList t ss
prQuotedString :: String -> String
prQuotedString x = "\"" ++ restoreEscapes x ++ "\""
prParenth :: String -> String
prParenth s = if s == "" then "" else "(" ++ s ++ ")"
prCurly, prBracket :: String -> String
prCurly s = "{" ++ s ++ "}"
prBracket s = "[" ++ s ++ "]"
prArgList, prSemicList, prCurlyList :: [String] -> String
prArgList = prParenth . prTList ","
prSemicList = prTList " ; "
prCurlyList = prCurly . prSemicList
restoreEscapes :: String -> String
restoreEscapes s =
case s of
[] -> []
'"' : t -> '\\' : '"' : restoreEscapes t
'\\': t -> '\\' : '\\' : restoreEscapes t
c : t -> c : restoreEscapes t
numberedParagraphs :: [[String]] -> [String]
numberedParagraphs t = case t of
[] -> []
p:[] -> p
_ -> concat [(show n ++ ".") : s | (n,s) <- zip [1..] t]
prConjList :: String -> [String] -> String
prConjList c [] = ""
prConjList c [s] = s
prConjList c [s,t] = s +++ c +++ t
prConjList c (s:tt) = s ++ "," +++ prConjList c tt
prIfEmpty :: String -> String -> String -> String -> String
prIfEmpty em _ _ [] = em
prIfEmpty em nem1 nem2 s = nem1 ++ s ++ nem2
-- | Thomas Hallgren's wrap lines
wrapLines :: Int -> String -> String
wrapLines n "" = ""
wrapLines n s@(c:cs) =
if isSpace c
then c:wrapLines (n+1) cs
else case lex s of
[(w,rest)] -> if n'>=76
then '\n':w++wrapLines l rest
else w++wrapLines n' rest
where n' = n+l
l = length w
_ -> s -- give up!!
--- optWrapLines = if argFlag "wraplines" True then wrapLines 0 else id
-- LaTeX code producing functions
dollar, mbox, ital, boldf, verbat :: String -> String
dollar s = '$' : s ++ "$"
mbox s = "\\mbox{" ++ s ++ "}"
ital s = "{\\em" +++ s ++ "}"
boldf s = "{\\bf" +++ s ++ "}"
verbat s = "\\verbat!" ++ s ++ "!"
mkLatexFile :: String -> String
mkLatexFile s = begindocument +++++ s +++++ enddocument
begindocument, enddocument :: String
begindocument =
"\\documentclass[a4paper,11pt]{article}" ++++ -- M.F. 25/01-02
"\\setlength{\\parskip}{2mm}" ++++
"\\setlength{\\parindent}{0mm}" ++++
"\\setlength{\\oddsidemargin}{0mm}" ++++
("\\setlength{\\evensidemargin}{"++"-2mm}") ++++ -- peb 27/5-04: to prevent hugs-mode
("\\setlength{\\topmargin}{"++"-8mm}") ++++ -- from treating the rest as comments
"\\setlength{\\textheight}{240mm}" ++++
"\\setlength{\\textwidth}{158mm}" ++++
"\\begin{document}\n"
enddocument =
"\n\\end{document}\n"
sortByLongest :: [[a]] -> [[a]]
sortByLongest = sortBy longer where
longer x y
| x' > y' = LT
| x' < y' = GT
| True = EQ
where
x' = length x
y' = length y
-- | 'combinations' is the same as @sequence@!!!
-- peb 30\/5-04
combinations :: [[a]] -> [[a]]
combinations t = case t of
[] -> [[]]
aa:uu -> [a:u | a <- aa, u <- combinations uu]
mkTextFile :: String -> IO ()
mkTextFile name = do
s <- readFile name
let s' = prelude name ++ "\n\n" ++ heading name ++ "\n" ++ object s
writeFile (name ++ ".hs") s'
where
prelude name = "module " ++ name ++ " where"
heading name = "txt" ++ name ++ " ="
object s = mk s ++ " \"\""
mk s = unlines [" \"" ++ escs line ++ "\" ++ \"\\n\" ++" | line <- lines s]
escs s = case s of
c:cs | elem c "\"\\" -> '\\' : c : escs cs
c:cs -> c : escs cs
_ -> s
initFilePath :: FilePath -> FilePath
initFilePath f = reverse (dropWhile (/='/') (reverse f))
-- | topological sorting with test of cyclicity
topoTest :: Eq a => [(a,[a])] -> Either [a] [[a]]
topoTest g = if length g' == length g then Left g' else Right (cyclesIn g ++[[]])
where
g' = topoSort g
cyclesIn :: Eq a => [(a,[a])] -> [[a]]
cyclesIn deps = nubb $ clean $ filt $ iterFix findDep immediate where
immediate = [[y,x] | (x,xs) <- deps, y <- xs]
findDep chains = [y:x:chain |
x:chain <- chains, (x',xs) <- deps, x' == x, y <- xs,
notElem y (init chain)]
clean = map remdup
nubb = nubBy (\x y -> y == reverse x)
filt = filter (\xs -> last xs == head xs)
remdup (x:xs) = x : remdup xs' where xs' = dropWhile (==x) xs
remdup [] = []
-- | topological sorting
topoSort :: Eq a => [(a,[a])] -> [a]
topoSort g = reverse $ tsort 0 [ffs | ffs@(f,_) <- g, inDeg f == 0] [] where
tsort _ [] r = r
tsort k (ffs@(f,fs) : cs) r
| elem f r = tsort k cs r
| k > lx = r
| otherwise = tsort (k+1) cs (f : tsort (k+1) (info fs) r)
info hs = [(f,fs) | (f,fs) <- g, elem f hs]
inDeg f = length [t | (h,hs) <- g, t <- hs, t == f]
lx = length g
-- | the generic fix point iterator
iterFix :: Eq a => ([a] -> [a]) -> [a] -> [a]
iterFix more start = iter start start
where
iter old new = if (null new')
then old
else iter (new' ++ old) new'
where
new' = filter (`notElem` old) (more new)
-- association lists
updateAssoc :: Eq a => (a,b) -> [(a,b)] -> [(a,b)]
updateAssoc ab@(a,b) as = case as of
(x,y): xs | x == a -> (a,b):xs
xy : xs -> xy : updateAssoc ab xs
[] -> [ab]
removeAssoc :: Eq a => a -> [(a,b)] -> [(a,b)]
removeAssoc a = filter ((/=a) . fst)
-- | chop into separator-separated parts
chunks :: Eq a => a -> [a] -> [[a]]
chunks sep ws = case span (/= sep) ws of
(a,_:b) -> a : bs where bs = chunks sep b
(a, []) -> if null a then [] else [a]
readIntArg :: String -> Int
readIntArg n = if (not (null n) && all isDigit n) then read n else 0
-- state monad with error; from Agda 6/11/2001
newtype STM s a = STM (s -> Err (a,s))
appSTM :: STM s a -> s -> Err (a,s)
appSTM (STM f) s = f s
stm :: (s -> Err (a,s)) -> STM s a
stm = STM
stmr :: (s -> (a,s)) -> STM s a
stmr f = stm (\s -> return (f s))
instance Monad (STM s) where
return a = STM (\s -> return (a,s))
STM c >>= f = STM (\s -> do
(x,s') <- c s
let STM f' = f x
f' s')
readSTM :: STM s s
readSTM = stmr (\s -> (s,s))
updateSTM :: (s -> s) -> STM s ()
updateSTM f = stmr (\s -> ((),f s))
writeSTM :: s -> STM s ()
writeSTM s = stmr (const ((),s))
done :: Monad m => m ()
done = return ()
class Monad m => ErrorMonad m where
raise :: String -> m a
handle :: m a -> (String -> m a) -> m a
handle_ :: m a -> m a -> m a
handle_ a b = a `handle` (\_ -> b)
instance ErrorMonad Err where
raise = Bad
handle a@(Ok _) _ = a
handle (Bad i) f = f i
instance ErrorMonad (STM s) where
raise msg = STM (\s -> raise msg)
handle (STM f) g = STM (\s -> (f s)
`handle` (\e -> let STM g' = (g e) in
g' s))
-- | if the first check fails try another one
checkAgain :: ErrorMonad m => m a -> m a -> m a
checkAgain c1 c2 = handle_ c1 c2
checks :: ErrorMonad m => [m a] -> m a
checks [] = raise "no chance to pass"
checks cs = foldr1 checkAgain cs
allChecks :: ErrorMonad m => [m a] -> m [a]
allChecks ms = case ms of
(m: ms) -> let rs = allChecks ms in handle_ (liftM2 (:) m rs) rs
_ -> return []
doUntil :: ErrorMonad m => (a -> Bool) -> [m a] -> m a
doUntil cond ms = case ms of
a:as -> do
v <- a
if cond v then return v else doUntil cond as
_ -> raise "no result"
-- subsequences sorted from longest to shortest ; their number is 2^n
subSequences :: [a] -> [[a]]
subSequences = sortBy (\x y -> compare (length y) (length x)) . subs where
subs xs = case xs of
[] -> [[]]
x:xs -> let xss = subs xs in [x:y | y <- xss] ++ xss