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,
18import Data.Foldable (find, foldl', foldr')
20import qualified Data.HashMap.Strict as Map
21import Data.List (delete, nub)
23import Data.Semigroup ((<>))
25import Data.Text (Text)
26import qualified Data.Text as Text
27import Data.Text.Manipulate
32import Language.Haskell.Exts.Build
33import Language.Haskell.Exts.SrcLoc
34import Language.Haskell.Exts.Syntax hiding (Alt, Int, Lit)
37urlSig n = TypeSig noLoc [n] (TyCon "ServiceConfig")
39urlDecl :: Service a -> Name -> Decl
40urlDecl s n = sfun noLoc n [] (UnGuardedRhs rhs) noBinds
42 rhs = appFun (var "defaultService")
43 [ app (var "ServiceId") (str (s ^. dId))
44 , str . stripSuffix "/" $ stripPrefix "https://" (s ^. dRootUrl)
47scopeSig :: Name -> Decl
48scopeSig n = TypeSig noLoc [n] (TyCon "OAuthScope")
50scopeDecl :: Name -> Text -> Decl
51scopeDecl n s = sfun noLoc n [] (UnGuardedRhs (str s)) noBinds
53 -- type API = :<|> ...
54apiAlias :: Name -> [Name] -> Decl
55apiAlias n ls = TypeDecl noLoc n [] alias
57 alias = case map (TyCon . UnQual) ls of
59 x:xs -> foldl' (\l r -> TyInfix l (UnQual (sym ":<|>")) r) x xs
61verbAlias :: HasDescription a s => a -> Name -> Method Solved -> Decl
62verbAlias d n m = TypeDecl noLoc n [] $ servantOr meta (catMaybes [down, up])
64 meta = path $ metadataAlias m
65 down = path <$> downloadAlias m
66 up = uploadAlias root m
68 path = flip servantSub root
70 root = subPaths (d ^. dServicePath) ++ requestPath m (_mPath m)
72metadataAlias :: Method Solved -> Type
73metadataAlias m = servantSub (jsonVerb m) (params ++ media)
75 params = requestQuery m (requestQueryParams m)
76 media = case _mRequest m of
79 [ TyApp (TyApp (TyCon "ReqBody") jsonMedia)
83downloadAlias :: Method Solved -> Maybe Type
85 | _mSupportsMediaDownload m = Just download
88 download = servantSub (downloadVerb m) (params ++ [altParam])
90 params = requestQuery m . Map.delete "alt" $ requestQueryParams m
92uploadAlias :: [Type] -> Method Solved -> Maybe Type
94 | _mSupportsMediaUpload m = Just upload
97 upload = servantSub (jsonVerb m) (path ++ params ++ [uploadParam, media])
99 params = requestQuery m (requestQueryParams m)
101 path = case _mMediaUpload m of
103 Just u -> requestPath m (_muSimplePath u)
105 media = case _mRequest m of
107 TyApp (TyApp (TyApp (TyCon "MultipartRelated") jsonMedia)
109 (TyCon "RequestBody")
112 TyApp (TyApp (TyCon "ReqBody") streamMedia)
113 (TyCon "RequestBody")
115requestPath :: Method Solved -> Text -> [Type]
116requestPath m = map go . extractPath
119 go (Right (l, c)) = case Map.lookup l (_mParameters m) of
120 Nothing -> error $ "Unable to find path parameter " ++ show l
122 Nothing | x ^. iRepeated ->
123 TyApp (TyApp (TyCon "Captures")
125 (terminalType (_type (_pParam x)))
128 TyApp (TyApp (TyCon "Capture")
130 (terminalType (_type (_pParam x)))
133 TyApp (TyApp (TyApp (TyCon "CaptureMode")
136 (terminalType (_type (_pParam x)))
138requestQuery :: Method Solved -> Map Local (Param Solved) -> [Type]
139requestQuery m xs = mapMaybe go $
140 orderParams fst (Map.toList xs) (_mParameterOrder m)
142 go (k, x) = case _pLocation x of
143 Query | x ^. iRepeated
144 -> Just $ TyApp (TyApp (TyCon "QueryParams") n) t
145 Query -> Just $ TyApp (TyApp (TyCon "QueryParam") n) t
148 t = terminalType (_type (_pParam x))
151requestQueryParams :: Method a -> Map Local (Param a)
152requestQueryParams = Map.filter ((/= Path) . _pLocation) . _mParameters
154servantOr, servantSub :: Type -> [Type] -> Type
155servantOr = foldl' (\l r -> TyInfix l (UnQual (sym ":<|>")) r)
156servantSub = foldr' (\l r -> TyInfix l (UnQual (sym ":>")) r)
158jsonVerb :: Method a -> Type
160 TyApp (TyApp (httpMethod m) jsonMedia)
161 $ maybe (TyCon "()") (tycon . ref) (_mResponse m)
163downloadVerb :: Method a -> Type
165 TyApp (TyApp (httpMethod m) streamMedia)
168httpMethod :: Method a -> Type
169httpMethod = TyCon . unqual . Text.unpack . Text.toTitle . _mHttpMethod
171subPaths :: Text -> [Type]
172subPaths = map sing . filter (not . Text.null) . Text.split (== '/')
174jsonMedia = tylist ["JSON"]
175streamMedia = tylist ["OctetStream"]
179 TyApp (TyApp (TyCon "QueryParam") (sing "alt"))
184 TyApp (TyApp (TyCon "QueryParam") (sing "uploadType"))
187metadataPat = pattern 1
188downloadPat = pattern 2
191pattern n m = case (n, down, up) of
192 (1, True, True) -> infixOr go (infixOr wild wild)
193 (2, True, True) -> infixOr wild (infixOr go wild)
194 (_, True, True) -> infixOr wild (infixOr wild go)
196 (1, True, _) -> infixOr go wild
197 (_, True, _) -> infixOr wild go
199 (1, _, True) -> infixOr go wild
200 (_, _, True) -> infixOr wild go
204 down = _mSupportsMediaDownload m
205 up = _mSupportsMediaUpload m
210 infixOr l r = PInfixApp l (UnQual (sym ":<|>")) r
212downloadDecl :: Global
219downloadDecl n p api url fs m =
220 googleRequestDecl n ty rs [alt] p api url fs m pat prec
222 ty = TyApp (TyCon "MediaDownload") (tycon n)
223 rs = InsType noLoc (TyApp (TyCon "Rs") ty) (TyCon "Stream")
225 alt = app (var "Just") (var "AltMedia")
228 prec = PApp (UnQual "MediaDownload")
229 [ PRec (UnQual (dname n)) [PFieldWildcard | not (null fs)]
239uploadDecl n p api url fs m =
240 googleRequestDecl n ty rs extras p api url fs m pat prec
242 ty = TyApp (TyCon "MediaUpload") (tycon n)
243 rs = InsType noLoc (TyApp (TyCon "Rs") ty) $
244 maybe unit_tycon (tycon . ref) (_mResponse m)
246 extras = maybeToList alt ++ [upl] ++ payload ++ [var media]
248 upl = app (var "Just") (var "AltMedia")
249 alt = app (var "Just") . var . name . Text.unpack . alternate <$>
250 (Map.lookup "alt" (_mParameters m) >>= view iDefault)
253 | isJust (_mRequest m) = [var (fname p "payload")]
258 prec = PApp (UnQual "MediaUpload")
259 [ PRec (UnQual (dname n)) [PFieldWildcard | not (null fs)]
272requestDecl n p api url fs m =
273 googleRequestDecl n (tycon n) rs extras p api url fs m pat prec
275 rs = InsType noLoc (TyApp (TyCon "Rs") (tycon n)) $
276 maybe unit_tycon (tycon . ref) (_mResponse m)
278 extras = catMaybes [alt, payload]
280 alt = app (var "Just") . var . name . Text.unpack . alternate <$>
281 (Map.lookup "alt" (_mParameters m) >>= view iDefault)
284 | isJust (_mRequest m) = Just $ var (fname p "payload")
285 | otherwise = Nothing
289 prec = PRec (UnQual (dname n)) [PFieldWildcard | not (null fs)]
291googleRequestDecl :: Global
303googleRequestDecl g n assoc extras p api url fields m pat prec =
304 InstDecl noLoc Nothing [] [] (unqual "GoogleRequest") [n]
309 request = InsDecl (FunBind [match])
311 match = Match noLoc (name "requestClient") [prec] Nothing rhs (Just decls)
314 [ patBind noLoc pat $
315 appFun (var "buildClient") $
316 [ ExpTypeSig noLoc (var "Proxy") $
317 TyApp (TyCon "Proxy") (TyCon (UnQual api))
322 rhs = UnGuardedRhs . appFun (var "go") $ map go fs ++ extras ++ [var url]
324 go l = case Map.lookup l ps of
325 Just p | _pLocation p == Query
327 , p ^. iRepeated -> v l
329 Just p | _pLocation p == Query
331 , p ^. iRepeated -> infixApp (v l) "^." (var "_Default")
333 Just p | _pLocation p == Query
334 , not (p ^. iRepeated)
335 , parameter p || defaulted p -> app (var "Just") (v l)
343 . orderParams id (Map.keys (_mParameters m))
345 $ map fst (rights (extractPath (_mPath m))) ++ _mParameterOrder m
347jsonDecls :: Global -> Prefix -> Map Local Solved -> [Decl]
348jsonDecls g p (Map.toList -> rs) = [from, to]
350 from = InstDecl noLoc Nothing [] [] (unqual "FromJSON") [tycon g]
352 app (app (var "withObject") (dstr g)) $
353 lamE noLoc [pvar "o"] $
354 ctorE g (map decode rs)
358 | _additional s = app (var "parseJSONObject") (var "o")
359 | Just x <- def s = defJS l x
360 | required s = reqJS l
361 | monoid s = defJS l (var "mempty")
362 | otherwise = optJS l
365 [(k, v)] | _additional v ->
366 InstDecl noLoc Nothing [] [] (unqual "ToJSON") [tycon g]
368 infixApp (var "toJSON") "." (var (fname p k))
372 InstDecl noLoc Nothing [] [] (unqual "ToJSON") [tycon g]
373 [ wildcardD "toJSON" g omit none (map encode rs)
376 omit = app (var "object")
377 . app (var "catMaybes")
380 none = var "emptyObject"
383 | TMaybe {} <- _type s = infixApp (paren (app n o)) "<$>" a
384 | otherwise = app (var "Just") (infixApp n ".=" a)
396wildcardD f n enc x = \case
398 xs -> InsDecl (FunBind [match prec xs])
401 Match noLoc (name f) [p] Nothing (UnGuardedRhs (enc es)) noBinds
403 prec = PRec (UnQual (dname n)) [PFieldWildcard]
405defJS :: Local -> Exp -> Exp
406defJS n x = infixApp (infixApp (var "o") ".:?" (fstr n)) ".!=" x
409reqJS = infixApp (var "o") ".:" . fstr
412optJS = infixApp (var "o") ".:?" . fstr
414funD :: String -> Exp -> InstDecl
415funD f = InsDecl . patBind noLoc (pvar (name f))
417constD :: String -> Exp -> InstDecl
418constD f x = InsDecl $
419 sfun noLoc (name f) [] (UnGuardedRhs (app (var "const") x)) noBinds
421ctorE :: Global -> [Exp] -> Exp
422ctorE n = seqE (var (dname n)) . map paren
424seqE :: Exp -> [Exp] -> Exp
425seqE l [] = app (var "pure") l
426seqE l (r:rs) = infixApp l "<$>" (infixE r "<*>" rs)
428objDecl :: Global -> Prefix -> [Derive] -> Map Local Solved -> Decl
429objDecl n p ds rs = DataDecl noLoc arity [] d [] [conDecl d p rs] (der ds)
433 arity | Map.size rs == 1 = NewType
434 | otherwise = DataType
436 der = map ((,[]) . unqual . drop 1 . show)
438conDecl :: Name -> Prefix -> Map Local Solved -> QualConDecl
439conDecl n p rs = QualConDecl noLoc [] [] body
441 body = case Map.toList rs of
443 [x] -> RecDecl n [field internalType x]
444 xs -> RecDecl n (map (field (strict . internalType)) xs)
446 field f (l, v) = ([fname p l], f (_type v))
448ctorSig :: Global -> Map Local Solved -> Decl
449ctorSig n rs = TypeSig noLoc [cname n] ts
451 ts = foldr' TyFun (TyCon (UnQual (dname n))) ps
452 ps = parameters (Map.elems rs)
454ctorDecl :: Global -> Prefix -> Map Local Solved -> Decl
455ctorDecl n p rs = sfun noLoc c ps (UnGuardedRhs rhs) noBinds
460 rhs | Map.null rs = var d
461 | otherwise = RecConstr (UnQual d) $
462 map (uncurry (fieldUpdate p)) (Map.toList rs)
464 ps = map (pname p) . Map.keys $ Map.filter parameter rs
466fieldUpdate :: Prefix -> Local -> Solved -> FieldUpdate
467fieldUpdate p l s = FieldUpdate (UnQual (fname p l)) rhs
469 rhs | Just x <- def s, s ^. iRepeated = listE [x]
470 | Just x <- def s = x
471 | Just x <- iso (_type s) = infixApp x "#" v
473 | otherwise = var (name "Nothing")
477lensSig :: Global -> Prefix -> Local -> Solved -> Decl
478lensSig n p l s = TypeSig noLoc [lname p l] $
479 TyApp (TyApp (TyCon "Lens'") (tycon n))
480 (externalType (_type s))
482lensDecl :: Prefix -> Local -> Solved -> Decl
483lensDecl p l s = sfun noLoc (lname p l) [] (UnGuardedRhs rhs) noBinds
489 app (app (var "lens") (var f))
490 (paren (lamE noLoc [pvar "s", pvar "a"]
491 (RecUpdate (var "s") [FieldUpdate (UnQual f) (var "a")])))
493parameters :: [Solved] -> [Type]
494parameters = map (externalType . _type) . filter parameter
496def :: Solved -> Maybe Exp
498 | Just x <- s ^. iDefault = Just (go x (_prefix s) (_schema s))
499 | otherwise = Nothing
502 SEnm {} -> var (bname p x)
503 SLit _ Bool -> lit (upperHead x)
506 e -> error $ "Unsupported default value: " ++ show e
508 lit = var . name . Text.unpack
510terminalType :: TType -> Type
511terminalType = internalType . go
517externalType :: TType -> Type
520 TMaybe t@TList {} -> externalType t
521 TMaybe t -> TyApp (TyCon "Maybe") (externalType t)
522 TList t -> TyList (externalType t)
523 TLit l -> externalLit l
525 TyApp (TyApp (TyCon "HashMap") (externalType k)) (externalType (require v))
527internalType :: TType -> Type
530 TMaybe t -> TyApp (TyCon "Maybe") (internalType t)
531 TList t -> TyList (internalType t)
532 TLit l -> internalLit l
534 TyApp (TyApp (TyCon "HashMap") (internalType k)) (internalType (require v))
536externalLit :: Lit -> Type
540 Time -> TyCon "TimeOfDay"
542 DateTime -> TyCon "UTCTime"
543 Nat -> TyCon "Natural"
544 Float -> TyCon "Double"
545 Double -> TyCon "Double"
546 Byte -> TyCon "Word8"
547 UInt32 -> TyCon "Word32"
548 UInt64 -> TyCon "Word64"
549 Int32 -> TyCon "Int32"
550 Int64 -> TyCon "Int64"
551 Alt t -> TyCon (unqual (Text.unpack t))
552 RqBody -> TyCon "RequestBody"
553 RsBody -> TyCon "Stream"
554 JSONValue -> TyCon "JSONValue"
556internalLit :: Lit -> Type
560 Time -> TyCon "Time'"
561 Date -> TyCon "Date'"
562 DateTime -> TyCon "DateTime'"
563 Nat -> TyApp (TyCon "Textual") (TyCon "Nat")
564 Float -> TyApp (TyCon "Textual") (TyCon "Double")
565 Double -> TyApp (TyCon "Textual") (TyCon "Double")
566 Byte -> TyApp (TyCon "Textual") (TyCon "Word8")
567 UInt32 -> TyApp (TyCon "Textual") (TyCon "Word32")
568 UInt64 -> TyApp (TyCon "Textual") (TyCon "Word64")
569 Int32 -> TyApp (TyCon "Textual") (TyCon "Int32")
570 Int64 -> TyApp (TyCon "Textual") (TyCon "Int64")
571 Alt t -> TyCon (unqual (Text.unpack t))
572 RqBody -> TyCon "RequestBody"
573 RsBody -> TyCon "Stream"
574 JSONValue -> TyCon "JSONValue"
576mapping :: TType -> Exp -> Exp
577mapping t e = infixE e "." (go t)
580 TMaybe x@(TList {}) -> var "_Default" : go x
581 TMaybe x -> nest (go x)
582 x -> maybeToList (iso x)
585 nest (x:xs) = [app (var "mapping") (infixE x "." xs)]
587iso :: TType -> Maybe Exp
589 TList {} -> Just (var "_Coerce")
590 TMap {} -> Just (var "_Coerce")
591 TLit Nat -> Just (var "_Coerce")
592 TLit Time -> Just (var "_Time")
593 TLit Date -> Just (var "_Date")
594 TLit DateTime -> Just (var "_DateTime")
595 TLit Float -> Just (var "_Coerce")
596 TLit Double -> Just (var "_Coerce")
597 TLit Byte -> Just (var "_Coerce")
598 TLit UInt32 -> Just (var "_Coerce")
599 TLit UInt64 -> Just (var "_Coerce")
600 TLit Int32 -> Just (var "_Coerce")
601 TLit Int64 -> Just (var "_Coerce")
604require :: TType -> TType
605require (TMaybe t) = t
608strict :: Type -> Type
609strict = TyBang BangedTy . \case
610 t@TyApp{} -> TyParen t
614sing = TyCon . unqual . Text.unpack . flip mappend "\"" . mappend "\""
616tylist :: [Text] -> Type
617tylist xs = TyCon . UnQual . name . Text.unpack $
618 "'[" <> Text.intercalate ", " xs <> "]"
620tycon :: Global -> Type
621tycon = TyCon . UnQual . dname
623unqual :: String -> QName
624unqual = UnQual . name
626infixE :: Exp -> QOp -> [Exp] -> Exp
628infixE l o (r:rs) = infixE (infixApp l o r) o rs
631str = 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