nvim-hs-2.3.2.3: src/Neovim/API/Parser.hs
{-# LANGUAGE OverloadedStrings #-}
{- |
Module : Neovim.API.Parser
Description : P.Parser for the msgpack output stram API
Copyright : (c) Sebastian Witte
License : Apache-2.0
Maintainer : woozletoff@gmail.com
Stability : experimental
-}
module Neovim.API.Parser (
NeovimAPI (..),
NeovimFunction (..),
NeovimType (..),
parseAPI,
) where
import Neovim.Classes
import Neovim.OS (isWindows)
import Control.Applicative (optional)
import Control.Monad (forM)
import Control.Monad.Except (MonadError (throwError))
import qualified Data.ByteString as B
import qualified Data.ByteString.Lazy as LB
import Data.Map (Map)
import qualified Data.Map as Map
import Data.MessagePack (Object)
import Data.Serialize (decode)
import Neovim.Compat.Megaparsec as P (
MonadParsec (eof, try),
Parser,
char,
noneOf,
oneOf,
parse,
some,
space,
string,
(<|>),
)
import System.Process.Typed (proc, readProcessStdout_)
import UnliftIO.Exception (
SomeException,
catch,
)
import Prelude
data NeovimType
= SimpleType String
| NestedType NeovimType (Maybe Int)
| Void
deriving (Show, Eq)
{- | This data type contains simple information about a function as received
throudh the @nvim --api-info@ command.
-}
data NeovimFunction = NeovimFunction
{ name :: String
-- ^ function name
, parameters :: [(NeovimType, String)]
-- ^ A list of type name and variable name.
, canFail :: Bool
-- ^ Indicator whether the function can fail/throws exceptions.
, async :: Bool
-- ^ Indicator whether the this function is asynchronous.
, returnType :: NeovimType
-- ^ Functions return type.
}
deriving (Show)
{- | This data type represents the top-level structure of the @nvim --api-info@
output.
-}
data NeovimAPI = NeovimAPI
{ errorTypes :: [(String, Int64)]
-- ^ The error types are defined by a name and an identifier.
, customTypes :: [(String, Int64)]
-- ^ Extension types defined by neovim.
, functions :: [NeovimFunction]
-- ^ The remotely executable functions provided by the neovim api.
}
deriving (Show)
-- | Run @nvim --api-info@ and parse its output.
parseAPI :: IO (Either (Doc AnsiStyle) NeovimAPI)
parseAPI = either (Left . pretty) extractAPI <$> go
where
go
| isWindows = readFromAPIFile
| otherwise = decodeAPI `catch` \(_ignored :: SomeException) -> readFromAPIFile
decodeAPI :: IO (Either String Object)
decodeAPI =
decode . LB.toStrict <$> readProcessStdout_ (proc "nvim" ["--api-info"])
extractAPI :: Object -> Either (Doc AnsiStyle) NeovimAPI
extractAPI apiObj =
fromObject apiObj >>= \apiMap ->
NeovimAPI
<$> extractErrorTypes apiMap
<*> extractCustomTypes apiMap
<*> extractFunctions apiMap
readFromAPIFile :: IO (Either String Object)
readFromAPIFile = (decode <$> B.readFile "api") `catch` returnNoApiForCodegeneratorErrorMessage
where
returnNoApiForCodegeneratorErrorMessage :: SomeException -> IO (Either String Object)
returnNoApiForCodegeneratorErrorMessage _ =
return . Left $
"The 'nvim' process could not be started and there is no file named 'api' in the working directory as a substitute."
oLookup :: (NvimObject o) => String -> Map String Object -> Either (Doc AnsiStyle) o
oLookup qry = maybe throwErrorMessage fromObject . Map.lookup qry
where
throwErrorMessage = throwError $ "No entry for:" <+> pretty qry
oLookupDefault :: (NvimObject o) => o -> String -> Map String Object -> Either (Doc AnsiStyle) o
oLookupDefault d qry m = maybe (return d) fromObject $ Map.lookup qry m
extractErrorTypes :: Map String Object -> Either (Doc AnsiStyle) [(String, Int64)]
extractErrorTypes objAPI = extractTypeNameAndID =<< oLookup "error_types" objAPI
extractTypeNameAndID :: Object -> Either (Doc AnsiStyle) [(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 AnsiStyle) [(String, Int64)]
extractCustomTypes objAPI = extractTypeNameAndID =<< oLookup "types" objAPI
extractFunctions :: Map String Object -> Either (Doc AnsiStyle) [NeovimFunction]
extractFunctions objAPI = mapM extractFunction =<< oLookup "functions" objAPI
toParameterlist :: [(String, String)] -> Either (Doc AnsiStyle) [(NeovimType, String)]
toParameterlist ps = forM ps $ \(t, n) -> do
t' <- parseType t
return (t', n)
extractFunction :: Map String Object -> Either (Doc AnsiStyle) 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 AnsiStyle) NeovimType
parseType s = either (throwError . pretty . show) return $ parse (pType <* eof) s s
pType :: P.Parser NeovimType
pType = pArray P.<|> pVoid P.<|> pSimple
pVoid :: P.Parser NeovimType
pVoid = Void <$ (P.try (string "void") <* eof)
pSimple :: P.Parser NeovimType
pSimple = SimpleType <$> P.some (noneOf [',', ')'])
pArray :: P.Parser NeovimType
pArray =
NestedType
<$> (P.try (string "ArrayOf(") *> pType)
<*> optional pNum
<* char ')'
pNum :: P.Parser Int
pNum = read <$> (P.try (char ',') *> space *> P.some (oneOf ['0' .. '9']))