-
Notifications
You must be signed in to change notification settings - Fork 64
/
Pretty.hs
321 lines (269 loc) · 11.7 KB
/
Pretty.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
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
{-# LANGUAGE PatternSynonyms #-}
module Plutarch.Pretty (prettyTerm, prettyTerm', prettyScript) where
import Control.Monad.Reader (ReaderT (runReaderT))
import Control.Monad.ST (runST)
import Control.Monad.State (MonadState (get, put), StateT (runStateT), modify, modify')
import Data.Foldable (fold)
import Data.Functor (($>), (<&>))
import Data.Text (Text)
import Data.Text qualified as Txt
import Data.Traversable (for)
import System.Random.Stateful (mkStdGen, newSTGenM)
import Prettyprinter ((<+>))
import Prettyprinter qualified as PP
import Plutarch.Internal (ClosedTerm, Config, compile)
import Plutarch.Script (Script (unScript))
import PlutusCore qualified as PLC
import UntypedPlutusCore (
DeBruijn (DeBruijn),
DefaultFun,
DefaultUni,
Program (_progTerm),
Term (Apply, Builtin, Case, Constant, Constr, Delay, Error, Force, LamAbs, Var),
)
import Plutarch.Pretty.Internal.BuiltinConstant (prettyConstant)
import Plutarch.Pretty.Internal.Config (indentWidth)
import Plutarch.Pretty.Internal.Name (freshVarName, smartName)
import Plutarch.Pretty.Internal.TermUtils (
unwrapApply,
unwrapBindings,
unwrapLamAbs,
pattern IfThenElseLikeAST,
)
import Plutarch.Pretty.Internal.Types (
PrettyCursor (Normal, Special),
PrettyMonad,
PrettyState (PrettyState, ps'cursor, ps'nameMap),
builtinFunAtRef,
forkState,
insertBindings,
insertName,
nameOfRef,
normalizeCursor,
specializeCursor,
)
-- | 'prettyTerm' for pre-compiled 'Script's.
prettyScript :: Script -> PP.Doc ()
prettyScript = prettyUPLC . _progTerm . unScript
{- | Prettify a Plutarch term.
This will call 'error' if there's a compilation failure. Use 'prettyTerm'' for a non-partial version.
== Example ==
@
import Plutarch.Prelude
import Plutarch.Api.V1
checkSignatory :: Term s (PPubKeyHash :--> PScriptContext :--> PUnit)
checkSignatory = plam $ \ph ctx' -> unTermCont $ do
ctx <- pletFieldsC @["txInfo", "purpose"] ctx'
purph <- pmatchC ctx.purpose
pure $ case purph of
PSpending _ ->
let signatories = pfield @"signatories" # ctx.txInfo
in pif
(pelem # pdata ph # pfromData signatories)
-- Success!
(pconstant ())
-- Signature not present.
perror
_ -> ptraceError "checkSignatoryCont: not a spending tx"
@
Prettification result:
@
let frSndPair = !!sndPair
unDataSum = (\xF -> frSndPair (unConstrData xF))
frTailList = !tailList
frHeadList = !headList
frIfThenElse = !ifThenElse
in (\oP4ECBT qsrxlF0Y7 ->
let cjlB6yrGk = unDataSum qsrxlF0Y7
cRFO = unConstrData (frHeadList (frTailList cjlB6yrGk))
cs9iR = !!fstPair cRFO
w4 = frSndPair cRFO
in if equalsInteger 1 cs9iR
then if (\vModHwqYB ->
let blM6d67 =
(\x5sad ePDSInSEC ->
!(!!chooseList
ePDSInSEC
~False
~(if equalsData
(frHeadList ePDSInSEC)
vModHwqYB
then True
else x5sad (frTailList ePDSInSEC))))
mC = (\jfZs -> blM6d67 (\itzT -> jfZs jfZs itzT))
in blM6d67 (\ispwp_oeT -> mC mC ispwp_oeT))
(bData oP4ECBT)
(unListData
let q6X3 = frHeadList cjlB6yrGk
in frHeadList
let olbZ = unDataSum q6X3
in frTailList
(frTailList
(frTailList
(frTailList
(frTailList
(frTailList
(frTailList olbZ)))))))
then ()
else ERROR
else !(!trace "checkSignatoryCont: not a spending tx" ~ERROR))
@
== Semantics ==
=== Constants ===
- Builtin integers are printed as regular integers. [0-9]+
- Builtin bytestrings are printed in hex notation, prefixed by `0x`. 0x[0-9a-f]+/i
- Builtin strings are printed as is.
- Builtin unit is printed as the unit literal. ()
- Builtin booleans are printed as the literal `True` or `False`.
- Builtin lists are prettified as list literals, i.e delimited with `[` and `]`.
- Builtin pairs are prettified as 2-ary tuple literals, e.g. `(a, b)`.
- `I` data (i.e data encoded integers) are prettified like builtin integers with a `#` prefix. #[0-9]+
- `B` data (i.e data encoded bytestrings) are prettified like builtin bytestrings with a `#` prefix. #0x[0-9a-f]+/i
- `List` data (i.e data encoded lists) are prettified like builtin lists with a `#` prefix.
- `Map` data is printed like record literals. Delimited by `{` and `}`.
Each key value pair is prettified like <key> = <value> and multiple pairs are joined with `,`.
For example, `Map [(I 42, I 0), (I 100, I 1)]` is prettified as `{ #42 = #0, #100 = #1 }`
- Constr data has two core elements in its prettified form:
- The constructor index, prettified as an integer prefixed with `Σ` (sigma).
- Its fields, prettified as a list.
These two elements are then joined with a `.` (period).
For example, `Constr 1 [I 42]` is prettified as "Σ1.[#42]".
=== Builtin functions ===
Builtin functions are prettified into their name, in title case.
=== Forced term ===
Forced terms are prefixed with a `!`. The unary operator `!` has higher fixity than function application.
=== Delayed term ===
Delayed terms are prefixed with a `~`. The unary operator `~` has higher fixity than function application.
=== Var ===
Random names are generated for all variable bindings, and these names are used to refer to them.
Names are always unique, between 1 and 8 characters in length, and begin with a lowercase letter.
Names may consist of alphanumeric characters, underscore, or single quotes.
=== LamAbs ===
Lambdas are prettified similar to haskell lambdas, i.e `\x -> ...`.
Lambdas with multiple arguments are detected and simplified: `\x y z -> ...`.
=== Apply ===
Application is, simply, a space - just like haskell. `f x`.
Multi arg applications to the same function are detected and simplified: `f x y`.
=== Error term ===
`perror` is represented by the literal `ERROR`.
=== Special handling ===
To achieve better prettification, certain AST structures are given special handling logic.
- The AST structure produced by `plet` (Single `Apply` + `LamAbs` pair) is prettified into Haskell-like let bindings.
- Lazy if/then/else (`pif` in particular, not `pif'`) is detected and prettified into Haskell-like syntax:
`if cond then expr1 else expr2`.
Chains of if/then/else are nested:
@
if cond
then expr1
else if cond
then expr2
else expr3
@
- When generating names for bindings, well known structures are identified and given special names.
This machinery is made to be extensible in the future.
For example, the structure of the `pfix` function is well known and constant - so it is simply called `fix` in the output.
Bindings to forced builtin functions inherit the builtin function name, prefixed with a `fr`.
-}
prettyTerm :: Config -> ClosedTerm a -> PP.Doc ()
prettyTerm conf x = either (error . Txt.unpack) id $ prettyTerm' conf x
-- | Non-partial 'prettyTerm'.
prettyTerm' :: Config -> ClosedTerm p -> Either Text (PP.Doc ())
prettyTerm' conf x = prettyScript <$> compile conf x
{- This isn't suitable for pretty printing UPLC from any source. It's primarily suited for Plutarch output.
Practically speaking though, it should work with any _idiomatic_ UPLC.
-}
prettyUPLC :: Term DeBruijn DefaultUni DefaultFun () -> PP.Doc ()
prettyUPLC uplc = runST $ do
stGen <- newSTGenM $ mkStdGen 42
(doc, _) <- runReaderT (go uplc) stGen `runStateT` PrettyState mempty mempty Normal
pure doc
where
go :: Term DeBruijn DefaultUni DefaultFun () -> PrettyMonad s (PP.Doc ())
go (Constant _ c) = pure $ prettyConstant c
go (Builtin _ b) = pure $ PP.pretty b
go (Error _) = pure "ERROR"
go (Var _ (DeBruijn x)) = do
PrettyState {ps'nameMap} <- get
pure $ case nameOfRef x ps'nameMap of
Just nm -> PP.pretty nm
Nothing -> error "impossible: free variable"
go (IfThenElseLikeAST (Force () (Builtin () PLC.IfThenElse)) cond trueBranch falseBranch) = prettyIfThenElse (forkState . go) cond trueBranch falseBranch
go ast@(IfThenElseLikeAST scrutinee cond trueBranch falseBranch) = do
PrettyState {ps'nameMap} <- get
case scrutinee of
Var () (DeBruijn (builtinFunAtRef ps'nameMap -> Just PLC.IfThenElse)) ->
prettyIfThenElse (forkState . go) cond trueBranch falseBranch
_ -> case ast of
Force _ t@Apply {} -> modify specializeCursor *> go t <&> ("!" <>)
_ -> error "impossible: IfThenElseLikeAST"
go (Force _ t) = modify specializeCursor *> go t <&> ("!" <>)
go (Delay _ t) = modify specializeCursor *> go t <&> ("~" <>)
go (LamAbs _ _ t') = do
currState@PrettyState {ps'cursor} <- get
let (depth, bodyTerm) = unwrapLamAbs 0 t'
names <- traverse (const freshVarName) [0 .. depth]
-- Add all the new names to the nameMap, starting with 0 index.
put $ insertBindings names currState
modify' normalizeCursor
funcBody <- forkState $ go bodyTerm
pure . parensOnCursor ps'cursor . PP.hang indentWidth $
PP.sep
[ "\\" <> PP.hsep (reverse $ fmap PP.pretty names) <+> "->"
, funcBody
]
go (Apply _ (LamAbs _ _ t) firstArg) = do
PrettyState {ps'cursor} <- get
let (restArgs, coreF) = unwrapBindings [] t
helper (name, expr) = do
modify' normalizeCursor
valueDoc <- forkState $ go expr
pure . PP.hang indentWidth $
PP.sep
[ PP.pretty name <+> "="
, valueDoc
]
firstName <- smartName firstArg
firstBindingDoc <- helper (firstName, firstArg)
modify' $ insertName firstName
restBindingDoc <- fmap fold . for (reverse restArgs) $ \argExpr -> do
newName <- smartName argExpr
bindingDoc <- helper (newName, argExpr)
modify' (insertName newName) $> PP.flatAlt PP.hardline "; " <> bindingDoc
modify' normalizeCursor
coreExprDoc <- go coreF
pure . parensOnCursor ps'cursor $
PP.align $
PP.vsep
[ "let" <+> PP.align (firstBindingDoc <> restBindingDoc)
, "in" <+> coreExprDoc
]
go (Apply _ t arg) = do
PrettyState {ps'cursor} <- get
let (l, f) = unwrapApply [] t
args = l <> [arg]
functionDoc <- forkState $ modify' specializeCursor *> go f
argsDoc <- modify' specializeCursor *> traverse (forkState . go) args
pure . parensOnCursor ps'cursor $
PP.hang indentWidth $
PP.sep $
functionDoc : argsDoc
go (Constr {}) = pure $ PP.pretty ("UPLC.Constr not implemented" :: String)
go (Case {}) = pure $ PP.pretty ("UPLC.Case not implemented" :: String)
prettyIfThenElse ::
(t -> PrettyMonad s (PP.Doc ann)) ->
t ->
t ->
t ->
PrettyMonad s (PP.Doc ann)
prettyIfThenElse cont cond trueBranch falseBranch = do
PrettyState {ps'cursor} <- get
modify' normalizeCursor
condAst <- cont cond
trueAst <- cont trueBranch
falseAst <- cont falseBranch
pure . parensOnCursor ps'cursor $
PP.hang indentWidth $
PP.vsep ["if" <+> condAst, "then" <+> trueAst, "else" <+> falseAst]
-- | Wrap prettification result parens depending on cursor state.
parensOnCursor :: PrettyCursor -> PP.Doc ann -> PP.Doc ann
parensOnCursor cursor = if cursor == Special then PP.parens else id