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
--- Module for handling ICurry and related files
--- @author Marc Andre Wittorf

module ICurry.Files where

import ICurry.Types
import FlatCurry.Types
import FlatCurry.Annotated.Types
import FlatCurry.Annotated.Files
import FlatCurry.Annotated.TypeInference
import FileGoodies hiding (splitPath)
import FilePath
import Distribution

import System.CurryPath
import System.FrontendExec

import System
import Unsafe

--- Default search paths (obtained from CURRYPATH environment variable)
defaultPaths :: [String]
defaultPaths = unsafePerformIO $ do
  currypath <- getEnviron "CURRYPATH"
  return $ if null currypath
              then []
              else splitSearchPath currypath ++ sysLibPath

--- Basically the Maybe functor instance
--- @param f the function to transform a Just constructor's content
--- @param v the value
--- @return  the transformed value
mapJust :: (a -> b) -> Maybe a -> Maybe b
mapJust _ Nothing  = Nothing
mapJust f (Just x) = Just $ f x

--- Bindable IO action to normalize a file path
--- @param fp the file path
--- @return   the normalized file path (if applicable)
normaliser :: Maybe FilePath -> IO (Maybe FilePath)
normaliser = return . mapJust normalise

--- Make a lookup (returns Maybe) function to a get function (uses error)
--- @param lookupper the lookup function
--- @param err       the fallback function if nothing is found
--- @param paths     the lookup paths
--- @param modname   the module name to search
--- @return          the lookup function's result (or err if not found)
lookupToGet :: ([String] -> String -> IO (Maybe String))
            -> IO String
            -> [String]
            -> String
            -> IO String
lookupToGet lookupper err paths modname = do
  res <- lookupper paths modname
  case res of
       Nothing -> err
       Just x  -> return x

--- Look up a Curry file in the search paths
--- @param paths   the search paths
--- @param modname the module name
--- @return        the module's path
lookupCurryFile :: [String] -> String -> IO (Maybe String)
lookupCurryFile paths modname = lookupFileInPath base
                                                 [".curry", ".lcurry"]
                                                 (map (</> dir) paths)
                                >>= normaliser
  where
    (dir, base) = splitDirectoryBaseName $ modNameToPath modname

--- Get a Curry file in the search paths. Error if not found
--- @param paths   the search paths
--- @param modname the module name
--- @return        the module's path
getCurryFile :: [String] -> String -> IO String
getCurryFile = lookupToGet lookupCurryFile (error "Cannot find Curry file")

--- Look up a Typed FlatCurry file in the search paths
--- @param paths   the search paths
--- @param modname the module name
--- @return        the FlatCurry module's path
lookupFlatFile :: [String] -> String -> IO (Maybe String)
lookupFlatFile paths modname =
    lookupFileInPath base
                     [workaround ".fcy" ".tfcy"]
                     (map ((</> dir) . addCurrySubdir) paths)
    >>= normaliser
  where
    (dir, base) = splitDirectoryBaseName $ modNameToPath modname

--- Get a Typed FlatCurry file in the search paths. Error if not found
--- @param paths   the search paths
--- @param modname the module name
--- @return        the FlatCurry module's path
getFlatFile :: [String] -> String -> IO String
getFlatFile = lookupToGet lookupFlatFile
                          (error "Cannot find Typed FlatCurry file")

--- Look up a Type Dependency file in the search paths
--- @param paths   the search paths
--- @param modname the module name
--- @return        the Type Dependecy file's path
lookupTypeDepsFile :: [String] -> String -> IO (Maybe String)
lookupTypeDepsFile paths modname =
    lookupFileInPath base
                     [".ictdeps"]
                     (map ((</> dir) . addCurrySubdir) paths)
    >>= normaliser
  where
    (dir, base) = splitDirectoryBaseName $ modNameToPath modname

--- Look up a Type Dependency file. Don't append the .curry subdirectory
--- @param paths   the search paths
--- @param modname the module name
--- @return        the Type Dependecy file's path
lookupTypeDepsFileRaw :: [String] -> String -> IO (Maybe String)
lookupTypeDepsFileRaw paths modname =
    lookupFileInPath base
                     [".ictdeps"]
                     (map (</> dir) paths)
    >>= normaliser
  where
    (dir, base) = splitDirectoryBaseName $ modNameToPath modname

--- Get a Type Dependency file in the search paths. Error if not found
--- @param paths   the search paths
--- @param modname the module name
--- @return        the Type Dependecy file's path
getTypeDepsFile :: [String] -> String -> IO String
getTypeDepsFile =
  lookupToGet lookupTypeDepsFile
              (error "Cannot find ICurry type dependencies file")

--- Get a Type Dependency file. Don't append the .curry subdirectory. Error if not found
--- @param paths   the search paths
--- @param modname the module name
--- @return        the Type Dependecy file's path
getTypeDepsFileRaw :: [String] -> String -> IO String
getTypeDepsFileRaw =
  lookupToGet lookupTypeDepsFileRaw
              (error "Cannot find ICurry type dependencies file")

