{- BNF Converter: Abstract syntax Generator Copyright (C) 2004 Author: Markus Forberg This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA -} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedStrings #-} module BNFC.Backend.Haskell.CFtoAbstract (cf2Abstract, definedRules) where import Prelude hiding ((<>)) import Data.Maybe import qualified Data.List as List import BNFC.CF import BNFC.Options ( TokenText(..) ) import BNFC.PrettyPrint import BNFC.Utils ( when ) import BNFC.Backend.Haskell.Utils ( avoidReservedWords, catToType, catvars, mkDefName , tokenTextImport, tokenTextType, typeToHaskell ) -- | Create a Haskell module containing data type definitions for the abstract syntax. cf2Abstract :: TokenText -- ^ Use @ByteString@ or @Text@ instead of @String@? -> Bool -- ^ Derive @Data@, Generic@, @Typeable@? -> Bool -- ^ Make the tree a functor? -> String -- ^ Module name. -> CF -- ^ Grammar. -> Doc cf2Abstract tokenText generic functor name cf = vsep . concat $ [ [ vcat [ "-- Haskell data types for the abstract syntax." , "-- Generated by the BNF converter." ] ] , [ vcat . concat $ [ [ "{-# LANGUAGE DeriveDataTypeable #-}" | gen ] , [ "{-# LANGUAGE DeriveGeneric #-}" | gen ] , [ "{-# LANGUAGE GeneralizedNewtypeDeriving #-}" | hasIdentLike ] -- for IsString ] ] , [ hsep [ "module", text name, "where" ] ] , [ vcat . concat $ [ [ text $ "import Prelude (" ++ typeImports ++ functorImportsUnqual ++ ")" ] , [ text $ "import qualified Prelude as C (Eq, Ord, Show, Read" ++ functorImportsQual ++ ")" ] , [ "import qualified Data.String" | hasIdentLike ] -- for IsString ] ] , [ vcat . concat $ [ map text $ tokenTextImport tokenText , [ "import qualified Data.Data as C (Data, Typeable)" | gen ] , [ "import qualified GHC.Generics as C (Generic)" | gen ] ] ] , (`map` specialCats cf) $ \ c -> let hasPos = isPositionCat cf c in prSpecialData tokenText hasPos (derivingClassesTokenType hasPos) c , concatMap (prData functorName derivingClasses) datas , definedRules functor cf , [ "" ] -- ensure final newline ] where hasIdentLike = hasIdentLikeTokens cf datas = cf2data cf gen = generic && not (null datas) derivingClasses = map ("C." ++) $ concat [ [ "Eq", "Ord", "Show", "Read" ] , when generic [ "Data", "Typeable", "Generic" ] ] derivingClassesTokenType hasPos = concat [ derivingClasses , [ "Data.String.IsString" | not hasPos ] ] typeImports = List.intercalate ", " $ concat [ [ "Char", "Double" ] , [ "Int" | hasPositionTokens cf ] , [ "Integer", "String" ] ] functorImportsUnqual | functor = ", map, fmap" | otherwise = "" functorImportsQual | functor = ", Functor" | otherwise = "" functorName | functor = "C.Functor" | otherwise = "" type FunctorName = String -- | -- -- >>> vsep $ prData "" ["Eq", "Ord", "Show", "Read"] (Cat "C", [("C1", [Cat "C"]), ("CIdent", [Cat "Ident"])]) -- data C = C1 C | CIdent Ident -- deriving (Eq, Ord, Show, Read) -- -- Note that the layout adapts if it does not fit in one line: -- >>> vsep $ prData "" ["Show"] (Cat "C", [("CAbracadabra",[]),("CEbrecedebre",[]),("CIbricidibri",[]),("CObrocodobro",[]),("CUbrucudubru",[])]) -- data C -- = CAbracadabra -- | CEbrecedebre -- | CIbricidibri -- | CObrocodobro -- | CUbrucudubru -- deriving (Show) -- -- If the first argument is not null, generate a functor: -- >>> vsep $ prData "Functor" ["Show"] (Cat "C", [("C1", [Cat "C"]), ("CIdent", [TokenCat "Ident"])]) -- data C a = C1 a (C a) | CIdent a Ident -- deriving (Show) -- -- instance Functor C where -- fmap f x = case x of -- C1 a c -> C1 (f a) (fmap f c) -- CIdent a ident -> CIdent (f a) ident -- -- The case for lists: -- >>> vsep $ prData "Functor" ["Show"] (Cat "ExpList", [("Exps", [ListCat (Cat "Exp")])]) -- data ExpList a = Exps a [Exp a] -- deriving (Show) -- -- instance Functor ExpList where -- fmap f x = case x of -- Exps a exps -> Exps (f a) (map (fmap f) exps) -- prData :: FunctorName -> [String] -> Data -> [Doc] prData functorName derivingClasses (cat,rules) = concat [ [ hang ("data" <+> dataType) 4 (constructors rules) $+$ nest 2 (deriving_ derivingClasses) ] , [ genFunctorInstance functorName (cat, rules) | functor ] ] where functor = not $ null functorName prRule (fun, cats) = hsep $ concat [ [text fun], ["a" | functor], map prArg cats ] dataType = hsep $ concat [ [text (show cat)], ["a" | functor] ] prArg = catToType id $ if functor then "a" else empty constructors [] = empty constructors (h:t) = sep $ ["=" <+> prRule h] ++ map (("|" <+>) . prRule) t -- | Generate a functor instance declaration: -- -- >>> genFunctorInstance "Functor" (Cat "C", [("C1", [Cat "C", Cat "C"]), ("CIdent", [TokenCat "Ident"])]) -- instance Functor C where -- fmap f x = case x of -- C1 a c1 c2 -> C1 (f a) (fmap f c1) (fmap f c2) -- CIdent a ident -> CIdent (f a) ident -- -- >>> genFunctorInstance "Functor" (Cat "SomeLists", [("Ints", [ListCat (TokenCat "Integer")]), ("Exps", [ListCat (Cat "Exp")])]) -- instance Functor SomeLists where -- fmap f x = case x of -- Ints a integers -> Ints (f a) integers -- Exps a exps -> Exps (f a) (map (fmap f) exps) -- genFunctorInstance :: FunctorName -> Data -> Doc genFunctorInstance functorName (cat, cons) = "instance" <+> text functorName <+> text (show cat) <+> "where" $+$ nest 4 ("fmap f x = case x of" $+$ nest 4 (vcat (map mkCase cons))) where mkCase (f, args) = hsep . concat $ [ [ text f, "a" ] , vars , [ "->", text f, "(f a)" ] , zipWith recurse vars args ] where vars = catvars args -- We recursively call fmap on non-terminals only if they are not token categories. recurse var = \case TokenCat{} -> var ListCat TokenCat{} -> var ListCat{} -> parens ("map (fmap f)" <+> var) _ -> parens ("fmap f" <+> var) -- | Generate a newtype declaration for Ident types -- -- >>> prSpecialData StringToken False ["Show","Data.String.IsString"] catIdent -- newtype Ident = Ident String -- deriving (Show, Data.String.IsString) -- -- >>> prSpecialData StringToken True ["Show"] catIdent -- newtype Ident = Ident ((Int, Int), String) -- deriving (Show) -- -- >>> prSpecialData TextToken False ["Show"] catIdent -- newtype Ident = Ident Data.Text.Text -- deriving (Show) -- -- >>> prSpecialData ByteStringToken False ["Show"] catIdent -- newtype Ident = Ident BS.ByteString -- deriving (Show) -- -- >>> prSpecialData ByteStringToken True ["Show"] catIdent -- newtype Ident = Ident ((Int, Int), BS.ByteString) -- deriving (Show) -- prSpecialData :: TokenText -- ^ Format of token content. -> Bool -- ^ If @True@, store the token position. -> [String] -- ^ Derived classes. -> TokenCat -- ^ Token category name. -> Doc prSpecialData tokenText position classes cat = vcat [ hsep [ "newtype", text cat, "=", text cat, contentSpec ] , nest 2 $ deriving_ classes ] where contentSpec | position = parens ( "(Int, Int), " <> stringType) | otherwise = stringType stringType = text $ tokenTextType tokenText -- | Generate 'deriving' clause -- -- >>> deriving_ ["Show", "Read"] -- deriving (Show, Read) -- deriving_ :: [String] -> Doc deriving_ cls = "deriving" <+> parens (hsep $ punctuate "," $ map text cls) -- | Generate Haskell code for the @define@d constructors. definedRules :: Bool -> CF -> [Doc] definedRules functor cf = [ mkDef f xs e | FunDef f xs e <- cfgPragmas cf ] where mkDef f xs e = vcat $ map text $ concat [ [ unwords [ mkDefName f, "::", typeToHaskell $ wpThing t ] | not functor -- TODO: make type signatures work with --functor , t <- maybeToList $ sigLookup f cf ] , [ unwords $ mkDefName f : xs' ++ [ "=", show $ sanitize e ] ] ] where xs' = addFunctorArg id $ map avoidReservedWords xs sanitize = \case App x es -> App x $ addFunctorArg (`App` []) $ map sanitize es Var x -> Var $ avoidReservedWords x e@LitInt{} -> e e@LitDouble{} -> e e@LitChar{} -> e e@LitString{} -> e -- Functor argument addFunctorArg g | functor = (g "_a" :) | otherwise = id