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
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
--- ----------------------------------------------------------------------------
--- Curry Partial Evaluator
---
--- Based on the partial evaluator originally developed
--- by Elvira Albert, German Vidal (UPV), and Michael Hanus (CAU Kiel).
---
--- @author  Björn Peemöller and the authors declared above
--- @version December 2018
--- ----------------------------------------------------------------------------
module peval (main) where

import FilePath                  ( FilePath, (<.>), dropExtension
                                 , replaceBaseName, takeBaseName, takeDirectory)
import Function                  ((***), first, second)
import List                      ((\\), find, nub)
import Text.Pretty               ( Doc, ($$), (<$+$>), (<+>), char, compose
                                 , pPrint, vsep, text)
import System                    (setEnviron)

import FlatCurry.Annotated.Goodies (unAnnFuncDecl)
import FlatCurry.Annotated.TypeInference (TypeEnv, inferNewFunctions)
import System.Console.ANSI.Codes ( green )

import Abstract                  (abstract)
import FlatCurry.Types
import FlatCurry.Files           (readFlatCurry, writeFCY)
import FlatCurryGoodies          (topSQ, sq, freeVars, funcsInExps, onBranchExps)
import FlatCurryPretty           (ppExp, ppFuncDecls, ppProg, indent)
import NameChange                (ncRenaming, ncResultants)
import Normalization             (renameFuncDecl, renameResultant)
import Output                    (colorWith, info, status, detail, traceDetail, traceDebug)
import PeLetRW   as LetRW        (pevalExpr)
import PeNatural as Natural      (pevalExpr)
import PeRLNT    as RLNT         (pevalExpr)
import PevalBase                 (Renaming, Resultant, ppRenaming, ppResultants)
import PevalOpts                 ( Options (..), Semantics (..)
                                 , Abstraction (None), getOpts)
import PostUnfold                (unAlias, postUnfold, removeCopies)
import Renaming                  (renameExpr)

import System.CurryPath          (inCurrySubdir)

-- ---------------------------------------------------------------------------
-- main program
-- ---------------------------------------------------------------------------

-- TODO: Replace inferNewFunctions by getTypeEnv and inferNewFunctionsEnv
-- once it is in the libraries.

--- Main function of the partial evaluator.
main :: IO ()
main = do
  (opts, files) <- getOpts
  mapIO_ (spec opts) files

--- Specialization of a single file.
spec :: Options -> FilePath -> IO ()
spec opts f = do
  -- we set the path to allow imports located
  -- in the directory of the file for type checking
  setEnviron "CURRYPATH" (takeDirectory f)
  readFlatCurry (dropExtension f) >>= specPE opts >>= writeSpec opts f

--- Write the specialised program to the destination file.
writeSpec :: Options -> FilePath -> Prog -> IO ()
writeSpec opts f p = do
  status opts $ "Writing specialized program into file '" ++ dest ++ "'."
  writeFCY dest p
 where dest = specFile opts f

--- Compute the `FilePath` of the specialised program.
specFile :: Options -> FilePath -> FilePath
specFile opts f = inCurrySubdir b' <.> "fcy"
 where b' = replaceBaseName (dropExtension f) (takeBaseName f ++ optSuffix opts)

--- Specialization of a program.
--- Note that during the specialization process, the generated functions are
--- repeatedly type inferred for trace output although the types are later
--- discarded (except for the last run). The reason for this is that some errors
--- in the specialization process manifest in type-incorrect programs so that
--- these errors can be discovered earlier in the process.
specPE :: Options -> Prog -> IO Prog
specPE opts p@(Prog m _ _ _ _) = do
  -- 0. Determine type environment
  -- tyEnv <- getTypeEnv p

  -- 1. Split program into non-annotated program and annotated expressions
  let (prog, es) = extractPeval opts p
  traceStep opts "Annotated Expressions" (vsep . map (indent . ppExp)) es

  -- 2. Partial Evaluation of annotated expressions
  let peResult = pevalProg opts prog es
  showDetail opts "Partial Evaluation Loop" peResult
  traceInfo  opts "Partially Evaluated (Sub)Expressions" ppPevals peResult

  -- 3. Create resultants
  let (ren, res0)     = createResultants m peResult
