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
  | 
import Control.Monad        ( when )
import Data.Char            ( digitToInt )
import Data.List
import System.Directory     ( copyFile, renameFile )
import System.FilePath
import AbstractCurry.Types
import AbstractCurry.Files  ( readCurry, readUntypedCurry )
import AbstractCurry.Pretty ( showCProg )
import System.CurryPath     ( stripCurrySuffix )
import System.CPUTime       ( getCPUTime )
import System.Environment   ( getEnv, getArgs, setEnv )
import System.Process       ( exitWith )
import CPP.DefaultRules     ( translateDefaultRulesAndDetOps )
import CPP.Contracts        ( translateContracts )
import TransICode           ( translateIntCode )
cppBanner :: String
cppBanner = unlines [bannerLine,bannerText,bannerLine]
 where
   bannerText = "Curry Preprocessor (version of 01/11/2021)"
   bannerLine = take (length bannerText) (repeat '=')
data PPTarget = ForeignCode | DefaultRules | Contracts
 deriving Eq
parseTarget :: String -> Maybe PPTarget
parseTarget t | t == "foreigncode"  = Just ForeignCode
              | t == "defaultrules" = Just DefaultRules
              | t == "contracts"    = Just Contracts
              | otherwise           = Nothing
data PPOpts =
  PPOpts { optHelp      :: Bool
         , optSave      :: Bool       
         , optVerb      :: Int        
         , optTgts      :: [PPTarget] 
         , optModel     :: String     
         , optDefRules  :: [String]   
         , optContracts :: [String]   
         }
initOpts :: PPOpts
initOpts = PPOpts { optHelp      = False
                  , optSave      = False
                  , optVerb      = 1
                  , optTgts      = []
                  , optModel     = ""
                  , optDefRules  = []
                  , optContracts = []
                  }
main :: IO ()
main = do
  args <- getArgs
  case args of
    (orgSourceFile:inFile:outFile:options) ->
       maybe (showUsage args)
             (\opts ->
               if optHelp opts
                 then putStrLn (cppBanner ++ usageText) >> exitWith 1
                 else do
                  cpath <- getEnv "CURRYPATH"
                  let (mbdir, modname) = pathToModName cpath orgSourceFile
                  when (optVerb opts > 1) $ putStr cppBanner
                  when (optVerb opts > 2) $ putStr $ unlines
                    ["CURRYPATH          : " ++ cpath
                    ,"Module name        : " ++ modname
                    ,"Original file name : " ++ orgSourceFile
                    ,"Input    file name : " ++ inFile
                    ,"Output   file name : " ++ outFile ]
                  addDir2CurryPath opts cpath mbdir $
                    preprocess opts modname orgSourceFile inFile outFile
                  when (optSave opts) $ saveFile orgSourceFile outFile
                  when (optVerb opts > 3) $ do
                    putStrLn "TRANSFORMED PROGRAM:"
                    putStrLn "===================="
                    readFile outFile >>= putStrLn
                    putStrLn "--------------------"
             )
             (processOptions initOpts options)
    _ -> maybe (showUsage args)
               (\opts -> if optHelp opts
                           then putStrLn (cppBanner ++ usageText)
                           else showUsage args)
               (processOptions initOpts args)
 where
  
  addDir2CurryPath _    _     Nothing    act = act
  addDir2CurryPath opts cpath (Just dir) act
    | dir == "." = act
    | otherwise = do
      when (optVerb opts > 2) $ putStrLn $
        "Adding directory '" ++ dir ++ "' to CURRYPATH"
      let newcpath = if null cpath then dir
                                   else dir ++ [searchPathSeparator] ++ cpath
      setEnv "CURRYPATH" newcpath
      act
      setEnv "CURRYPATH" cpath
  saveFile orgSourceFile outFile = do
    let sFile = orgSourceFile++".CURRYPP"
    copyFile outFile sFile
    putStrLn $ "Translated Curry file written to '" ++ sFile ++ "'"
processOptions :: PPOpts -> [String] -> Maybe PPOpts
processOptions opts optargs = case optargs of
    []                -> Just opts
    ("-h":_)          -> Just opts { optHelp = True}
    ("-?":_)          -> Just opts { optHelp = True}
    ("-o":os)         -> processOptions opts { optSave = True } os
    ("-v":os)         -> processOptions opts { optVerb = 2 } os
    (['-','v',vl]:os) -> if isDigit vl
                         then processOptions opts { optVerb = digitToInt vl } os
                         else Nothing
    (('-':'-':ts):os) -> if isPrefixOf "model:" ts
                         then processOptions
                                opts {optModel = tail (dropWhile (/=':') ts) }
                                os
                         else Nothing
    (o:os)  -> if o `elem` ["-e","-t"]
               then processOptions
                      opts {optContracts = optContracts opts ++ [o]} os
               else
                if o `elem` ["nodupscheme","specscheme"]
                then processOptions
                       opts {optDefRules = optDefRules opts ++ [o]} os
                else
                  maybe Nothing
                        (\t -> processOptions
                                 opts {optTgts = t : optTgts opts} os)
                        (parseTarget o)
showUsage :: [String] -> IO ()
showUsage args = do
  putStr cppBanner
  putStrLn $ "\nERROR: Illegal arguments: " ++ unwords args ++ "\n"
  putStrLn usageText
  exitWith 1
