{-# LANGUAGE CPP #-}
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE ScopedTypeVariables #-}
module Language.C.Inline.HaskellIdentifier
( HaskellIdentifier
, unHaskellIdentifier
, haskellIdentifierFromString
, haskellCParserContext
, parseHaskellIdentifier
, mangleHaskellIdentifier
, haskellReservedWords
) where
import Control.Applicative ((<|>))
import Control.Monad (when, msum, void)
import Data.Char (ord)
import qualified Data.HashSet as HashSet
import Data.Hashable (Hashable)
import Data.List (intercalate, partition, intersperse)
import Data.Monoid ((<>))
import Data.String (IsString(..))
import Data.Typeable (Typeable)
import Numeric (showHex)
import Text.Parser.Char (upper, lower, digit, char)
import Text.Parser.Combinators (many, eof, try, unexpected, (<?>))
import Text.Parser.Token (IdentifierStyle(..), highlight, TokenParsing)
import qualified Text.Parser.Token.Highlight as Highlight
import qualified Text.PrettyPrint.ANSI.Leijen as PP
import qualified Language.C.Types.Parse as C
#if __GLASGOW_HASKELL__ < 710
import Control.Applicative ((<*), (<$>), (<*>))
#endif
newtype HaskellIdentifier = HaskellIdentifier {unHaskellIdentifier :: String}
deriving (Typeable, Eq, Ord, Show, Hashable)
instance IsString HaskellIdentifier where
fromString s =
case haskellIdentifierFromString True s of
Left err -> error $ "HaskellIdentifier fromString: invalid string " ++ s ++ ":\n" ++ err
Right x -> x
instance PP.Pretty HaskellIdentifier where
pretty = PP.text . unHaskellIdentifier
haskellIdentifierFromString :: Bool -> String -> Either String HaskellIdentifier
haskellIdentifierFromString useCpp s =
case C.runCParser cpc "haskellIdentifierFromString" s (parseHaskellIdentifier <* eof) of
Left err -> Left $ show err
Right x -> Right x
where
cpc = haskellCParserContext useCpp HashSet.empty
haskellCParserContext :: Bool -> C.TypeNames -> C.CParserContext HaskellIdentifier
haskellCParserContext useCpp typeNames = C.CParserContext
{ C.cpcTypeNames = typeNames
, C.cpcParseIdent = parseHaskellIdentifier
, C.cpcIdentName = "Haskell identifier"
, C.cpcIdentToString = unHaskellIdentifier
, C.cpcEnableCpp = useCpp
}
haskellIdentStyle :: C.CParser i m => IdentifierStyle m
haskellIdentStyle = IdentifierStyle
{ _styleName = "Haskell identifier"
, _styleStart = small
, _styleLetter = small <|> large <|> digit <|> char '\''
, _styleReserved = haskellReservedWords
, _styleHighlight = Highlight.Identifier
, _styleReservedHighlight = Highlight.ReservedIdentifier
}
where
small = lower <|> char '_'
large = upper
haskellReservedWords :: HashSet.HashSet String
haskellReservedWords = C.cReservedWords <> HashSet.fromList
[ "case", "class", "data", "default", "deriving", "do", "else"
, "foreign", "if", "import", "in", "infix", "infixl"
, "infixr", "instance", "let", "module", "newtype", "of"
, "then", "type", "where"
]
parseHaskellIdentifier :: forall i m. C.CParser i m => m HaskellIdentifier
parseHaskellIdentifier = do
segments <- go
return $ HaskellIdentifier $ intercalate "." segments
where
small = lower <|> char '_'
large = upper
conid :: m String
conid = try $ highlight Highlight.Identifier $
((:) <$> large <*> many (small <|> large <|> digit <|> char '\'')) <?> "Haskell constructor"
varid :: m String
varid = identNoLex haskellIdentStyle
go = msum
[ do con <- conid
msum
[ do void $ char '.'
(con :) <$> go
, return [con]
]
, do var <- varid
return [var]
]
mangleHaskellIdentifier :: Bool -> HaskellIdentifier -> C.CIdentifier
mangleHaskellIdentifier useCpp (HaskellIdentifier hs) =
let cs = (if null valid then "_" else "") ++
valid ++
(if null mangled || null valid then "" else "_") ++
mangled
in case C.cIdentifierFromString useCpp cs of
Left err -> error $ "mangleHaskellIdentifier: produced bad C identifier\n" ++ err
Right x -> x
where
(valid, invalid) = partition (`elem` C.cIdentLetter) hs
mangled = concat $ intersperse "_" $ map (`showHex` "") $ map ord invalid
identNoLex :: (TokenParsing m, Monad m, IsString s) => IdentifierStyle m -> m s
identNoLex s = fmap fromString $ try $ do
name <- highlight (_styleHighlight s)
((:) <$> _styleStart s <*> many (_styleLetter s) <?> _styleName s)
when (HashSet.member name (_styleReserved s)) $ unexpected $ "reserved " ++ _styleName s ++ " " ++ show name
return name