{-# LANGUAGE PatternGuards #-} -- Quickly hacked program to fixup Data.Collections.Properties, comments and property names. -- This implementation is highly insatisfactory, because it basically doesn't understand anything to -- haskell syntax and still modifies programs. -- However, at the time of writing, there exists no haskell parsing library that supports both: -- * parsing and retaining comments; -- * pretty printing with the comments retained. -- Therefore, I try to get with it by looking at indentation and a few key characters. import Data.Tree import Data.List import Data.Char indentationLevel (' ':xs) = 1 + indentationLevel xs indentationLevel ('\t':xs) = 8 + indentationLevel xs indentationLevel _ = 0 mkForest = map mkTree . items mkTree (x:xs) = Node x (mkForest xs) items [] = [] items (x:xs) = (x:l) : items r where (l,r) = span (subordinatedTo x) xs subordinatedTo s1 s2 | commentLine s1 = False | commentLine s2 = True | otherwise = indentationLevel s2 > indentationLevel s1 emptyLine = all isSpace nullNode (Node lab subs) = emptyLine lab && all nullNode subs commentLine [] = True commentLine ('-':'-':_) = True commentLine (c:cs) = isSpace c && commentLine cs dropComments = concatMap dropComment' dropComment' x = [n,Node (unlines $ filter (not . emptyLine) $ cmts) []] where (n,cmts) = dropComment x dropComment :: Tree String -> (Tree String, [String]) dropComment (Node l f) = (Node l f', rest ++ concatMap flatten (reverse f1)) where (f1,f2) = span commentNode (reverse f) (rest, f') = if null f2 then ([], []) else let (n',rest) = dropComment (head f2) in (rest, reverse (n':tail f2)) commentNode = commentLine . rootLabel emptyNode = Node [] [] groupFcts (n0:n1@(Node s1 f1):n2@(Node s2 f2):ns) | isTyp s1, isDef s2, name n1 == name n2 = (name n1, if commentNode n0 then n0 else emptyNode, n1,n2) : groupFcts (emptyNode:ns) groupFcts (n1:ns) = ("",emptyNode,emptyNode,n1) : groupFcts ns groupFcts [] = [] isTyp = any ("::" `isPrefixOf`) . tails isDef = elem '=' def t = map (drop lhs) $ filter (not . commentLine) $ text where lhs = 1 + length (takeWhile (/='=') (head text)) text = flatten t name = takeWhile (not . isSpace) . trim . rootLabel trim = dropWhile isSpace . reverse . dropWhile isSpace . reverse escape = concatMap e where e c = if c `elem` "/\\\'`@<>*-" then ['\\',c] else [c] subTrees t = t : concatMap subTrees (subForest t) type Def = (String, Tree String, Tree String, Tree String) -- TODO: quote the identifiers in definitions so they get cross-referenced. fixDefinition :: Def -> Def fixDefinition fct@(x, cmt, typ, n@(Node lab subnodes)) | "properties" `isSuffixOf` name n = (x, Node cmt' [], typ, Node lab' subnodes) | otherwise = fct where lab' = takeWhile (/= '=') lab ++ "= [" ++ (concat $ intersperse ", " $ map propCouple propNames) ++ "]" props = filter (("prop_" `isPrefixOf`) . name) (concatMap subTrees subnodes) propNames = map name props propCouple p = "(property " ++ p ++ "," ++ show (dropPrefix p) ++ ")" propComment p = ["--", "-- [/" ++ dropPrefix (name p) ++ "/]", "--"] ++ map ("-- > " ++) (def p) -- text@" ++ escape (def p) ++ "@" cmt' = unlines $ ("-- | " ++ name n ++ " returns the following properties: ") : concatMap propComment props dropPrefix = tail . dropWhile (/= '_') fctForest (name, cmts, typ, def) = [cmts,typ,def,Node "" []] cleanBlanks ls = map fst $ filter (not . bothEmpty) $ zip ls ("":ls) where bothEmpty (a,b) = emptyLine a && emptyLine b cleanBlanks' ls = map fst $ filter (not . bothEmpty) $ zip ls (' ':ls) where bothEmpty (a,b) = isSpace a && isSpace b main = do src <- getContents let forest = filter (not . nullNode) $ dropComments $ mkForest $ lines src --putStr $ drawForest forest --mapM_ putStrLn $ map showGrp $ groupFcts forest let forest' = concat $ map fctForest $ map fixDefinition $ groupFcts $ forest mapM_ putStrLn $ cleanBlanks $ concatMap flatten forest' showGrp (n,cmts,_,t) = n ++ "--->\n" ++ unlines (flatten cmts) ++ "\n" ++ drawTree t