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
-----------------------------------------------------------------------
--- This module defines a datatype to represent the analysis information.
---
--- @author Heiko Hoffmann, Michael Hanus
--- @version July 2024
-----------------------------------------------------------------------
{-# OPTIONS_FRONTEND -Wno-incomplete-patterns #-}

module Analysis.ProgInfo
  ( ProgInfo, emptyProgInfo, lookupProgInfo, combineProgInfo
  , lists2ProgInfo, publicListFromProgInfo, progInfo2Lists
  , mapProgInfo, publicProgInfo
  , showProgInfo, equalProgInfo
  , readAnalysisFiles, readAnalysisPublicFile, writeAnalysisFiles
  ) where

import Prelude hiding   ( empty, lookup )

import Debug.Profile    ( getElapsedTimeNF )
import Data.Map
import Data.Time        ( compareClockTime )
import FlatCurry.Types
import RW.Base
import System.Directory ( doesFileExist, getModificationTime, removeFile )
import System.FilePath  ( (<.>) )
import System.IO        ( hPutChar )

import Analysis.Logging ( DLevel, debugMessage )

--- Type to represent analysis information.
--- The first component are public declarations, the second the private ones.
data ProgInfo a = ProgInfo (Map QName a) (Map QName a)

--- The empty program information.
emptyProgInfo:: ProgInfo a
emptyProgInfo = ProgInfo empty empty

--- Gets the information about an entity.
lookupProgInfo:: QName -> ProgInfo a -> Maybe a
lookupProgInfo key (ProgInfo map1 map2) =
 case lookup key map1 of
  Just x -> Just x
  Nothing ->  lookup key map2

--- Combines two analysis informations.
combineProgInfo :: ProgInfo a -> ProgInfo a -> ProgInfo a
combineProgInfo (ProgInfo x1 x2) (ProgInfo y1 y2) =
  ProgInfo (union x1 y1) (union x2 y2)

--- Converts a public and a private analysis list into a program info.
lists2ProgInfo :: ([(QName,a)],[(QName,a)]) -> ProgInfo a
lists2ProgInfo (xs,ys) = ProgInfo (fromList xs) (fromList ys)

--- Returns the infos of public operations as a list.
publicListFromProgInfo:: ProgInfo a -> [(QName,a)]
publicListFromProgInfo (ProgInfo fm1 _) = toList fm1

--- Transforms a program information into a pair of lists
--- containing the information about public and private entities.
progInfo2Lists :: ProgInfo a -> ([(QName,a)],[(QName,a)])
progInfo2Lists (ProgInfo map1 map2) = (toList map1, toList map2)

--- Transforms a program information by applying a function to all
--- information entities.
mapProgInfo:: (a -> b) -> ProgInfo a -> ProgInfo b
mapProgInfo func (ProgInfo map1 map2) =
  ProgInfo (mapWithKey (\_ b->func b) map1) (mapWithKey (\_ b->func b) map2)

--- Transforms a program information into a program information
--- about interface entities only.
publicProgInfo :: ProgInfo a -> ProgInfo a
publicProgInfo (ProgInfo pub _) = ProgInfo pub empty

--- Show a ProgInfo as a string (used for debugging only).
showProgInfo :: Show a => ProgInfo a -> String
showProgInfo (ProgInfo fm1 fm2) =
  "Public: "++show fm1++"\nPrivate: "++show fm2

-- Equality on ProgInfo
equalProgInfo :: Eq a => ProgInfo a -> ProgInfo a -> Bool
equalProgInfo (ProgInfo pi1p pi1v) (ProgInfo pi2p pi2v) =
   pi1p == pi2p && pi1v == pi2v

--- Writes a ProgInfo into a file.
writeAnalysisFiles :: (ReadWrite a, Show a) => DLevel -> String -> ProgInfo a
                   -> IO ()
writeAnalysisFiles dl basefname (ProgInfo pub priv) = do
  debugMessage dl 3 $ "Writing analysis files '" ++ basefname ++ "'..."
  writeTermFile dl (basefname <.> "priv") priv
  writeTermFile dl (basefname <.> "pub" )  pub

--- Reads a ProgInfo from the analysis files where the base file name is given.
readAnalysisFiles :: (Read a, ReadWrite a) => DLevel -> String
                  -> IO (ProgInfo a)
readAnalysisFiles dl basefname = do
  debugMessage dl 3 $ "Reading analysis files '" ++ basefname ++ "'..."
  let pubcontfile  = basefname <.> "pub"
      privcontfile = basefname <.> "priv"
  pubinfo  <- readTermFile (fromEnum dl > 2) pubcontfile
  privinfo <- readTermFile (fromEnum dl > 2) privcontfile
  let pinfo = ProgInfo pubinfo privinfo
  catch (return $!! pinfo)
        (\err -> do
           putStrLn $ "Buggy analysis files detected and removed:\n" ++
                      basefname
           mapM_ removeFile [pubcontfile,privcontfile]
           putStrLn "Please try to re-run the analysis!"
           ioError err)

--- Reads the public ProgInfo from the public analysis file.
readAnalysisPublicFile :: (Read a, ReadWrite a) => DLevel -> String
                       -> IO (ProgInfo a)
readAnalysisPublicFile dl fname = do
  debugMessage dl 3 $ "Reading public analysis file '" ++ fname ++ "'..."
  pubinfo <- readTermFile (fromEnum dl > 2) fname
  let pinfo = ProgInfo pubinfo empty
  catch (return $!! pinfo)
        (\err -> do
           putStrLn $ "Buggy analysis files detected and removed:\n" ++ fname
           removeFile fname
           putStrLn "Please try to re-run the analysis!"
           ioError err)

------------------------------------------------------------------------------
-- Reading/writing data files.

--- Writes data as a term and a compact term into a file.
writeTermFile :: (ReadWrite a, Show a) => DLevel -> String -> a -> IO ()
writeTermFile _ fname x = do
  writeFile fname (show x)
  writeDataFile (fname <.> "rw") x

--- Reads data in term representation from a file.
--- Try to read compact representation if it exists and
--- is not older than the term file.
--- If the first argument is `True`, read also the term file and report
--- the timings of reading this file and the compact data file.
readTermFile :: (ReadWrite a, Read a) => Bool -> String -> IO a
readTermFile reporttimings fname = do
  let rwfile = fname <.> "rw"
      readtermfile = fmap read (readFile fname)
  rwex <- doesFileExist rwfile
  if rwex
    then do
      ftime   <- getModificationTime fname
      rwftime <- getModificationTime rwfile
      if compareClockTime rwftime ftime == LT
        then readtermfile
        else do
          (mbterms,rwtime) <- getElapsedTimeNF (readDataFile rwfile)
          maybe (error $ "Illegal data in file " ++ rwfile)
                (\rwterms ->
                  if not reporttimings
                    then return rwterms
                    else do
                      putStrLn $ "\nReading " ++ fname
                      (_,ttime) <- getElapsedTimeNF readtermfile
                      putStr $ "Time: " ++ show ttime ++
                               " msecs / Compact reading: " ++
                               show rwtime ++ " msecs / speedup: " ++
                               show (fromInt ttime / fromInt rwtime)
                      return rwterms )
                mbterms
    else readtermfile

------------------------------------------------------------------------------
--- `ReadWrite` instance of `Map`.
instance (ReadWrite a,ReadWrite b) => ReadWrite (Map a b) where
  readRW _    ('0' : r0) = (Tip,r0)
  readRW strs ('1' : r0) = (Bin a' b' c' d' e',r5)
    where
      (a',r1) = readRW strs r0
      (b',r2) = readRW strs r1
      (c',r3) = readRW strs r2
      (d',r4) = readRW strs r3
      (e',r5) = readRW strs r4

  showRW _      strs0 Tip = (strs0,showChar '0')
  showRW params strs0 (Bin a' b' c' d' e') =
    (strs5,showChar '1' . (show1 . (show2 . (show3 . (show4 . show5)))))
    where
      (strs1,show1) = showRW params strs0 a'
      (strs2,show2) = showRW params strs1 b'
      (strs3,show3) = showRW params strs2 c'
      (strs4,show4) = showRW params strs3 d'
      (strs5,show5) = showRW params strs4 e'

  writeRW _      h Tip strs = hPutChar h '0' >> return strs
  writeRW params h (Bin a' b' c' d' e') strs =
    hPutChar h '1'
     >> ((((writeRW params h a' strs >>= writeRW params h b')
            >>= writeRW params h c')
           >>= writeRW params h d')
          >>= writeRW params h e')

  typeOf n = RWType "Map" [typeOf (get_a n),typeOf (get_b n)]
    where
      get_a :: Map a' b' -> a'
      get_a _ = failed
      get_b :: Map a' b' -> b'
      get_b _ = failed

------------------------------------------------------------------------------