{- Copyright 2015 Markus Ongyerth, Stephan Guenther This file is part of Monky. Monky is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Monky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with Monky. If not, see . -} {-# LANGUAGE CPP, TemplateHaskell #-} {-| Module : Monky.Template Description : This module provides a template haskell splice for including librarys Maintainer : ongy, moepi Stability : testing Portability : Linux This module is intended to be used by Monky modules, /not/ for configuration. This module provides the 'importLib' templateHaskell function to import a C-library easily. To use this, set the __LANGUAGE TemplateHaskell__ pragma in your module file and include this module. Usage: Use 'importLib' as top-level declaration in your file. Like: @ importLib "LibAlsa" "libasound.so" [] @ This will create a data type for the library, and a function to get a handle for this library (data and get). To call your functions use the record syntax on that handle. -} module Monky.Template ( importLib , module Foreign.Ptr ) where import Control.Monad (liftM2) import Data.Char (isSpace) import Data.List (nub) import Foreign.Ptr (Ptr, FunPtr, castFunPtr) import Language.Haskell.TH import Monky.Utility import Data.Maybe (fromMaybe) import System.Posix.DynamicLinker (DL, dlclose, dlopen, dlsym, RTLDFlags(RTLD_LAZY)) #if MIN_VERSION_base(4,8,0) #else import Control.Applicative ((<$>)) #endif #if MIN_VERSION_base(4,9,0) monkyStrict :: Bang monkyStrict = Bang NoSourceUnpackedness NoSourceStrictness #else monkyStrict :: Strict monkyStrict = NotStrict #endif -- trim a string ltrim :: String -> String ltrim = dropWhile isSpace rtrim :: String -> String rtrim = reverse . ltrim . reverse trim :: String -> String trim = rtrim . ltrim -- Split type string prepareFun :: String -> [String] prepareFun = map trim . splitAtEvery "->" . trim -- get constructor name getName :: String -> Q Name getName name = fromMaybe (error $ "Could not find constructor: " ++ name) <$> lookupTypeName name -- Get a type from a String, this can do ONE application, so IO works getType :: String -> Q Type getType xs = if ' ' `elem` xs then let [t,a] = words xs in liftM2 AppT (getT t) (getT a) else getT xs where getT "()" = return (TupleT 0) getT ys = ConT <$> getName ys -- Apply arrows to create a function from types applyArrows :: [Type] -> Type applyArrows [] = error "Cannot work with empty function type" applyArrows [x] = x applyArrows (x:xs) = AppT (AppT ArrowT x) (applyArrows xs) -- Create function declarations for the constructor mkFunDesc :: (String, String) -> VarStrictTypeQ mkFunDesc (x,y) = do t <- applyArrows <$> mapM getType (prepareFun y) return (mkName x, monkyStrict, t) cleanName :: Char -> String cleanName '(' = "vo" cleanName ')' = "id" cleanName x = return x -- Get the transformer name, this is some ugly name mangeling transName :: String -> String transName = concatMap cleanName . ("mkFun" ++) . filter isOk where isOk c = not (isSpace c) && c /= '-' && c /= '>' -- Get the function described by the three-tuple (Alias, C-Name, TypeString) getFunQ :: Name -> (String, String, String) -> Q Stmt getFunQ handle (alias, name, typeString) = do let castFPtr = [| $(varE . mkName $ transName typeString) . castFunPtr |] let getSym = [| dlsym $(varE handle) name |] BindS (VarP (mkName (alias ++ "_"))) <$> [| fmap $(castFPtr) $(getSym) |] -- Create the return statement, this applies the constructor mkRet :: Name -> [String] -> Name -> Exp -> Q Stmt mkRet hname xs rawN raw= do let funs = map (\x -> return (mkName x, VarE (mkName (x ++ "_")))) xs let con = recConE hname (return (rawN,raw):funs) NoBindS <$> [| return $(con) |] -- Create the statement to get the handle mkGetHandle :: Name -> String -> Q Stmt mkGetHandle h libname = BindS (VarP h) <$> [| dlopen libname [RTLD_LAZY] |] -- Create the get function mkGetFun :: String -> String -> Name -> [(String, String, String)] -> Name -> Q [Dec] mkGetFun lname name hname funs raw = do let funName = mkName ("get" ++ name) let handle = mkName "handle" ghandle <- mkGetHandle handle lname funStmts <- mapM (getFunQ handle) funs ret <- mkRet hname (map (\(x,_,_) -> x) funs) raw (VarE handle) let fun = FunD funName [Clause [] (NormalB $ DoE (ghandle:funStmts ++ [ret])) []] sig <- sigD funName [t| IO $(conT . mkName $ name) |] return [sig,fun] -- Create the transformer function used by get mkTransformer :: String -> Q Dec mkTransformer f = do let name = mkName . transName $ f let ty = applyArrows <$> mapM getType (prepareFun f) ForeignD . ImportF CCall Safe "dynamic" name <$> [t| (FunPtr $(ty)) -> $(ty) |] mkDestroyFun :: String -> Name -> Q [Dec] mkDestroyFun name raw = do let libT = mkName name let hname = mkName "handle" let funName = mkName ("destroy" ++ name) let body = [| dlclose ($(varE raw) $(varE hname)) |] sig <- sigD funName [t| $(conT libT) -> IO () |] fun <- funD funName [clause [varP hname] (normalB body) []] return [sig ,fun] -- |Import a library importLib :: String -- ^The name of the library data type -> String -- ^The name of the library -> [(String, String, String)] -- ^The functions in the library (Name, CName, Declaration) -> Q [Dec] importLib hname lname xs = do let name = mkName hname funs <- mapM (mkFunDesc . (\(x,_,y) -> (x,y))) xs transformers <- mapM mkTransformer $ nub $ map (\(_,_,x) -> x) xs let rawRN = mkName "rawDL" let raw = (rawRN, monkyStrict, ConT ''DL) #if MIN_VERSION_base(4,9,0) let dhandle = DataD [] name [] Nothing [RecC (mkName hname) (raw:funs)] [] #else let dhandle = DataD [] name [] [RecC (mkName hname) (raw:funs)] [] #endif fun <- mkGetFun lname hname name xs rawRN dest <- mkDestroyFun hname rawRN return (dhandle:dest ++ transformers ++ fun)