{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}

-- | A module for parsing of pragmas from comments.
module Ormolu.Parser.Pragma
  ( Pragma (..),
    parsePragma,
  )
where

import Data.Char (isSpace)
import Data.Text (Text)
import Data.Text qualified as T
import Data.Text.Encoding qualified as T
import GHC.Data.FastString (bytesFS, mkFastString)
import GHC.Driver.Config.Parser (initParserOpts)
import GHC.DynFlags (baseDynFlags)
import GHC.Parser.Lexer qualified as L
import GHC.Types.SrcLoc
import Ormolu.Utils (textToStringBuffer)

-- | Ormolu's representation of pragmas.
data Pragma
  = -- | Language pragma
    PragmaLanguage [Text]
  | -- | GHC options pragma
    PragmaOptionsGHC Text
  | -- | Haddock options pragma
    PragmaOptionsHaddock Text
  deriving (Show, Eq)

-- | Extract a pragma from a comment if possible, or return 'Nothing'
-- otherwise.
parsePragma ::
  -- | Comment to try to parse
  Text ->
  Maybe Pragma
parsePragma input = do
  contents <- T.stripSuffix "#-}" =<< T.stripPrefix "{-#" input
  let (pragmaName, cs) = (T.break isSpace . T.dropWhile isSpace) contents
  case T.toLower pragmaName of
    "language" -> PragmaLanguage <$> parseExtensions cs
    "options_ghc" -> Just $ PragmaOptionsGHC (T.strip cs)
    "options_haddock" -> Just $ PragmaOptionsHaddock (T.strip cs)
    _ -> Nothing

-- | Assuming the input consists of a series of tokens from a language
-- pragma, return the set of enabled extensions.
parseExtensions :: Text -> Maybe [Text]
parseExtensions str = tokenize str >>= go
  where
    go = \case
      [L.ITconid ext] -> return [fsToText ext]
      (L.ITconid ext : L.ITcomma : xs) -> (fsToText ext :) <$> go xs
      _ -> Nothing
    fsToText = T.decodeUtf8 . bytesFS

-- | Tokenize a given input using GHC's lexer.
tokenize :: Text -> Maybe [L.Token]
tokenize input =
  case L.unP pLexer parseState of
    L.PFailed {} -> Nothing
    L.POk _ x -> Just x
  where
    location = mkRealSrcLoc (mkFastString "") 1 1
    buffer = textToStringBuffer input
    parseState = L.initParserState parserOpts buffer location
    parserOpts = initParserOpts baseDynFlags

-- | Haskell lexer.
pLexer :: L.P [L.Token]
pLexer = go
  where
    go = do
      r <- L.lexer False return
      case unLoc r of
        L.ITeof -> return []
        x -> (x :) <$> go