usageText :: String
usageText = unlines $
 [ ""
 , "Usage: currypp <OrgFileName> <InputFilePath> <OutputFilePath> <options>\n"
 , "<OrgFileName>   : name of original program source file"
 , "<InputFilePath> : name of the actual input file"
 , "<OutputFilePath>: name of the file where output should be written\n"
 , "where <options> contain preprocessing targets"
 , "(if no target is given, 'foreigncode defaultrules contracts' are used)\n"
 , "foreigncode  : translate foreign code pieces in the source file"
 , "--model:<ERD_Name>_UniSQLCode.info :"
 , "               data model to translate embedded SQL statements"
 , "defaultrules : implement default rules"
 , "contracts    : implement dynamic contract checking"
 , ""
 , "and optional settings:"
 , "-o           : store output also in file <OrgFileName>.CURRYPP"
 , "-v           : same as -v2"
 , "-v<n>        : show more information about the preprocessor:"
 , "               <n>=0 : quiet"
 , "               <n>=1 : show some information (default)"
 , "               <n>=2 : show more information, e.g., version, timing"
 , "               <n>=3 : show much more information, e.g., used file names"
 , "               <n>=4 : show also transformed Curry program"
 , "-h|-?        : show help message and quit"
 , ""
 , "For target 'defaultrules':"
 , "specscheme   : default translation scheme (as in PADL'16 paper)"
 , "nodupscheme  : translation scheme without checking conditions twice"
 , ""
 , "For target 'contracts':"
 , "-e           : encapsulate nondeterminism of assertions"
 , "-t           : assert contracts only to top-level (not recursive) calls"
 ]
preprocess :: PPOpts -> String -> String -> String -> String -> IO ()
preprocess opts modname orgfile infile outfile
  | null pptargets
  = 
    preprocess opts { optTgts = [ForeignCode, DefaultRules, Contracts] }
               modname orgfile infile outfile
  | otherwise
  = do let savefile = orgfile++".SAVEPPORG"
       starttime <- getCPUTime
       renameFile orgfile savefile
       srcprog <- readFile (if orgfile==infile then savefile else infile)
                    >>= return . replaceOptionsLine
       
       writeFile orgfile srcprog
       outtxt <- catch (callPreprocessors opts (optionLines srcprog)
                                          modname srcprog orgfile)
                       (\err -> renameFile savefile orgfile >> ioError err)
       writeFile outfile outtxt
       renameFile savefile orgfile
       stoptime <- getCPUTime
       when (optVerb opts > 1) $ putStrLn
         ("Transformation time: " ++
         show (stoptime-starttime) ++ " ms")
 where
  pptargets = optTgts opts
callPreprocessors :: PPOpts -> String -> String -> String -> String
                  -> IO String
callPreprocessors opts optlines modname srcprog orgfile
  | ForeignCode `elem` pptargets
  = do icouttxt <- translateIntCode verb (optModel opts) orgfile srcprog
       if null (intersect [DefaultRules, Contracts] pptargets)
        then return icouttxt 
        else do writeFile orgfile icouttxt
                let rpptargets = delete ForeignCode pptargets
                callPreprocessors opts {optTgts = rpptargets}
                                  optlines modname icouttxt orgfile
  | DefaultRules `elem` pptargets
  = do 
       
       mbdefprog <- readUntypedCurry modname >>=
                    translateDefaultRulesAndDetOps verb defopts srcprog
       let newsrcprog = maybe srcprog showCProg mbdefprog
       if Contracts `elem` pptargets
        then do
          maybe (return ())
                (\defprog -> writeFile orgfile (optlines ++ showCProg defprog))
                mbdefprog
          readCurry modname >>= translateContracts verb contopts modname
                                                   srcprog
                            >>= return . maybe newsrcprog showCProg
        else return newsrcprog
  | Contracts `elem` pptargets
  = readCurry modname >>= translateContracts verb contopts modname srcprog
                      >>= return . maybe srcprog showCProg
  | otherwise
  = error "currypp internal error during dispatching"
 where
  pptargets = optTgts opts
  verb      = optVerb opts
  defopts   = optDefRules opts
  contopts  = optContracts opts
pathToModName :: String -> String -> (Maybe String, String)
pathToModName currypath psf =
  tryRemovePathPrefix (prefixLast (splitSearchPath currypath))
 where
  pp = stripCurrySuffix psf
  tryRemovePathPrefix [] =
    let (dir,bname) = splitFileName pp
    in (Just (dropTrailingPathSeparator dir), bname)
  tryRemovePathPrefix (dir:dirs)
    | dir `isPrefixOf` pp = (Nothing, dirPath2mod $ drop (length dir + 1) pp)
    | otherwise           = tryRemovePathPrefix dirs
  
  dirPath2mod = intercalate "." . splitDirectories
  
  
  
  prefixLast []     = []
  prefixLast (x:xs) =
    let (longer,rest) = partition (x `isPrefixOf`) xs
    in if null longer then x : prefixLast xs
                      else prefixLast (filter (/=x) longer ++ x : rest)
replaceOptionsLine :: String -> String
replaceOptionsLine = unlines . map replOptLine . lines
 where
  replOptLine s = if isOptionLine s && "currypp" `isInfixOf` s
                  then " "
                  else s
isOptionLine :: String -> Bool
isOptionLine s =
 "{-# OPTIONS_CYMAKE "   `isPrefixOf` dropWhile isSpace s ||
 "{-# OPTIONS_FRONTEND " `isPrefixOf` dropWhile isSpace s     
optionLines :: String -> String
optionLines = unlines . filter isOptionLine . lines
 |