-
-
Notifications
You must be signed in to change notification settings - Fork 389
/
Copy pathDocumentation.hs
237 lines (194 loc) · 7.43 KB
/
Documentation.hs
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
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
{-# LANGUAGE OverloadedStrings #-}
{-# OPTIONS_GHC -Wno-deprecations #-}
module Wingman.Metaprogramming.Parser.Documentation where
import Data.Functor ((<&>))
import Data.List (sortOn)
import Data.String (IsString)
import Data.Text (Text)
import Prettyprinter hiding (parens)
import Prettyprinter.Render.String (renderString)
import Development.IDE.GHC.Compat (OccName)
import qualified Text.Megaparsec as P
import Wingman.Metaprogramming.Lexer (Parser, identifier, variable, parens)
import Wingman.Types (TacticsM)
import {-# SOURCE #-} Wingman.Metaprogramming.Parser (tactic)
------------------------------------------------------------------------------
-- | Is a tactic deterministic or not?
data Determinism
= Deterministic
| Nondeterministic
prettyDeterminism :: Determinism -> Doc b
prettyDeterminism Deterministic = "deterministic"
prettyDeterminism Nondeterministic = "non-deterministic"
------------------------------------------------------------------------------
-- | How many arguments does the tactic take?
data Count a where
One :: Count OccName
Many :: Count [OccName]
prettyCount :: Count a -> Doc b
prettyCount One = "single"
prettyCount Many = "variadic"
------------------------------------------------------------------------------
-- | What sorts of arguments does the tactic take? Currently there is no
-- distinction between 'Ref' and 'Bind', other than documentation.
--
-- The type index here is used for the shape of the function the parser should
-- take.
data Syntax a where
Nullary :: Syntax (Parser (TacticsM ()))
Ref :: Count a -> Syntax (a -> Parser (TacticsM ()))
Bind :: Count a -> Syntax (a -> Parser (TacticsM ()))
Tactic :: Syntax (TacticsM () -> Parser (TacticsM ()))
prettySyntax :: Syntax a -> Doc b
prettySyntax Nullary = "none"
prettySyntax (Ref co) = prettyCount co <+> "reference"
prettySyntax (Bind co) = prettyCount co <+> "binding"
prettySyntax Tactic = "tactic"
------------------------------------------------------------------------------
-- | An example for the documentation.
data Example = Example
{ ex_ctx :: Maybe Text -- ^ Specific context information about when the tactic is applicable
, ex_args :: [Var] -- ^ Arguments the tactic was called with
, ex_hyp :: [ExampleHyInfo] -- ^ The hypothesis
, ex_goal :: Maybe ExampleType -- ^ Current goal. Nothing indicates it's uninteresting.
, ex_result :: Text -- ^ Resulting extract.
}
------------------------------------------------------------------------------
-- | An example 'HyInfo'.
data ExampleHyInfo = EHI
{ ehi_name :: Var -- ^ Name of the variable
, ehi_type :: ExampleType -- ^ Type of the variable
}
------------------------------------------------------------------------------
-- | A variable
newtype Var = Var
{ getVar :: Text
}
deriving newtype (IsString, Pretty)
------------------------------------------------------------------------------
-- | A type
newtype ExampleType = ExampleType
{ getExampleType :: Text
}
deriving newtype (IsString, Pretty)
------------------------------------------------------------------------------
-- | A command to expose to the parser
data MetaprogramCommand a = MC
{ mpc_name :: Text -- ^ Name of the command. This is the token necessary to run the command.
, mpc_syntax :: Syntax a -- ^ The command's arguments
, mpc_det :: Determinism -- ^ Determinism of the command
, mpc_description :: Text -- ^ User-facing description
, mpc_tactic :: a -- ^ Tactic to run
, mpc_examples :: [Example] -- ^ Collection of documentation examples
}
------------------------------------------------------------------------------
-- | Existentialize the pain away
data SomeMetaprogramCommand where
SMC :: MetaprogramCommand a -> SomeMetaprogramCommand
------------------------------------------------------------------------------
-- | Run the 'Parser' of a 'MetaprogramCommand'
makeMPParser :: MetaprogramCommand a -> Parser (TacticsM ())
makeMPParser (MC name Nullary _ _ t _) = do
identifier name
t
makeMPParser (MC name (Ref One) _ _ t _) = do
identifier name
variable >>= t
makeMPParser (MC name (Ref Many) _ _ t _) = do
identifier name
P.many variable >>= t
makeMPParser (MC name (Bind One) _ _ t _) = do
identifier name
variable >>= t
makeMPParser (MC name (Bind Many) _ _ t _) = do
identifier name
P.many variable >>= t
makeMPParser (MC name Tactic _ _ t _) = do
identifier name
parens tactic >>= t
------------------------------------------------------------------------------
-- | Compile a collection of metaprogram commands into a parser.
makeParser :: [SomeMetaprogramCommand] -> Parser (TacticsM ())
makeParser ps = P.choice $ ps <&> \(SMC mp) -> makeMPParser mp
------------------------------------------------------------------------------
-- | Pretty print a command.
prettyCommand :: MetaprogramCommand a -> Doc b
prettyCommand (MC name syn det desc _ exs) = vsep
[ "##" <+> pretty name
, mempty
, "arguments:" <+> prettySyntax syn <> ". "
, prettyDeterminism det <> "."
, mempty
, ">" <+> align (pretty desc)
, mempty
, vsep $ fmap (prettyExample name) exs
, mempty
]
------------------------------------------------------------------------------
-- | Pretty print a hypothesis.
prettyHyInfo :: ExampleHyInfo -> Doc a
prettyHyInfo hi = pretty (ehi_name hi) <+> "::" <+> pretty (ehi_type hi)
------------------------------------------------------------------------------
-- | Append the given term only if the first argument has elements.
mappendIfNotNull :: [a] -> a -> [a]
mappendIfNotNull [] _ = []
mappendIfNotNull as a = as <> [a]
------------------------------------------------------------------------------
-- | Pretty print an example.
prettyExample :: Text -> Example -> Doc a
prettyExample name (Example m_txt args hys goal res) =
align $ vsep
[ mempty
, "### Example"
, maybe mempty ((line <>) . (<> line) . (">" <+>) . align . pretty) m_txt
, "Given:"
, mempty
, codeFence $ vsep
$ mappendIfNotNull (fmap prettyHyInfo hys) mempty
<> [ "_" <+> maybe mempty (("::" <+>). pretty) goal
]
, mempty
, hsep
[ "running "
, enclose "`" "`" $ pretty name <> hsep (mempty : fmap pretty args)
, "will produce:"
]
, mempty
, codeFence $ align $ pretty res
]
------------------------------------------------------------------------------
-- | Make a haskell code fence.
codeFence :: Doc a -> Doc a
codeFence d = align $ vsep
[ "```haskell"
, d
, "```"
]
------------------------------------------------------------------------------
-- | Render all of the commands.
prettyReadme :: [SomeMetaprogramCommand] -> String
prettyReadme
= renderString
. layoutPretty defaultLayoutOptions
. vsep
. fmap (\case SMC c -> prettyCommand c)
. sortOn (\case SMC c -> mpc_name c)
------------------------------------------------------------------------------
-- | Helper function to build a 'SomeMetaprogramCommand'.
command
:: Text
-> Determinism
-> Syntax a
-> Text
-> a
-> [Example]
-> SomeMetaprogramCommand
command txt det syn txt' a exs = SMC $
MC
{ mpc_name = txt
, mpc_det = det
, mpc_syntax = syn
, mpc_description = txt'
, mpc_tactic = a
, mpc_examples = exs
}