annotate src/clojure/contrib/monadic_io_streams.clj @ 10:ef7dbbd6452c

added clojure source goodness
author Robert McIntyre <rlm@mit.edu>
date Sat, 21 Aug 2010 06:25:44 -0400
parents
children
rev   line source
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