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
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
|
{-# OPTIONS_FRONTEND -Wno-incomplete-patterns #-}
module RW.Generator where
import Data.List ( nub, (\\), intercalate, intersperse, last )
import Data.Maybe ( catMaybes )
import Control.Applicative
import Control.Monad
import qualified FlatCurry.Files as FCF
import qualified FlatCurry.Types as FCT
import AbstractCurry.Files
import AbstractCurry.Select
import AbstractCurry.Build
import AbstractCurry.Pretty
import AbstractCurry.Types
import Numeric (readNat)
import System.Environment
import System.FilePath ( (</>) )
import System.Process
import System.CurryPath ( lookupModuleSourceInLoadPath, runModuleAction
, stripCurrySuffix, splitModuleIdentifiers )
import System.Console.GetOpt
import RW.Build
import RW.Monad
version :: String
version = "2.0.0"
genClass :: [CFuncDecl] -> RWM CClassDecl
genClass pdcfs = do
module' <- getModuleName
fls <- getFunctionLayouts
return $ CClass (module', rwClassName rwNaming) Public (CContext []) [genericTypeVariableName]
[] (map (makeFunc module') fls ++ pdcfs)
where
makeFunc :: MName -> FunctionLayout -> CFuncDecl
makeFunc module' (FunctionLayout name t _) =
CFunc (module', name) 0 Public (CQualType (CContext []) t) []
genInstance :: CTypeDecl -> RWM CInstanceDecl
genInstance t = case t of
CType _ _ tvs _ _ -> do
fls <- getFunctionLayouts
funs <- mapM (genFunction t) fls
let baseMod = rwBaseModuleName rwNaming
return $ CInstance (baseMod, rwClassName rwNaming)
(CContext (classConstraint (rwClassName rwNaming) baseMod tvs))
[typeDeclToTypeExpr t] funs
_ -> error "(internal) Should've been a data declaration!"
genInstances :: [CTypeDecl] -> RWM [CInstanceDecl]
genInstances tds = mapM genInstance (filter (not . isTypeSyn) tds)
gen :: RWM CurryProg
gen = do
modname <- getModuleName
a <- getProgram
generatedInstances <- genInstances (types a)
return $ CurryProg modname
[progName a, (rwBaseModuleName rwNaming), "System.IO"]
Nothing [] generatedInstances [] [] []
genFunction :: CTypeDecl -> FunctionLayout -> RWM CFuncDecl
genFunction type' (FunctionLayout name _ genF) = do
rs <- genF type'
modname <- getModuleName
return $ CFunc (modname, name) 0 Public (CQualType (CContext [])
(typeDeclToTypeExpr type')) rs
allDataDefs :: CurryProg -> [QName]
allDataDefs = nub . map typeName . types
allDataUsed :: CurryProg -> [QName]
allDataUsed = nub . concatMap allDataInConstructor . constructors
where
allDataInConstructor cons = case cons of
(CCons _ _ tes) -> concatMap tconsOfType tes
(CRecord _ _ fds) -> concatMap tconsOfField fds
tconsOfField (CField _ _ te) = tconsOfType te
allPredefined :: Naming -> [CInstanceDecl] -> [QName]
allPredefined (Naming _ cn _) =
nub . map allPredefined' . filter ((== cn) . snd . instanceName)
where
instanceName (CInstance n _ _ _) = n
allPredefined' :: CInstanceDecl -> QName
allPredefined' (CInstance _ _ [te] _) = case te of
(CTCons n) -> n
_ -> case tconsArgsOfType te of
(Just (n, _)) -> n
Nothing -> error $ "allPredefined: " ++ show te ++
" should have been a base type!"
containsFunction :: CTypeDecl -> Bool
containsFunction td =
case td of
(CType _ _ _ cds _) -> any cf_ConsDecl cds
(CTypeSyn {}) -> False
where
cf_ConsDecl cd = case cd of
(CCons _ _ tes) -> any cf_TypeExpr tes
(CRecord _ _ fds) -> any cf_FieldDecl fds
cf_FieldDecl (CField _ _ te) = cf_TypeExpr te
cf_TypeExpr te = case te of
(CTCons _) -> False
(CFuncType {}) -> True
(CTApply te1 te2) -> cf_TypeExpr te1 || cf_TypeExpr te2
_ -> False
modules :: [QName] -> [MName]
modules = nub . map fst
defaultStrLn :: Int
defaultStrLn = 5
defaultAlphabetLength :: Int
defaultAlphabetLength = 26
runTool :: [String] -> [FunctionLayout] -> IO ()
runTool args fls = do
putStrLn toolBanner
(opts, prog) <- processOptions args
case prog of
[] -> putStrLn "No modules specified!\nUsage information: `--help'"
>> exitWith 1
ps -> do
mapM_ (runModuleAction (tryTransform opts) . stripCurrySuffix) ps
if optGenOpsFile opts
then do
putStrLn $ "\nGenerating parameterized read and write operations"
let generatedProg = generateOperations opts
modcmt = ["This module contains parameterized read/write operations",
"which allows to influence the form of generated",
"compact data (alphabet length, string length).\n"]
writeProg opts "." modcmt "" generatedProg
putStrLn $ "Generated module: " ++ show (progName generatedProg)
else return ()
where
tryTransform opts modname =
lookupModuleSourceInLoadPath modname >>=
maybe (putStrLn ("Module '" ++ modname ++ "' not found!") >> exitWith 1)
(\(dir,_) -> transform opts dir modname)
transform opts basedir modname = do
prog <- flatProgToAbstract <$> FCF.readFlatCurry modname
putStrLn $ "\nGenerating ReadWrite instances for '" ++ progName prog ++ "'"
let resultRWM = runRWM gen (Runtime (progName prog ++ "RW") fls prog [] [])
let generatedProg = fst resultRWM
let predefineds = map pre ["Int", "Float", "Char", "Bool", "[]", "Either",
"Maybe", "Ordering", "()", "(,)", "(,,)", "(,,,)"]
missing = (allDataUsed prog \\ allDataDefs prog) \\ predefineds
modcmt = ["This module has been generated by the tool `curry-rw-data`.",
"It contains instances of class `ReadWrite` for all types",
"defined in module `" ++ progName prog ++ "`.\n"]
warnopts = "-Wno-incomplete-patterns"
writeProg opts basedir modcmt warnopts generatedProg
putStrLn $ "ReadWrite instances generated for: " ++ ppData (allDataDefs prog)
putStrLn $ "Module 'RW.Base' defines instances for:\n" ++
ppData predefineds
unless (null missing) $
do putStrLn $
"\nMissing data definitions: " ++ show (ppData missing) ++
"\nPlease provide the definitions for the missing data types,\n" ++
"either by manually inserting them or by running this tool on\n" ++
"the following module(s) and then importing the resulting module(s):"
putStrLn $ " " ++ unwords (modules missing) ++ "\n"
let cfs = filter containsFunction (types prog)
unless (null cfs) $ putStrLn $
"Warning: The following data definitions contain function types: " ++
show (ppData $ map typeName cfs) ++
" Functions cannot be read or written."
unless (null $ getIllTypedDefinitions (snd resultRWM)) $ putStrLn $
"Warning: Typing of the following polymorphic type declarations " ++
"might be incomplete:\n" ++
show (intercalate ", " (getIllTypedDefinitions (snd resultRWM)))
mapM_ putStrLn (getErrors $ snd resultRWM)
writeProg opts basedir modcmt frontendopts p = do
let modids = splitModuleIdentifiers (progName p)
let fn = if null (optOutDir opts)
then (if basedir `elem` [".","./"] then id else (basedir </>))
(foldr1 (</>) modids ++ ".curry")
else optOutDir opts </> last modids ++ ".curry"
putStrLn $ "as module '" ++ progName p ++ "' stored in file '" ++
fn ++ "'..."
writeFile fn $
unlines (map ("-- "++) modcmt) ++
(if null frontendopts
then ""
else "{-# OPTIONS_FRONTEND " ++ frontendopts ++ " #-}\n\n") ++
showProg p ++ "\n"
ppData = intercalate ", " . map snd
showProg = prettyCurryProg
(setNoQualification (setShowLocalSigs True defaultOptions))
toolBanner :: String
toolBanner = unlines $ [border, text, border]
where
text = "ReadWrite instance generator for Curry (Version " ++ version ++ " of 19/10/2024)"
border = replicate (length text) '-'
processOptions :: [String] -> IO (CLOptions, [String])
processOptions args = do
let (funopts, args', opterrors) = getOpt Permute options args
dfltopts = CLOptions defaultStrLn defaultAlphabetLength "" False False
opts = foldl (flip id) dfltopts funopts
unless (null opterrors) $
putStrLn (unlines opterrors) >> printUsage >> exitWith 1
when (optHelp opts) $ printUsage >> exitWith 0
return (opts, args')
printUsage :: IO ()
printUsage = putStrLn $
usageInfo "Usage: curry-rw-data [options] <module names> ..." options
options :: [OptDescr (CLOptions -> CLOptions)]
options =
[ Option "h?"["help"] (NoArg (\opts -> opts {optHelp=True}))
"Print this help message"
, Option "d" ["outdir"] (ReqArg (\s opts -> opts { optOutDir = s }) "<d>")
"Write generated modules into directory <d>"
, Option "p" ["paramops"] (NoArg (\opts -> opts { optGenOpsFile = True }))
("Generate module containing read/write operations\n" ++
"with parameters (as in the subsequent options)")
, Option "s" ["stringlen"] (ReqArg (safeReadNat checkSl) "SLEN")
("Minimum length of extracted strings (default: " ++
show defaultStrLn ++ ")")
, Option "a" ["alphabet"] (ReqArg (safeReadNat checkAl) "ALEN")
("Alphabet length (default: " ++ show defaultAlphabetLength ++
")\nAlphabet length must be within [1..94]")
]
where
safeReadNat opttrans s opts = case readNat s of
[(n, "")] -> opttrans n opts
_ -> error ("Invalid number argument: " ++ s)
checkAl al opt
| al >= 1 && al <= 94 = opt {optAlphabetLength=al}
| otherwise = error "Alphabet length must be within [1..95]."
checkSl sl opt
| sl >= 0 = opt {optStringLength=sl}
| otherwise = error "Minimum string length must be non-negative."
generateOperations :: CLOptions -> CurryProg
generateOperations (CLOptions sl al _ _ _) =
CurryProg (modName) [baseModName] Nothing [] [] [] fs []
where
fs = [cfunc (modName, "writeDataFile") 1 Public qt1 [r1],
cfunc (modName, "showData") 1 Public qt2 [r2],
cfunc (modName, "readData") 1 Public qt3 [r3],
cfunc (modName, "readDataFile") 1 Public qt4 [r4],
stCmtFunc
("The parameters of the show/write operations:\n" ++
"minimum length of extract strings and alphabet length.")
paramsName 0 Public
(baseType (baseModName, "RWParameters")) [r5]]
context = CContext [((baseModName, "ReadWrite"), [genericTypeVariable])]
qt1 = CQualType context (stringType ~> genericTypeVariable ~> ioType unitType)
qt2 = CQualType context (genericTypeVariable ~> stringType)
qt3 = CQualType context (stringType ~> maybeType genericTypeVariable)
qt4 = CQualType context (stringType ~> ioType (maybeType genericTypeVariable))
r1 = CRule [] $ simpleRhs (applyF (baseModName, "writeDataFileP")
[CSymbol paramsName])
r2 = CRule [] $ simpleRhs (applyF (baseModName, "showDataP")
[CSymbol paramsName])
r3 = CRule [] $ simpleRhs (constF (baseModName, baseModName ++ ".readData"))
r4 = CRule [] $ simpleRhs (constF (baseModName, baseModName ++ ".readDataFile"))
r5 = CRule [] $ simpleRhs (applyF (baseModName, "RWParameters ")
[cInt sl, cInt al])
modName = rwParametrizedModuleName rwNaming
baseModName = rwBaseModuleName rwNaming
paramsName = (modName, "rwParams")
coding :: [Char]
coding = ['0'..'9'] ++ ['a'..'f']
logI :: Int -> Int -> Int
logI b n | b == 1 = n
| n <= b = 1
| otherwise = 1 + logI b (n `div` b)
codingI :: Int -> Int -> [Char]
codingI i cs | cs == 1 = ""
| otherwise = prefix ++ result
where
l = logI (length coding) cs
result = codingI' i
prefix = replicate (l - length result) '0'
codingI' n = (if n < length coding
then ""
else codingI' (n `div` length coding)) ++
[coding !! (n `mod` length coding)]
generatorShow :: FunctionGenerator
generatorShow typedecl =
return $ zipWith (forCons (rwBaseModuleName rwNaming, "showRW")) [0..] tcs
where
tcs = typeCons typedecl
forCons wfn i (CCons name _ tes) =
CRule (lhs name tes)
(CSimpleRhs (tupleExpr [CVar (length tes, "strs" ++ show (length tes)), outputExpr]) wheres)
where
outputExpr | length tcs == 1 && null tes = CApply (CSymbol ("Prelude", "showString")) (string2ac "")
| otherwise = applyExpr $ optimizeSingleConstructor tcs (outputConstr : rest)
outputConstr | length tcs <= length coding = applyF ("Prelude", "showChar") [cChar (coding !! i)]
| otherwise = applyF ("Prelude", "showString") [string2ac (codingI i (length tcs))]
rest = map (\index -> CVar (index, "show" ++ show (index + 1))) (fromIndex0 tes)
wheres = map (\rn -> CLocalPat (
tuplePattern [CPVar (rn + 1, "strs" ++ show (rn + 1)), CPVar (rn + 1 + length tes, "show" ++ show (rn+1))]
) (
CSimpleRhs (applyF wfn [CVar (0, "params"), CVar (rn, "strs" ++ show rn), CVar (rn + length tes, varName rn ++ "'")]) []
)) (fromIndex0 tes)
lhs name tes = [ paramsPat
, CPVar (0, "strs0")
, CPComb name (map (\index -> CPVar (index + (length tes + 1), varName index ++ "'")) (fromIndex0 tes))]
where
paramsPat | null tes = anonPattern
| otherwise = CPVar (0, "params")
generatorRead :: FunctionGenerator
generatorRead typedecl = return $ zipWith (forTargetCons (rwBaseModuleName rwNaming, "readRW")) [0..] tcs
where
tcs = typeCons typedecl
forTargetCons rfn i (CCons name _ tes)
= CRule (lhs (null tes) (codingI i (length tcs)))
(CSimpleRhs (
tupleExpr [
applyF name (map (\index -> CVar (index + length tes, varName index ++ "'")) (fromIndex0 tes)),
CVar (length tes, "r"++show (length tes))
]
) wheres)
where
wheres = map (\rn -> CLocalPat (
tuplePattern [CPVar (rn + length tes, varName rn ++ "'"), CPVar (rn+1, "r"++show (rn+1))]
) (
CSimpleRhs (CApply (CApply (CSymbol rfn) (CVar (0, "strs"))) (CVar (rn, "r"++show rn))) []
)) (fromIndex0 tes)
lhs anon cs = [ strsPat
, listRestPattern (map pChar cs ++ [CPVar (1, "r0")])]
where
strsPat | anon = anonPattern
| otherwise = CPVar (0, "strs")
generatorWrite :: FunctionGenerator
generatorWrite typedecl = return $ zipWith (forCons (rwBaseModuleName rwNaming, "writeRW")) [0..] tcs
where
tcs = typeCons typedecl
trivialCons = case tcs of
[CCons _ _ []] -> True
_ -> False
handlePat | trivialCons = anonPattern
| otherwise = CPVar (0, "h")
forCons wfn i (CCons name _ tes)
= CRule lhs (CSimpleRhs rhs [])
where
rhs | trivialCons = CApply (CSymbol $ pre "return") (CVar (length tes + 1, "strs"))
| null tes = applyF (pre ">>") [writeCons, CApply (CSymbol $ pre "return") (CVar (length tes + 1, "strs"))]
| length tcs == 1 = monad
| otherwise = applyF (pre ">>") [writeCons, monad]
writeCons | length tcs <= length coding = applyF ("System.IO","hPutChar") [CVar (0,"h"), cChar (coding !! i)]
| otherwise = applyF ("System.IO","hPutStr") [CVar (0,"h"), string2ac (codingI i (length tcs))]
monad = combineWithL (pre ">>=") (map (\index -> applyF wfn (CVar (0, "params") : (args index))) (fromIndex0 tes))
args index = appendIf (index == 0) [CVar (0, "h"), CVar (index + 1, varName index ++ "'")] (CVar (length tes + 1, "strs"))
lhs = [paramsPat, handlePat,
CPComb name (map (\index -> CPVar (index + 1, varName index ++ "'"))
(fromIndex0 tes)),
CPVar (length tes + 1, "strs")]
where
paramsPat | null tes = anonPattern
| otherwise = CPVar (0, "params")
generatorTypeOf :: FunctionGenerator
generatorTypeOf typedecl = do
if isMonomorphic typedecl
then return [CRule [anonPattern]
(CSimpleRhs (applyF (rwbaseName "monoRWType")
[string2ac (snd $ typeName typedecl)]) [])]
else return [rule]
where
rule = CRule [CPVar (0, "n")] (CSimpleRhs (resultExpr) getters)
resultExpr = applyF (rwbaseName "RWType")
[string2ac (snd $ typeName typedecl),
list2ac (map (\(_, n) -> applyF (rwbaseName "typeOf")
[CApply (CSymbol $ pre $ "get_" ++ n)
(CVar (0, "n"))])
(typeVars typedecl))]
getters = map getter (typeVars typedecl)
where
getter (_, n) = CLocalFunc (CFunc (rwbaseName $ "get_" ++ n) 1 Public
qual [undefRule])
where
qual = CQualType (CContext []) $
applyTC (typeName typedecl)
(map (\(i, a) -> CTVar (i, a ++ "'" )) (typeVars typedecl))
~> CTVar (length $ typeVars typedecl, n ++ "'" )
undefRule = CRule [anonPattern] (CSimpleRhs (CSymbol $ pre "failed") [])
rwbaseName :: String -> QName
rwbaseName s = ("RW.Base",s)
main :: IO ()
main = do
args <- getArgs
runWith args
runWith :: [String] -> Prelude.IO ()
runWith args = runTool args
[ FunctionLayout "readRW"
(listType (tupleType [stringType, stringType]) ~> stringType
~> tupleType [genericTypeVariable, stringType])
generatorRead
, FunctionLayout "showRW"
(mapStrStr ~> genericTypeVariable
~> tupleType [mapStrStr, CTCons ("Text.Show", "ShowS")])
generatorShow
, FunctionLayout "writeRW"
(CTCons ("System.IO", "Handle") ~> genericTypeVariable ~> mapStrStr
~> ioType mapStrStr)
generatorWrite
, FunctionLayout "typeOf"
(genericTypeVariable ~> CTCons (rwbaseName "RWType"))
generatorTypeOf
]
where
mapStrStr =
CTApply (CTApply (CTCons ("Data.Map", "Map")) stringType) stringType
|