----------------------------------------------------------------------------- -- -- Module : Language.Elm.TH.Util -- Copyright : Copyright: (c) 2011-2013 Joey Eremondi -- License : BSD3 -- -- Maintainer : joey.eremondi@usask.ca -- Stability : experimental -- Portability : portable -- -- | -- ----------------------------------------------------------------------------- {-# LANGUAGE TemplateHaskell, QuasiQuotes, MultiWayIf #-} module Language.Elm.TH.Util where import Language.Haskell.TH.Syntax import Data.Aeson.TH import qualified SourceSyntax.Module as M import qualified SourceSyntax.Declaration as D import qualified SourceSyntax.Expression as E import qualified SourceSyntax.Literal as L import qualified SourceSyntax.Location as Lo import qualified SourceSyntax.Pattern as P import qualified SourceSyntax.Type as T import Data.List (isPrefixOf) import Language.Haskell.TH.Desugar.Sweeten import Language.Haskell.TH.Desugar --import Parse.Expression (makeFunction) import Control.Applicative import Control.Monad.State (StateT) import qualified Control.Monad.State as S import qualified Data.Map as Map --translate newName into our new monad liftNewName :: String -> SQ Name liftNewName s = do oldState <- S.get let num = currentNum oldState name <- S.lift $ newName $ s ++ "__xxfreshxx__" ++ show num S.put $ oldState {currentNum = num + 1} return name doEmitWarning :: String -> SQ [a] doEmitWarning s = S.lift $ emitWarning s --State information type SQ a = StateT TranslationState Q a --Enum for the different state vars we can access data TranslationState = TranslationState { records :: Map.Map String [String], currentNum :: Int } defaultState = TranslationState (Map.fromList []) 1 -- | General error function for unimplemented features unImplemented s = error $ "Translation of the The following haskell feature is not yet implemented: " ++ s emitWarning :: String -> Q [a] emitWarning s = do runIO $ putStrLn $ "Warning! Ignoring feature in Haskell source: " ++ s return [] -- |Stolen from Parse.Expression so we don't have to change any internal Elm code makeFunction :: [P.Pattern] -> E.LExpr -> E.LExpr makeFunction args body@(Lo.L s _) = foldr (\arg body' -> Lo.L s $ E.Lambda arg body') body args -- |Translate a type variable to a name, ignoring its kind tyVarToName :: TyVarBndr -> Name tyVarToName (PlainTV n) = n tyVarToName (KindedTV n _ ) = n --Abstract out the translation of names to strings --So that we can modify if need be --Right now is just a synonym nameToString :: Name -> String nameToString name = case nameModule name of Nothing -> nameBase name--TODO fancier? Just base -> if "GHC." `isPrefixOf` base then nameBase name else showName name --Split a list into two alternating lists --from http://www.haskell.org/haskellwiki/Blow_your_mind splitList :: [a] -> ([a], [a]) splitList = foldr (\a ~(x,y) -> (a:y,x)) ([],[]) splitListN :: Int -> [a] -> [[a]] splitListN 0 l = [] splitListN 1 l = [l] splitListN 2 l = let (l1, l2) = splitList l in [l1, l2] splitListN n l | even n = let (l1, l2) = splitList l in (splitListN (quot n 2) l1) ++ (splitListN (quot n 2) l2) | otherwise = let (l1, l2) = splitList l in [l1] ++ (splitListN (n-1) l2) -------------------------------------------------------------------------- -- |Type helper functions -- We use these, since String comparison is insufficients: -- Template Haskell returns GHC.Types.Int instead of Inst int = [t| Int |] string = [t| String |] float = [t| Float |] bool = [t| Bool |] isIntType t = do tint <- int --runIO $ putStrLn $ "Checking if int " ++ (show (t == tint)) return (t == tint) isStringType t = do tstr <- string return (t == tstr) isFloatType t = do tfloat <- float return (t == tfloat) isBoolType t = do tbool <- bool return (t == tbool) -- | Helper function to traverse a tree of AppTs and check if a type is a tuple all the way down isTupleType (AppT (TupleT _arity) _) = True isTupleType (AppT t1 t2) = isTupleType t1 isTupleType _ = False isMaybeType (AppT (ConT name) _) = (nameToString name) == "Maybe" isMaybeType _ = False isMapType (AppT (AppT (ConT name) _) _) = (nameToString name) `elem` ["Map", "Data.Map.Map", "Map.Map"] --TODO deeper comparison isMapType _ = False -- | Helper function to linearize the AppT of tuple types tupleTypeToList (AppT (TupleT _arity) t) = [t] tupleTypeToList (AppT t1 t2) = tupleTypeToList t1 ++ [t2] -- | Given a record dictionary, find constructor for one containing all the given fields recordWithFields :: Map.Map String [String] -> [String] -> String recordWithFields recMap fields = case ctors of [] -> unImplemented $ "Records from other modules\n" ++ (show recMap) ++ "\n" ++ (show fields) [(ctor, _)] -> ctor _ -> unImplemented "Records sharing field names" where recList = Map.toList recMap hasFields (_, fieldsInRecord) = not $ null (filter (`elem` fields) fieldsInRecord) ctors = filter hasFields recList -- | Helper to get all subtypes of a type subTypes :: Type -> [Type] subTypes (ForallT _ _ t) = [t] subTypes (VarT _) = [] subTypes (ConT _) = [] subTypes (TupleT _) = [] subTypes ArrowT = [] subTypes ListT = [] subTypes (AppT t1 t2) = [t1, t2] subTypes (SigT t _) = [t] subTypes _ = [] --TODO better catch-all?