@@ -0,0 +1,227 @@
1+{-# LANGUAGE DataKinds #-}
2+{-# LANGUAGE DeriveGeneric #-}
3+{-# LANGUAGE FlexibleContexts #-}
4+{-# LANGUAGE FlexibleInstances #-}
5+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
6+{-# LANGUAGE KindSignatures #-}
7+{-# LANGUAGE LambdaCase #-}
8+{-# LANGUAGE OverloadedStrings #-}
9+{-# LANGUAGE RankNTypes #-}
10+{-# LANGUAGE RecordWildCards #-}
11+{-# LANGUAGE TemplateHaskell #-}
12+{-# LANGUAGE ViewPatterns #-}
13+
14+-- Module : Gen.AST.Solve
15+-- Copyright : (c) 2015 Brendan Hay
16+-- License : Mozilla Public License, v. 2.0.
17+-- Maintainer : Brendan Hay <brendan.g.hay@gmail.com>
18+-- Stability : provisional
19+-- Portability : non-portable (GHC extensions)
20+
21+module Gen.AST.Solve
22+ ( solve
23+ ) where
24+
25+import Control.Applicative
26+import Control.Error
27+import Control.Lens hiding (enum)
28+import Control.Monad.Except
29+import Data.CaseInsensitive (CI)
30+import qualified Data.CaseInsensitive as CI
31+import Data.Hashable
32+import qualified Data.HashMap.Strict as Map
33+import qualified Data.HashSet as Set
34+import Data.List (intersect)
35+import Data.Maybe
36+import Data.Semigroup ((<>))
37+import Data.Text (Text)
38+import qualified Data.Text as Text
39+import Data.Text.Manipulate
40+import Debug.Trace
41+import Gen.Formatting
42+import Gen.Text
43+import Gen.Types
44+import Prelude hiding (sum)
45+
46+solve :: Service Global -> AST (Service Solved)
47+solve s = do
48+ ss <- Map.traverseWithKey (const . getSolved) (s ^. dSchemas)
49+ ps <- traverse param (s ^. dParameters)
50+ rs <- traverse resource (s ^. dResources)
51+ ms <- traverse method (s ^. dMethods)
52+
53+ let d = (s ^. sDescription)
54+ { _dSchemas = ss
55+ , _dParameters = ps
56+ , _dResources = rs
57+ , _dMethods = ms
58+ }
59+
60+ pure $! s { _sDescription = d }
61+
62+param :: Param Global -> AST (Param Solved)
63+param = traverse getSolved
64+
65+resource :: Resource Global -> AST (Resource Solved)
66+resource = traverse getSolved
67+
68+method :: Method Global -> AST (Method Solved)
69+method = traverse getSolved
70+
71+getSolved :: Global -> AST Solved
72+getSolved g = loc "getSolved" g $
73+ Solved g <$> getPrefix g
74+ <*> getSchema g
75+ <*> getType g
76+ <*> getDerive g
77+
78+getSchema :: Global -> AST (Schema Global)
79+getSchema g = do
80+ m <- uses schemas (Map.lookup g)
81+ case m of
82+ Nothing -> failure ("Missing Schema: " % gid) g
83+ Just x -> pure x
84+
85+getType :: Global -> AST TType
86+getType g = loc "getType" g $ memo typed g go
87+ where
88+ go s = fmap (may s) $
89+ case s of
90+ SAny {} -> pure (TType "JSON")
91+ SRef _ r -> getType (ref r)
92+ SLit _ l -> pure (TLit l)
93+ SEnm {} -> pure (TType g)
94+ SArr _ (Arr e) -> TList <$> getType e
95+ SObj {} -> pure (TType g)
96+
97+ may s | required s = id
98+ | defaulted s = id
99+ | otherwise = TMaybe
100+
101+getDerive :: Global -> AST [Derive]
102+getDerive g = loc "getDerive" g $ memo derived g go
103+ where
104+ go = \case
105+ SAny {} -> pure base
106+ SRef {} -> pure base
107+ SLit _ l -> pure (literal l)
108+ SEnm {} -> pure enum
109+ SArr _ (Arr e) -> mappend list . intersect base <$> getDerive e
110+ SObj _ (Obj _ ps) -> foldM props base (Map.elems ps)
111+
112+ literal = \case
113+ Text -> base <> [DOrd, DIsString]
114+ Bool -> enum
115+ Time -> base
116+ Date -> base
117+ _ -> [DNum, DIntegral, DReal] <> enum
118+
119+ props ds x = intersect ds <$> getDerive x
120+
121+ list = [DMonoid]
122+ enum = [DOrd, DEnum] <> base
123+ base = [DEq, DRead, DShow, DData, DTypeable, DGeneric]
124+
125+getPrefix :: Global -> AST Prefix
126+getPrefix g = loc "getPrefix" g $ memo prefixed g go
127+ where
128+ go = \case
129+ SObj _ (Obj _ ps) -> field ps
130+ SEnm _ (Enm vs) -> branch (map fst vs)
131+ _ -> pure mempty
132+
133+ field rs = do
134+ let ls = Map.keys rs
135+ ks = Set.fromList (map (CI.mk . local) ls)
136+ p <- uniq fields (acronymPrefixes g) ks
137+ pure (Prefix p)
138+
139+ branch vs = do
140+ p <- uniq branches ("" : acronymPrefixes g) $
141+ Set.fromList (map CI.mk vs)
142+ pure (Prefix p)
143+
144+ uniq :: Lens' Memo Seen
145+ -> [CI Text]
146+ -> Set (CI Text)
147+ -> AST Text
148+ uniq seen [] ks = do
149+ s <- use seen
150+ let hs = acronymPrefixes g
151+ f x = sformat ("\n" % stext % " => " % shown)
152+ (CI.foldedCase x) (Map.lookup x s)
153+ throwError $
154+ format ("Error prefixing: " % gid %
155+ "\n Fields: " % shown %
156+ "\n Matches: " % stext)
157+ g (Set.toList ks) (foldMap f hs)
158+
159+ uniq seen (x:xs) ks = do
160+ m <- uses seen (Map.lookup x)
161+ case m of
162+ Just ys | overlap ys ks
163+ -> uniq seen xs ks
164+ _ -> do
165+ seen %= Map.insertWith (<>) x ks
166+ return (CI.foldedCase x)
167+
168+overlap :: (Eq a, Hashable a) => Set a -> Set a -> Bool
169+overlap xs ys = not . Set.null $ Set.intersection xs ys
170+
171+acronymPrefixes :: Global -> [CI Text]
172+acronymPrefixes (global -> g) = map CI.mk (xs ++ map suffix ys ++ zs)
173+ where
174+ -- Take the next char
175+ suffix x = Text.snoc x c
176+ where
177+ c | Text.length x >= 2 = Text.head (Text.drop 1 x)
178+ | otherwise = Text.head x
179+
180+ zs = zipWith (\n x -> Text.snoc x (head (show n))) ([1..] :: [Int]) xs
181+
182+ xs = catMaybes [r1, r2, r3, r4, r5, r6]
183+ ys = catMaybes [r1, r2, r3, r4, r6]
184+
185+ a = camelAcronym g
186+ a' = upperAcronym g
187+
188+ limit = 3
189+
190+ -- Full name if leq limit
191+ r1 | Text.length g <= limit = Just g
192+ | otherwise = Nothing
193+
194+ -- VpcPeeringInfo -> VPI
195+ r2 = toAcronym a
196+
197+ -- VpcPeeringInfo -> VPCPI
198+ r3 | x /= r2 = x
199+ | otherwise = Nothing
200+ where
201+ x = toAcronym a'
202+
203+ -- SomeTestTType -> S
204+ r4 = Text.toUpper <$> safeHead g
205+
206+ -- SomeTypes -> STS (retain pural)
207+ r5 | Text.isSuffixOf "s" g = flip Text.snoc 's' <$> (r2 <|> r3)
208+ | otherwise = Nothing
209+
210+ -- SomeTestTType -> Som
211+ r6 = Text.take limit <$> listToMaybe (splitWords a)
212+
213+loc :: String -> Global -> a -> a
214+loc _ _ = id --trace (n ++ ": " ++ Text.unpack (idToText g))
215+
216+memo :: Lens' Memo (Map Global a)
217+ -> Global
218+ -> (Schema Global -> AST a)
219+ -> AST a
220+memo l g f = do
221+ m <- uses l (Map.lookup g)
222+ case m of
223+ Just x -> pure x
224+ Nothing -> do
225+ x <- f =<< getSchema g
226+ l %= Map.insert g x
227+ pure x