rlm@10
|
1 ;; Monadic I/O
|
rlm@10
|
2
|
rlm@10
|
3 ;; by Konrad Hinsen
|
rlm@10
|
4 ;; last updated June 24, 2009
|
rlm@10
|
5
|
rlm@10
|
6 ;; Copyright (c) Konrad Hinsen, 2009. All rights reserved. The use
|
rlm@10
|
7 ;; and distribution terms for this software are covered by the Eclipse
|
rlm@10
|
8 ;; Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
|
rlm@10
|
9 ;; which can be found in the file epl-v10.html at the root of this
|
rlm@10
|
10 ;; distribution. By using this software in any fashion, you are
|
rlm@10
|
11 ;; agreeing to be bound by the terms of this license. You must not
|
rlm@10
|
12 ;; remove this notice, or any other, from this software.
|
rlm@10
|
13
|
rlm@10
|
14 (ns
|
rlm@10
|
15 ^{:author "Konrad Hinsen"
|
rlm@10
|
16 :doc "Monadic I/O with Java input/output streams
|
rlm@10
|
17 Defines monadic I/O statements to be used in a state monad
|
rlm@10
|
18 with an input or output stream as the state. The macro
|
rlm@10
|
19 monadic-io creates a stream, runs a monadic I/O statement
|
rlm@10
|
20 on it, and closes the stream. This structure permits the
|
rlm@10
|
21 definition of purely functional compound I/O statements
|
rlm@10
|
22 which are applied to streams that can never escape from the
|
rlm@10
|
23 monadic statement sequence."}
|
rlm@10
|
24 clojure.contrib.monadic-io-streams
|
rlm@10
|
25 (:refer-clojure :exclude (read-line print println flush))
|
rlm@10
|
26 (:use [clojure.contrib.monads
|
rlm@10
|
27 :only (with-monad domonad state-m state-m-until)])
|
rlm@10
|
28 (:use [clojure.contrib.generic.functor :only (fmap)])
|
rlm@10
|
29 (:use [clojure.java.io :only (reader writer)]))
|
rlm@10
|
30
|
rlm@10
|
31 ;
|
rlm@10
|
32 ; Wrap the state into a closure to make sure that "evil" code
|
rlm@10
|
33 ; can't obtain the stream using fetch-state and manipulate it.
|
rlm@10
|
34 ;
|
rlm@10
|
35 (let [key (Object.)
|
rlm@10
|
36 lock (fn [state] (fn [x] (if (identical? x key) state nil)))
|
rlm@10
|
37 unlock (fn [state] (state key))]
|
rlm@10
|
38
|
rlm@10
|
39 ;
|
rlm@10
|
40 ; Basic stream I/O statements as provided by Java
|
rlm@10
|
41 ;
|
rlm@10
|
42 (defn read-char
|
rlm@10
|
43 "Read a single character"
|
rlm@10
|
44 []
|
rlm@10
|
45 (fn [s] [(.read (unlock s)) s]))
|
rlm@10
|
46
|
rlm@10
|
47 (defn read-line
|
rlm@10
|
48 "Read a single line"
|
rlm@10
|
49 []
|
rlm@10
|
50 (fn [s] [(.readLine (unlock s)) s]))
|
rlm@10
|
51
|
rlm@10
|
52 (defn skip-chars
|
rlm@10
|
53 "Skip n characters"
|
rlm@10
|
54 [n]
|
rlm@10
|
55 (fn [s] [(.skip (unlock s) n) s]))
|
rlm@10
|
56
|
rlm@10
|
57 (defn write
|
rlm@10
|
58 "Write text (a string)"
|
rlm@10
|
59 [^String text]
|
rlm@10
|
60 (fn [s] [(.write (unlock s) text) s]))
|
rlm@10
|
61
|
rlm@10
|
62 (defn flush
|
rlm@10
|
63 "Flush"
|
rlm@10
|
64 []
|
rlm@10
|
65 (fn [s] [(.flush (unlock s)) s]))
|
rlm@10
|
66
|
rlm@10
|
67 (defn print
|
rlm@10
|
68 "Print obj"
|
rlm@10
|
69 [obj]
|
rlm@10
|
70 (fn [s] [(.print (unlock s) obj) s]))
|
rlm@10
|
71
|
rlm@10
|
72 (defn println
|
rlm@10
|
73 "Print obj followed by a newline"
|
rlm@10
|
74 ([]
|
rlm@10
|
75 (fn [s] [(.println (unlock s)) s]))
|
rlm@10
|
76 ([obj]
|
rlm@10
|
77 (fn [s] [(.println (unlock s) obj) s])))
|
rlm@10
|
78
|
rlm@10
|
79 ;
|
rlm@10
|
80 ; Inject I/O streams into monadic I/O statements
|
rlm@10
|
81 ;
|
rlm@10
|
82 (defn with-reader
|
rlm@10
|
83 "Create a reader from reader-spec, run the monadic I/O statement
|
rlm@10
|
84 on it, and close the reader. reader-spec can be any object accepted
|
rlm@10
|
85 by clojure.contrib.io/reader."
|
rlm@10
|
86 [reader-spec statement]
|
rlm@10
|
87 (with-open [r (reader reader-spec)]
|
rlm@10
|
88 (first (statement (lock r)))))
|
rlm@10
|
89
|
rlm@10
|
90 (defn with-writer
|
rlm@10
|
91 "Create a writer from writer-spec, run the monadic I/O statement
|
rlm@10
|
92 on it, and close the writer. writer-spec can be any object accepted
|
rlm@10
|
93 by clojure.contrib.io/writer."
|
rlm@10
|
94 [writer-spec statement]
|
rlm@10
|
95 (with-open [w (writer writer-spec)]
|
rlm@10
|
96 (first (statement (lock w)))))
|
rlm@10
|
97
|
rlm@10
|
98 (defn with-io-streams
|
rlm@10
|
99 "Open one or more streams as specified by io-spec, run a monadic
|
rlm@10
|
100 I/O statement on them, and close the streams. io-spec is
|
rlm@10
|
101 a binding-like vector in which each stream is specified by
|
rlm@10
|
102 three element: a keyword by which the stream can be referred to,
|
rlm@10
|
103 the stream mode (:read or :write), and a stream specification as
|
rlm@10
|
104 accepted by clojure.contrib.io/reader (mode :read) or
|
rlm@10
|
105 clojure.contrib.io/writer (mode :write). The statement
|
rlm@10
|
106 is run on a state which is a map from keywords to corresponding
|
rlm@10
|
107 streams. Single-stream monadic I/O statements must be wrapped
|
rlm@10
|
108 with clojure.contrib.monads/with-state-field."
|
rlm@10
|
109 [io-specs statement]
|
rlm@10
|
110 (letfn [(run-io [io-specs state statement]
|
rlm@10
|
111 (if (zero? (count io-specs))
|
rlm@10
|
112 (first (statement state))
|
rlm@10
|
113 (let [[[key mode stream-spec] & r] io-specs
|
rlm@10
|
114 opener (cond (= mode :read) reader
|
rlm@10
|
115 (= mode :write) writer
|
rlm@10
|
116 :else (throw
|
rlm@10
|
117 (Exception.
|
rlm@10
|
118 "Mode must be :read or :write")))]
|
rlm@10
|
119 (with-open [stream (opener stream-spec)]
|
rlm@10
|
120 (run-io r (assoc state key (lock stream)) statement)))))]
|
rlm@10
|
121 (run-io (partition 3 io-specs) {} statement))))
|
rlm@10
|
122
|
rlm@10
|
123 ;
|
rlm@10
|
124 ; Compound I/O statements
|
rlm@10
|
125 ;
|
rlm@10
|
126 (with-monad state-m
|
rlm@10
|
127
|
rlm@10
|
128 (defn- add-line
|
rlm@10
|
129 "Read one line and add it to the end of the vector lines. Return
|
rlm@10
|
130 [lines eof], where eof is an end-of-file flag. The input eof argument
|
rlm@10
|
131 is not used."
|
rlm@10
|
132 [[lines eof]]
|
rlm@10
|
133 (domonad
|
rlm@10
|
134 [line (read-line)]
|
rlm@10
|
135 (if (nil? line)
|
rlm@10
|
136 [lines true]
|
rlm@10
|
137 [(conj lines line) false])))
|
rlm@10
|
138
|
rlm@10
|
139 (defn read-lines
|
rlm@10
|
140 "Read all lines and return them in a vector"
|
rlm@10
|
141 []
|
rlm@10
|
142 (domonad
|
rlm@10
|
143 [[lines eof] (state-m-until second add-line [[] false])]
|
rlm@10
|
144 lines)))
|
rlm@10
|
145
|