Mercurial > lasercutter
view src/clojure/contrib/pprint/pretty_writer.clj @ 10:ef7dbbd6452c
added clojure source goodness
author | Robert McIntyre <rlm@mit.edu> |
---|---|
date | Sat, 21 Aug 2010 06:25:44 -0400 |
parents | |
children |
line wrap: on
line source
1 ;;; pretty_writer.clj -- part of the pretty printer for Clojure3 ;; by Tom Faulhaber4 ;; April 3, 20095 ;; Revised to use proxy instead of gen-class April 20107 ; Copyright (c) Tom Faulhaber, Jan 2009. All rights reserved.8 ; The use and distribution terms for this software are covered by the9 ; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)10 ; which can be found in the file epl-v10.html at the root of this distribution.11 ; By using this software in any fashion, you are agreeing to be bound by12 ; the terms of this license.13 ; You must not remove this notice, or any other, from this software.15 ;; This module implements a wrapper around a java.io.Writer which implements the16 ;; core of the XP algorithm.18 (ns clojure.contrib.pprint.pretty-writer19 (:refer-clojure :exclude (deftype))20 (:use clojure.contrib.pprint.utilities)21 (:use [clojure.contrib.pprint.column-writer22 :only (column-writer get-column get-max-column)])23 (:import24 [clojure.lang IDeref]25 [java.io Writer]))27 ;; TODO: Support for tab directives30 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;31 ;;; Forward declarations32 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;34 (declare get-miser-width)36 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;37 ;;; Macros to simplify dealing with types and classes. These are38 ;;; really utilities, but I'm experimenting with them here.39 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;41 (defmacro ^{:private true}42 getf43 "Get the value of the field a named by the argument (which should be a keyword)."44 [sym]45 `(~sym @@~'this))47 (defmacro ^{:private true}48 setf [sym new-val]49 "Set the value of the field SYM to NEW-VAL"50 `(alter @~'this assoc ~sym ~new-val))52 (defmacro ^{:private true}53 deftype [type-name & fields]54 (let [name-str (name type-name)]55 `(do56 (defstruct ~type-name :type-tag ~@fields)57 (defn- ~(symbol (str "make-" name-str))58 [& vals#] (apply struct ~type-name ~(keyword name-str) vals#))59 (defn- ~(symbol (str name-str "?")) [x#] (= (:type-tag x#) ~(keyword name-str))))))61 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;62 ;;; The data structures used by pretty-writer63 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;65 (defstruct ^{:private true} logical-block66 :parent :section :start-col :indent67 :done-nl :intra-block-nl68 :prefix :per-line-prefix :suffix69 :logical-block-callback)71 (defn ancestor? [parent child]72 (loop [child (:parent child)]73 (cond74 (nil? child) false75 (identical? parent child) true76 :else (recur (:parent child)))))78 (defstruct ^{:private true} section :parent)80 (defn buffer-length [l]81 (let [l (seq l)]82 (if l83 (- (:end-pos (last l)) (:start-pos (first l)))84 0)))86 ; A blob of characters (aka a string)87 (deftype buffer-blob :data :trailing-white-space :start-pos :end-pos)89 ; A newline90 (deftype nl-t :type :logical-block :start-pos :end-pos)92 (deftype start-block-t :logical-block :start-pos :end-pos)94 (deftype end-block-t :logical-block :start-pos :end-pos)96 (deftype indent-t :logical-block :relative-to :offset :start-pos :end-pos)98 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;99 ;;; Functions to write tokens in the output buffer100 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;102 (declare emit-nl)104 (defmulti write-token #(:type-tag %2))105 (defmethod write-token :start-block-t [^Writer this token]106 (when-let [cb (getf :logical-block-callback)] (cb :start))107 (let [lb (:logical-block token)]108 (dosync109 (when-let [^String prefix (:prefix lb)]110 (.write (getf :base) prefix))111 (let [col (get-column (getf :base))]112 (ref-set (:start-col lb) col)113 (ref-set (:indent lb) col)))))115 (defmethod write-token :end-block-t [^Writer this token]116 (when-let [cb (getf :logical-block-callback)] (cb :end))117 (when-let [^String suffix (:suffix (:logical-block token))]118 (.write (getf :base) suffix)))120 (defmethod write-token :indent-t [^Writer this token]121 (let [lb (:logical-block token)]122 (ref-set (:indent lb)123 (+ (:offset token)124 (condp = (:relative-to token)125 :block @(:start-col lb)126 :current (get-column (getf :base)))))))128 (defmethod write-token :buffer-blob [^Writer this token]129 (.write (getf :base) ^String (:data token)))131 (defmethod write-token :nl-t [^Writer this token]132 ; (prlabel wt @(:done-nl (:logical-block token)))133 ; (prlabel wt (:type token) (= (:type token) :mandatory))134 (if (or (= (:type token) :mandatory)135 (and (not (= (:type token) :fill))136 @(:done-nl (:logical-block token))))137 (emit-nl this token)138 (if-let [^String tws (getf :trailing-white-space)]139 (.write (getf :base) tws)))140 (dosync (setf :trailing-white-space nil)))142 (defn- write-tokens [^Writer this tokens force-trailing-whitespace]143 (doseq [token tokens]144 (if-not (= (:type-tag token) :nl-t)145 (if-let [^String tws (getf :trailing-white-space)]146 (.write (getf :base) tws)))147 (write-token this token)148 (setf :trailing-white-space (:trailing-white-space token)))149 (let [^String tws (getf :trailing-white-space)]150 (when (and force-trailing-whitespace tws)151 (.write (getf :base) tws)152 (setf :trailing-white-space nil))))154 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;155 ;;; emit-nl? method defs for each type of new line. This makes156 ;;; the decision about whether to print this type of new line.157 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;160 (defn- tokens-fit? [^Writer this tokens]161 ;;; (prlabel tf? (get-column (getf :base) (buffer-length tokens))162 (let [maxcol (get-max-column (getf :base))]163 (or164 (nil? maxcol)165 (< (+ (get-column (getf :base)) (buffer-length tokens)) maxcol))))167 (defn- linear-nl? [this lb section]168 ; (prlabel lnl? @(:done-nl lb) (tokens-fit? this section))169 (or @(:done-nl lb)170 (not (tokens-fit? this section))))172 (defn- miser-nl? [^Writer this lb section]173 (let [miser-width (get-miser-width this)174 maxcol (get-max-column (getf :base))]175 (and miser-width maxcol176 (>= @(:start-col lb) (- maxcol miser-width))177 (linear-nl? this lb section))))179 (defmulti emit-nl? (fn [t _ _ _] (:type t)))181 (defmethod emit-nl? :linear [newl this section _]182 (let [lb (:logical-block newl)]183 (linear-nl? this lb section)))185 (defmethod emit-nl? :miser [newl this section _]186 (let [lb (:logical-block newl)]187 (miser-nl? this lb section)))189 (defmethod emit-nl? :fill [newl this section subsection]190 (let [lb (:logical-block newl)]191 (or @(:intra-block-nl lb)192 (not (tokens-fit? this subsection))193 (miser-nl? this lb section))))195 (defmethod emit-nl? :mandatory [_ _ _ _]196 true)198 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;199 ;;; Various support functions200 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;203 (defn- get-section [buffer]204 (let [nl (first buffer)205 lb (:logical-block nl)206 section (seq (take-while #(not (and (nl-t? %) (ancestor? (:logical-block %) lb)))207 (next buffer)))]208 [section (seq (drop (inc (count section)) buffer))]))210 (defn- get-sub-section [buffer]211 (let [nl (first buffer)212 lb (:logical-block nl)213 section (seq (take-while #(let [nl-lb (:logical-block %)]214 (not (and (nl-t? %) (or (= nl-lb lb) (ancestor? nl-lb lb)))))215 (next buffer)))]216 section))218 (defn- update-nl-state [lb]219 (dosync220 (ref-set (:intra-block-nl lb) false)221 (ref-set (:done-nl lb) true)222 (loop [lb (:parent lb)]223 (if lb224 (do (ref-set (:done-nl lb) true)225 (ref-set (:intra-block-nl lb) true)226 (recur (:parent lb)))))))228 (defn emit-nl [^Writer this nl]229 (.write (getf :base) (int \newline))230 (dosync (setf :trailing-white-space nil))231 (let [lb (:logical-block nl)232 ^String prefix (:per-line-prefix lb)]233 (if prefix234 (.write (getf :base) prefix))235 (let [^String istr (apply str (repeat (- @(:indent lb) (count prefix))236 \space))]237 (.write (getf :base) istr))238 (update-nl-state lb)))240 (defn- split-at-newline [tokens]241 (let [pre (seq (take-while #(not (nl-t? %)) tokens))]242 [pre (seq (drop (count pre) tokens))]))244 ;;; Methods for showing token strings for debugging246 (defmulti tok :type-tag)247 (defmethod tok :nl-t [token]248 (:type token))249 (defmethod tok :buffer-blob [token]250 (str \" (:data token) (:trailing-white-space token) \"))251 (defmethod tok :default [token]252 (:type-tag token))253 (defn toks [toks] (map tok toks))255 ;;; write-token-string is called when the set of tokens in the buffer256 ;;; is longer than the available space on the line258 (defn- write-token-string [this tokens]259 (let [[a b] (split-at-newline tokens)]260 ;; (prlabel wts (toks a) (toks b))261 (if a (write-tokens this a false))262 (if b263 (let [[section remainder] (get-section b)264 newl (first b)]265 ;; (prlabel wts (toks section)) (prlabel wts (:type newl)) (prlabel wts (toks remainder))266 (let [do-nl (emit-nl? newl this section (get-sub-section b))267 result (if do-nl268 (do269 ;; (prlabel emit-nl (:type newl))270 (emit-nl this newl)271 (next b))272 b)273 long-section (not (tokens-fit? this result))274 result (if long-section275 (let [rem2 (write-token-string this section)]276 ;;; (prlabel recurse (toks rem2))277 (if (= rem2 section)278 (do ; If that didn't produce any output, it has no nls279 ; so we'll force it280 (write-tokens this section false)281 remainder)282 (into [] (concat rem2 remainder))))283 result)284 ;; ff (prlabel wts (toks result))285 ]286 result)))))288 (defn- write-line [^Writer this]289 (dosync290 (loop [buffer (getf :buffer)]291 ;; (prlabel wl1 (toks buffer))292 (setf :buffer (into [] buffer))293 (if (not (tokens-fit? this buffer))294 (let [new-buffer (write-token-string this buffer)]295 ;; (prlabel wl new-buffer)296 (if-not (identical? buffer new-buffer)297 (recur new-buffer)))))))299 ;;; Add a buffer token to the buffer and see if it's time to start300 ;;; writing301 (defn- add-to-buffer [^Writer this token]302 ; (prlabel a2b token)303 (dosync304 (setf :buffer (conj (getf :buffer) token))305 (if (not (tokens-fit? this (getf :buffer)))306 (write-line this))))308 ;;; Write all the tokens that have been buffered309 (defn- write-buffered-output [^Writer this]310 (write-line this)311 (if-let [buf (getf :buffer)]312 (do313 (write-tokens this buf true)314 (setf :buffer []))))316 ;;; If there are newlines in the string, print the lines up until the last newline,317 ;;; making the appropriate adjustments. Return the remainder of the string318 (defn- write-initial-lines319 [^Writer this ^String s]320 (let [lines (.split s "\n" -1)]321 (if (= (count lines) 1)322 s323 (dosync324 (let [^String prefix (:per-line-prefix (first (getf :logical-blocks)))325 ^String l (first lines)]326 (if (= :buffering (getf :mode))327 (let [oldpos (getf :pos)328 newpos (+ oldpos (count l))]329 (setf :pos newpos)330 (add-to-buffer this (make-buffer-blob l nil oldpos newpos))331 (write-buffered-output this))332 (.write (getf :base) l))333 (.write (getf :base) (int \newline))334 (doseq [^String l (next (butlast lines))]335 (.write (getf :base) l)336 (.write (getf :base) (int \newline))337 (if prefix338 (.write (getf :base) prefix)))339 (setf :buffering :writing)340 (last lines))))))343 (defn write-white-space [^Writer this]344 (if-let [^String tws (getf :trailing-white-space)]345 (dosync346 (.write (getf :base) tws)347 (setf :trailing-white-space nil))))349 (defn- write-char [^Writer this ^Integer c]350 (if (= (getf :mode) :writing)351 (do352 (write-white-space this)353 (.write (getf :base) c))354 (if (= c \newline)355 (write-initial-lines this "\n")356 (let [oldpos (getf :pos)357 newpos (inc oldpos)]358 (dosync359 (setf :pos newpos)360 (add-to-buffer this (make-buffer-blob (str (char c)) nil oldpos newpos)))))))363 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;364 ;;; Initialize the pretty-writer instance365 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;368 (defn pretty-writer [writer max-columns miser-width]369 (let [lb (struct logical-block nil nil (ref 0) (ref 0) (ref false) (ref false))370 fields (ref {:pretty-writer true371 :base (column-writer writer max-columns)372 :logical-blocks lb373 :sections nil374 :mode :writing375 :buffer []376 :buffer-block lb377 :buffer-level 1378 :miser-width miser-width379 :trailing-white-space nil380 :pos 0})]381 (proxy [Writer IDeref] []382 (deref [] fields)384 (write385 ([x]386 ;; (prlabel write x (getf :mode))387 (condp = (class x)388 String389 (let [^String s0 (write-initial-lines this x)390 ^String s (.replaceFirst s0 "\\s+$" "")391 white-space (.substring s0 (count s))392 mode (getf :mode)]393 (dosync394 (if (= mode :writing)395 (do396 (write-white-space this)397 (.write (getf :base) s)398 (setf :trailing-white-space white-space))399 (let [oldpos (getf :pos)400 newpos (+ oldpos (count s0))]401 (setf :pos newpos)402 (add-to-buffer this (make-buffer-blob s white-space oldpos newpos))))))404 Integer405 (write-char this x)406 Long407 (write-char this x))))409 (flush []410 (if (= (getf :mode) :buffering)411 (dosync412 (write-tokens this (getf :buffer) true)413 (setf :buffer []))414 (write-white-space this)))416 (close []417 (.flush this)))))420 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;421 ;;; Methods for pretty-writer422 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;424 (defn start-block425 [^Writer this426 ^String prefix ^String per-line-prefix ^String suffix]427 (dosync428 (let [lb (struct logical-block (getf :logical-blocks) nil (ref 0) (ref 0)429 (ref false) (ref false)430 prefix per-line-prefix suffix)]431 (setf :logical-blocks lb)432 (if (= (getf :mode) :writing)433 (do434 (write-white-space this)435 (when-let [cb (getf :logical-block-callback)] (cb :start))436 (if prefix437 (.write (getf :base) prefix))438 (let [col (get-column (getf :base))]439 (ref-set (:start-col lb) col)440 (ref-set (:indent lb) col)))441 (let [oldpos (getf :pos)442 newpos (+ oldpos (if prefix (count prefix) 0))]443 (setf :pos newpos)444 (add-to-buffer this (make-start-block-t lb oldpos newpos)))))))446 (defn end-block [^Writer this]447 (dosync448 (let [lb (getf :logical-blocks)449 ^String suffix (:suffix lb)]450 (if (= (getf :mode) :writing)451 (do452 (write-white-space this)453 (if suffix454 (.write (getf :base) suffix))455 (when-let [cb (getf :logical-block-callback)] (cb :end)))456 (let [oldpos (getf :pos)457 newpos (+ oldpos (if suffix (count suffix) 0))]458 (setf :pos newpos)459 (add-to-buffer this (make-end-block-t lb oldpos newpos))))460 (setf :logical-blocks (:parent lb)))))462 (defn nl [^Writer this type]463 (dosync464 (setf :mode :buffering)465 (let [pos (getf :pos)]466 (add-to-buffer this (make-nl-t type (getf :logical-blocks) pos pos)))))468 (defn indent [^Writer this relative-to offset]469 (dosync470 (let [lb (getf :logical-blocks)]471 (if (= (getf :mode) :writing)472 (do473 (write-white-space this)474 (ref-set (:indent lb)475 (+ offset (condp = relative-to476 :block @(:start-col lb)477 :current (get-column (getf :base))))))478 (let [pos (getf :pos)]479 (add-to-buffer this (make-indent-t lb relative-to offset pos pos)))))))481 (defn get-miser-width [^Writer this]482 (getf :miser-width))484 (defn set-miser-width [^Writer this new-miser-width]485 (dosync (setf :miser-width new-miser-width)))487 (defn set-logical-block-callback [^Writer this f]488 (dosync (setf :logical-block-callback f)))