-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathLineProducer.fr
166 lines (137 loc) · 7.11 KB
/
LineProducer.fr
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
module suggest.LineProducer where
{--
@LineProducer@ is a class for all types that support a common interface for in-place
line-by-line processing.
Notable examples are @BufferedReader@, @File@, @URL@, and @stdin@, while
some line producers are not even IO dependent like @StringReader@.
-}
class LineProducer lp where
{-- The _onLine_ function is called to process a line whenever a line is produced.
> onLine lineProducer startValue handler
where _handler_ is a function that takes an accumulator of the same type as the _startValue_ and
the newly produced line as a @String@. The _handler_ returns the accumulated value as the result of a
state transition (most often that is IO).
The _onLine_ function returns the total accumulated value as the result of a state transition.
-}
onLine :: Mutable s lp → a → (a → String → ST s a) → ST s a
--- The _offLine_ function stops further production of lines.
offLine :: Mutable s lp → ST s ()
instance LineProducer BufferedReader where
onLine :: Mutable s BufferedReader → a → (a → String → ST s a) → ST s a
onLine bufferedReader value handler = do
line <- bufferedReader.readLine `catch` (\(e :: IOException) -> return Nothing)
case line of
Nothing -> do
offLine bufferedReader
return value
Just line -> do
newValue <- handler value line
onLine bufferedReader newValue handler
offLine :: Mutable s BufferedReader → ST s ()
offLine bufferedReader = bufferedReader.close
data StringReader = native java.io.StringReader where
native new :: String -> STMutable s StringReader
native close :: Mutable s StringReader -> ST s ()
instance LineProducer StringReader where
onLine :: Mutable s StringReader → a → (a → String → ST s a) → ST s a
onLine stringReader value handler = (BufferedReader.new stringReader) >>= \br -> onLine br value handler
offLine :: Mutable s StringReader → ST s ()
offLine stringReader = stringReader.close
data FileInputStream = native java.io.FileInputStream where
native new :: String -> STMutable s FileInputStream -- todo: needs to be changed in frege
throws FileNotFoundException
openReader :: String -> STMutable s BufferedReader -- todo: needs to be changed in frege
openReader fileName = do
fis <- FileInputStream.new (fileName :: String )
isr <- InputStreamReader.new fis "UTF-8"
BufferedReader.new isr
data LpFile = native java.io.File where
native new :: String -> STMutable s LpFile
native getPath :: Mutable s LpFile -> ST s String
-- convenience function, not really needed
makeMutable :: File -> STMutable s LpFile
makeMutable file = LpFile.new file.getPath
instance LineProducer LpFile where
onLine :: Mutable s LpFile → a → (a → String → ST s a) → ST s a
onLine file value handler = do
path <- file.getPath
bufferedReader <- openReader path -- todo allow more encodings than UTF-8
onLine bufferedReader value handler
offLine :: Mutable s LpFile → ST s ()
offLine file = do
throwST (EOFException.new "no more lines should be read")
return ()
data MalformedURLException = pure native java.net.MalformedURLException -- not needed after integration
derive Exceptional MalformedURLException
data LpURL = native java.net.URL where
native new :: String -> STMutable s LpURL throws MalformedURLException
native openStream :: Mutable s LpURL -> STMutable s InputStream throws IOException
instance LineProducer LpURL where
onLine :: Mutable s LpURL → a → (a → String → ST s a) → ST s a
onLine xurl value handler = do
uis <- LpURL.openStream xurl
isr <- InputStreamReader.new uis "UTF-8" -- todo: encoding depends on the protocol ...
bufferedReader <- BufferedReader.new isr
BufferedReader.onLine bufferedReader value handler
offLine :: Mutable s LpURL → ST s ()
offLine url = do
throwST (EOFException.new "no more lines should be read")
return ()
-- convenience output
printIOValue prefix ioValue = do -- same as: (prefix ++) . show <$> ioValue >>= println
value <- ioValue
println $ prefix ++ show value
main = do
testFile = File.new "TestFile.txt" -- since version 3.25, otherwise <- instead of =
println "----------------------------------------"
println "general file handling"
-- delete test file if it already existed
printIOValue "Test file deleted to clean up before start: " testFile.delete
println "create test file"
writeFile testFile.getPath $ unlines ["first line","second line","third line"]
printIOValue "File now exists: " testFile.exists
println "read test file in toto"
content <- readFile testFile.getPath
println "file content was:"
println content
println "append 2 lines"
appendFile testFile.getPath $ unlines ["fourth line","fifth line"]
println "----------------------------------------"
println "processing each line with a buffered reader, while keeping track of line numbers"
bufferedReader <- openReader testFile.getPath
count <- bufferedReader.onLine 0 $ \num line -> do
println $ show (num + 1) ++ ": " ++ line
return $ num + 1
println $ "total number of lines: " ++ show count
println "----------------------------------------"
println "processing each line from a file, pushing each line on a stack"
mutFile <- makeMutable testFile
stack <- mutFile.onLine [] $ \stack line -> return (line : stack)
println $ "total stack" ++ show stack
println "----------------------------------------"
println "reading only one line (a header for example)"
oneLineReader <- openReader testFile.getPath
header <- oneLineReader.onLine "" $ \state line -> do
offLine oneLineReader -- make sure the next read yields no more lines
return line
println $ "the header line is: " ++ show header
println "----------------------------------------"
println "processing each line with a non-IO impure reader, here: StringReader. (great for testing)"
stringReader <- StringReader.new $ unlines ["first","second","third"]
result <- stringReader.onLine 0 $ \num _ -> return $ num + 1
println $ "processing strings with eachLine works as expected: " ++ show (result == 3)
println "----------------------------------------"
println "reading from a URL"
urlReader <- LpURL.new "http://google.com"
result <- urlReader.onLine 0 $ \num line -> do
println $ show num ++ ": " ++ line
if (num > 3) then urlReader.offLine else return ()
return $ num + 1
println $ "processing strings with eachLine works as expected: " ++ show (result == 3)
cli = do
println "command line input (max 10 lines or send EOF via Cmd/Ctrl-D)"
stdin.onLine 0 $ \num line -> do
println $ show (num + 1) ++ ": " ++ line
if (num > 9) then stdin.offLine else return ()
return $ num + 1
return ()