File: Quote.hs

package info (click to toggle)
haskell-cmdargs 0.10.14-3
  • links: PTS, VCS
  • area: main
  • in suites: stretch
  • size: 348 kB
  • ctags: 1
  • sloc: haskell: 2,972; makefile: 3
file content (213 lines) | stat: -rw-r--r-- 8,673 bytes parent folder | download | duplicates (5)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
{-# LANGUAGE TemplateHaskell, PatternGuards, MagicHash #-}

-- | This module provides a quotation feature to let you write command line
--   arguments in the impure style, but have them translated into the pure style,
--   as per "System.Console.CmdArgs.Implicit". An example:
--
-- > {-# LANGUAGE TemplateHaskell, DeriveDataTypeable, MagicHash #-}
-- > import System.Console.CmdArgs.Implicit
-- > import System.Console.CmdArgs.Quote
-- >
-- > data Sample = Sample {hello :: String} deriving (Show, Data, Typeable)
-- >
-- > $(cmdArgsQuote [d|
-- >     sample = Sample{hello = def &=# help "World argument" &=# opt "world"}
-- >                    &=# summary "Sample v1"
-- > 
-- >     run = cmdArgs# sample :: IO Sample
-- >     |])
-- >
-- > main = print =<< run
--
--   Inside 'cmdArgsQuote' you supply the command line parser using attributes in the
--   impure style. If you run with @-ddump-splices@ (to see the Template Haskell output),
--   you would see:
--
-- > run = cmdArgs_
-- >     (record Sample{} [hello := def += help "World argument" += opt "world"]
-- >         += summary "Sample v1")
-- >     :: IO Sample
--
--   /Stubs/
--
--   To define the original parser you may use either the standard impure annotations ('(&=)', 'modes'), or
--   the stub annotations versions defined in this module ('(&=#)', 'modes'). The stub versions do not include
--   a "Data" constraint, so can be used in situations where the Data instance is not yet available - typically
--   when defining the parser in the same module as the data type on GHC 7.2 and above. The stub versions should
--   never be used outside 'cmdArgsQuote' and will always raise an error.
--
--   /Explicit types/
--
--   There will be a limited number of situations where an impure parser will require additional types, typically
--   on the result of 'cmdArgs' if the result is used without a fixed type - for example if you 'show' it. Most users
--   will not need to add any types. In some cases you may need to remove some explicit types, where the intermediate
--   type of the annotations has changed - but again, this change should be rare.
--
--   /Completeness/
--
--   The translation is not complete, although works for all practical instances I've tried. The translation works
--   by first expanding out the expression (inlining every function defined within the quote, inlining let bindings),
--   then performs the translation. This scheme leads to two consequences: 1) Any expensive computation executed inside
--   the quotation to produce the command line flags may be duplicated (a very unlikely scenario). 2) As I do not yet
--   have expansion rules for all possible expressions, the expansion (and subsequently the translation) may fail.
--   I am interested in any bug reports where the feature does not work as intended.
module System.Console.CmdArgs.Quote(
    -- * Template Haskell quotation function
    cmdArgsQuote,
    -- * Stub versions of the impure annotations
    (&=#), modes#, cmdArgsMode#, cmdArgs#, enum#
    ) where

import Language.Haskell.TH
import Control.Arrow
import Control.Monad
import Data.Data
import Data.Maybe
import System.Console.CmdArgs.Implicit

stub name = error $
    "System.Console.CmdArgs.Quote." ++ name ++
    ": this function is provided only for use inside cmdArgsQuote, and should never be called"

-- | Version of '&=' without a 'Data' context, only to be used within 'cmdArgsQuote'.
(&=#) :: a -> Ann -> a
(&=#) = stub "(&=#)"

-- | Version of 'modes' without a 'Data' context, only to be used within 'cmdArgsQuote'.
modes# :: [a] -> a
modes# = stub "modes#"

-- | Version of 'cmdArgsMode' without a 'Data' context, only to be used within 'cmdArgsQuote'.
cmdArgsMode# :: a -> Mode (CmdArgs a)
cmdArgsMode# = stub "cmdArgsMode#"

-- | Version of 'cmdArgs' without a 'Data' context, only to be used within 'cmdArgsQuote'.
cmdArgs# :: a -> IO a
cmdArgs# = stub "cmdArgs#"

-- | Version of 'enum' without a 'Data' context, only to be used within 'cmdArgsQuote'.
enum# :: [a] -> a
enum# = stub "enum#"


-- | Quotation function to turn an impure version of "System.Console.CmdArgs.Implicit" into a pure one.
--   For details see "System.Console.CmdArgs.Quote".
cmdArgsQuote :: Q [Dec] -> Q [Dec]
cmdArgsQuote x = do
    x <- x
    translate $ rename $ simplify $ inline x


-- | Apply the rewrite rules
translate :: [Dec] -> Q [Dec]
translate = descendBiM f
    where
        dull = ['Just, 'Left, 'Right, '(:)] -- Prelude constructors of non-zero arity

        f (RecConE x xs) = return $
            let args = [anns (InfixE (Just $ VarE lbl) (ConE '(:=)) (Just val)) as | (lbl,x) <- xs, let (val, as) = asAnns x]
            in VarE 'record `AppE` RecConE x [] `AppE` ListE args

        f x | (ConE x, xs@(_:_)) <- asApps x, x `notElem` dull = do
            names <- forM [1..length xs] $ \i -> newName $ "_" ++ nameBase x ++ show i
            let (vals, ass) = unzip $ map asAnns xs
                bind = [ValD (VarP name) (NormalB val) [] | (name,val) <- zip names vals]
                args = [anns (VarE 'atom `AppE` VarE name) as | (name,as) <- zip names ass]
            return $ LetE bind $ VarE 'record `AppE` (ConE x `apps` map VarE names) `AppE` ListE args
        
        f x = descendM f x

        apps x [] = x
        apps x (y:ys) = apps (x `AppE` y) ys

        asApps (AppE x y) = let (a,b) = asApps x in (a,b++[y])
        asApps x = (x,[])

        anns x [] = x
        anns x (a:as) = anns (InfixE (Just x) (VarE '(+=)) (Just a)) as

        asAnns (InfixE (Just x) (VarE op) (Just y)) | op == '(+=) = let (a,b) = asAnns x in (a,b++[y])
        asAnns (AppE (AppE (VarE op) x) y) | op == '(+=) = let (a,b) = asAnns x in (a,b++[y])
        asAnns x = (x, [])


-- | Move from the old names to the new names, sufficient for where that is the full translation
rename :: [Dec] -> [Dec]
rename = transformBi f
    where
        rep = let f a b c = [(a,c),(b,c)] in concat
            [f '(&=) '(&=#) '(+=)
            ,f 'modes 'modes# 'modes_
            ,f 'enum 'enum# 'enum_
            ,f 'cmdArgsMode 'cmdArgsMode# 'cmdArgsMode_
            ,f 'cmdArgs 'cmdArgs# 'cmdArgs_]

        f (VarE x) | Just x <- lookup x rep = VarE x
        f x = x


-- | Simplify the syntax tree - things like application of a lambda
simplify :: [Dec] -> [Dec]
simplify = transformBi f
    where
        f (AppE (LamE [VarP v] bod) x) = f $ subst v x bod
        f x = x

        subst v x bod = transform f bod
            where f (VarE v2) | v == v2 = x
                  f x = x


-- | Evaluate through all locally defined functions and let expressions, at most once per defn
inline :: [Dec] -> [Dec]
inline xs = map (dec $ addEnv xs []) xs
    where
        newEnv = concatMap $ \x -> case x of
            FunD x [Clause ps (NormalB e) ds] -> [(x, LamE ps $ let_ ds e)]
            ValD (VarP x) (NormalB e) ds -> [(x, let_ ds e)]
            _ -> []

        addEnv xs env = without [] (newEnv xs) ++ env
            where
                -- create an environment where everything in ns is missing, recursively drop one thing each time
                without ns new = [(n, exp (new2 ++ env) e) | (n,e) <- new, n `notElem` ns, let new2 = without (n:ns) new]
                

        dec env (FunD n cs) = FunD n $ map (clause env) cs
        dec env (ValD p x ds) = ValD p (body (addEnv ds env) x) ds

        clause env (Clause ps x ds) = Clause ps (body (addEnv ds env) x) ds

        body env (GuardedB xs) = GuardedB $ map (second $ exp env) xs
        body env (NormalB x) = NormalB $ exp env x

        -- FIXME: propagating the env ignores variables shadowed by LamE/CaseE
        exp env (LetE ds x) = LetE ds $ exp (addEnv ds env) x
        exp env (VarE x) | Just x <- lookup x env = x
        exp env x = descend (exp env) x

        let_ ds e = if null ds then e else LetE ds e


---------------------------------------------------------------------
-- MINI UNIPLATE - Avoid the dependency just for one small module

descendBi :: (Data a, Data b) => (b -> b) -> a -> a
descendBi f x | Just f <- cast f = f x
              | otherwise = gmapT (descendBi f) x

descend :: Data a => (a -> a) -> a -> a
descend f = gmapT (descendBi f)

transform :: Data a => (a -> a) -> a -> a
transform f = f . descend (transform f)

transformBi :: (Data a, Data b) => (b -> b) -> a -> a
transformBi f = descendBi (transform f)

descendBiM :: (Data a, Data b, Monad m) => (b -> m b) -> a -> m a
descendBiM f x | Just x <- cast x = liftM (fromJust . cast) $ f x -- guaranteed safe
               | otherwise = gmapM (descendBiM f) x

descendM :: (Data a, Monad m) => (a -> m a) -> a -> m a
descendM f = gmapM (descendBiM f)