Skip to content


Subversion checkout URL

You can clone with
Download ZIP
Fetching contributors…

Cannot retrieve contributors at this time

218 lines (190 sloc) 6.863 kB
(ns ^{:doc "Protocols and default Reader types implementation"
:author "Bronsa"}
(:refer-clojure :exclude [char read-line])
(:import clojure.lang.LineNumberingPushbackReader
( InputStream BufferedReader)))
(defmacro ^:private update! [what f]
(list 'set! what (list f what)))
;; reader protocols
(defprotocol Reader
(read-char [reader]
"Returns the next char from the Reader, nil if the end of stream has been reached")
(peek-char [reader]
"Returns the next char from the Reader without removing it from the reader stream"))
(defprotocol IPushbackReader
(unread [reader ch]
"Pushes back a single character on to the stream"))
(defprotocol IndexingReader
(get-line-number [reader]
"Returns the line number of the next character to be read from the stream")
(get-column-number [reader]
"Returns the line number of the next character to be read from the stream"))
;; reader deftypes
(deftype StringReader
[^String s s-len ^:unsynchronized-mutable s-pos]
(read-char [reader]
(when (> s-len s-pos)
(let [r (nth s s-pos)]
(update! s-pos inc)
(peek-char [reader]
(when (> s-len s-pos)
(nth s s-pos))))
(deftype InputStreamReader [^InputStream is ^:unsynchronized-mutable ^"[B" buf]
(read-char [reader]
(if buf
(let [c (aget buf 0)]
(set! buf nil)
(char c))
(let [c (.read is)]
(when (>= c 0)
(char c)))))
(peek-char [reader]
(when-not buf
(set! buf (byte-array 1))
(when (== -1 (.read is buf))
(set! buf nil)))
(when buf
(char (aget buf 0)))))
(deftype PushbackReader
[rdr ^"[Ljava.lang.Object;" buf buf-len ^:unsynchronized-mutable buf-pos]
(read-char [reader]
(if (< buf-pos buf-len)
(let [r (aget buf buf-pos)]
(update! buf-pos inc)
(read-char rdr))))
(peek-char [reader]
(if (< buf-pos buf-len)
(aget buf buf-pos)
(peek-char rdr))))
(unread [reader ch]
(when ch
(if (zero? buf-pos) (throw (RuntimeException. "Pushback buffer is full")))
(update! buf-pos dec)
(aset buf buf-pos ch))))
(defn- normalize-newline [rdr ch]
(if (identical? \return ch)
(let [c (peek-char rdr)]
(when (identical? \formfeed c)
(read-char rdr))
(deftype IndexingPushbackReader
[rdr ^:unsynchronized-mutable line ^:unsynchronized-mutable column
^:unsynchronized-mutable line-start? ^:unsynchronized-mutable prev]
(read-char [reader]
(when-let [ch (read-char rdr)]
(let [ch (normalize-newline rdr ch)]
(set! prev line-start?)
(set! line-start? (newline? ch))
(when line-start?
(set! column 0)
(update! line inc))
(update! column inc)
(peek-char [reader]
(peek-char rdr))
(unread [reader ch]
(when line-start? (update! line dec))
(set! line-start? prev)
(update! column dec)
(unread rdr ch))
(get-line-number [reader] (int (inc line)))
(get-column-number [reader] (int column)))
(read-char [rdr]
(let [c (.read ^ rdr)]
(when (>= c 0)
(normalize-newline rdr (char c)))))
(peek-char [rdr]
(when-let [c (read-char rdr)]
(unread rdr c)
(unread [rdr c]
(when c
(.unread ^ rdr (int c)))))
(extend LineNumberingPushbackReader
{:get-line-number (fn [rdr] (.getLineNumber ^LineNumberingPushbackReader rdr))
:get-column-number (compile-if >=clojure-1-5-alpha*?
(fn [rdr]
(.getColumnNumber ^LineNumberingPushbackReader rdr))
(fn [rdr] 0))})
;; Public API
;; fast check for provided implementations
(defn indexing-reader?
"Returns true if the reader satisfies IndexingReader"
(or (instance? rdr)
(instance? LineNumberingPushbackReader rdr)
(and (not (instance? rdr))
(not (instance? rdr))
(not (instance? rdr))
(get (:impls IndexingReader) (class rdr)))))
(defn string-reader
"Creates a StringReader from a given string"
([^String s]
(StringReader. s (count s) 0)))
(defn string-push-back-reader
"Creates a PushbackReader from a given string"
(string-push-back-reader s 1))
([^String s buf-len]
(PushbackReader. (string-reader s) (object-array buf-len) buf-len buf-len)))
(defn input-stream-reader
"Creates an InputStreamReader from an InputStream"
(InputStreamReader. is nil))
(defn input-stream-push-back-reader
"Creates a PushbackReader from a given InputStream"
(input-stream-push-back-reader is 1))
([^InputStream is buf-len]
(PushbackReader. (input-stream-reader is) (object-array buf-len) buf-len buf-len)))
(defn indexing-push-back-reader
"Creates an IndexingPushbackReader from a given string or Reader"
(indexing-push-back-reader s-or-rdr 1))
([s-or-rdr buf-len]
(if (string? s-or-rdr) (string-push-back-reader s-or-rdr buf-len) s-or-rdr) 0 1 true nil)))
(defn read-line
"Reads a line from the reader or from *in* if no reader is specified"
([] (read-line *in*))
(if (or (instance? LineNumberingPushbackReader rdr)
(instance? BufferedReader rdr))
(clojure.core/read-line rdr)
(loop [c (read-char rdr) s (StringBuilder.)]
(if (newline? c)
(str s)
(recur (read-char rdr) (.append s c)))))))
(defn reader-error
"Throws an ExceptionInfo with the given message.
If rdr is an IndexingReader, additional information about column and line number is provided"
[rdr & msg]
(throw (ex-info (apply str msg)
(merge {:type :reader-exception}
(when (indexing-reader? rdr)
{:line (get-line-number rdr)
:column (get-column-number rdr)})))))
Jump to Line
Something went wrong with that request. Please try again.