apigen-0.0.1: src/Apigen/Language/Haskell.hs
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE Strict #-}
{-# LANGUAGE StrictData #-}
{-# LANGUAGE TupleSections #-}
module Apigen.Language.Haskell where
import Control.Monad.State.Strict (State)
import qualified Control.Monad.State.Strict as State
import Data.Fix (Fix (..))
import Data.Maybe (catMaybes)
import Data.Text (Text)
import qualified Data.Text as Text
import Language.Cimple (Lexeme (..), LexemeClass (..),
Node, NodeF (..))
import Language.Cimple.TraverseAst (AstActions, astActions, doNode,
traverseAst)
import qualified Text.Casing as Casing
joinLines :: [Text] -> Text
joinLines = Text.intercalate "\n"
idToHaskell :: Text -> Text
idToHaskell =
Text.pack
. Casing.toPascal
. Casing.Identifier
. dropNamespace
. Casing.unIdentifier
. Casing.fromSnake
. Text.unpack
where
-- Drop the first component of the name, but only if there are at least
-- 2 components.
dropNamespace (_:name@(_:_)) = name
dropNamespace name = name
maybeParen :: Text -> Text
maybeParen name
| ' ' `elem` Text.unpack name = "(" <> name <> ")"
| otherwise = name
isErrorEnum :: Text -> Bool
isErrorEnum tyName =
case Casing.unIdentifier . Casing.fromSnake . Text.unpack $ tyName of
(_:"Err":_) -> True
_ -> False
genStdType :: Text -> Text
genStdType "uint16_t" = "Word16"
genStdType "uint32_t" = "Word32"
genStdType "uint64_t" = "Word64"
genStdType "size_t" = "CSize"
genStdType "bool" = "Bool"
genStdType tyName = error $ show tyName
genType :: Node (Lexeme Text) -> Text
genType (Fix (TyUserDefined (L _ IdSueType tyName))) =
"CEnum " <> idToHaskell tyName
genType (Fix (TyPointer (Fix (TyUserDefined (L _ IdSueType tyName))) ))
| isErrorEnum tyName = "CErr " <> idToHaskell tyName
| otherwise = idToHaskell tyName <> "Ptr"
genType (Fix (TyPointer (Fix (TyConst (Fix (TyUserDefined (L _ IdSueType tyName))))))) =
idToHaskell tyName <> "Ptr"
genType (Fix (TyPointer (Fix (TyConst (Fix (TyStruct (L _ IdSueType tyName))))))) =
idToHaskell tyName <> "Ptr"
genType (Fix (TyPointer (Fix (TyStruct (L _ IdSueType tyName))) )) =
idToHaskell tyName <> "Ptr"
genType (Fix (TyPointer (Fix (TyStd (L _ IdStdType "uint8_t"))) )) =
"CString"
genType (Fix (TyPointer (Fix (TyConst (Fix (TyStd (L _ IdStdType "uint8_t"))))))) =
"CString"
genType (Fix (TyPointer (Fix (TyConst (Fix (TyStd (L _ IdStdType "char"))))))) =
"CString"
genType (Fix (TyPointer (Fix (TyStd (L _ KwVoid _))))) =
"Ptr ()"
genType (Fix (TyPointer (Fix (TyFunc (L _ IdFuncType tyName))))) =
"FunPtr " <> idToHaskell tyName
genType (Fix (TyStd (L _ IdStdType tyName))) =
genStdType tyName
genType (Fix (TyPointer (Fix (TyStd (L _ IdStdType tyName))))) =
"Ptr " <> genStdType tyName
genType (Fix (TyStd (L _ KwVoid _))) =
"()"
genType ty = error $ show ty
genArg :: Node (Lexeme Text) -> Text
genArg (Fix (VarDecl ty _ _)) = genType ty
genArg arg = error $ show arg
genArgs :: [Node (Lexeme Text)] -> Text
genArgs args = Text.intercalate " -> " (map genArg args) <> " -> "
genFunction :: Node (Lexeme Text) -> Text -> [Node (Lexeme Text)] -> Text
genFunction retTy name [(Fix (TyStd (L _ KwVoid _)))] =
"foreign import ccall " <> name <> " :: " <> genType retTy
genFunction retTy name args =
"foreign import ccall " <> name <> " :: " <> genArgs args <> "IO " <> maybeParen (genType retTy)
genFuncType :: Node (Lexeme Text) -> Text -> [Node (Lexeme Text)] -> Text
genFuncType retTy name args = joinLines
[ "type " <> hsName <> " = " <> genArgs args <> "IO " <> maybeParen (genType retTy)
, "foreign import ccall \"wrapper\" wrap" <> hsName <> " :: " <> hsName <> " -> IO (FunPtr " <> hsName <> ")"
]
where
hsName = idToHaskell name
genEnumerator :: Node (Lexeme Text) -> Maybe Text
genEnumerator (Fix Comment{}) = Nothing
genEnumerator (Fix (Enumerator (L _ _ name) _)) = Just $ idToHaskell name
genEnumerator x = error $ show x
genEnum :: Text -> [Node (Lexeme Text)] -> Text
genEnum name enums = joinLines $
[ "data " <> idToHaskell name
, " = " <> Text.intercalate "\n | " (catMaybes (map genEnumerator enums))
, " deriving (Eq, Ord, Enum, Bounded, Read, Show)"
]
linter :: AstActions (State [Text]) Text
linter = astActions
{ doNode = \_file node act ->
case unFix node of
FunctionDecl _ (Fix (FunctionPrototype retTy (L _ _ name) args)) ->
State.modify (genFunction retTy name args:)
TypedefFunction (Fix (FunctionPrototype retTy (L _ _ name) args)) ->
State.modify (genFuncType retTy name args:)
EnumDecl (L _ _ name) enums _ ->
State.modify (genEnum name enums:)
_ -> act
}
generate :: (FilePath, [Node (Lexeme Text)]) -> (FilePath, Text)
generate = ("file.hs",) . joinLines . reverse . flip State.execState [] . traverseAst linter