{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
module Plugin.GhcTags.Vim.Parser
( parseTagsFile
, parseTags
, parseTag
, parseField
) where
import Control.Arrow ((***))
import Control.Applicative (many, (<|>))
import Data.Attoparsec.Text (Parser, (<?>))
import qualified Data.Attoparsec.Text as AT
import Data.Either (rights)
import Data.Functor (void, ($>))
import Data.Text (Text)
import qualified Data.Text as Text
import Plugin.GhcTags.Tag
parseTag :: Parser Tag
parseTag =
(\tagName tagFile tagAddr (tagKind, tagFields)
-> Tag { tagName, tagFile, tagAddr, tagKind, tagFields })
<$> parseTagName
<* separator
<*> parseFileName
<* separator
<*> AT.eitherP parseAddr parseExCommand
<*> (
((,) <$> ( separator *> parseKindField )
<*> ( separator *> parseFields <* AT.endOfLine
<|>
AT.endOfLine $> [])
)
<|> curry id NoKind
<$> ( separator *> parseFields <* AT.endOfLine
<|>
AT.endOfLine $> []
)
<|> curry (charToTagKind *** id)
<$> ( separator *> AT.satisfy notTabOrNewLine )
<*> ( separator *> parseFields <* AT.endOfLine
<|>
AT.endOfLine $> []
)
<|> AT.endOfLine $> (NoKind, [])
)
where
separator :: Parser Char
separator = AT.char '\t'
notTabOrNewLine :: Char -> Bool
notTabOrNewLine = \x -> x /= '\t' && x /= '\n'
parseTagName :: Parser TagName
parseTagName = TagName <$> AT.takeWhile (/= '\t')
<?> "parsing tag name failed"
parseFileName :: Parser TagFile
parseFileName = TagFile <$> AT.takeWhile (/= '\t')
parseExCommand :: Parser Text
parseExCommand = (\x -> Text.take (Text.length x - 1) x) <$>
AT.scan "" go
<* AT.anyChar
where
go :: String -> Char -> Maybe String
go _ '\n' = Nothing
go !s c | l == "\";" = Nothing
| otherwise = Just l
where
l = take 2 (c : s)
parseAddr :: Parser Int
parseAddr = AT.decimal
<* AT.eitherP
AT.endOfLine
(AT.char ';' *> AT.char '"')
parseKindField :: Parser TagKind
parseKindField =
charToTagKind <$>
(AT.string "kind:" *> AT.satisfy notTabOrNewLine)
parseFields :: Parser [TagField]
parseFields = AT.sepBy parseField separator
charToTagKind :: Char -> TagKind
charToTagKind c = case charToGhcKind c of
Nothing -> CharKind c
Just ghcTag -> GhcKind ghcTag
parseField :: Parser TagField
parseField =
TagField
<$> AT.takeWhile (\x -> x /= ':' && x /= '\n' && x /= '\t')
<* AT.char ':'
<*> AT.takeWhile (\x -> x /= '\t' && x /= '\n')
parseTags :: Parser [Tag]
parseTags = rights <$> many parseTagLine
parseTagLine :: Parser (Either () Tag)
parseTagLine =
AT.eitherP
(parseHeader <?> "failed parsing tag")
(parseTag <?> "failed parsing header")
parseHeader :: Parser ()
parseHeader = AT.choice
[ AT.string (Text.pack "!_TAG_FILE_FORMAT") *> params
, AT.string (Text.pack "!_TAG_FILE_SORTED") *> params
, AT.string (Text.pack "!_TAG_FILE_ENCODING") *> params
, AT.string (Text.pack "!_TAG_PROGRAM_AUTHOR") *> params
, AT.string (Text.pack "!_TAG_PROGRAM_NAME") *> params
, AT.string (Text.pack "!_TAG_PROGRAM_URL") *> params
, AT.string (Text.pack "!_TAG_PROGRAM_VERSION") *> params
]
where
params = void $ AT.char '\t' *> AT.skipWhile (/= '\n') *> AT.char '\n'
parseTagsFile :: Text
-> IO (Either String [Tag])
parseTagsFile =
fmap AT.eitherResult
. AT.parseWith (pure mempty) parseTags