lushtags-0.0.1: src/Tags.hs
----------------------------------------------------------------------------
-- |
-- Module : Tags
-- License : MIT (see LICENSE)
-- Authors : Bit Connor <bit@mutantlemon.com>
--
-- Maintainer : Bit Connor <bit@mutantlemon.com>
-- Stability : unstable
-- Portability : portable
--
-- Extract Tags from a Haskell Module
--
-----------------------------------------------------------------------------
module Tags
(
Tag(..)
, createTags
, tagToString
) where
import Data.Vector(Vector, (!))
import Language.Haskell.Exts.Annotated (SrcSpan(..), SrcSpanInfo(..))
import Language.Haskell.Exts.Annotated.Syntax
import Language.Haskell.Exts.Pretty (prettyPrintStyleMode, Style(..), Mode(OneLineMode), defaultMode)
data Tag = Tag
{ tagName :: String
, tagFile :: String
, tagPattern :: String
, tagKind :: TagKind
, tagLine :: Int
, tagParent :: Maybe (TagKind, String)
, tagSignature :: Maybe String
, tagAccess :: Maybe TagAccess
}
deriving (Eq, Ord, Show)
data TagKind
= TModule
| TExport
| TImport
| TType
| TData
| TNewType
| TConstructor
| TFunction
deriving (Eq, Ord, Show)
-- Access modifiers comes from the C++ world. Haskell doesn't have them, but
-- they are useful for marking our tags with similar meanings. For example,
-- marking all exported functions as public, or marking qualified imports
-- differently from unqualified ones.
data TagAccess
= AccessPublic
| AccessPrivate
| AccessProtected
deriving (Eq, Ord, Show)
-- First letter of each kind name must be unique!
tagKindName :: TagKind -> String
tagKindName TModule = "module"
tagKindName TExport = "export"
tagKindName TImport = "import"
tagKindName TType = "type"
tagKindName TData = "data"
tagKindName TNewType = "newtype"
tagKindName TConstructor = "constructor"
tagKindName TFunction = "function"
tagKindLetter :: TagKind -> Char
tagKindLetter = head . tagKindName
tagToString :: Tag -> String
tagToString tag =
let parentStr = case tagParent tag of
Nothing -> ""
Just (kind, name) -> "\t" ++ tagKindName kind ++ ":" ++ name
signatureStr = case tagSignature tag of
Nothing -> ""
Just sig -> "\tsignature:("++ sig ++ ")"
accessStr = case tagAccess tag of
Nothing -> ""
Just AccessPublic -> "\taccess:public"
Just AccessPrivate -> "\taccess:private"
Just AccessProtected -> "\taccess:protected"
in tagName tag ++ "\t" ++
tagFile tag ++ "\t" ++
tagPattern tag ++ ";\"\t" ++
tagKindLetter (tagKind tag) : "\t" ++
"line:" ++ show (tagLine tag) ++
parentStr ++
signatureStr ++
accessStr
type FileLines = Vector String
type TagC = FileLines -> Tag
createTags :: (Module SrcSpanInfo, FileLines) -> [Tag]
createTags (Module _ mbHead _ imports decls, fileLines) =
let moduleTags = map tagC (maybe [] createModuleTags mbHead)
importTags = map (tagC . createImportTag) imports
declsTags = map tagC (concatMap createDeclTags decls)
exportTags = filter ((==TExport) . tagKind) moduleTags
-- TODO If there is no ModuleHead then apply public access modifier to all
-- declarations tags
in moduleTags ++ importTags ++ (applyAccessModifiers exportTags declsTags)
where
tagC :: TagC -> Tag
tagC = ($ fileLines)
createTags _ = error "TODO Module is XmlPage/XmlHybrid (!)"
-- Apply a public access modifier to all declarations that are listed in the
-- module export specification
applyAccessModifiers :: [Tag] -> [Tag] -> [Tag]
applyAccessModifiers exportTags declTags = map applySingle declTags
where
applySingle :: Tag -> Tag
applySingle tag =
let name = tagName tag
exported = any ((==name) . tagName) exportTags
in if exported
then tag { tagAccess = Just AccessPublic }
else tag
createTag :: String -> TagKind -> Maybe (TagKind, String) -> Maybe String -> Maybe TagAccess -> SrcSpanInfo -> TagC
createTag name kind parent signature access (SrcSpanInfo (SrcSpan file line _ _ _) _) fileLines = Tag
{ tagName = name
, tagFile = file
-- TODO This probably needs to be escaped:
, tagPattern = "/^" ++ (fileLines ! (line - 1)) ++ "$/"
, tagKind = kind
, tagLine = line
, tagParent = parent
, tagSignature = signature
, tagAccess = access
}
createModuleTags :: ModuleHead SrcSpanInfo -> [TagC]
createModuleTags (ModuleHead _ (ModuleName moduleLoc moduleName) _ mbExportSpecList) =
case mbExportSpecList of
Nothing -> [moduleTag]
Just (ExportSpecList _ exports) ->
moduleTag : map createExportTag exports
where
moduleTag = createTag moduleName TModule Nothing Nothing Nothing moduleLoc
createExportTag :: ExportSpec SrcSpanInfo -> TagC
createExportTag exportSpec =
let (name, loc) = extractExportSpec exportSpec
in createTag name TExport Nothing Nothing Nothing loc
createImportTag :: ImportDecl SrcSpanInfo -> TagC
createImportTag (ImportDecl loc (ModuleName _ name) qualified _ _ mbAlias mbSpecs) =
let signature = case mbAlias of
Nothing -> Nothing
Just (ModuleName _ alias) -> Just alias
access = case mbSpecs of
Just (ImportSpecList _ False _) ->
if qualified then Just AccessProtected else Nothing
Just (ImportSpecList _ True _) ->
-- imported names are excluded by 'hiding'
if qualified then Just AccessProtected else Just AccessPrivate
Nothing -> if qualified then Just AccessProtected else Just AccessPublic
in createTag name TImport Nothing signature access loc
createDeclTags :: Decl SrcSpanInfo -> [TagC]
createDeclTags (TypeDecl _ hd _) =
let (name, loc) = extractDeclHead hd
in [createTag name TType Nothing Nothing Nothing loc]
createDeclTags (DataDecl _ dataOrNew _ hd constructors _) =
let (name, loc) = extractDeclHead hd
kind = case dataOrNew of
DataType _ -> TData
NewType _ -> TNewType
dataTag = createTag name kind Nothing Nothing Nothing loc
in dataTag : map (createConstructorTag (kind, name)) constructors
createDeclTags (TypeSig _ names t) =
map createFunctionTag names
where
sig = prettyPrintStyleMode (Style OneLineMode 0 0) defaultMode t
createFunctionTag :: Name SrcSpanInfo -> TagC
createFunctionTag name =
let (n, loc) = extractName name
in createTag n TFunction Nothing (Just sig) Nothing loc
createDeclTags _ = []
-- TODO Also create tags for record fields
createConstructorTag :: (TagKind, String) -> QualConDecl SrcSpanInfo -> TagC
createConstructorTag parent (QualConDecl _ _ _ con) =
let (name, loc) = extractConDecl con
in createTag name TConstructor (Just parent) Nothing Nothing loc
extractExportSpec :: ExportSpec SrcSpanInfo -> (String, SrcSpanInfo)
extractExportSpec (EVar _ name) = extractQName name
extractExportSpec (EAbs _ name) = extractQName name
extractExportSpec (EThingAll _ name) = extractQName name
extractExportSpec (EThingWith _ name _) = extractQName name
extractExportSpec (EModuleContents _ (ModuleName loc name)) = (name, loc)
extractDeclHead :: DeclHead SrcSpanInfo -> (String, SrcSpanInfo)
extractDeclHead (DHead _ name _) = extractName name
extractDeclHead (DHInfix _ _ name _) = extractName name
extractDeclHead (DHParen _ hd') = extractDeclHead hd'
extractConDecl :: ConDecl SrcSpanInfo -> (String, SrcSpanInfo)
extractConDecl (ConDecl _ name _) = extractName name
extractConDecl (InfixConDecl _ _ name _) = extractName name
extractConDecl (RecDecl _ name _) = extractName name
extractName :: Name SrcSpanInfo -> (String, SrcSpanInfo)
extractName (Ident loc name) = (name, loc)
extractName (Symbol loc name) = (name, loc)
extractQName :: QName SrcSpanInfo -> (String, SrcSpanInfo)
extractQName (Qual loc (ModuleName _ moduleName) name) =
(moduleName ++ "." ++ fst (extractName name), loc)
extractQName (UnQual loc name) = (fst (extractName name), loc)
extractQName (Special loc _) = ("_special_", loc) -- TODO