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
|
|
|
-}
|
|
|
|
|
2019-07-12 04:16:40 +03:00
|
|
|
module Noun.TH (deriveNoun) where
|
2019-06-27 00:13:25 +03:00
|
|
|
|
2019-07-12 22:24:44 +03:00
|
|
|
import ClassyPrelude hiding (fromList)
|
2019-06-27 00:13:25 +03:00
|
|
|
import Language.Haskell.TH
|
|
|
|
import Language.Haskell.TH.Syntax
|
2019-07-12 22:24:44 +03:00
|
|
|
import Noun.Convert
|
2019-06-27 00:13:25 +03:00
|
|
|
|
2019-06-28 00:28:58 +03:00
|
|
|
import RIO (decodeUtf8Lenient)
|
|
|
|
|
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-07-21 02:47:35 +03:00
|
|
|
= Vod
|
|
|
|
| Tup ConInfo
|
|
|
|
| Enu [(String, Name)]
|
|
|
|
| Sum [(String, 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-07-21 02:47:35 +03:00
|
|
|
prefix = getPrefix (nameStr . fst <$> cs)
|
2019-06-27 00:13:25 +03:00
|
|
|
|
|
|
|
if allEmpty
|
2019-07-21 02:47:35 +03:00
|
|
|
then pure $ Enu $ tagName prefix . fst <$> cs
|
2019-06-27 00:13:25 +03:00
|
|
|
else
|
|
|
|
case cs of
|
2019-07-21 02:47:35 +03:00
|
|
|
[] -> pure $ Vod
|
2019-06-27 01:51:30 +03:00
|
|
|
[c] -> pure $ Tup c
|
2019-07-21 02:47:35 +03:00
|
|
|
cs -> pure $ Sum (tagConInfo prefix <$> cs)
|
2019-06-27 00:13:25 +03:00
|
|
|
|
|
|
|
where
|
|
|
|
badSynonym = "deriveFunctor: tyCon may not be a type synonym."
|
|
|
|
|
2019-07-21 02:47:35 +03:00
|
|
|
tagConInfo :: Int -> ConInfo -> (String, ConInfo)
|
|
|
|
tagConInfo pre ci@(nm, _) = (tagString pre nm, ci)
|
|
|
|
|
|
|
|
tagName :: Int -> Name -> (String, Name)
|
|
|
|
tagName pre n = (tagString pre n, n)
|
|
|
|
|
|
|
|
tyStr = nameStr tyName
|
|
|
|
tyAbbrv = filter C.isUpper tyStr
|
|
|
|
|
|
|
|
typePrefixed = (tyStr `isPrefixOf`)
|
|
|
|
abbrvPrefixed = (tyAbbrv `isPrefixOf`)
|
|
|
|
|
|
|
|
getPrefix :: [String] -> Int
|
|
|
|
getPrefix cs | all typePrefixed cs = length tyStr
|
|
|
|
getPrefix cs | all abbrvPrefixed cs = length tyAbbrv
|
|
|
|
getPrefix _ = 0
|
|
|
|
|
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-07-21 02:47:35 +03:00
|
|
|
body <- typeShape tyName <&> \case Vod -> vodToNoun
|
|
|
|
Tup con -> tupToNoun con
|
2019-06-27 01:51:30 +03:00
|
|
|
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]
|
2019-06-27 02:27:37 +03:00
|
|
|
deriveFromNoun tyName = do
|
2019-07-21 02:47:35 +03:00
|
|
|
body <- typeShape tyName <&> \case Vod -> vodFromNoun
|
|
|
|
Tup con -> tupFromNoun con
|
2019-06-27 02:27:37 +03:00
|
|
|
Enu cons -> enumFromAtom cons
|
|
|
|
Sum cons -> sumFromNoun cons
|
|
|
|
|
2019-07-21 02:47:35 +03:00
|
|
|
let errTag = LitE $ StringL $ nameStr tyName
|
2019-06-27 02:27:37 +03:00
|
|
|
|
2019-06-27 00:13:25 +03:00
|
|
|
[d|
|
2019-06-27 02:27:37 +03:00
|
|
|
instance FromNoun $(conT tyName) where
|
2019-07-21 02:47:35 +03:00
|
|
|
parseNoun = named $(pure errTag) . $(pure body)
|
2019-06-27 02:27:37 +03:00
|
|
|
|]
|
2019-06-27 00:13:25 +03:00
|
|
|
|
2019-07-21 02:47:35 +03:00
|
|
|
enumFromAtom :: [(String, Name)] -> Exp
|
|
|
|
enumFromAtom cons = LamE [VarP x] body
|
2019-06-27 02:27:37 +03:00
|
|
|
where
|
2019-06-27 03:58:55 +03:00
|
|
|
(x, c) = (mkName "x", mkName "c")
|
|
|
|
getCord = BindS (VarP c) $ AppE (VarE 'parseNoun) (VarE x)
|
2019-06-27 02:27:37 +03:00
|
|
|
examine = NoBindS $ CaseE (VarE c) (matches ++ [fallback])
|
2019-07-21 02:47:35 +03:00
|
|
|
matches = mkMatch <$> cons
|
2019-06-27 02:27:37 +03:00
|
|
|
fallback = Match WildP (NormalB $ AppE (VarE 'fail) matchFail) []
|
|
|
|
body = DoE [getCord, examine]
|
|
|
|
matchFail = LitE $ StringL ("Expected one of: " <> possible)
|
2019-07-21 02:47:35 +03:00
|
|
|
possible = intercalate " " (('%':) . fst <$> cons)
|
|
|
|
mkMatch = \(tag, nm) ->
|
|
|
|
Match (ConP 'Cord [LitP $ StringL tag])
|
|
|
|
(NormalB $ AppE (VarE 'pure) (ConE nm))
|
|
|
|
[]
|
2019-06-27 02:27:37 +03:00
|
|
|
|
2019-06-27 02:40:31 +03:00
|
|
|
applyE :: Exp -> [Exp] -> Exp
|
|
|
|
applyE e [] = e
|
|
|
|
applyE e (a:as) = applyE (AppE e a) as
|
|
|
|
|
2019-07-21 02:47:35 +03:00
|
|
|
vodFromNoun :: Exp
|
|
|
|
vodFromNoun = LamE [WildP] body
|
|
|
|
where
|
|
|
|
body = AppE (VarE 'fail)
|
|
|
|
$ LitE $ StringL "Can't FromNoun on uninhabited data type"
|
|
|
|
|
2019-06-27 02:27:37 +03:00
|
|
|
tupFromNoun :: ConInfo -> Exp
|
2019-06-27 02:40:31 +03:00
|
|
|
tupFromNoun (n, tys) = LamE [VarP x] body
|
|
|
|
where
|
|
|
|
x = mkName "x"
|
|
|
|
vars = mkName . singleton . fst <$> zip ['a'..] tys
|
|
|
|
body = DoE [getTup, convert]
|
|
|
|
convert = NoBindS $ AppE (VarE 'pure) $ applyE (ConE n) (VarE <$> vars)
|
|
|
|
getTup = BindS (TupP $ VarP <$> vars) $ AppE (VarE 'parseNoun) (VarE x)
|
2019-06-27 02:27:37 +03:00
|
|
|
|
2019-07-21 02:47:35 +03:00
|
|
|
unexpectedTag :: [String] -> Exp -> Exp
|
2019-06-27 03:58:55 +03:00
|
|
|
unexpectedTag expected got =
|
2019-06-28 00:28:58 +03:00
|
|
|
applyE (VarE 'mappend) [LitE (StringL prefix), got]
|
2019-06-27 03:58:55 +03:00
|
|
|
where
|
2019-07-21 02:47:35 +03:00
|
|
|
possible = intercalate " " (('%':) <$> expected)
|
2019-06-28 00:28:58 +03:00
|
|
|
prefix = "Expected one of: " <> possible <> " but got %"
|
2019-06-27 03:58:55 +03:00
|
|
|
|
2019-07-21 02:47:35 +03:00
|
|
|
sumFromNoun :: [(String, ConInfo)] -> Exp
|
2019-06-27 03:58:55 +03:00
|
|
|
sumFromNoun cons = LamE [VarP x] (DoE [getHead, getTag, examine])
|
|
|
|
where
|
|
|
|
(x, h, t, c) = (mkName "x", mkName "h", mkName "t", mkName "c")
|
|
|
|
|
|
|
|
getHead = BindS (TupP [VarP h, VarP t])
|
|
|
|
$ AppE (VarE 'parseNoun) (VarE x)
|
|
|
|
|
|
|
|
getTag = BindS (ConP 'Cord [VarP c])
|
|
|
|
$ AppE (VarE 'parseNoun) (VarE h)
|
|
|
|
|
|
|
|
examine = NoBindS
|
|
|
|
$ CaseE (VarE c) (matches ++ [fallback])
|
|
|
|
|
|
|
|
matches = mkMatch <$> cons
|
2019-07-21 02:47:35 +03:00
|
|
|
mkMatch = \(tag, (n, tys)) ->
|
|
|
|
let body = AppE (tupFromNoun (n, tys)) (VarE t)
|
|
|
|
in Match (LitP $ StringL tag) (NormalB body) []
|
2019-06-27 03:58:55 +03:00
|
|
|
|
|
|
|
fallback = Match WildP (NormalB $ AppE (VarE 'fail) matchFail) []
|
2019-06-28 00:28:58 +03:00
|
|
|
matchFail = unexpectedTag (fst <$> cons)
|
|
|
|
$ AppE (VarE 'unpack)
|
|
|
|
$ AppE (VarE 'decodeUtf8Lenient)
|
|
|
|
$ VarE 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-07-21 02:47:35 +03:00
|
|
|
tagString :: Int -> Name -> String
|
|
|
|
tagString prefix = hsToHoon . drop prefix . nameStr
|
2019-07-19 21:37:20 +03:00
|
|
|
|
|
|
|
nameStr :: Name -> String
|
|
|
|
nameStr (Name (OccName n) _) = n
|
2019-06-27 02:27:37 +03:00
|
|
|
|
2019-07-21 02:47:35 +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-07-21 02:47:35 +03:00
|
|
|
-}
|
|
|
|
|
|
|
|
tagNoun' :: String -> Exp
|
|
|
|
tagNoun' = AppE (VarE 'toNoun)
|
|
|
|
. AppE (ConE 'Cord)
|
|
|
|
. LitE
|
|
|
|
. StringL
|
2019-06-27 00:13:25 +03:00
|
|
|
|
2019-07-21 02:47:35 +03:00
|
|
|
tagTup :: String -> [Name] -> Exp
|
|
|
|
tagTup tag args = AppE (VarE 'toNoun) $ TupE (tagNoun' tag : 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-07-21 02:47:35 +03:00
|
|
|
vodToNoun :: Exp
|
|
|
|
vodToNoun = enumToAtom []
|
|
|
|
|
|
|
|
enumToAtom :: [(String, Name)] -> Exp
|
2019-06-27 01:51:30 +03:00
|
|
|
enumToAtom cons =
|
2019-07-21 02:47:35 +03:00
|
|
|
LamCaseE $ cons <&> \(tag, nm) ->
|
|
|
|
Match (ConP nm []) (NormalB $ tagNoun' tag) []
|
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
|
|
|
|
|
2019-07-21 02:47:35 +03:00
|
|
|
sumToNoun :: [(String, ConInfo)] -> Exp
|
2019-06-27 01:51:30 +03:00
|
|
|
sumToNoun cons = LamCaseE (cons <&> mkMatch)
|
|
|
|
where
|
2019-07-21 02:47:35 +03:00
|
|
|
mkMatch :: (String, ConInfo) -> Match
|
|
|
|
mkMatch (tag, (nm, tys)) =
|
|
|
|
Match (ConP nm params) (NormalB body) []
|
2019-06-27 01:51:30 +03:00
|
|
|
where vars = (zip tys ['a'..]) <&> (mkName . singleton . snd)
|
|
|
|
params = VarP <$> vars
|
2019-07-21 02:47:35 +03:00
|
|
|
body = tagTup tag 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
|