packages feed

homplexity-0.2: Language/Haskell/Homplexity/CodeFragment.hs

{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE DeriveDataTypeable    #-}
{-# LANGUAGE ScopedTypeVariables   #-}
{-# LANGUAGE RecordWildCards       #-}
{-# LANGUAGE FlexibleContexts      #-}
{-# LANGUAGE FlexibleInstances     #-}
{-# LANGUAGE UndecidableInstances  #-}
{-# LANGUAGE TypeFamilies          #-}
{-# LANGUAGE ViewPatterns          #-}
-- | This module generalizes over types of code fragments
-- that may need to be iterated upon and measured separately.
module Language.Haskell.Homplexity.CodeFragment (
    CodeFragment(fragmentName)
  , occurs
  , allOccurs
  , Program      (..)
  , programT
  , Module       (..)
  , moduleT
  , Function     (..)
  , functionT
  , TypeSignature(..)
  , typeSignatureT
  , fragmentLoc
  -- TODO: add ClassSignature
  ) where

import Data.Data
import Data.Generics.Uniplate.Data
import Data.List
import Data.Maybe
import Control.Arrow
import Control.Exception
import Language.Haskell.Exts.Syntax
import Language.Haskell.Exts.SrcLoc
import Language.Haskell.Exts
import Language.Haskell.Homplexity.SrcSlice

-- | Program
data Program = Program { allModules :: [Module] }
  deriving (Data, Typeable, Show)

-- | Smart constructor for adding cross-references in the future.
program  = Program

-- | Proxy for passing @Program@ type as an argument.
programT :: Proxy Program
programT  = Proxy

-- * Type aliases for type-based matching of substructures
-- | Alias for a function declaration
data Function = Function {
                  functionNames     :: [String]
                , functionLocations :: [SrcLoc]
                , functionRhs       :: [Rhs]
                , functionBinds     :: [Binds]
                }
  deriving (Data, Typeable, Show)

-- | Proxy for passing @Function@ type as an argument.
functionT :: Proxy Function
functionT  = Proxy

-- ** Alias for a type signature of a function
data TypeSignature = TypeSignature { loc         :: SrcLoc
                                   , identifiers :: [Name]
                                   , theType     :: Type }
  deriving (Data, Typeable, Show)

-- | Proxy for passing @Program@ type as an argument.
typeSignatureT :: Proxy TypeSignature
typeSignatureT  = Proxy

-- TODO: class signatures (number of function decls inside)
-- ** Alias for a class signature
data ClassSignature = ClassSignature
  deriving (Data, Typeable)

-- TODO: need combination of Fold and Biplate
-- Resulting record may be created to make pa

-- | Class @CodeFragment@ allows for:
-- * both selecting direct or all descendants
--   of the given type of object within another structure
--   (with @occurs@ and @allOccurs@)
-- * naming the object to allow user to distinguish it.
--
-- In order to compute selection, we just need to know which
-- @AST@ nodes contain the given object, and how to extract
-- this given object from @AST@, if it is there (@matchAST@).:w
class (Show c, Data (AST c), Data c) => CodeFragment c where
  type             AST c
  matchAST      :: AST c -> Maybe c
  fragmentName  ::     c -> String
  fragmentSlice ::     c -> SrcSlice
  fragmentSlice  = srcSlice

fragmentLoc :: (CodeFragment c) => c -> SrcLoc
fragmentLoc =  getPointLoc
            .  fragmentSlice

instance CodeFragment Function where
  type AST Function     = Decl
  matchAST (FunBind matches) = Just Function {..}
    where
      (functionLocations, (unName <$>) . take 1 -> functionNames, functionRhs, functionBinds) = unzip4 $ map extract matches
      extract (Match srcLoc name _ _ rhs binds) = (srcLoc, name, rhs, binds)
  matchAST (PatBind (singleton -> functionLocations) pat
                    (singleton -> functionRhs      )
                    (singleton -> functionBinds    )) = Just Function {..}
    where
      functionNames  = wildcards ++ map unName (universeBi pat)
      wildcards = mapMaybe wildcard $ universeBi pat
        where
          wildcard PWildCard = Just    ".."
          wildcard _         = Nothing
  matchAST _                                          = Nothing
  fragmentName (Function {..}) = unwords $ "function":functionNames

-- | Make a single element list.
singleton :: a -> [a]
singleton  = (:[])

-- | Direct occurences of given @CodeFragment@ fragment within another structure.
occurs :: (CodeFragment c, Data from) => from -> [c]
occurs  = mapMaybe matchAST . childrenBi

-- | All occurences of given type of @CodeFragment@ fragment within another structure.
allOccurs :: (CodeFragment c, Data from) => from -> [c]
allOccurs = mapMaybe matchAST . universeBi

instance CodeFragment Program where
  type AST Program = Program
  matchAST         = Just
  fragmentName _   = "program"

instance CodeFragment Module where
  type AST Module = Module
  matchAST = Just 
  fragmentName (Module _ (ModuleName theName) _ _ _ _ _) = "module " ++ theName

-- | Proxy for passing @Module@ type as an argument.
moduleT :: Proxy Module
moduleT  = Proxy

instance CodeFragment TypeSignature where
  type AST TypeSignature = Decl
  matchAST (TypeSig loc identifiers theType) = Just TypeSignature {..}
  matchAST  _                                = Nothing
  fragmentName (TypeSignature {..}) = "type signature for " ++ intercalate ", " (map unName identifiers)

-- | Unpack @Name@ identifier into a @String@.
unName ::  Name -> String
unName (Symbol s) = s
unName (Ident  i) = i