--- Look up an ICurry file in the search paths
--- @param paths   the search paths
--- @param modname the module name
--- @return        the ICurry module's path
lookupICurryFile :: [String] -> String -> IO (Maybe String)
lookupICurryFile paths modname =
    lookupFileInPath base
                     [".icy"]
                     (map ((</> dir) . addCurrySubdir) paths)
    >>= normaliser
  where
    (dir, base) = splitDirectoryBaseName $ modNameToPath modname

--- Look up an ICurry file in the search paths. Don't append .curry subdirectory
--- @param paths   the search paths
--- @param modname the module name
--- @return        the ICurry module's path
lookupICurryFileRaw :: [String] -> String -> IO (Maybe String)
lookupICurryFileRaw paths modname = lookupFileInPath base
                                                     [".icy"]
                                                     (map (</> dir) paths)
                                    >>= normaliser
  where
    (dir, base) = splitDirectoryBaseName $ modNameToPath modname

--- Get an ICurry file in the search paths. Error if not found
--- @param paths   the search paths
--- @param modname the module name
--- @return        the ICurry module's path
getICurryFile :: [String] -> String -> IO String
getICurryFile = lookupToGet lookupICurryFile
                            (error "Cannot find ICurry file")

--- Get an ICurry file in the search paths. Don't append .curry subdirectory. Error if not found
--- @param paths   the search paths
--- @param modname the module name
--- @return        the ICurry module's path
getICurryFileRaw :: [String] -> String -> IO String
getICurryFileRaw = lookupToGet lookupICurryFileRaw
                               (error "Cannot find ICurry file")

--- Get the root directory where a module is located under
---
--- Example: a module Foo.Bar that can be found under /dir/subdir/Foo/Bar.curry
--- will cause a return value of /dir/subdir
---
--- @param paths   the search paths
--- @param modname the module name
--- @return        the source's root directory
getPathForModule :: [String] ->  String -> IO String
getPathForModule paths modname = do
  curryfile <- getCurryFile paths modname
  let s = splitDirectories curryfile
      modIds = splitModuleIdentifiers modname
      l = length $ modIds
      pathParts = take (length s - l) s
      path = joinPath $ pathParts ++ currySubdir : modIds
  return path

--- Read a FlatCurry file. Error if not found
--- @param paths   the search paths
--- @param modname the module name
--- @return        the source file's abstract representation
readFlat :: [String] -> String -> IO (AProg TypeExpr)
readFlat paths modname = do
  fname <- getFlatFile paths modname
  contents <- readFile fname
  workaround (inferProg (read contents)
              >>= either
                    (\e -> putStrLn ("Error during FlatCurry type inference:\n"
                                                     ++ e)
                           >> exitWith 1)
                    return)
             (return $ read contents)

--- Read a Type Dependency file. Error if not found
--- @param paths   the search paths
--- @param modname the module name
--- @return        the Type Dependencies
readTypeDeps :: [String] -> String -> IO [NeededMapping]
readTypeDeps paths modname = do
  fname <- getTypeDepsFile paths modname
  contents <- readFile fname
  return $ read contents

--- Read an ICurry file. Error if not found
--- @param paths   the search paths
--- @param modname the module name
--- @return        the ICurry abstract representation
readICurry :: [String] -> String -> IO (IProg)
readICurry paths modname = do
  fname <- getICurryFile paths modname
  contents <- readFile fname
  return $ read contents

--- Read an ICurry file. Don't append .curry subdir. Error if not found
--- @param paths   the search paths
--- @param modname the module name
--- @return        the ICurry abstract representation
readICurryRaw :: [String] -> String -> IO (IProg)
readICurryRaw paths modname = do
  fname <- getICurryFileRaw paths modname
  contents <- readFile fname
  return $ read contents

--- Write a Type Dependency file. Find target directory based on source file
--- @param paths   the search paths
--- @param modname the module name (for finding correct path)
--- @param ms      the Type Dependencies
writeTypeDeps :: [String] -> String -> [NeededMapping] -> IO ()
writeTypeDeps paths modname ms = do
  filename <- getPathForModule paths modname >>= return . (<.> "ictdeps")
  writeFile filename $ show ms

--- Write an ICurry file. Find target directory based on source file
--- @param paths   the search paths
--- @param modname the module name (for finding correct path)
--- @param prog      the ICurry module
writeICurry :: [String] -> String -> IProg -> IO ()
writeICurry paths modname prog = do
  filename <- getPathForModule paths modname >>= return . (<.> "icy")
  writeFile filename $ show prog

--- The correct target for frontend invocation to translate to Typed FlatCurry
icurryFrontendTarget :: FrontendTarget
icurryFrontendTarget = workaround FCY TFCY

--- Get the module root path from a module path and its name
--- @param modname the module name
--- @param path    the module's path
--- @return        the root path
moduleRoot :: String -> FilePath -> FilePath
moduleRoot modname path = concat $ take (partsLen - remove) pathParts
  where
    remove = length $ splitModuleIdentifiers modname
    pathParts = splitPath path
    partsLen = length pathParts

--- Dispatch something based on if a compiler version is buggy
--- @param yes use this if buggy
--- @param no  use this if not buggy
--- @return    yes or no
workaround :: a -> a -> a
workaround yes no = if curryCompiler == "pakcs"
                       && (curryCompilerMajorVersion,
                           curryCompilerMinorVersion) <= (2,0)
                       then yes
                       else no