module Helpers ( mkIncludeBlock , getDefinitions , getEnums , selectDefines , selectEnum , mkEnum , mkFlag , selectEnums ) where --import Control.Applicative ((<$>)) import Control.Monad (join) import Data.Char (isNumber) import Data.Function (on) import Data.List (isPrefixOf, sortBy, nubBy) import Data.Map (Map, elems, filterWithKey, fromList, insert, keys, mapKeys, toList, lookup, empty) import Data.Maybe (mapMaybe, fromJust) import Language.C.Analysis (runTrav_) import Language.C.Analysis.AstAnalysis (analyseAST) import Language.C.Analysis.SemRep (GlobalDecls(..), TagDef(EnumDef), EnumType(..), Enumerator(..)) import Language.C.Data.Ident (Ident(..)) import Language.C.Data.InputStream (inputStreamFromString) import Language.C.Data.Position (position) import Language.C.Parser (parseC) import Language.C.Pretty (pretty) import Language.C.Syntax.Constants (getCInteger) import Language.C.Syntax.AST (CExpression(..), CExpr, CConstant(CIntConst), CBinaryOp(..)) import System.Process (readProcess) import Text.Regex.PCRE ((=~)) import Prelude hiding (lookup) mkIncludeBlock :: [String] -> String mkIncludeBlock = unlines . map (\e -> "#include <" ++ e ++ ">") mkFlag :: String -> Map String Integer -> ([String], [String]) mkFlag name vals = (name : map fst values, ty : "" : join (map makeConst values)) where ty = "newtype " ++ name ++ " = " ++ name ++ " Int deriving (Bits, Eq, Enum, Integral, Num, Ord, Real, Show)" makeConst (n, v) = [n ++ " :: (Num a, Bits a) => a", n ++ " = " ++ show v] values = sortBy (compare `on` snd) . toList . mapKeys ("f" ++) $ vals mkEnum :: String -> Map String Integer -> ([String], [String]) mkEnum name vals = (name : fName : map fst values, ty : "" : fun : "" : join (map makeConst values)) where ty = "newtype " ++ name ++ " = " ++ name ++ " Int deriving (Eq, Enum, Integral, Num, Ord, Real, Show)" makeConst (n, v) = [n ++ " :: (Num a) => a", n ++ " = " ++ show v] values = sortBy (compare `on` snd) . toList . mapKeys ('e' :) $ vals (fName, fun) = showEnum name vals showEnum :: String -> Map String Integer -> (String, String) showEnum name vals = (fName, fName ++ " :: (Num a) => (Show a) => (Eq a) => a -> String\n" ++ concatMap makeLine values ++ fName ++ " i = \"" ++ name ++ " #\" ++ (show i)\n") where makeLine (n, v) = fName ++ ' ':(show v) ++ " = \"" ++ n ++ "\"\n" values = nubBy ((==) `on` snd) . sortBy (compare `on` snd) . toList $vals fName = "show" ++ name selectDefines :: String -> Map String Integer -> Map String Integer selectDefines regex = filterWithKey (\k _ -> k =~ regex) selectEnum :: String -> [Map String Integer] -> Map String Integer selectEnum regex m = case selectEnums regex m of (x:_) -> x [] -> error ("Couldn't find enum for " ++ regex) selectEnums :: String -> [Map String Integer] -> [Map String Integer] selectEnums regex = filter (all (=~ regex) . keys) --full :: String -> String --full regex = "^" ++ regex ++ "$" getEnums :: String -> IO [Map String Integer] getEnums source = do parsed <- flip parseC initPos . inputStreamFromString <$> preprocessed let unit = gTags . fst . check $ runTrav_ (analyseAST $ check parsed) enums = mapMaybe getEnum (elems unit) return $ map cleanEnums enums where check (Left err) = error $ show err check (Right a) = a preprocessed = readProcess "gcc" ["-E", "-"] source initPos = position 0 "" 0 0 cleanEnums = filterWithKey (\k _ -> not ("_" `isPrefixOf` k)) getEnum :: TagDef -> Maybe (Map String Integer) getEnum (EnumDef (EnumType _ es _ _)) = Just $foldl getEnumValue empty es getEnum _ = Nothing getEnumValue :: Map String Integer -> Enumerator -> Map String Integer getEnumValue m (Enumerator (Ident s _ _) v _ _) = insert s a m where a = evalEExpr m v evalEExpr :: Map String Integer -> CExpr -> Integer evalEExpr _ (CConst (CIntConst v _)) = getCInteger v evalEExpr m (CBinary CAddOp a b _) = evalEExpr m a + evalEExpr m b evalEExpr m (CBinary CSubOp a b _) = evalEExpr m a - evalEExpr m b evalEExpr m (CBinary CShlOp a b _) = evalEExpr m a * (2 ^ evalEExpr m b) evalEExpr m (CVar (Ident a _ _) _) = fromJust $lookup a m evalEExpr _ other = error $ "Other: " ++ show (pretty other) --evalCExpr :: CExpr -> Integer --evalCExpr = evalEExpr empty sanitize :: [[String]] -> [[String]] sanitize (["@define",n]:_:[y]:xs) = ["@define",n,y]:sanitize xs sanitize (["@define",x,y]:xs) = ["@define",x,y]:sanitize xs sanitize (_:xs) = sanitize xs sanitize [] = [] getDefinitions :: String -> IO (Map String Integer) getDefinitions headers = do defines <- map words . lines <$> readDefines headers let isDefine (c:n:_) = c == "#define" && '(' `notElem` n && not ("_" `isPrefixOf` n) isDefine xs = error ("isDefine: Couldn't check: " ++ show xs) hasValue = (>= 3) . length names = map (!! 1) $ filter (\d -> isDefine d && hasValue d) defines kludge = map (\n -> "@define \"" ++ n ++ "\" " ++ n) names defines2 <- map words . lines <$> preprocess (headers ++ unlines kludge) let isInteresting d = hasValue d && ["@define"] `isPrefixOf` d && (all isNumber (d !! 2) || "0x" `isPrefixOf` (d !! 2) && all isNumber (drop 2 (d !! 2))) realDefines = map (take 2 . drop 1) $ filter isInteresting $ sanitize defines2 clean [(_:k),v] = (init k, read v) clean _ = error "Clean: got a weird list" return $ fromList (map clean realDefines) where readDefines = readProcess "gcc" ["-E", "-dM", "-"] preprocess = readProcess "gcc" ["-E", "-"]