--       typedRes0 <- createFunctions p res0
  traceInfo opts "Independent Renaming"   ppRenaming   ren
  traceInfo opts "Pre-Partial Evaluation" ppResultants res0

  let res             = renameResultants opts ren res0
  typedResultants <- createFunctions p res
  showDetail opts "Application of Renaming" res
  traceInfo  opts "Partial Evaluation" ppFuncDecls  typedResultants

  -- 4. Remove aliases and duplicates in the list of resultants
  let (ren2, res2) = unAlias ren res
  typedRes2 <- createFunctions p res2
  traceInfo opts "Independent Renaming after Compression" ppRenaming  ren2
  traceInfo opts "Partial Evaluation after Compression"   ppFuncDecls typedRes2

  -- 5. Compress the remaining resultants
  let (ren3, res3) = postUnfolding opts es ren2 res2
  typedRes3 <- createFunctions p res3
  showDetail opts "Compression of Resultants" typedRes3
  traceInfo  opts "Independent Renaming after Inlining" ppRenaming  ren3
  traceInfo  opts "Partial Evaluation after Inlining"   ppFuncDecls typedRes3

  -- 6. Remove any resultants that are copies of program functions
  (ren4, res4) <- if optClosed opts
                    then return (ren3, res3)
                    else do
    let (ren4', res4') = removeCopies prog ren3 res3
    typedRes4 <- createFunctions p res4'
    traceInfo opts "Independent Renaming after Copy Removal" ppRenaming  ren4'
    traceInfo opts "Partial Evaluation after Copy Removal"   ppFuncDecls typedRes4
    return (ren4', res4')

  -- 7. Change names so that resultants are numbered from 0 upwards
  let (ren5, res5) = nameChange m ren4 res4
  typedRes5 <- createFunctions p res5
  traceInfo opts "Final Independent Renaming" ppRenaming  ren5
  traceStep opts "Final Partial Evaluation"   ppFuncDecls typedRes5

  -- 8. Creating the result program
  let p' = integratePeval opts p ren5 typedRes5
  showDetail opts "Renaming in original program" p'
  traceInfo  opts "Resulting program" ppProg p'

  -- Print out resulting program if in debug mode
  when (optDebug opts) $ putStrLn $ pPrint $ ppProg p'

  return p'

--- Pretty-print a list of partial evaluated expressions.
ppPevals :: [(Expr, Expr)] -> Doc
ppPevals = compose (<$+$>) . map ppPeval
  where ppPeval (l, r) = indent (ppExp l) $$ char '\8658'  <+> indent (ppExp r)

--- Trace a step in the process of evaluation.
traceStep :: Options -> String -> (a -> Doc) -> a -> IO ()
traceStep opts str pp x = showStatus opts str (pPrint . pp $!! x)

--- Trace a step in the process of evaluation.
traceInfo :: Options -> String -> (a -> Doc) -> a -> IO ()
traceInfo opts str pp x = showInfo opts str (pPrint . pp $!! x)

--- Show a status information.
showStatus :: Options -> String -> String -> IO ()
showStatus opts hdr cnt = status opts $
  hdr ++ "\n" ++ replicate (length hdr) '-' ++ "\n" ++ cnt ++ "\n"

--- Show additional information.
showInfo :: Options -> String -> String -> IO ()
showInfo opts hdr cnt = info opts $
  hdr ++ "\n" ++ replicate (length hdr) '-' ++ "\n" ++ cnt ++ "\n"

--- Show additional information.
showDetail :: Options -> String -> a -> IO ()
showDetail opts hdr val = detail opts $
  hdr ++ "\n" ++ replicate (length hdr) '-' ++ "\n" ++ (const "" $!! val)

-- ---------------------------------------------------------------------------
-- 1. Extract all PEVAL annotated expressions and functional patterns.
--    This is a simple structural recursion on the AST.
-- ---------------------------------------------------------------------------

--- Extract all expressions `e` embedded in a call `PEVAL e` in the given
--- program and replaces these calls by `e`.
extractPeval :: Options -> Prog -> (Prog, [Expr])
extractPeval opts (Prog m is ts oldfs ops) = (Prog m is ts newfs ops, concat es)
  where (newfs, es) = unzip (map (extractFunc opts) oldfs)

--- Extract all annotated expressions in a function declaration.
extractFunc :: Options -> FuncDecl -> (FuncDecl, [Expr])
extractFunc opts (Func n a v ty r) = first (Func n a v ty) (extractRule opts r)

--- Extract all annotated expressions in a function rule.
extractRule :: Options -> Rule -> (Rule, [Expr])
extractRule _    e@(External _) = (e, [])
extractRule opts   (Rule  vs e) = first (Rule vs) (extractExpr opts e)

--- Extract all annotated expressions in an expression.
extractExpr :: Options -> Expr -> (Expr, [Expr])
extractExpr _    v@(Var        _) = (v, [])
extractExpr _    l@(Lit        _) = (l, [])
extractExpr opts c@(Comb ct n es) = case getPevalTarget opts c of
  Just e  -> (e, [e])
  Nothing -> (Comb ct n *** concat) (unzip (map (extractExpr opts) es))
