2019-06-27 00:13:25 +03:00
|
|
|
{-
|
2019-06-27 01:51:30 +03:00
|
|
|
Generate FromNoun and ToNoun instances.
|
2019-06-27 00:13:25 +03:00
|
|
|
-}
|
|
|
|
|
|
|
|
module Data.Noun.Poet.TH where
|
|
|
|
|
|
|
|
import ClassyPrelude hiding (fromList)
|
|
|
|
import Control.Lens
|
|
|
|
import Data.Noun.Poet hiding (hsToHoon)
|
|
|
|
|
|
|
|
import Language.Haskell.TH
|
|
|
|
import Language.Haskell.TH.Syntax
|
|
|
|
|
2019-06-27 01:51:30 +03:00
|
|
|
import qualified Data.Char as C
|
2019-06-27 00:13:25 +03:00
|
|
|
|
|
|
|
|
2019-06-27 01:51:30 +03:00
|
|
|
--------------------------------------------------------------------------------
|
2019-06-27 00:13:25 +03:00
|
|
|
|
2019-06-27 01:51:30 +03:00
|
|
|
type ConInfo = (Name, [Type])
|
2019-06-27 00:13:25 +03:00
|
|
|
|
|
|
|
data Shape
|
2019-06-27 01:51:30 +03:00
|
|
|
= Tup ConInfo
|
|
|
|
| Enu [Name]
|
|
|
|
| Sum [ConInfo]
|
2019-06-27 00:13:25 +03:00
|
|
|
deriving (Eq, Ord, Show)
|
|
|
|
|
|
|
|
typeShape :: Name -> Q Shape
|
|
|
|
typeShape tyName = do
|
2019-06-27 01:51:30 +03:00
|
|
|
cs <- reify tyName >>= \case
|
|
|
|
TyConI (DataD _ nm [] _ cs _) -> pure $ unpackCon <$> cs
|
|
|
|
TyConI (NewtypeD _ nm [] _ c _) -> pure $ [unpackCon c]
|
|
|
|
TyConI (DataD _ nm _ _ cs _) -> fail "Type variables are unsupported"
|
|
|
|
TyConI (NewtypeD _ nm _ _ c _) -> fail "Type variables are unsupported"
|
|
|
|
TyConI _ -> fail badSynonym
|
|
|
|
_ -> fail "not type"
|
2019-06-27 00:13:25 +03:00
|
|
|
|
2019-06-27 01:51:30 +03:00
|
|
|
let allEmpty = all (null . snd) cs
|
2019-06-27 00:13:25 +03:00
|
|
|
|
|
|
|
if allEmpty
|
2019-06-27 01:51:30 +03:00
|
|
|
then pure $ Enu $ fst <$> cs
|
2019-06-27 00:13:25 +03:00
|
|
|
else
|
|
|
|
case cs of
|
2019-06-27 01:51:30 +03:00
|
|
|
[] -> pure $ Enu []
|
|
|
|
[c] -> pure $ Tup c
|
|
|
|
cs -> pure $ Sum cs
|
2019-06-27 00:13:25 +03:00
|
|
|
|
|
|
|
where
|
|
|
|
badSynonym = "deriveFunctor: tyCon may not be a type synonym."
|
|
|
|
|
2019-06-27 01:51:30 +03:00
|
|
|
unpackCon :: Con -> ConInfo
|
|
|
|
unpackCon = \case
|
|
|
|
NormalC nm bangTypes -> (nm, snd <$> bangTypes)
|
|
|
|
RecC nm varBangTypes -> (nm, varBangTypes <&> (\(_, _, t) -> t))
|
|
|
|
InfixC bangType1 nm bangType2 -> error "Infix Cnstrs are not supported"
|
|
|
|
ForallC tyVarBndrs ctx con -> error "Polymorphic tys are not supported"
|
|
|
|
GadtC nm bangTypes ty -> error "GADTs are not supported"
|
|
|
|
RecGadtC nm varBangTypes ty -> error "GADTs are not supported"
|
2019-06-27 00:13:25 +03:00
|
|
|
|
2019-06-27 01:51:30 +03:00
|
|
|
--------------------------------------------------------------------------------
|
2019-06-27 00:13:25 +03:00
|
|
|
|
|
|
|
deriveNoun :: Name -> Q [Dec]
|
2019-06-27 01:51:30 +03:00
|
|
|
deriveNoun n = (<>) <$> deriveToNoun n <*> deriveFromNoun n
|
|
|
|
|
|
|
|
--------------------------------------------------------------------------------
|
2019-06-27 00:13:25 +03:00
|
|
|
|
|
|
|
deriveToNoun :: Name -> Q [Dec]
|
|
|
|
deriveToNoun tyName = do
|
2019-06-27 01:51:30 +03:00
|
|
|
body <- typeShape tyName <&> \case Tup con -> tupToNoun con
|
|
|
|
Enu cons -> enumToAtom cons
|
|
|
|
Sum cons -> sumToNoun cons
|
2019-06-27 00:13:25 +03:00
|
|
|
|
|
|
|
[d|
|
2019-06-27 01:51:30 +03:00
|
|
|
instance ToNoun $(conT tyName) where
|
|
|
|
toNoun = $(pure body)
|
2019-06-27 00:13:25 +03:00
|
|
|
|]
|
|
|
|
|
2019-06-27 01:51:30 +03:00
|
|
|
--------------------------------------------------------------------------------
|
2019-06-27 00:13:25 +03:00
|
|
|
|
|
|
|
deriveFromNoun :: Name -> Q [Dec]
|
|
|
|
deriveFromNoun tyName =
|
|
|
|
[d|
|
|
|
|
instance FromNoun $t where
|
|
|
|
parseNoun = $body
|
|
|
|
|]
|
|
|
|
where
|
|
|
|
t = conT tyName
|
|
|
|
|
|
|
|
body = [| \_ -> fail "unimplemented" |]
|
|
|
|
|
2019-06-27 01:51:30 +03:00
|
|
|
--------------------------------------------------------------------------------
|
2019-06-27 00:13:25 +03:00
|
|
|
|
2019-06-27 01:51:30 +03:00
|
|
|
tagNoun :: Name -> Exp
|
|
|
|
tagNoun = AppE (VarE 'toNoun)
|
|
|
|
. AppE (ConE 'Cord)
|
|
|
|
. LitE
|
|
|
|
. StringL
|
|
|
|
. hsToHoon
|
|
|
|
. nameStr
|
|
|
|
where
|
|
|
|
nameStr :: Name -> String
|
|
|
|
nameStr (Name (OccName n) _) = n
|
2019-06-27 00:13:25 +03:00
|
|
|
|
2019-06-27 01:51:30 +03:00
|
|
|
tagTup :: Name -> [Name] -> Exp
|
|
|
|
tagTup c args = AppE (VarE 'toNoun) $ TupE (tagNoun c : fmap VarE args)
|
2019-06-27 00:13:25 +03:00
|
|
|
|
2019-06-27 01:51:30 +03:00
|
|
|
tup :: [Name] -> Exp
|
|
|
|
tup = AppE (VarE 'toNoun) . TupE . fmap VarE
|
2019-06-27 00:13:25 +03:00
|
|
|
|
2019-06-27 01:51:30 +03:00
|
|
|
--------------------------------------------------------------------------------
|
2019-06-27 00:13:25 +03:00
|
|
|
|
2019-06-27 01:51:30 +03:00
|
|
|
enumToAtom :: [Name] -> Exp
|
|
|
|
enumToAtom cons =
|
|
|
|
LamCaseE $ cons <&> \nm ->
|
|
|
|
Match (ConP nm []) (NormalB $ tagNoun nm) []
|
2019-06-27 00:13:25 +03:00
|
|
|
|
2019-06-27 01:51:30 +03:00
|
|
|
tupToNoun :: ConInfo -> Exp
|
|
|
|
tupToNoun cons = LamCaseE [mkMatch cons]
|
|
|
|
where
|
|
|
|
mkMatch :: ConInfo -> Match
|
|
|
|
mkMatch (nm, tys) = Match (ConP nm params) (NormalB body) []
|
|
|
|
where vars = (zip tys ['a'..]) <&> (mkName . singleton . snd)
|
|
|
|
params = VarP <$> vars
|
|
|
|
body = tup vars
|
|
|
|
|
|
|
|
sumToNoun :: [ConInfo] -> Exp
|
|
|
|
sumToNoun cons = LamCaseE (cons <&> mkMatch)
|
|
|
|
where
|
|
|
|
mkMatch :: ConInfo -> Match
|
|
|
|
mkMatch (nm, tys) = Match (ConP nm params) (NormalB body) []
|
|
|
|
where vars = (zip tys ['a'..]) <&> (mkName . singleton . snd)
|
|
|
|
params = VarP <$> vars
|
|
|
|
body = tagTup nm vars
|
2019-06-27 00:13:25 +03:00
|
|
|
|
2019-06-27 01:51:30 +03:00
|
|
|
--------------------------------------------------------------------------------
|
2019-06-27 00:13:25 +03:00
|
|
|
|
2019-06-27 01:51:30 +03:00
|
|
|
hsToHoon :: String -> String
|
2019-06-27 00:13:25 +03:00
|
|
|
hsToHoon = go []
|
|
|
|
where
|
2019-06-27 01:51:30 +03:00
|
|
|
go acc [] = intercalate "-" $ reverse acc
|
2019-06-27 00:13:25 +03:00
|
|
|
go acc (c:cs) = go (elem:acc) remain
|
|
|
|
where
|
|
|
|
head = C.toLower c
|
|
|
|
(tail, remain) = break C.isUpper cs
|
|
|
|
elem = head:tail
|