BNFC-2.9.2: src/BNFC/Backend/Haskell/Utils.hs
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
module BNFC.Backend.Haskell.Utils
( posType, posConstr, noPosConstr
, hasPositionClass, hasPositionMethod
, noWarnUnusedMatches
, parserName
, hsReservedWords, avoidReservedWords, mkDefName
, typeToHaskell, typeToHaskell'
, catToType
, catToVar, catvars
, tokenTextImport, tokenTextType
, tokenTextPack, tokenTextPackParens, tokenTextUnpack
) where
import Data.Char
import Data.String (IsString)
import BNFC.PrettyPrint
import qualified BNFC.PrettyPrint as P
import BNFC.CF (Cat(..), catToStr, identCat, baseTokenCatNames, Base, Type(FunT), IsFun(..))
import BNFC.Options (TokenText(..))
import BNFC.Utils (mkNames, NameStyle(..))
-- * GHC pragmas
noWarnUnusedMatches :: IsString a => a
noWarnUnusedMatches =
"{-# OPTIONS_GHC -fno-warn-unused-matches #-}"
-- ALT: only from GHC 8
-- "{-# OPTIONS_GHC -Wno-unused-matches #-}"
-- * Names for position data type.
posType, posConstr, noPosConstr :: IsString a => a
posType = "BNFC'Position"
posConstr = "BNFC'Position"
noPosConstr = "BNFC'NoPosition"
-- * The @HasPosition@ class for position-carrying abstract syntax.
hasPositionClass, hasPositionMethod :: IsString a => a
hasPositionClass = "HasPosition"
hasPositionMethod = "hasPosition"
-- * Parameterization by 'TokenText'.
tokenTextImport :: TokenText -> [String]
tokenTextImport = \case
StringToken -> []
ByteStringToken -> [ "import qualified Data.ByteString.Char8 as BS" ]
TextToken -> [ "import qualified Data.Text" ]
tokenTextType :: TokenText -> String
tokenTextType = \case
StringToken -> "String"
ByteStringToken -> "BS.ByteString"
TextToken -> "Data.Text.Text"
tokenTextPack :: TokenText -> String -> String
tokenTextPack = \case
StringToken -> id
ByteStringToken -> ("BS.pack " ++)
TextToken -> ("Data.Text.pack " ++)
tokenTextPackParens :: TokenText -> String -> String
tokenTextPackParens = \case
StringToken -> id
ByteStringToken -> parens . ("BS.pack " ++)
TextToken -> parens . ("Data.Text.pack " ++)
where
parens :: String -> String
parens s = "(" ++ s ++ ")"
tokenTextUnpack :: TokenText -> String -> String
tokenTextUnpack t s = case t of
StringToken -> s
ByteStringToken -> "(BS.unpack " ++ s ++ ")"
TextToken -> "(Data.Text.unpack " ++ s ++ ")"
-- * Other Utililites
-- | Create a valid parser function name for a given category.
--
-- >>> parserName (Cat "Abcd")
-- pAbcd
--
-- >>> parserName (ListCat (Cat "Xyz"))
-- pListXyz
--
parserName :: Cat -> Doc
parserName = ("p" P.<>) . text . identCat
-- | Haskell's reserved words.
--
hsReservedWords :: [String]
hsReservedWords =
[ "as"
, "case"
, "class"
, "data"
, "default"
, "deriving"
, "do"
, "else"
, "family"
, "forall"
, "foreign"
, "hiding"
, "if"
, "import"
, "in"
, "infix"
, "infixl"
, "infixr"
, "instance"
, "let"
, "mdo"
, "module"
, "newtype"
, "of"
, "pattern"
, "proc"
, "qualified"
, "rec"
, "then"
, "type"
, "where"
]
-- | Avoid Haskell keywords plus additional reserved words.
avoidReservedWords :: [String] -> String -> String
avoidReservedWords additionalReserved x
| x `elem` reserved = x ++ "'"
| otherwise = x
where
reserved = additionalReserved ++ hsReservedWords
-- | Modifier to avoid clashes in definition.
mkDefName :: IsFun f => f -> String
mkDefName = avoidReservedWords [] . funName
-- | Render a category from the grammar to a Haskell type.
--
-- >>> catToType id empty (Cat "A")
-- A
-- >>> catToType id empty (ListCat (Cat "A"))
-- [A]
-- >>> catToType ("Foo." P.<>) empty (TokenCat "Ident")
-- Foo.Ident
--
-- Note that there is no haskell type for coerced categories: they should be normalized:
-- >>> catToType id empty (CoercCat "Expr" 2)
-- Expr
--
-- If a type parameter is given it is added to the type name:
-- >>> catToType id (text "a") (Cat "A")
-- (A a)
--
-- >>> catToType id (text "a") (ListCat (Cat "A"))
-- [A a]
--
-- but not added to Token categories:
-- >>> catToType ("Foo." P.<>) (text "a") (TokenCat "Integer")
-- Integer
--
-- >>> catToType id (text "a") (ListCat (TokenCat "Integer"))
-- [Integer]
--
-- >>> catToType id empty (ListCat (CoercCat "Exp" 2))
-- [Exp]
--
-- >>> catToType ("Foo." P.<>) (text "()") (ListCat (CoercCat "Exp" 2))
-- [Foo.Exp ()]
--
catToType :: (Doc -> Doc) -> Doc -> Cat -> Doc
catToType qualify param cat = parensIf isApp $ loop cat
where
isApp = case cat of
Cat _ -> not $ isEmpty param
_ -> False
loop = \case
ListCat c -> brackets $ loop c
Cat c -> qualify (text c) <+> param -- note: <+> goes away if param==empty
CoercCat c _ -> qualify (text c) <+> param
TokenCat c
| c `elem` baseTokenCatNames
-> text c
| otherwise -> qualify (text c)
-- | Convert a base type to Haskell syntax.
baseTypeToHaskell :: Base -> String
baseTypeToHaskell = show
-- | Convert a function type to Haskell syntax in curried form.
typeToHaskell :: Type -> String
typeToHaskell = typeToHaskell' "->"
typeToHaskell' :: String -> Type -> String
typeToHaskell' arr (FunT ts t) =
foldr f (baseTypeToHaskell t) $ map baseTypeToHaskell ts
where f a b = unwords [a, arr, b]
-- | Make a variable name for a category.
catToVar :: [String] -> Cat -> String
catToVar rs = avoidReservedWords rs . var
where
var (ListCat cat) = var cat ++ "s"
var (Cat "Ident") = "x"
var (Cat "Integer") = "n"
var (Cat "String") = "str"
var (Cat "Char") = "c"
var (Cat "Double") = "d"
var xs = map toLower $ catToStr xs
-- | Gives a list of variables usable for pattern matching.
--
-- Example: Given the rule @Aba. S ::= A B A ;@ with the generated data type
-- @
-- data S = Aba A B A
-- @
-- from the list of categories on the RHS of the rule [A,B,A], we generate the
-- list [a1,b,a2] to be used in a pattern matching like
-- @
-- case s of
-- Aba a1 b a2 -> ...
-- ...
-- @
--
-- >>> catvars [] [Cat "A", Cat "B", Cat "A"]
-- [a1,b,a2]
--
-- It should avoid reserved words:
-- >>> catvars ["foo"] [Cat "Foo", Cat "IF", Cat "Case", Cat "Type", Cat "If"]
-- [foo_,if_1,case_,type_,if_2]
--
-- It uses a suffix -s to mark lists:
-- >>> catvars [] [Cat "A", ListCat (Cat "A"), ListCat (ListCat (Cat "A"))]
-- [a,as_,ass]
--
catvars :: [String] -> [Cat] -> [Doc]
catvars rs = map text . mkNames (rs ++ hsReservedWords) LowerCase . map var
where
var (ListCat c) = var c ++ "s"
var c = catToStr c