extractExpr opts f@(Free    vs e) = case getPevalTarget opts f of
  Just fp -> (f, [fp])
  Nothing -> first (Free vs) (extractExpr opts e)
extractExpr opts o@(Or     e1 e2) = case getPevalTarget opts o of
  Just fp -> (o, [fp])
  Nothing -> let (ne1, pe1) = extractExpr opts e1
                 (ne2, pe2) = extractExpr opts e2
             in  (Or ne1 ne2, pe1 ++ pe2)
extractExpr opts (Case   ct e bs) = let (ne , pe1) = extractExpr opts e
                                        (nbs, pe2) = unzip (map getBranch bs)
                                    in  (Case ct ne nbs, concat (pe1 : pe2))
  where getBranch (Branch p be) = first (Branch p) (extractExpr opts be)
extractExpr opts (Let       bs e) = let (ne , pe1) = extractExpr opts e
                                        (nbs, pe2) = unzip (map getBinding bs)
                                    in  (Let nbs ne, concat (pe1 : pe2))
  where getBinding (v, ve) = first (\e' -> (v, e')) (extractExpr opts ve)
extractExpr opts (Typed     e ty) = first (flip Typed ty) (extractExpr opts e)

--- Check if the expression should be evaluated.
getPevalTarget :: Options -> Expr -> Maybe Expr
getPevalTarget opts e = case e of
  Comb FuncCall (_, "PEVAL") [e'] -> Just e'
  _ | optFunPats opts && hasFP e  -> Just e
    | otherwise                   -> Nothing

--- Is an expression a (combination of) function pattern?
hasFP :: Expr -> Bool
hasFP e = case e of
  Comb FuncCall ("Prelude", "=:<=") _       -> True
  Comb FuncCall ("Prelude", "&"   ) [e1, _] -> hasFP e1
  Comb FuncCall ("Prelude", "cond") [e1, _] -> hasFP e1
  Free _ e1                                 -> hasFP e1
  Or e1 e2                                  -> hasFP e1 || hasFP e2
  _                                         -> False

-- ---------------------------------------------------------------------------
-- 2. Perform partial evaluation
-- ---------------------------------------------------------------------------

--- Partial evaluation of a list of expressions w.r.t. a given program.
--- (e1, e2) => expression `e1` was partially evaluated to expression `e2`
pevalProg :: Options -> Prog -> [Expr] -> [(Expr, Expr)]
pevalProg opts p = reverse . pevalI [] . abstract opts p [] . map topSQ . reverse
  where
  -- global iterative algorithm for PE (fix-point iteration).
  -- @param cache: All expressions and their evaluation
  -- @param s    :The set of already evaluated expressions
  -- @param es   : The list of expressions to be evaluated
  pevalI cache es
    -- termination criteria
    | es == es' = cache'
    | otherwise = pevalI cache' es'
    where
    -- The new cache, extends the old one
    cache' = foldl update cache es
    -- The new set of expressions
    es'    = abstract opts p es (map snd cache' \\ map snd cache)

  -- peval of a list of expressions
  update cache e = case lookup e cache of
    Nothing -> (e, tracePeval e) : cache
    Just _  -> cache

  tracePeval e = traceExp False "Evaluating expression" e
                (traceDebug opts (colorWith opts green "using derivation")
                (traceExp True  "to result expression"  e' $!! e'))
    where
    e'        = pevalExpr opts p e
    pevalExpr = case optSemantics opts of
      RLNT    -> RLNT.pevalExpr
      Natural -> Natural.pevalExpr
      LetRW   -> LetRW.pevalExpr
    traceExp nl str x = Output.traceDetail opts $ colorWith opts green
                      $ pPrint (indent (text str $$ ppExp x))
                        ++ if nl then "\n" else ""

-- ---------------------------------------------------------------------------
-- 3. Building resultants
-- ---------------------------------------------------------------------------

--- Create the initial renaming and resultants from the partially evaluated
--- expressions.
createResultants :: String -> [(Expr, Expr)] -> (Renaming, [Resultant])
createResultants m = unzip . zipWith create [0 ..] . filter madeProgress
  where
  -- We ignore expressions that did not make any progress.
  -- The first part corresponds to expressions that could not be evaluated at
  -- all (for instance: variables, primitive functions such as (v1 + v2), ...).
  -- The second parts corresponds to expressions that evaluate to themselves,
  -- either because its a loop or because of missing information inside
  -- constructor calls (see test/base/double_case for an example).
  madeProgress (e, e') = e /= e' && topSQ e /= e'
  -- Create a new name for the resultants to build
  create n (e, e') = ((e, lhs), (lhs, e'))
    where lhs  = ((m, "_pe" ++ show n), freeVars e)

