module Neovim.API.Parser
( NeovimAPI(..)
, NeovimFunction(..)
, NeovimType(..)
, parseAPI
) where
import Neovim.Classes
import Control.Applicative
import Control.Exception.Lifted
import Control.Monad.Except
import qualified Data.ByteString as B
import Data.Map (Map)
import qualified Data.Map as Map
import Data.MessagePack
import Data.Monoid
import Data.Serialize
import System.IO (hClose)
import System.Process
import Text.Megaparsec as P
import Text.Megaparsec.String
import Text.PrettyPrint.ANSI.Leijen (Doc)
import qualified Text.PrettyPrint.ANSI.Leijen as P
import Prelude
data NeovimType = SimpleType String
| NestedType NeovimType (Maybe Int)
| Void
deriving (Show, Eq)
data NeovimFunction
= NeovimFunction
{ name :: String
, parameters :: [(NeovimType, String)]
, canFail :: Bool
, async :: Bool
, returnType :: NeovimType
}
deriving (Show)
data NeovimAPI
= NeovimAPI
{ errorTypes :: [(String, Int64)]
, customTypes :: [(String, Int64)]
, functions :: [NeovimFunction]
}
deriving (Show)
parseAPI :: IO (Either Doc NeovimAPI)
parseAPI = either (Left . P.text) extractAPI <$> (decodeAPI `catch` readFromAPIFile)
extractAPI :: Object -> Either Doc NeovimAPI
extractAPI apiObj = fromObject apiObj >>= \apiMap -> NeovimAPI
<$> extractErrorTypes apiMap
<*> extractCustomTypes apiMap
<*> extractFunctions apiMap
readFromAPIFile :: SomeException -> IO (Either String Object)
readFromAPIFile _ = (decode <$> B.readFile "api") `catch` returnPreviousExceptionAsText
where
returnPreviousExceptionAsText :: SomeException -> IO (Either String Object)
returnPreviousExceptionAsText _ = return . Left $
"The 'nvim' process could not be started and there is no file named\
\ 'api' in the working directory as a substitute."
decodeAPI :: IO (Either String Object)
decodeAPI = bracket queryNeovimAPI clean $ \(out, _) ->
decode <$> B.hGetContents out
where
queryNeovimAPI = do
(_, Just out, _, ph) <- createProcess $
(proc "nvim" ["--api-info"]) { std_out = CreatePipe }
return (out, ph)
clean (out, ph) = do
hClose out
terminateProcess ph
oLookup :: (NvimObject o) => String -> Map String Object -> Either Doc o
oLookup qry = maybe throwErrorMessage fromObject . Map.lookup qry
where
throwErrorMessage = throwError . P.text $ "No entry for: " <> show qry
oLookupDefault :: (NvimObject o) => o -> String -> Map String Object -> Either Doc o
oLookupDefault d qry m = maybe (return d) fromObject $ Map.lookup qry m
extractErrorTypes :: Map String Object -> Either Doc [(String, Int64)]
extractErrorTypes objAPI = extractTypeNameAndID =<< oLookup "error_types" objAPI
extractTypeNameAndID :: Object -> Either Doc [(String, Int64)]
extractTypeNameAndID m = do
types <- Map.toList <$> fromObject m
forM types $ \(errName, idMap) -> do
i <- oLookup "id" idMap
return (errName,i)
extractCustomTypes :: Map String Object -> Either Doc [(String, Int64)]
extractCustomTypes objAPI = extractTypeNameAndID =<< oLookup "types" objAPI
extractFunctions :: Map String Object -> Either Doc [NeovimFunction]
extractFunctions objAPI = mapM extractFunction =<< oLookup "functions" objAPI
toParameterlist :: [(String, String)] -> Either Doc [(NeovimType, String)]
toParameterlist ps = forM ps $ \(t,n) -> do
t' <- parseType t
return (t', n)
extractFunction :: Map String Object -> Either Doc NeovimFunction
extractFunction funDefMap = NeovimFunction
<$> (oLookup "name" funDefMap)
<*> (oLookup "parameters" funDefMap >>= toParameterlist)
<*> (oLookupDefault True "can_fail" funDefMap)
<*> (oLookupDefault False "async" funDefMap)
<*> (oLookup "return_type" funDefMap >>= parseType)
parseType :: String -> Either Doc NeovimType
parseType s = either (throwError . P.text . show) return $ parse (pType <* eof) s s
pType :: Parser NeovimType
pType = pArray P.<|> pVoid P.<|> pSimple
pVoid :: Parser NeovimType
pVoid = const Void <$> (P.try (string "void") <* eof)
pSimple :: Parser NeovimType
pSimple = SimpleType <$> some (noneOf [',', ')'])
pArray :: Parser NeovimType
pArray = NestedType <$> (P.try (string "ArrayOf(") *> pType)
<*> optional pNum <* char ')'
pNum :: Parser Int
pNum = read <$> (P.try (char ',') *> space *> some (oneOf ['0'..'9']))