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
------------------------------------------------------------------------------
--- This library defines I/O actions to read Curry programs and
--- transform them into the FlatCurry representation.
--- This library has the same interface as `FlatCurry.Files`
--- but uses the compact data representation to store and read files,
--- if possible.
---
--- @author Michael Hanus, Finn Teegen
--- @version July 2024
------------------------------------------------------------------------------
{-# LANGUAGE CPP #-}

module FlatCurry.FilesRW where

import Control.Monad       ( unless, when )

import Data.Time           ( compareClockTime )
import Debug.Profile       ( getElapsedTimeNF )
import System.Directory    ( doesFileExist, findFileWithSuffix
                           , getFileWithSuffix, getModificationTime )
import System.FilePath     ( takeFileName, (</>), (<.>))
import System.CurryPath    ( inCurrySubdir, isValidModuleName
                           , stripCurrySuffix, modNameToPath
                           , lookupModuleSourceInLoadPath, getLoadPathForModule
                           )
import System.FrontendExec ( FrontendParams(..), FrontendTarget (..)
                           , defaultParams, setQuiet, callFrontendWithParams
                           )
import ReadShowTerm        ( readUnqualifiedTerm, showTerm )
import RW.Base             ( readDataFile, writeDataFile )

import FlatCurry.Types
import FlatCurry.TypesRW

-- For debugging purposes:
-- Show timings and differences between term and compact term representation?
showTimings :: Bool
showTimings = False

--- I/O action which parses a Curry module and returns the corresponding
--- FlatCurry program.
--- The argument is the module name (without suffix ".curry"
--- or ".lcurry") and the result is a FlatCurry term representing this
--- module.
---
--- If one wants to parse a Curry module in another directory,
--- e.g., the file `examples/Mod.curry`, one can use the operation
--- `runModuleAction` from module `System.CurryPath` of package `currypath`
--- to transform this I/O action so that it switches into the directory
--- before reading:
---
---     > runModuleAction readFlatCurry examples/Mod.curry
---
readFlatCurry :: String -> IO Prog
readFlatCurry modname =
  readFlatCurryWithParseOptions modname (setQuiet True defaultParams)

--- I/O action which parses a Curry module
--- with respect to some parser options and returns the
--- corresponding FlatCurry program.
--- The argument is the module name (without suffix ".curry"
--- or ".lcurry") and the result is a FlatCurry term representing this
--- module.
---
--- @param modname - the module name (without suffix ".curry")
--- @param options - parameters passed to the front end
readFlatCurryWithParseOptions :: String -> FrontendParams -> IO Prog
readFlatCurryWithParseOptions modname options = do
  unless (isValidModuleName modname) $ putStrLn $
    "WARNING: '" ++ modname ++ "' is not a valid module name!"
  mbsrc <- lookupModuleSourceInLoadPath modname
  case mbsrc of
    Nothing -> do -- no source file, try to find FlatCurry file in load path:
      loadpath <- getLoadPathForModule modname
      filename <- getFileWithSuffix
                     (flatCurryFileName (takeFileName modname)) [""]
                     loadpath
      readFlatCurryFile filename
    Just (dir,_) -> do
      callFrontendWithParams FCY options modname
      let fcyfile = dir </> outdir options </>
                    modNameToPath (takeFileName modname) <.> "fcy"
      readFlatCurryFile fcyfile

--- Transforms a name of a Curry module (with or without suffix ".curry"
--- or ".lcurry") into the name of the file containing the
--- corresponding FlatCurry program.
flatCurryFileName :: String -> String
flatCurryFileName prog = inCurrySubdir (stripCurrySuffix prog) <.> "fcy"

--- Transforms a name of a Curry module (with or without suffix ".curry"
--- or ".lcurry") into the name of the file containing the
--- corresponding FlatCurry program.
flatCurryIntName :: String -> String
flatCurryIntName prog = inCurrySubdir (stripCurrySuffix prog) <.> "fint"

--- I/O action which reads a FlatCurry program from a file in `.fcy` format.
--- In contrast to `readFlatCurry`, this action does not parse
--- a source program. Thus, the argument must be the name of an existing
--- file (with suffix `.fcy`) containing a FlatCurry program in `.fcy`
--- format and the result is a FlatCurry term representing this program.
--- For faster reading, it tries to read a compact representation
--- if it exists and is not older than the FlatCurry file.
--- If the compact representation does not exist, it is written (if possible)
--- to support faster readings in the future.
readFlatCurryFile :: String -> IO Prog
readFlatCurryFile filename = do
  let rwfile = filename <.> "rw"
  fnex <- doesFileExist filename
  rwex <- doesFileExist rwfile
  if fnex && rwex
    then do
      ftime   <- getModificationTime filename
      rwftime <- getModificationTime rwfile
      if compareClockTime rwftime ftime == LT
        then readFlatCurryTermFile
        else do
          (mbprog,rwtime) <- getElapsedTimeNF (readDataFile rwfile)
          maybe (error $ "Illegal data in file " ++ rwfile)
                (\prog ->
                  if not showTimings
                    then return prog
                    else do
                      putStrLn $ "\nReading " ++ filename
                      (terms,ttime) <- getElapsedTimeNF (readFlatCurryFile' filename)
                      putStrLn $ "Time: " ++ show ttime ++
                                " msecs / Compact reading: " ++
                                show rwtime ++ " msecs / speedup: " ++
                                show (fromInt ttime / fromInt rwtime)
                      if prog == terms -- safety check
                        then return prog
                        else error "Difference in compact terms!" )
                mbprog
    else readFlatCurryTermFile
 where
  readFlatCurryTermFile = do
    prog <- readFlatCurryFile' filename
    -- Ignore errors if the file is not writable:
    catch (writeFlatCurryDataFile filename prog) (\_ -> return ())
    return prog

readFlatCurryFile' :: String -> IO Prog
readFlatCurryFile' filename = do
  exfcy <- doesFileExist filename
  if exfcy
    then readExistingFCY filename
    else error $
           "EXISTENCE ERROR: FlatCurry file '" ++ filename ++ "' does not exist"
 where
  readExistingFCY fname = do
    filecontents <- readFile fname



    return (readUnqualifiedTerm ["FlatCurry.Types","Prelude"] filecontents)


--- I/O action which returns the interface of a Curry module, i.e.,
--- a FlatCurry program containing only "Public" entities and function
--- definitions without rules (i.e., external functions).
--- The argument is the module name (without suffix ".curry"
--- or ".lcurry") and the result is a FlatCurry term representing the
--- interface of this module.
---
--- If one wants to parse a Curry module in another directory,
--- e.g., the file `examples/Mod.curry`, one can use the operation
--- `runModuleAction` from module `System.CurryPath` of package `currypath`
--- to transform this I/O action so that it switches into the directory
--- before reading:
---
---     > runModuleAction readFlatCurryInt "examples/Mod.curry"
---
readFlatCurryInt :: String -> IO Prog
readFlatCurryInt modname = do
  readFlatCurryIntWithParseOptions modname (setQuiet True defaultParams)

--- I/O action which parses a Curry module
--- with respect to some parser options and returns the FlatCurry
--- interface of this program, i.e.,
--- a FlatCurry program containing only "Public" entities and function
--- definitions without rules (i.e., external functions).
--- The argument is the module name without suffix ".curry"
--- (or ".lcurry") and the result is a FlatCurry term representing the
--- interface of this module.
readFlatCurryIntWithParseOptions :: String -> FrontendParams -> IO Prog
readFlatCurryIntWithParseOptions modname options = do
  unless (isValidModuleName modname) $ putStrLn $
    "WARNING: '" ++ modname ++ "' is not a valid module name!"
  mbsrc <- lookupModuleSourceInLoadPath modname
  case mbsrc of
    Nothing -> do -- no source file, try to find FlatCurry file in load path:
      loadpath <- getLoadPathForModule modname
      filename <- getFileWithSuffix
                    (flatCurryIntName (takeFileName modname)) [""]
                    loadpath
      readFlatCurryFile filename
    Just (dir,_) -> do
      callFrontendWithParams FINT options modname
      let fintfile = dir </> outdir options </>
                     modNameToPath (takeFileName modname) <.> "fint"
      readFlatCurryFile fintfile

--- Writes a FlatCurry program into a file in `.fcy` format.
--- The file is written in the standard location for intermediate files,
--- i.e., in the 'flatCurryFileName' relative to the directory of the
--- Curry source program (which must exist!).
writeFlatCurry :: Prog -> IO ()
writeFlatCurry prog@(Prog mname _ _ _ _) = do
  mbsrc <- lookupModuleSourceInLoadPath mname
  case mbsrc of
    Nothing      -> error $ "Curry source file for module '" ++ mname ++
                            "' not found!"
    Just (dir,_) -> writeFlatCurryFile (flatCurryFileName (dir </> mname)) prog

--- Writes a FlatCurry program into a file in `.fcy` format.
--- The first argument must be the name of the target file
--- (usually with suffix `.fcy`).
writeFlatCurryFile :: String -> Prog -> IO ()
writeFlatCurryFile file prog = do



  writeFile file (showTerm prog)

  writeFlatCurryDataFile file prog

writeFlatCurryDataFile :: String -> Prog -> IO ()
writeFlatCurryDataFile file prog =
  if showTimings
    then do
      (_,rwtime) <- getElapsedTimeNF $ writeDataFile (file <.> "rw") prog
      putStrLn $ "File " ++ file  <.> "rw written in " ++ show rwtime ++ " ms"
    else writeDataFile (file <.> "rw") prog

--- Writes a FlatCurry program into a file in `.fcy` format.
--- The first argument must be the name of the target file
--- (usually with suffix `.fcy`).
writeFCY :: String -> Prog -> IO ()
writeFCY = writeFlatCurryFile

--- Returns the name of the FlatCurry file of a module in the load path,
--- if this file exists.
lookupFlatCurryFileInLoadPath :: String -> IO (Maybe String)
lookupFlatCurryFileInLoadPath modname =
  getLoadPathForModule modname >>=
  findFileWithSuffix (flatCurryFileName modname) [""]

--- Returns the name of the FlatCurry file of a module in the load path,
--- if this file exists.
getFlatCurryFileInLoadPath :: String -> IO String
getFlatCurryFileInLoadPath modname =
  getLoadPathForModule modname >>=
  getFileWithSuffix (flatCurryFileName modname) [""]