--- Rename the resultants by (recursively) applying the renaming.
renameResultants :: Options -> Renaming -> [Resultant] -> [Resultant]
renameResultants opts ren = map (renameResultant . second (renameExpr opts ren))

-- ---------------------------------------------------------------------------
-- 5. Post-Unfolding
-- ---------------------------------------------------------------------------

--- Compress the remaining resultants to eliminate intermediate functions.
postUnfolding :: Options -> [Expr] -> Renaming -> [Resultant]
              -> (Renaming, [Resultant])
postUnfolding opts es ren rs = postUnfold opts fs ren rs
  -- fs contains the names of those functions that are called by the
  -- specialisation of the annotated expressions. These functions can not be
  -- removed during postUnfolding.
  where fs = nub $ funcsInExps $ map (renameExpr opts ren . topSQ) es

-- -----------------------------------------------------------------------------
-- 7. Change the names of the resultants
-- -----------------------------------------------------------------------------

--- Changes the names of the given resultants to "pe_0", "pe_1" etc. and
--- adjusts the renaming accordingly.
nameChange :: String -> Renaming -> [Resultant] -> (Renaming, [Resultant])
nameChange mid ren res = (ncRenaming nc ren, ncResultants nc res)
  where nc = zip (map (fst . fst) res) [(mid, "_pe" ++ show i) | i <- [0 ..]]

-- -----------------------------------------------------------------------------
-- 8. Integrate the partially evaluated function declarations.
-- -----------------------------------------------------------------------------

--- Add all function declarations from the partially evaluated program
--- to the original program and replace the PEVAL calls in the original
--- program by the renamed pe-functions calls.
integratePeval :: Options -> Prog -> Renaming -> [FuncDecl] -> Prog
integratePeval opts (Prog m is ts fs ops) ren newfs = Prog m is ts fs' ops
  where fs' = map (replaceFunc (opts, ren)) fs ++ newfs

--- replace all PEVAL annotations by renamed expressions in a function.
replaceFunc :: (Options, Renaming) -> FuncDecl -> FuncDecl
replaceFunc env (Func n a v ty r) = Func n a v ty (replaceRule env r)

--- replace all PEVAL annotations by renamed expressions in a rule.
replaceRule :: (Options, Renaming) -> Rule -> Rule
replaceRule _   e@(External _) = e
replaceRule env (Rule    vs e) = Rule vs (replace env e)

--- replace all PEVAL annotations by renamed expressions in an expression.
replace :: (Options, Renaming) -> Expr -> Expr
replace _   v@(Var        _) = v
replace _   l@(Lit        _) = l
replace env c@(Comb ct n es) = case getPevalTarget (fst env) c of
  Just e' -> uncurry renameExpr env (topSQ e')
  Nothing -> Comb ct n (map (replace env) es)
replace env f@(Free    vs e) = case getPevalTarget (fst env) f of
  Just e' -> uncurry renameExpr env (topSQ e')
  Nothing -> Free vs (replace env e)
replace env o@(Or     e1 e2) = case getPevalTarget (fst env) o of
  Just e' -> uncurry renameExpr env (topSQ e')
  Nothing -> Or (replace env e1) (replace env e2)
replace env (Case   ct e bs) = Case ct (replace env e)
                                       (replace env `onBranchExps` bs)
replace env (Let       bs e) = Let     (map (second (replace env)) bs)
                                       (replace env e)
replace env (Typed     e ty) = Typed   (replace env e) ty

-- -----------------------------------------------------------------------------
-- Helper: Convert resultants to type-inferrec function declarations
-- ---------------------------------------------------------------------------

--- Create function declarations for a list of resultants
createFunctions :: Prog -> [Resultant] -> IO [FuncDecl]
createFunctions p rs = inferTypes p (map resultant2fundecl rs)

--- Infer the types for a list of function w.r.t. a given program.
inferTypes :: Prog -> [FuncDecl] -> IO [FuncDecl]
inferTypes p fs = inferNewFunctions p fs >>= \res -> case res of
  Left  err -> error $ "Error during type inference: " ++ err
  Right fs' -> return (map unAnnFuncDecl fs')

--- Transform a single resultant into a function declaration.
resultant2fundecl :: Resultant -> FuncDecl
resultant2fundecl ((f, vs), rhs)
  = renameFuncDecl $ Func f arity Private ty (Rule vs rhs)
  where
  arity = length vs
  ty    = foldr1 FuncType $ map TVar [0 .. arity]