1{-# LANGUAGE LambdaCase #-}
2{-# LANGUAGE OverloadedStrings #-}
3{-# LANGUAGE TupleSections #-}
4{-# LANGUAGE ViewPatterns #-}
7-- Copyright : (c) 2015 Brendan Hay
8-- License : Mozilla Public License, v. 2.0.
9-- Maintainer : Brendan Hay <brendan.g.hay@gmail.com>
10-- Stability : provisional
11-- Portability : non-portable (GHC extensions)
13module Gen.Syntax where
15import Control.Lens hiding (iso, mapping, op, pre,
17import Data.Foldable (find, foldl', foldr')
19import qualified Data.HashMap.Strict as Map
22import Data.Semigroup ((<>))
24import Data.Text (Text)
25import qualified Data.Text as Text
26import Data.Text.Manipulate
30import Language.Haskell.Exts.Build
31import Language.Haskell.Exts.SrcLoc
32import Language.Haskell.Exts.Syntax hiding (Int, Lit)
35urlSig n = TypeSig noLoc [n] (TyCon "BaseUrl")
37urlDecl :: Name -> String -> Text -> Integer -> Decl
38urlDecl n s u p = sfun noLoc n [] (UnGuardedRhs rhs) noBinds
40 rhs = appFun (var "BaseUrl") [var (name s), str u, intE p]
42 -- type API = :<|> ...
43apiAlias :: Name -> [Name] -> Decl
44apiAlias n ls = TypeDecl noLoc n [] alias
46 alias = case map (TyCon . UnQual) ls of
48 x:xs -> foldl' (\l r -> TyInfix l (UnQual (sym ":<|>")) r) x xs
50-- type Method = :> ...
51verbAlias :: Name -> Method Solved -> Decl
52verbAlias n m = TypeDecl noLoc n [] alias
54 alias = foldr' (\l r -> TyInfix l (UnQual (sym ":>")) r) verb (path ++ qry)
56 -- FIXME: rather than using 'terminal' to get the terminal type,
57 -- write a custom 'Link' for servant to specify required params.
61 . filter (not . Text.null)
62 $ Text.split (== '/') (_mPath m)
64 -- FIXME: should error if '{' '}' is found and cannot find the ref.
66 | Just (c, t) <- Text.uncons x
68 , Just k <- Map.lookup (toKey (Text.init t)) params
69 , _pLocation k == Path
70 = TyApp (TyApp (TyCon "Capture")
72 (terminalType (_type (_pParam k)))
75 -- FIXME: order by _mParameterOrder
77 qry = mapMaybe f (Map.toList params)
80 let t = terminalType (_type (_pParam x))
82 in case _pLocation x of
83 Query | x ^. iRepeated
84 -> Just $ TyApp (TyApp (TyCon "QueryParams") n) t
85 Query -> Just $ TyApp (TyApp (TyCon "QueryParam") n) t
88 params = _mParameters m
91 verb = TyApp (TyApp meth typ) res
93 meth = TyCon . unqual . Text.unpack $ Text.toTitle (_mHttpMethod m)
95 res = maybe (TyCon "()") (tycon . ref) (_mResponse m)
102 -> Method Solved -> Decl
103requestDecl n p api url fs m =
104 -- FIXME: associated Rs type
105 InstDecl noLoc Nothing [] [] (unqual "GoogleRequest") [tycon n]
111 response = InsType noLoc (TyApp (TyCon "Rs") (tycon n)) $
112 maybe unit_tycon (tycon . ref) (_mResponse m)
114 request = funD "request" $
115 appFun (var "requestWithRoute") [var "defReq", var url]
117 requestWithRoute = InsDecl (FunBind [match])
119 match = Match noLoc (name "requestWithRoute") pats Nothing rhs decls
122 [ patBind noLoc (pvar "go") $
123 appFun (var "clientWithRoute") $
124 [ ExpTypeSig noLoc (var "Proxy") $
125 TyApp (TyCon "Proxy") (TyCon (UnQual api))
131 rhs = UnGuardedRhs $ appFun (var "go") (map go fs)
133 go l | Just p <- Map.lookup l ps
134 , _pLocation p == Query
135 , parameter p || defaulted p = app (var "Just") (v l)
141 pats = [pvar "r", pvar "u", prec]
142 prec = PRec (UnQual (dname n)) [PFieldWildcard]
144jsonDecls :: Global -> Prefix -> Map Local Solved -> [Decl]
145jsonDecls g p (Map.toList -> rs) = [from, to]
147 from = InstDecl noLoc Nothing [] [] (unqual "FromJSON") [tycon g]
149 app (app (var "withObject") (dstr g)) $
150 lamE noLoc [pvar "o"] $
151 ctorE g (map decode rs)
155 | Just x <- def s = defJS l x
156 | required s = reqJS l
157 | monoid s = defJS l (var "mempty")
158 | otherwise = optJS l
160 to = InstDecl noLoc Nothing [] [] (unqual "ToJSON") [tycon g]
161 [ wildcardD "toJSON" g omit none (map encode rs)
164 omit = app (var "object")
165 . app (var "catMaybes")
168 none = paren $ app (var "Object") (var "mempty")
171 | TMaybe {} <- _type s = infixApp (paren (app n o)) "<$>" a
172 | otherwise = app (var "Just") (infixApp n ".=" a)
184wildcardD f n enc x = \case
186 xs -> InsDecl (FunBind [match prec xs])
189 Match noLoc (name f) [p] Nothing (UnGuardedRhs (enc es)) noBinds
191 prec = PRec (UnQual (dname n)) [PFieldWildcard]
193defJS :: Local -> Exp -> Exp
194defJS n x = infixApp (infixApp (var "o") ".:?" (fstr n)) ".!=" x
197reqJS = infixApp (var "o") ".:" . fstr
200optJS = infixApp (var "o") ".:?" . fstr
202funD :: String -> Exp -> InstDecl
203funD f = InsDecl . patBind noLoc (pvar (name f))
205constD :: String -> Exp -> InstDecl
206constD f x = InsDecl $
207 sfun noLoc (name f) [] (UnGuardedRhs (app (var "const") x)) noBinds
209ctorE :: Global -> [Exp] -> Exp
210ctorE n = seqE (var (dname n)) . map paren
212seqE :: Exp -> [Exp] -> Exp
213seqE l [] = app (var "pure") l
214seqE l (r:rs) = infixApp l "<$>" (infixE r "<*>" rs)
216objDecl :: Global -> Prefix -> [Derive] -> Map Local Solved -> Decl
217objDecl n p ds rs = DataDecl noLoc arity [] d [] [conDecl d p rs] (der ds)
221 arity | Map.size rs == 1 = NewType
222 | otherwise = DataType
224 der = map ((,[]) . unqual . drop 1 . show)
226conDecl :: Name -> Prefix -> Map Local Solved -> QualConDecl
227conDecl n p rs = QualConDecl noLoc [] [] body
229 body = case Map.toList rs of
231 [x] -> RecDecl n [field internalType x]
232 xs -> RecDecl n (map (field (strict . internalType)) xs)
234 field f (l, v) = ([fname p l], f (_type v))
236ctorSig :: Global -> Map Local Solved -> Decl
237ctorSig n rs = TypeSig noLoc [cname n] ts
239 ts = foldr' TyFun (TyCon (UnQual (dname n))) ps
240 ps = parameters (Map.elems rs)
242ctorDecl :: Global -> Prefix -> Map Local Solved -> Decl
243ctorDecl n p rs = sfun noLoc c ps (UnGuardedRhs rhs) noBinds
248 rhs | Map.null rs = var d
249 | otherwise = RecConstr (UnQual d) $
250 map (uncurry (fieldUpdate p)) (Map.toList rs)
252 ps = map (pname p) . Map.keys $ Map.filter required rs
254fieldUpdate :: Prefix -> Local -> Solved -> FieldUpdate
255fieldUpdate p l s = FieldUpdate (UnQual (fname p l)) rhs
258 | Just x <- def s = x
259 | Just x <- iso (_type s) = infixApp x "#" v
260 | otherwise = var (name "Nothing")
264lensSig :: Global -> Prefix -> Local -> Solved -> Decl
265lensSig n p l s = TypeSig noLoc [lname p l] $
266 TyApp (TyApp (TyCon "Lens'") (tycon n))
267 (externalType (_type s))
269lensDecl :: Prefix -> Local -> Solved -> Decl
270lensDecl p l s = sfun noLoc (lname p l) [] (UnGuardedRhs rhs) noBinds
276 app (app (var "lens") (var f))
277 (paren (lamE noLoc [pvar "s", pvar "a"]
278 (RecUpdate (var "s") [FieldUpdate (UnQual f) (var "a")])))
280parameters :: [Solved] -> [Type]
281parameters = map (externalType . _type) . filter required
283def :: Solved -> Maybe Exp
285 | Just x <- s ^. iDefault = Just (go x (_prefix s) (_schema s))
286 | otherwise = Nothing
289 SEnm {} -> var (bname p x)
290 SLit _ Bool -> lit (upperHead x)
293 e -> error $ "Unsupported default value: " ++ show e
295 lit = var . name . Text.unpack
297terminalType :: TType -> Type
298terminalType = internalType . go
304externalType :: TType -> Type
307 TMaybe t@TList {} -> externalType t
308 TMaybe t -> TyApp (TyCon "Maybe") (externalType t)
309 TList t -> TyList (externalType t)
310 TLit l -> externalLit l
312internalType :: TType -> Type
315 TMaybe t -> TyApp (TyCon "Maybe") (internalType t)
316 TList t -> TyList (internalType t)
317 TLit l -> internalLit l
319externalLit :: Lit -> Type
323 Time -> TyCon "UTCTime"
324 Date -> TyCon "UTCTime"
325 Nat -> TyCon "Natural"
326 Float -> TyCon "Float"
327 Double -> TyCon "Double"
328 Byte -> TyCon "Word8"
329 UInt32 -> TyCon "Word32"
330 UInt64 -> TyCon "Word64"
331 Int32 -> TyCon "Int32"
332 Int64 -> TyCon "Int64"
334internalLit :: Lit -> Type
338 Time -> TyCon "UTCTime"
339 Date -> TyCon "UTCTime"
341 Float -> TyCon "Float"
342 Double -> TyCon "Double"
343 Byte -> TyCon "Word8"
344 UInt32 -> TyCon "Word32"
345 UInt64 -> TyCon "Word64"
346 Int32 -> TyCon "Int32"
347 Int64 -> TyCon "Int64"
349mapping :: TType -> Exp -> Exp
350mapping t e = infixE e "." (go t)
353 TMaybe x@(TList {}) -> var "_Default" : go x
354 TMaybe x -> nest (go x)
355 x -> maybeToList (iso x)
358 nest (x:xs) = [app (var "mapping") (infixE x "." xs)]
360iso :: TType -> Maybe Exp
362 TList {} -> Just (var "_Coerce")
363 TLit Nat -> Just (var "_Nat")
366strict :: Type -> Type
367strict = TyBang BangedTy . \case
368 t@TyApp{} -> TyParen t
372sing = TyCon . unqual . Text.unpack . flip mappend "\"" . mappend "\""
374tycon :: Global -> Type
375tycon = TyCon . UnQual . dname
377unqual :: String -> QName
378unqual = UnQual . name
380infixE :: Exp -> QOp -> [Exp] -> Exp
382infixE l o (r:rs) = infixE (infixApp l o r) o rs
385str = strE . Text.unpack
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list