1{-# LANGUAGE LambdaCase #-}
2{-# LANGUAGE OverloadedStrings #-}
3{-# LANGUAGE TupleSections #-}
4{-# LANGUAGE ViewPatterns #-}
7-- Copyright : (c) 2015-2016 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,
18import Data.Foldable (foldl', foldr')
19import qualified Data.HashMap.Strict as Map
20import Data.List (delete, nub)
22import Data.Semigroup ((<>))
23import Data.Text (Text)
24import qualified Data.Text as Text
25import Data.Text.Manipulate
28import Language.Haskell.Exts.Build (app, appFun, infixApp, lamE,
29 listE, name, noBinds, paren,
30 patBind, pvar, sfun, strE, sym,
32import Language.Haskell.Exts.SrcLoc
33import Language.Haskell.Exts.Syntax hiding (Alt, Int, Lit)
35serviceSig :: Name -> Decl
36serviceSig n = TypeSig noLoc [n] (TyCon "ServiceConfig")
38serviceDecl :: Service a -> Name -> Decl
39serviceDecl s n = sfun noLoc n [] (UnGuardedRhs rhs) noBinds
41 rhs = appFun (var "defaultService")
42 [ app (var "ServiceId") (str (s ^. dId))
43 , str . stripSuffix "/" $ stripPrefix "https://" (s ^. dRootUrl)
46scopeSig :: Name -> Text -> Decl
47scopeSig n v = TypeSig noLoc [n] $
48 TyApp (TyCon "Proxy") $
49 TyPromoted $ PromotedList True
50 [ TyPromoted $ PromotedString (Text.unpack v)
53scopeDecl :: Name -> Decl
54scopeDecl n = sfun noLoc n [] (UnGuardedRhs (var "Proxy")) noBinds
56apiAlias :: Name -> [Name] -> Decl
57apiAlias n ls = TypeDecl noLoc n [] alias
59 alias = case map (TyCon . UnQual) ls of
61 x:xs -> foldl' (\l r -> TyInfix l (UnQual (sym ":<|>")) r) x xs
63verbAlias :: HasDescription a s => a -> Name -> Method Solved -> Decl
64verbAlias d n m = TypeDecl noLoc n [] $ servantOr meta (catMaybes [down, up])
66 meta = path $ metadataAlias m
67 down = path <$> downloadAlias m
68 up = uploadAlias root m
70 path = flip servantSub root
72 root = subPaths (d ^. dServicePath) ++ requestPath m (_mPath m)
74metadataAlias :: Method Solved -> Type
75metadataAlias m = servantSub (jsonVerb m) (params ++ media)
77 params = requestQuery m (requestQueryParams m)
78 media = case _mRequest m of
81 [ TyApp (TyApp (TyCon "ReqBody") jsonMedia)
85downloadAlias :: Method Solved -> Maybe Type
87 | _mSupportsMediaDownload m = Just download
90 download = servantSub (downloadVerb m) (params ++ [downloadParam])
92 params = requestQuery m . Map.delete "alt" $ requestQueryParams m
94uploadAlias :: [Type] -> Method Solved -> Maybe Type
96 | _mSupportsMediaUpload m = Just upload
99 upload = servantSub (jsonVerb m) (path ++ params ++ media)
101 params = requestQuery m (requestQueryParams m)
103 path = case _mMediaUpload m of
105 Just u -> requestPath m (_muSimplePath u)
107 media = case _mRequest m of
110 , TyApp (TyApp (TyCon "MultipartRelated") jsonMedia)
119requestPath :: Method Solved -> Text -> [Type]
120requestPath m = map go . extractPath
123 go (Right (l, c)) = case Map.lookup l (_mParameters m) of
124 Nothing -> error $ "Unable to find path parameter " ++ show l
126 Nothing | x ^. iRepeated ->
127 TyApp (TyApp (TyCon "Captures")
129 (terminalType (_type (_pParam x)))
132 TyApp (TyApp (TyCon "Capture")
134 (terminalType (_type (_pParam x)))
137 TyApp (TyApp (TyApp (TyCon "CaptureMode")
140 (terminalType (_type (_pParam x)))
142requestQuery :: Method Solved -> Map Local (Param Solved) -> [Type]
143requestQuery m xs = mapMaybe go $
144 orderParams fst (Map.toList xs) (_mParameterOrder m)
146 go (k, x) = case _pLocation x of
147 Query | x ^. iRepeated
148 -> Just $ TyApp (TyApp (TyCon "QueryParams") n) t
149 Query -> Just $ TyApp (TyApp (TyCon "QueryParam") n) t
152 t = terminalType (_type (_pParam x))
155requestQueryParams :: Method a -> Map Local (Param a)
156requestQueryParams = Map.filter ((/= Path) . _pLocation) . _mParameters
158servantOr, servantSub :: Type -> [Type] -> Type
159servantOr = foldl' (\l r -> TyInfix l (UnQual (sym ":<|>")) r)
160servantSub = foldr' (\l r -> TyInfix l (UnQual (sym ":>")) r)
162jsonVerb :: Method a -> Type
164 TyApp (TyApp (httpMethod m) jsonMedia)
165 $ maybe (TyCon "()") (tycon . ref) (_mResponse m)
167downloadVerb :: Method a -> Type
169 TyApp (TyApp (httpMethod m) streamMedia)
172httpMethod :: Method a -> Type
173httpMethod = TyCon . unqual . Text.unpack . Text.toTitle . _mHttpMethod
175subPaths :: Text -> [Type]
176subPaths = map sing . filter (not . Text.null) . Text.split (== '/')
178jsonMedia, streamMedia :: Type
179jsonMedia = tylist ["JSON"]
180streamMedia = tylist ["OctetStream"]
184 TyApp (TyApp (TyCon "QueryParam") (sing "alt"))
189 TyApp (TyApp (TyCon "QueryParam") (sing "uploadType"))
192multipartParam :: Type
194 TyApp (TyApp (TyCon "QueryParam") (sing "uploadType"))
197metadataPat, downloadPat, uploadPat :: Method a -> Pat
198metadataPat = pattern 1
199downloadPat = pattern 2
202pattern :: Integer -> Method a -> Pat
203pattern n m = case (n, down, up) of
204 (1, True, True) -> infixOr go (infixOr wild wild)
205 (2, True, True) -> infixOr wild (infixOr go wild)
206 (_, True, True) -> infixOr wild (infixOr wild go)
208 (1, True, _) -> infixOr go wild
209 (_, True, _) -> infixOr wild go
211 (1, _, True) -> infixOr go wild
212 (_, _, True) -> infixOr wild go
216 down = _mSupportsMediaDownload m
217 up = _mSupportsMediaUpload m
222 infixOr l r = PInfixApp l (UnQual (sym ":<|>")) r
224downloadDecl :: Global
231downloadDecl n pre api url fs m =
232 googleRequestDecl ty [rs, ss] [alt] pre api url m pat prec
234 ty = TyApp (TyCon "MediaDownload") (tycon n)
236 rs = InsType noLoc (TyApp (TyCon "Rs") ty) (TyCon "Stream")
238 ss = InsType noLoc (TyApp (TyCon "Scopes") ty) $
239 TyApp (TyCon "Scopes") (tycon n)
241 alt = app (var "Just") (var "AltMedia")
244 prec = PApp (UnQual "MediaDownload")
245 [ PRec (UnQual (dname' n)) [PFieldWildcard | not (null fs)]
255uploadDecl n pre api url fs m =
256 googleRequestDecl ty [rs, ss] extras pre api url m pat prec
258 ty = TyApp (TyCon "MediaUpload") (tycon n)
260 rs = InsType noLoc (TyApp (TyCon "Rs") ty) $
261 maybe unit_tycon (tycon . ref) (_mResponse m)
263 ss = InsType noLoc (TyApp (TyCon "Scopes") ty) $
264 TyApp (TyCon "Scopes") (tycon n)
266 extras = maybeToList alt ++ [upl] ++ payload ++ [var media]
268 upl = app (var "Just") $
269 if isJust (_mRequest m)
273 alt = app (var "Just") . var . name . Text.unpack . alternate <$>
274 (Map.lookup "alt" (_mParameters m) >>= view iDefault)
277 | isJust (_mRequest m) = [var (fname pre "payload")]
282 prec = PApp (UnQual "MediaUpload")
283 [ PRec (UnQual (dname' n)) [PFieldWildcard | not (null fs)]
296requestDecl n pre api url fs m =
297 googleRequestDecl (tycon n) [rs, ss] extras pre api url m pat prec
299 rs = InsType noLoc (TyApp (TyCon "Rs") (tycon n)) $
300 maybe unit_tycon (tycon . ref) (_mResponse m)
302 ss = InsType noLoc (TyApp (TyCon "Scopes") (tycon n)) $
305 map (TyPromoted . PromotedString . Text.unpack) (_mScopes m)
307 extras = catMaybes [alt, payload]
309 alt = app (var "Just") . var . name . Text.unpack . alternate <$>
310 (Map.lookup "alt" (_mParameters m) >>= view iDefault)
313 | isJust (_mRequest m) = Just $ var (fname pre "payload")
314 | otherwise = Nothing
318 prec = PRec (UnQual (dname' n)) [PFieldWildcard | not (null fs)]
320googleRequestDecl :: Type
330googleRequestDecl n assoc extras pre api url m pat prec =
331 InstDecl noLoc Nothing [] [] (unqual "GoogleRequest") [n] (assoc ++ [request])
333 request = InsDecl (FunBind [match])
335 match = Match noLoc (name "requestClient") [prec] Nothing rhs (Just decls)
338 [ patBind noLoc pat $
339 appFun (var "buildClient") $
340 [ ExpTypeSig noLoc (var "Proxy") $
341 TyApp (TyCon "Proxy") (TyCon (UnQual api))
346 rhs = UnGuardedRhs . appFun (var "go") $ map go fs ++ extras ++ [var url]
348 go l = case Map.lookup l ps of
349 Just p | _pLocation p == Query
351 , p ^. iRepeated -> v l
353 Just p | _pLocation p == Query
355 , p ^. iRepeated -> infixApp (v l) "^." (var "_Default")
357 Just p | _pLocation p == Query
358 , not (p ^. iRepeated)
359 , parameter p || defaulted p -> app (var "Just") (v l)
367 . orderParams id (Map.keys (_mParameters m))
369 $ map fst (rights (extractPath (_mPath m))) ++ _mParameterOrder m
371jsonDecls :: Global -> Prefix -> Map Local Solved -> [Decl]
372jsonDecls g p (Map.toList -> rs) = [from', to']
374 from' = InstDecl noLoc Nothing [] [] (unqual "FromJSON") [tycon g]
376 app (app (var "withObject") (dstr g)) $
377 lamE noLoc [pvar "o"] $
378 ctorE g (map decode rs)
382 | _additional s = app (var "parseJSONObject") (var "o")
383 | Just x <- def s = defJS l x
384 | required s = reqJS l
385 | monoid s = defJS l (var "mempty")
386 | otherwise = optJS l
389 [(k, v)] | _additional v ->
390 InstDecl noLoc Nothing [] [] (unqual "ToJSON") [tycon g]
392 infixApp (var "toJSON") "." (var (fname p k))
396 InstDecl noLoc Nothing [] [] (unqual "ToJSON") [tycon g]
397 [ wildcardD "toJSON" g omit emptyObj (map encode rs)
400 omit = app (var "object")
401 . app (var "catMaybes")
404 emptyObj = var "emptyObject"
407 | TMaybe {} <- _type s = infixApp (paren (app n o)) "<$>" a
408 | otherwise = app (var "Just") (infixApp n ".=" a)
420wildcardD f n enc x = \case
422 xs -> InsDecl (FunBind [match prec xs])
425 Match noLoc (name f) [p] Nothing (UnGuardedRhs (enc es)) noBinds
427 prec = PRec (UnQual (dname' n)) [PFieldWildcard]
429defJS :: Local -> Exp -> Exp
430defJS n x = infixApp (infixApp (var "o") ".:?" (fstr n)) ".!=" x
433reqJS = infixApp (var "o") ".:" . fstr
436optJS = infixApp (var "o") ".:?" . fstr
438funD :: String -> Exp -> InstDecl
439funD f = InsDecl . patBind noLoc (pvar (name f))
441constD :: String -> Exp -> InstDecl
442constD f x = InsDecl $
443 sfun noLoc (name f) [] (UnGuardedRhs (app (var "const") x)) noBinds
445ctorE :: Global -> [Exp] -> Exp
446ctorE n = seqE (var (dname' n)) . map paren
448seqE :: Exp -> [Exp] -> Exp
449seqE l [] = app (var "pure") l
450seqE l (r:rs) = infixApp l "<$>" (infixE r "<*>" rs)
452objDecl :: Global -> Prefix -> [Derive] -> Map Local Solved -> Decl
454 DataDecl noLoc arity [] (dname n) [] [conDecl (dname' n) p rs] (der ds)
456 arity | Map.size rs == 1 = NewType
457 | otherwise = DataType
459 der = map ((,[]) . unqual . drop 1 . show)
461conDecl :: Name -> Prefix -> Map Local Solved -> QualConDecl
462conDecl n p rs = QualConDecl noLoc [] [] body
464 body = case Map.toList rs of
466 [x] -> RecDecl n [field internalType x]
467 xs -> RecDecl n (map (field (strict . internalType)) xs)
469 field f (l, v) = ([fname p l], f (_type v))
471ctorSig :: Global -> Map Local Solved -> Decl
472ctorSig n rs = TypeSig noLoc [cname n] ts
474 ts = foldr' TyFun (TyCon (UnQual (dname n))) ps
475 ps = parameters (Map.elems rs)
477ctorDecl :: Global -> Prefix -> Map Local Solved -> Decl
478ctorDecl n p rs = sfun noLoc c ps (UnGuardedRhs rhs) noBinds
483 rhs | Map.null rs = var d
484 | otherwise = RecConstr (UnQual d) $
485 map (uncurry (fieldUpdate p)) (Map.toList rs)
487 ps = map (pname p) . Map.keys $ Map.filter parameter rs
489fieldUpdate :: Prefix -> Local -> Solved -> FieldUpdate
490fieldUpdate p l s = FieldUpdate (UnQual (fname p l)) rhs
492 rhs | Just x <- def s, s ^. iRepeated = listE [x]
493 | Just x <- def s = x
494 | Just x <- iso (_type s) = infixApp x "#" v
496 | otherwise = var (name "Nothing")
500lensSig :: Global -> Prefix -> Local -> Solved -> Decl
501lensSig n p l s = TypeSig noLoc [lname p l] $
502 TyApp (TyApp (TyCon "Lens'") (tycon n))
503 (externalType (_type s))
505lensDecl :: Prefix -> Local -> Solved -> Decl
506lensDecl p l s = sfun noLoc (lname p l) [] (UnGuardedRhs rhs) noBinds
512 app (app (var "lens") (var f))
513 (paren (lamE noLoc [pvar "s", pvar "a"]
514 (RecUpdate (var "s") [FieldUpdate (UnQual f) (var "a")])))
516parameters :: [Solved] -> [Type]
517parameters = map (externalType . _type) . filter parameter
519def :: Solved -> Maybe Exp
521 | Just x <- s ^. iDefault = Just (go x (_prefix s) (_schema s))
522 | otherwise = Nothing
525 SEnm {} -> var (bname p x)
526 SLit _ Bool -> lit (upperHead x)
529 e -> error $ "Unsupported default value: " ++ show e
531 lit = var . name . Text.unpack
533terminalType :: TType -> Type
534terminalType = internalType . go
540externalType :: TType -> Type
543 TMaybe t@TList {} -> externalType t
544 TMaybe t -> TyApp (TyCon "Maybe") (externalType t)
545 TList t -> TyList (externalType t)
546 TLit l -> externalLit l
548 TyApp (TyApp (TyCon "HashMap") (externalType k)) (externalType (require v))
550internalType :: TType -> Type
553 TMaybe t -> TyApp (TyCon "Maybe") (internalType t)
554 TList t -> TyList (internalType t)
555 TLit l -> internalLit l
557 TyApp (TyApp (TyCon "HashMap") (internalType k)) (internalType (require v))
559externalLit :: Lit -> Type
563 Time -> TyCon "TimeOfDay"
565 DateTime -> TyCon "UTCTime"
566 Nat -> TyCon "Natural"
567 Float -> TyCon "Double"
568 Double -> TyCon "Double"
569 Byte -> TyCon "ByteString"
570 UInt32 -> TyCon "Word32"
571 UInt64 -> TyCon "Word64"
572 Int32 -> TyCon "Int32"
573 Int64 -> TyCon "Int64"
574 Alt t -> TyCon (unqual (Text.unpack t))
575 RqBody -> TyCon "RequestBody"
576 RsBody -> TyCon "Stream"
577 JSONValue -> TyCon "JSONValue"
578 FieldMask -> TyCon "FieldMask"
579 Duration -> TyCon "Scientific"
581internalLit :: Lit -> Type
585 Time -> TyCon "Time'"
586 Date -> TyCon "Date'"
587 DateTime -> TyCon "DateTime'"
588 Nat -> TyApp (TyCon "Textual") (TyCon "Nat")
589 Float -> TyApp (TyCon "Textual") (TyCon "Double")
590 Double -> TyApp (TyCon "Textual") (TyCon "Double")
591 Byte -> TyCon "Base64"
592 UInt32 -> TyApp (TyCon "Textual") (TyCon "Word32")
593 UInt64 -> TyApp (TyCon "Textual") (TyCon "Word64")
594 Int32 -> TyApp (TyCon "Textual") (TyCon "Int32")
595 Int64 -> TyApp (TyCon "Textual") (TyCon "Int64")
596 Alt t -> TyCon (unqual (Text.unpack t))
597 RqBody -> TyCon "RequestBody"
598 RsBody -> TyCon "Stream"
599 JSONValue -> TyCon "JSONValue"
600 FieldMask -> TyCon "FieldMask"
601 Duration -> TyCon "Duration"
603mapping :: TType -> Exp -> Exp
604mapping t e = infixE e "." (go t)
607 TMaybe x@(TList {}) -> var "_Default" : go x
608 TMaybe x -> nest (go x)
609 x -> maybeToList (iso x)
612 nest (x:xs) = [app (var "mapping") (infixE x "." xs)]
614iso :: TType -> Maybe Exp
616 TList {} -> Just (var "_Coerce")
617 TMap {} -> Just (var "_Coerce")
618 TLit Nat -> Just (var "_Coerce")
619 TLit Time -> Just (var "_Time")
620 TLit Date -> Just (var "_Date")
621 TLit DateTime -> Just (var "_DateTime")
622 TLit Duration -> Just (var "_Duration")
623 TLit Float -> Just (var "_Coerce")
624 TLit Double -> Just (var "_Coerce")
625 TLit Byte -> Just (var "_Base64")
626 TLit UInt32 -> Just (var "_Coerce")
627 TLit UInt64 -> Just (var "_Coerce")
628 TLit Int32 -> Just (var "_Coerce")
629 TLit Int64 -> Just (var "_Coerce")
632require :: TType -> TType
633require (TMaybe t) = t
636strict :: Type -> Type
637strict = TyBang BangedTy . \case
638 t@TyApp{} -> TyParen t
642sing = TyCon . unqual . Text.unpack . flip mappend "\"" . mappend "\""
644tylist :: [Text] -> Type
645tylist xs = TyCon . UnQual . name . Text.unpack $
646 "'[" <> Text.intercalate ", " xs <> "]"
648tycon :: Global -> Type
649tycon = TyCon . UnQual . dname
651unqual :: String -> QName
652unqual = UnQual . name
654infixE :: Exp -> QOp -> [Exp] -> Exp
656infixE l o (r:rs) = infixE (infixApp l o r) o rs
659str = 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