Mercurial > lasercutter
view src/clojure/pprint/cl_format.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 ;;; cl_format.clj -- part of the pretty printer for Clojure3 ; Copyright (c) Rich Hickey. All rights reserved.4 ; The use and distribution terms for this software are covered by the5 ; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)6 ; which can be found in the file epl-v10.html at the root of this distribution.7 ; By using this software in any fashion, you are agreeing to be bound by8 ; the terms of this license.9 ; You must not remove this notice, or any other, from this software.11 ;; Author: Tom Faulhaber12 ;; April 3, 200915 ;; This module implements the Common Lisp compatible format function as documented16 ;; in "Common Lisp the Language, 2nd edition", Chapter 22 (available online at:17 ;; http://www.cs.cmu.edu/afs/cs.cmu.edu/project/ai-repository/ai/html/cltl/clm/node200.html#SECTION002633000000000000000)19 (in-ns 'clojure.pprint)21 ;;; Forward references22 (declare compile-format)23 (declare execute-format)24 (declare init-navigator)25 ;;; End forward references27 (defn cl-format28 "An implementation of a Common Lisp compatible format function. cl-format formats its29 arguments to an output stream or string based on the format control string given. It30 supports sophisticated formatting of structured data.32 Writer is an instance of java.io.Writer, true to output to *out* or nil to output33 to a string, format-in is the format control string and the remaining arguments34 are the data to be formatted.36 The format control string is a string to be output with embedded 'format directives'37 describing how to format the various arguments passed in.39 If writer is nil, cl-format returns the formatted result string. Otherwise, cl-format40 returns nil.42 For example:43 (let [results [46 38 22]]44 (cl-format true \"There ~[are~;is~:;are~]~:* ~d result~:p: ~{~d~^, ~}~%\"45 (count results) results))47 Prints to *out*:48 There are 3 results: 46, 38, 2250 Detailed documentation on format control strings is available in the \"Common Lisp the51 Language, 2nd edition\", Chapter 22 (available online at:52 http://www.cs.cmu.edu/afs/cs.cmu.edu/project/ai-repository/ai/html/cltl/clm/node200.html#SECTION002633000000000000000)53 and in the Common Lisp HyperSpec at54 http://www.lispworks.com/documentation/HyperSpec/Body/22_c.htm55 "56 {:added "1.2",57 :see-also [["http://www.cs.cmu.edu/afs/cs.cmu.edu/project/ai-repository/ai/html/cltl/clm/node200.html#SECTION002633000000000000000"58 "Common Lisp the Language"]59 ["http://www.lispworks.com/documentation/HyperSpec/Body/22_c.htm"60 "Common Lisp HyperSpec"]]}61 [writer format-in & args]62 (let [compiled-format (if (string? format-in) (compile-format format-in) format-in)63 navigator (init-navigator args)]64 (execute-format writer compiled-format navigator)))66 (def ^{:private true} *format-str* nil)68 (defn- format-error [message offset]69 (let [full-message (str message \newline *format-str* \newline70 (apply str (repeat offset \space)) "^" \newline)]71 (throw (RuntimeException. full-message))))73 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;74 ;;; Argument navigators manage the argument list75 ;;; as the format statement moves through the list76 ;;; (possibly going forwards and backwards as it does so)77 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;79 (defstruct ^{:private true}80 arg-navigator :seq :rest :pos )82 (defn- init-navigator83 "Create a new arg-navigator from the sequence with the position set to 0"84 {:skip-wiki true}85 [s]86 (let [s (seq s)]87 (struct arg-navigator s s 0)))89 ;; TODO call format-error with offset90 (defn- next-arg [ navigator ]91 (let [ rst (:rest navigator) ]92 (if rst93 [(first rst) (struct arg-navigator (:seq navigator ) (next rst) (inc (:pos navigator)))]94 (throw (new Exception "Not enough arguments for format definition")))))96 (defn- next-arg-or-nil [navigator]97 (let [rst (:rest navigator)]98 (if rst99 [(first rst) (struct arg-navigator (:seq navigator ) (next rst) (inc (:pos navigator)))]100 [nil navigator])))102 ;; Get an argument off the arg list and compile it if it's not already compiled103 (defn- get-format-arg [navigator]104 (let [[raw-format navigator] (next-arg navigator)105 compiled-format (if (instance? String raw-format)106 (compile-format raw-format)107 raw-format)]108 [compiled-format navigator]))110 (declare relative-reposition)112 (defn- absolute-reposition [navigator position]113 (if (>= position (:pos navigator))114 (relative-reposition navigator (- (:pos navigator) position))115 (struct arg-navigator (:seq navigator) (drop position (:seq navigator)) position)))117 (defn- relative-reposition [navigator position]118 (let [newpos (+ (:pos navigator) position)]119 (if (neg? position)120 (absolute-reposition navigator newpos)121 (struct arg-navigator (:seq navigator) (drop position (:rest navigator)) newpos))))123 (defstruct ^{:private true}124 compiled-directive :func :def :params :offset)126 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;127 ;;; When looking at the parameter list, we may need to manipulate128 ;;; the argument list as well (for 'V' and '#' parameter types).129 ;;; We hide all of this behind a function, but clients need to130 ;;; manage changing arg navigator131 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;133 ;; TODO: validate parameters when they come from arg list134 (defn- realize-parameter [[param [raw-val offset]] navigator]135 (let [[real-param new-navigator]136 (cond137 (contains? #{ :at :colon } param) ;pass flags through unchanged - this really isn't necessary138 [raw-val navigator]140 (= raw-val :parameter-from-args)141 (next-arg navigator)143 (= raw-val :remaining-arg-count)144 [(count (:rest navigator)) navigator]146 true147 [raw-val navigator])]148 [[param [real-param offset]] new-navigator]))150 (defn- realize-parameter-list [parameter-map navigator]151 (let [[pairs new-navigator]152 (map-passing-context realize-parameter navigator parameter-map)]153 [(into {} pairs) new-navigator]))155 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;156 ;;; Functions that support individual directives157 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;159 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;160 ;;; Common handling code for ~A and ~S161 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;163 (declare opt-base-str)165 (def ^{:private true}166 special-radix-markers {2 "#b" 8 "#o", 16 "#x"})168 (defn- format-simple-number [n]169 (cond170 (integer? n) (if (= *print-base* 10)171 (str n (if *print-radix* "."))172 (str173 (if *print-radix* (or (get special-radix-markers *print-base*) (str "#" *print-base* "r")))174 (opt-base-str *print-base* n)))175 (ratio? n) (str176 (if *print-radix* (or (get special-radix-markers *print-base*) (str "#" *print-base* "r")))177 (opt-base-str *print-base* (.numerator n))178 "/"179 (opt-base-str *print-base* (.denominator n)))180 :else nil))182 (defn- format-ascii [print-func params arg-navigator offsets]183 (let [ [arg arg-navigator] (next-arg arg-navigator)184 ^String base-output (or (format-simple-number arg) (print-func arg))185 base-width (.length base-output)186 min-width (+ base-width (:minpad params))187 width (if (>= min-width (:mincol params))188 min-width189 (+ min-width190 (* (+ (quot (- (:mincol params) min-width 1)191 (:colinc params) )192 1)193 (:colinc params))))194 chars (apply str (repeat (- width base-width) (:padchar params)))]195 (if (:at params)196 (print (str chars base-output))197 (print (str base-output chars)))198 arg-navigator))200 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;201 ;;; Support for the integer directives ~D, ~X, ~O, ~B and some202 ;;; of ~R203 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;205 (defn- integral?206 "returns true if a number is actually an integer (that is, has no fractional part)"207 [x]208 (cond209 (integer? x) true210 (decimal? x) (>= (.ulp (.stripTrailingZeros (bigdec 0))) 1) ; true iff no fractional part211 (float? x) (= x (Math/floor x))212 (ratio? x) (let [^clojure.lang.Ratio r x]213 (= 0 (rem (.numerator r) (.denominator r))))214 :else false))216 (defn- remainders217 "Return the list of remainders (essentially the 'digits') of val in the given base"218 [base val]219 (reverse220 (first221 (consume #(if (pos? %)222 [(rem % base) (quot % base)]223 [nil nil])224 val))))226 ;;; TODO: xlated-val does not seem to be used here.227 (defn- base-str228 "Return val as a string in the given base"229 [base val]230 (if (zero? val)231 "0"232 (let [xlated-val (cond233 (float? val) (bigdec val)234 (ratio? val) (let [^clojure.lang.Ratio r val]235 (/ (.numerator r) (.denominator r)))236 :else val)]237 (apply str238 (map239 #(if (< % 10) (char (+ (int \0) %)) (char (+ (int \a) (- % 10))))240 (remainders base val))))))242 (def ^{:private true}243 java-base-formats {8 "%o", 10 "%d", 16 "%x"})245 (defn- opt-base-str246 "Return val as a string in the given base, using clojure.core/format if supported247 for improved performance"248 [base val]249 (let [format-str (get java-base-formats base)]250 (if (and format-str (integer? val))251 (clojure.core/format format-str val)252 (base-str base val))))254 (defn- group-by* [unit lis]255 (reverse256 (first257 (consume (fn [x] [(seq (reverse (take unit x))) (seq (drop unit x))]) (reverse lis)))))259 (defn- format-integer [base params arg-navigator offsets]260 (let [[arg arg-navigator] (next-arg arg-navigator)]261 (if (integral? arg)262 (let [neg (neg? arg)263 pos-arg (if neg (- arg) arg)264 raw-str (opt-base-str base pos-arg)265 group-str (if (:colon params)266 (let [groups (map #(apply str %) (group-by* (:commainterval params) raw-str))267 commas (repeat (count groups) (:commachar params))]268 (apply str (next (interleave commas groups))))269 raw-str)270 ^String signed-str (cond271 neg (str "-" group-str)272 (:at params) (str "+" group-str)273 true group-str)274 padded-str (if (< (.length signed-str) (:mincol params))275 (str (apply str (repeat (- (:mincol params) (.length signed-str))276 (:padchar params)))277 signed-str)278 signed-str)]279 (print padded-str))280 (format-ascii print-str {:mincol (:mincol params) :colinc 1 :minpad 0281 :padchar (:padchar params) :at true}282 (init-navigator [arg]) nil))283 arg-navigator))285 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;286 ;;; Support for english formats (~R and ~:R)287 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;289 (def ^{:private true}290 english-cardinal-units291 ["zero" "one" "two" "three" "four" "five" "six" "seven" "eight" "nine"292 "ten" "eleven" "twelve" "thirteen" "fourteen"293 "fifteen" "sixteen" "seventeen" "eighteen" "nineteen"])295 (def ^{:private true}296 english-ordinal-units297 ["zeroth" "first" "second" "third" "fourth" "fifth" "sixth" "seventh" "eighth" "ninth"298 "tenth" "eleventh" "twelfth" "thirteenth" "fourteenth"299 "fifteenth" "sixteenth" "seventeenth" "eighteenth" "nineteenth"])301 (def ^{:private true}302 english-cardinal-tens303 ["" "" "twenty" "thirty" "forty" "fifty" "sixty" "seventy" "eighty" "ninety"])305 (def ^{:private true}306 english-ordinal-tens307 ["" "" "twentieth" "thirtieth" "fortieth" "fiftieth"308 "sixtieth" "seventieth" "eightieth" "ninetieth"])310 ;; We use "short scale" for our units (see http://en.wikipedia.org/wiki/Long_and_short_scales)311 ;; Number names from http://www.jimloy.com/math/billion.htm312 ;; We follow the rules for writing numbers from the Blue Book313 ;; (http://www.grammarbook.com/numbers/numbers.asp)314 (def ^{:private true}315 english-scale-numbers316 ["" "thousand" "million" "billion" "trillion" "quadrillion" "quintillion"317 "sextillion" "septillion" "octillion" "nonillion" "decillion"318 "undecillion" "duodecillion" "tredecillion" "quattuordecillion"319 "quindecillion" "sexdecillion" "septendecillion"320 "octodecillion" "novemdecillion" "vigintillion"])322 (defn- format-simple-cardinal323 "Convert a number less than 1000 to a cardinal english string"324 [num]325 (let [hundreds (quot num 100)326 tens (rem num 100)]327 (str328 (if (pos? hundreds) (str (nth english-cardinal-units hundreds) " hundred"))329 (if (and (pos? hundreds) (pos? tens)) " ")330 (if (pos? tens)331 (if (< tens 20)332 (nth english-cardinal-units tens)333 (let [ten-digit (quot tens 10)334 unit-digit (rem tens 10)]335 (str336 (if (pos? ten-digit) (nth english-cardinal-tens ten-digit))337 (if (and (pos? ten-digit) (pos? unit-digit)) "-")338 (if (pos? unit-digit) (nth english-cardinal-units unit-digit)))))))))340 (defn- add-english-scales341 "Take a sequence of parts, add scale numbers (e.g., million) and combine into a string342 offset is a factor of 10^3 to multiply by"343 [parts offset]344 (let [cnt (count parts)]345 (loop [acc []346 pos (dec cnt)347 this (first parts)348 remainder (next parts)]349 (if (nil? remainder)350 (str (apply str (interpose ", " acc))351 (if (and (not (empty? this)) (not (empty? acc))) ", ")352 this353 (if (and (not (empty? this)) (pos? (+ pos offset)))354 (str " " (nth english-scale-numbers (+ pos offset)))))355 (recur356 (if (empty? this)357 acc358 (conj acc (str this " " (nth english-scale-numbers (+ pos offset)))))359 (dec pos)360 (first remainder)361 (next remainder))))))363 (defn- format-cardinal-english [params navigator offsets]364 (let [[arg navigator] (next-arg navigator)]365 (if (= 0 arg)366 (print "zero")367 (let [abs-arg (if (neg? arg) (- arg) arg) ; some numbers are too big for Math/abs368 parts (remainders 1000 abs-arg)]369 (if (<= (count parts) (count english-scale-numbers))370 (let [parts-strs (map format-simple-cardinal parts)371 full-str (add-english-scales parts-strs 0)]372 (print (str (if (neg? arg) "minus ") full-str)))373 (format-integer ;; for numbers > 10^63, we fall back on ~D374 10375 { :mincol 0, :padchar \space, :commachar \, :commainterval 3, :colon true}376 (init-navigator [arg])377 { :mincol 0, :padchar 0, :commachar 0 :commainterval 0}))))378 navigator))380 (defn- format-simple-ordinal381 "Convert a number less than 1000 to a ordinal english string382 Note this should only be used for the last one in the sequence"383 [num]384 (let [hundreds (quot num 100)385 tens (rem num 100)]386 (str387 (if (pos? hundreds) (str (nth english-cardinal-units hundreds) " hundred"))388 (if (and (pos? hundreds) (pos? tens)) " ")389 (if (pos? tens)390 (if (< tens 20)391 (nth english-ordinal-units tens)392 (let [ten-digit (quot tens 10)393 unit-digit (rem tens 10)]394 (if (and (pos? ten-digit) (not (pos? unit-digit)))395 (nth english-ordinal-tens ten-digit)396 (str397 (if (pos? ten-digit) (nth english-cardinal-tens ten-digit))398 (if (and (pos? ten-digit) (pos? unit-digit)) "-")399 (if (pos? unit-digit) (nth english-ordinal-units unit-digit))))))400 (if (pos? hundreds) "th")))))402 (defn- format-ordinal-english [params navigator offsets]403 (let [[arg navigator] (next-arg navigator)]404 (if (= 0 arg)405 (print "zeroth")406 (let [abs-arg (if (neg? arg) (- arg) arg) ; some numbers are too big for Math/abs407 parts (remainders 1000 abs-arg)]408 (if (<= (count parts) (count english-scale-numbers))409 (let [parts-strs (map format-simple-cardinal (drop-last parts))410 head-str (add-english-scales parts-strs 1)411 tail-str (format-simple-ordinal (last parts))]412 (print (str (if (neg? arg) "minus ")413 (cond414 (and (not (empty? head-str)) (not (empty? tail-str)))415 (str head-str ", " tail-str)417 (not (empty? head-str)) (str head-str "th")418 :else tail-str))))419 (do (format-integer ;; for numbers > 10^63, we fall back on ~D420 10421 { :mincol 0, :padchar \space, :commachar \, :commainterval 3, :colon true}422 (init-navigator [arg])423 { :mincol 0, :padchar 0, :commachar 0 :commainterval 0})424 (let [low-two-digits (rem arg 100)425 not-teens (or (< 11 low-two-digits) (> 19 low-two-digits))426 low-digit (rem low-two-digits 10)]427 (print (cond428 (and (= low-digit 1) not-teens) "st"429 (and (= low-digit 2) not-teens) "nd"430 (and (= low-digit 3) not-teens) "rd"431 :else "th")))))))432 navigator))435 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;436 ;;; Support for roman numeral formats (~@R and ~@:R)437 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;439 (def ^{:private true}440 old-roman-table441 [[ "I" "II" "III" "IIII" "V" "VI" "VII" "VIII" "VIIII"]442 [ "X" "XX" "XXX" "XXXX" "L" "LX" "LXX" "LXXX" "LXXXX"]443 [ "C" "CC" "CCC" "CCCC" "D" "DC" "DCC" "DCCC" "DCCCC"]444 [ "M" "MM" "MMM"]])446 (def ^{:private true}447 new-roman-table448 [[ "I" "II" "III" "IV" "V" "VI" "VII" "VIII" "IX"]449 [ "X" "XX" "XXX" "XL" "L" "LX" "LXX" "LXXX" "XC"]450 [ "C" "CC" "CCC" "CD" "D" "DC" "DCC" "DCCC" "CM"]451 [ "M" "MM" "MMM"]])453 (defn- format-roman454 "Format a roman numeral using the specified look-up table"455 [table params navigator offsets]456 (let [[arg navigator] (next-arg navigator)]457 (if (and (number? arg) (> arg 0) (< arg 4000))458 (let [digits (remainders 10 arg)]459 (loop [acc []460 pos (dec (count digits))461 digits digits]462 (if (empty? digits)463 (print (apply str acc))464 (let [digit (first digits)]465 (recur (if (= 0 digit)466 acc467 (conj acc (nth (nth table pos) (dec digit))))468 (dec pos)469 (next digits))))))470 (format-integer ;; for anything <= 0 or > 3999, we fall back on ~D471 10472 { :mincol 0, :padchar \space, :commachar \, :commainterval 3, :colon true}473 (init-navigator [arg])474 { :mincol 0, :padchar 0, :commachar 0 :commainterval 0}))475 navigator))477 (defn- format-old-roman [params navigator offsets]478 (format-roman old-roman-table params navigator offsets))480 (defn- format-new-roman [params navigator offsets]481 (format-roman new-roman-table params navigator offsets))483 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;484 ;;; Support for character formats (~C)485 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;487 (def ^{:private true}488 special-chars { 8 "Backspace", 9 "Tab", 10 "Newline", 13 "Return", 32 "Space"})490 (defn- pretty-character [params navigator offsets]491 (let [[c navigator] (next-arg navigator)492 as-int (int c)493 base-char (bit-and as-int 127)494 meta (bit-and as-int 128)495 special (get special-chars base-char)]496 (if (> meta 0) (print "Meta-"))497 (print (cond498 special special499 (< base-char 32) (str "Control-" (char (+ base-char 64)))500 (= base-char 127) "Control-?"501 :else (char base-char)))502 navigator))504 (defn- readable-character [params navigator offsets]505 (let [[c navigator] (next-arg navigator)]506 (condp = (:char-format params)507 \o (cl-format true "\\o~3,'0o" (int c))508 \u (cl-format true "\\u~4,'0x" (int c))509 nil (pr c))510 navigator))512 (defn- plain-character [params navigator offsets]513 (let [[char navigator] (next-arg navigator)]514 (print char)515 navigator))517 ;; Check to see if a result is an abort (~^) construct518 ;; TODO: move these funcs somewhere more appropriate519 (defn- abort? [context]520 (let [token (first context)]521 (or (= :up-arrow token) (= :colon-up-arrow token))))523 ;; Handle the execution of "sub-clauses" in bracket constructions524 (defn- execute-sub-format [format args base-args]525 (second526 (map-passing-context527 (fn [element context]528 (if (abort? context)529 [nil context] ; just keep passing it along530 (let [[params args] (realize-parameter-list (:params element) context)531 [params offsets] (unzip-map params)532 params (assoc params :base-args base-args)]533 [nil (apply (:func element) [params args offsets])])))534 args535 format)))537 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;538 ;;; Support for real number formats539 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;541 ;; TODO - return exponent as int to eliminate double conversion542 (defn- float-parts-base543 "Produce string parts for the mantissa (normalized 1-9) and exponent"544 [^Object f]545 (let [^String s (.toLowerCase (.toString f))546 exploc (.indexOf s (int \e))]547 (if (neg? exploc)548 (let [dotloc (.indexOf s (int \.))]549 (if (neg? dotloc)550 [s (str (dec (count s)))]551 [(str (subs s 0 dotloc) (subs s (inc dotloc))) (str (dec dotloc))]))552 [(str (subs s 0 1) (subs s 2 exploc)) (subs s (inc exploc))])))555 (defn- float-parts556 "Take care of leading and trailing zeros in decomposed floats"557 [f]558 (let [[m ^String e] (float-parts-base f)559 m1 (rtrim m \0)560 m2 (ltrim m1 \0)561 delta (- (count m1) (count m2))562 ^String e (if (and (pos? (count e)) (= (nth e 0) \+)) (subs e 1) e)]563 (if (empty? m2)564 ["0" 0]565 [m2 (- (Integer/valueOf e) delta)])))567 (defn- round-str [m e d w]568 (if (or d w)569 (let [len (count m)570 round-pos (if d (+ e d 1))571 round-pos (if (and w (< (inc e) (dec w))572 (or (nil? round-pos) (< (dec w) round-pos)))573 (dec w)574 round-pos)575 [m1 e1 round-pos len] (if (= round-pos 0)576 [(str "0" m) (inc e) 1 (inc len)]577 [m e round-pos len])]578 (if round-pos579 (if (neg? round-pos)580 ["0" 0 false]581 (if (> len round-pos)582 (let [round-char (nth m1 round-pos)583 ^String result (subs m1 0 round-pos)]584 (if (>= (int round-char) (int \5))585 (let [result-val (Integer/valueOf result)586 leading-zeros (subs result 0 (min (prefix-count result \0) (- round-pos 1)))587 round-up-result (str leading-zeros588 (String/valueOf (+ result-val589 (if (neg? result-val) -1 1))))590 expanded (> (count round-up-result) (count result))]591 [round-up-result e1 expanded])592 [result e1 false]))593 [m e false]))594 [m e false]))595 [m e false]))597 (defn- expand-fixed [m e d]598 (let [m1 (if (neg? e) (str (apply str (repeat (dec (- e)) \0)) m) m)599 len (count m1)600 target-len (if d (+ e d 1) (inc e))]601 (if (< len target-len)602 (str m1 (apply str (repeat (- target-len len) \0)))603 m1)))605 (defn- insert-decimal606 "Insert the decimal point at the right spot in the number to match an exponent"607 [m e]608 (if (neg? e)609 (str "." m)610 (let [loc (inc e)]611 (str (subs m 0 loc) "." (subs m loc)))))613 (defn- get-fixed [m e d]614 (insert-decimal (expand-fixed m e d) e))616 (defn- insert-scaled-decimal617 "Insert the decimal point at the right spot in the number to match an exponent"618 [m k]619 (if (neg? k)620 (str "." m)621 (str (subs m 0 k) "." (subs m k))))623 ;; the function to render ~F directives624 ;; TODO: support rationals. Back off to ~D/~A is the appropriate cases625 (defn- fixed-float [params navigator offsets]626 (let [w (:w params)627 d (:d params)628 [arg navigator] (next-arg navigator)629 [sign abs] (if (neg? arg) ["-" (- arg)] ["+" arg])630 [mantissa exp] (float-parts abs)631 scaled-exp (+ exp (:k params))632 add-sign (or (:at params) (neg? arg))633 append-zero (and (not d) (<= (dec (count mantissa)) scaled-exp))634 [rounded-mantissa scaled-exp expanded] (round-str mantissa scaled-exp635 d (if w (- w (if add-sign 1 0))))636 fixed-repr (get-fixed rounded-mantissa (if expanded (inc scaled-exp) scaled-exp) d)637 prepend-zero (= (first fixed-repr) \.)]638 (if w639 (let [len (count fixed-repr)640 signed-len (if add-sign (inc len) len)641 prepend-zero (and prepend-zero (not (>= signed-len w)))642 append-zero (and append-zero (not (>= signed-len w)))643 full-len (if (or prepend-zero append-zero)644 (inc signed-len)645 signed-len)]646 (if (and (> full-len w) (:overflowchar params))647 (print (apply str (repeat w (:overflowchar params))))648 (print (str649 (apply str (repeat (- w full-len) (:padchar params)))650 (if add-sign sign)651 (if prepend-zero "0")652 fixed-repr653 (if append-zero "0")))))654 (print (str655 (if add-sign sign)656 (if prepend-zero "0")657 fixed-repr658 (if append-zero "0"))))659 navigator))662 ;; the function to render ~E directives663 ;; TODO: support rationals. Back off to ~D/~A is the appropriate cases664 ;; TODO: define ~E representation for Infinity665 (defn- exponential-float [params navigator offsets]666 (let [[arg navigator] (next-arg navigator)]667 (loop [[mantissa exp] (float-parts (if (neg? arg) (- arg) arg))]668 (let [w (:w params)669 d (:d params)670 e (:e params)671 k (:k params)672 expchar (or (:exponentchar params) \E)673 add-sign (or (:at params) (neg? arg))674 prepend-zero (<= k 0)675 ^Integer scaled-exp (- exp (dec k))676 scaled-exp-str (str (Math/abs scaled-exp))677 scaled-exp-str (str expchar (if (neg? scaled-exp) \- \+)678 (if e (apply str679 (repeat680 (- e681 (count scaled-exp-str))682 \0)))683 scaled-exp-str)684 exp-width (count scaled-exp-str)685 base-mantissa-width (count mantissa)686 scaled-mantissa (str (apply str (repeat (- k) \0))687 mantissa688 (if d689 (apply str690 (repeat691 (- d (dec base-mantissa-width)692 (if (neg? k) (- k) 0)) \0))))693 w-mantissa (if w (- w exp-width))694 [rounded-mantissa _ incr-exp] (round-str695 scaled-mantissa 0696 (cond697 (= k 0) (dec d)698 (pos? k) d699 (neg? k) (dec d))700 (if w-mantissa701 (- w-mantissa (if add-sign 1 0))))702 full-mantissa (insert-scaled-decimal rounded-mantissa k)703 append-zero (and (= k (count rounded-mantissa)) (nil? d))]704 (if (not incr-exp)705 (if w706 (let [len (+ (count full-mantissa) exp-width)707 signed-len (if add-sign (inc len) len)708 prepend-zero (and prepend-zero (not (= signed-len w)))709 full-len (if prepend-zero (inc signed-len) signed-len)710 append-zero (and append-zero (< full-len w))]711 (if (and (or (> full-len w) (and e (> (- exp-width 2) e)))712 (:overflowchar params))713 (print (apply str (repeat w (:overflowchar params))))714 (print (str715 (apply str716 (repeat717 (- w full-len (if append-zero 1 0) )718 (:padchar params)))719 (if add-sign (if (neg? arg) \- \+))720 (if prepend-zero "0")721 full-mantissa722 (if append-zero "0")723 scaled-exp-str))))724 (print (str725 (if add-sign (if (neg? arg) \- \+))726 (if prepend-zero "0")727 full-mantissa728 (if append-zero "0")729 scaled-exp-str)))730 (recur [rounded-mantissa (inc exp)]))))731 navigator))733 ;; the function to render ~G directives734 ;; This just figures out whether to pass the request off to ~F or ~E based735 ;; on the algorithm in CLtL.736 ;; TODO: support rationals. Back off to ~D/~A is the appropriate cases737 ;; TODO: refactor so that float-parts isn't called twice738 (defn- general-float [params navigator offsets]739 (let [[arg _] (next-arg navigator)740 [mantissa exp] (float-parts (if (neg? arg) (- arg) arg))741 w (:w params)742 d (:d params)743 e (:e params)744 n (if (= arg 0.0) 0 (inc exp))745 ee (if e (+ e 2) 4)746 ww (if w (- w ee))747 d (if d d (max (count mantissa) (min n 7)))748 dd (- d n)]749 (if (<= 0 dd d)750 (let [navigator (fixed-float {:w ww, :d dd, :k 0,751 :overflowchar (:overflowchar params),752 :padchar (:padchar params), :at (:at params)}753 navigator offsets)]754 (print (apply str (repeat ee \space)))755 navigator)756 (exponential-float params navigator offsets))))758 ;; the function to render ~$ directives759 ;; TODO: support rationals. Back off to ~D/~A is the appropriate cases760 (defn- dollar-float [params navigator offsets]761 (let [[^Double arg navigator] (next-arg navigator)762 [mantissa exp] (float-parts (Math/abs arg))763 d (:d params) ; digits after the decimal764 n (:n params) ; minimum digits before the decimal765 w (:w params) ; minimum field width766 add-sign (or (:at params) (neg? arg))767 [rounded-mantissa scaled-exp expanded] (round-str mantissa exp d nil)768 ^String fixed-repr (get-fixed rounded-mantissa (if expanded (inc scaled-exp) scaled-exp) d)769 full-repr (str (apply str (repeat (- n (.indexOf fixed-repr (int \.))) \0)) fixed-repr)770 full-len (+ (count full-repr) (if add-sign 1 0))]771 (print (str772 (if (and (:colon params) add-sign) (if (neg? arg) \- \+))773 (apply str (repeat (- w full-len) (:padchar params)))774 (if (and (not (:colon params)) add-sign) (if (neg? arg) \- \+))775 full-repr))776 navigator))778 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;779 ;;; Support for the '~[...~]' conditional construct in its780 ;;; different flavors781 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;783 ;; ~[...~] without any modifiers chooses one of the clauses based on the param or784 ;; next argument785 ;; TODO check arg is positive int786 (defn- choice-conditional [params arg-navigator offsets]787 (let [arg (:selector params)788 [arg navigator] (if arg [arg arg-navigator] (next-arg arg-navigator))789 clauses (:clauses params)790 clause (if (or (neg? arg) (>= arg (count clauses)))791 (first (:else params))792 (nth clauses arg))]793 (if clause794 (execute-sub-format clause navigator (:base-args params))795 navigator)))797 ;; ~:[...~] with the colon reads the next argument treating it as a truth value798 (defn- boolean-conditional [params arg-navigator offsets]799 (let [[arg navigator] (next-arg arg-navigator)800 clauses (:clauses params)801 clause (if arg802 (second clauses)803 (first clauses))]804 (if clause805 (execute-sub-format clause navigator (:base-args params))806 navigator)))808 ;; ~@[...~] with the at sign executes the conditional if the next arg is not809 ;; nil/false without consuming the arg810 (defn- check-arg-conditional [params arg-navigator offsets]811 (let [[arg navigator] (next-arg arg-navigator)812 clauses (:clauses params)813 clause (if arg (first clauses))]814 (if arg815 (if clause816 (execute-sub-format clause arg-navigator (:base-args params))817 arg-navigator)818 navigator)))821 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;822 ;;; Support for the '~{...~}' iteration construct in its823 ;;; different flavors824 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;827 ;; ~{...~} without any modifiers uses the next argument as an argument list that828 ;; is consumed by all the iterations829 (defn- iterate-sublist [params navigator offsets]830 (let [max-count (:max-iterations params)831 param-clause (first (:clauses params))832 [clause navigator] (if (empty? param-clause)833 (get-format-arg navigator)834 [param-clause navigator])835 [arg-list navigator] (next-arg navigator)836 args (init-navigator arg-list)]837 (loop [count 0838 args args839 last-pos -1]840 (if (and (not max-count) (= (:pos args) last-pos) (> count 1))841 ;; TODO get the offset in here and call format exception842 (throw (RuntimeException. "%{ construct not consuming any arguments: Infinite loop!")))843 (if (or (and (empty? (:rest args))844 (or (not (:colon (:right-params params))) (> count 0)))845 (and max-count (>= count max-count)))846 navigator847 (let [iter-result (execute-sub-format clause args (:base-args params))]848 (if (= :up-arrow (first iter-result))849 navigator850 (recur (inc count) iter-result (:pos args))))))))852 ;; ~:{...~} with the colon treats the next argument as a list of sublists. Each of the853 ;; sublists is used as the arglist for a single iteration.854 (defn- iterate-list-of-sublists [params navigator offsets]855 (let [max-count (:max-iterations params)856 param-clause (first (:clauses params))857 [clause navigator] (if (empty? param-clause)858 (get-format-arg navigator)859 [param-clause navigator])860 [arg-list navigator] (next-arg navigator)]861 (loop [count 0862 arg-list arg-list]863 (if (or (and (empty? arg-list)864 (or (not (:colon (:right-params params))) (> count 0)))865 (and max-count (>= count max-count)))866 navigator867 (let [iter-result (execute-sub-format868 clause869 (init-navigator (first arg-list))870 (init-navigator (next arg-list)))]871 (if (= :colon-up-arrow (first iter-result))872 navigator873 (recur (inc count) (next arg-list))))))))875 ;; ~@{...~} with the at sign uses the main argument list as the arguments to the iterations876 ;; is consumed by all the iterations877 (defn- iterate-main-list [params navigator offsets]878 (let [max-count (:max-iterations params)879 param-clause (first (:clauses params))880 [clause navigator] (if (empty? param-clause)881 (get-format-arg navigator)882 [param-clause navigator])]883 (loop [count 0884 navigator navigator885 last-pos -1]886 (if (and (not max-count) (= (:pos navigator) last-pos) (> count 1))887 ;; TODO get the offset in here and call format exception888 (throw (RuntimeException. "%@{ construct not consuming any arguments: Infinite loop!")))889 (if (or (and (empty? (:rest navigator))890 (or (not (:colon (:right-params params))) (> count 0)))891 (and max-count (>= count max-count)))892 navigator893 (let [iter-result (execute-sub-format clause navigator (:base-args params))]894 (if (= :up-arrow (first iter-result))895 (second iter-result)896 (recur897 (inc count) iter-result (:pos navigator))))))))899 ;; ~@:{...~} with both colon and at sign uses the main argument list as a set of sublists, one900 ;; of which is consumed with each iteration901 (defn- iterate-main-sublists [params navigator offsets]902 (let [max-count (:max-iterations params)903 param-clause (first (:clauses params))904 [clause navigator] (if (empty? param-clause)905 (get-format-arg navigator)906 [param-clause navigator])907 ]908 (loop [count 0909 navigator navigator]910 (if (or (and (empty? (:rest navigator))911 (or (not (:colon (:right-params params))) (> count 0)))912 (and max-count (>= count max-count)))913 navigator914 (let [[sublist navigator] (next-arg-or-nil navigator)915 iter-result (execute-sub-format clause (init-navigator sublist) navigator)]916 (if (= :colon-up-arrow (first iter-result))917 navigator918 (recur (inc count) navigator)))))))920 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;921 ;;; The '~< directive has two completely different meanings922 ;;; in the '~<...~>' form it does justification, but with923 ;;; ~<...~:>' it represents the logical block operation of the924 ;;; pretty printer.925 ;;;926 ;;; Unfortunately, the current architecture decides what function927 ;;; to call at form parsing time before the sub-clauses have been928 ;;; folded, so it is left to run-time to make the decision.929 ;;;930 ;;; TODO: make it possible to make these decisions at compile-time.931 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;933 (declare format-logical-block)934 (declare justify-clauses)936 (defn- logical-block-or-justify [params navigator offsets]937 (if (:colon (:right-params params))938 (format-logical-block params navigator offsets)939 (justify-clauses params navigator offsets)))941 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;942 ;;; Support for the '~<...~>' justification directive943 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;945 (defn- render-clauses [clauses navigator base-navigator]946 (loop [clauses clauses947 acc []948 navigator navigator]949 (if (empty? clauses)950 [acc navigator]951 (let [clause (first clauses)952 [iter-result result-str] (binding [*out* (java.io.StringWriter.)]953 [(execute-sub-format clause navigator base-navigator)954 (.toString *out*)])]955 (if (= :up-arrow (first iter-result))956 [acc (second iter-result)]957 (recur (next clauses) (conj acc result-str) iter-result))))))959 ;; TODO support for ~:; constructions960 (defn- justify-clauses [params navigator offsets]961 (let [[[eol-str] new-navigator] (when-let [else (:else params)]962 (render-clauses else navigator (:base-args params)))963 navigator (or new-navigator navigator)964 [else-params new-navigator] (when-let [p (:else-params params)]965 (realize-parameter-list p navigator))966 navigator (or new-navigator navigator)967 min-remaining (or (first (:min-remaining else-params)) 0)968 max-columns (or (first (:max-columns else-params))969 (get-max-column *out*))970 clauses (:clauses params)971 [strs navigator] (render-clauses clauses navigator (:base-args params))972 slots (max 1973 (+ (dec (count strs)) (if (:colon params) 1 0) (if (:at params) 1 0)))974 chars (reduce + (map count strs))975 mincol (:mincol params)976 minpad (:minpad params)977 colinc (:colinc params)978 minout (+ chars (* slots minpad))979 result-columns (if (<= minout mincol)980 mincol981 (+ mincol (* colinc982 (+ 1 (quot (- minout mincol 1) colinc)))))983 total-pad (- result-columns chars)984 pad (max minpad (quot total-pad slots))985 extra-pad (- total-pad (* pad slots))986 pad-str (apply str (repeat pad (:padchar params)))]987 (if (and eol-str (> (+ (get-column (:base @@*out*)) min-remaining result-columns)988 max-columns))989 (print eol-str))990 (loop [slots slots991 extra-pad extra-pad992 strs strs993 pad-only (or (:colon params)994 (and (= (count strs) 1) (not (:at params))))]995 (if (seq strs)996 (do997 (print (str (if (not pad-only) (first strs))998 (if (or pad-only (next strs) (:at params)) pad-str)999 (if (pos? extra-pad) (:padchar params))))1000 (recur1001 (dec slots)1002 (dec extra-pad)1003 (if pad-only strs (next strs))1004 false))))1005 navigator))1007 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;1008 ;;; Support for case modification with ~(...~).1009 ;;; We do this by wrapping the underlying writer with1010 ;;; a special writer to do the appropriate modification. This1011 ;;; allows us to support arbitrary-sized output and sources1012 ;;; that may block.1013 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;1015 (defn- downcase-writer1016 "Returns a proxy that wraps writer, converting all characters to lower case"1017 [^java.io.Writer writer]1018 (proxy [java.io.Writer] []1019 (close [] (.close writer))1020 (flush [] (.flush writer))1021 (write ([^chars cbuf ^Integer off ^Integer len]1022 (.write writer cbuf off len))1023 ([x]1024 (condp = (class x)1025 String1026 (let [s ^String x]1027 (.write writer (.toLowerCase s)))1029 Integer1030 (let [c ^Character x]1031 (.write writer (int (Character/toLowerCase (char c))))))))))1033 (defn- upcase-writer1034 "Returns a proxy that wraps writer, converting all characters to upper case"1035 [^java.io.Writer writer]1036 (proxy [java.io.Writer] []1037 (close [] (.close writer))1038 (flush [] (.flush writer))1039 (write ([^chars cbuf ^Integer off ^Integer len]1040 (.write writer cbuf off len))1041 ([x]1042 (condp = (class x)1043 String1044 (let [s ^String x]1045 (.write writer (.toUpperCase s)))1047 Integer1048 (let [c ^Character x]1049 (.write writer (int (Character/toUpperCase (char c))))))))))1051 (defn- capitalize-string1052 "Capitalizes the words in a string. If first? is false, don't capitalize the1053 first character of the string even if it's a letter."1054 [s first?]1055 (let [^Character f (first s)1056 s (if (and first? f (Character/isLetter f))1057 (str (Character/toUpperCase f) (subs s 1))1058 s)]1059 (apply str1060 (first1061 (consume1062 (fn [s]1063 (if (empty? s)1064 [nil nil]1065 (let [m (re-matcher #"\W\w" s)1066 match (re-find m)1067 offset (and match (inc (.start m)))]1068 (if offset1069 [(str (subs s 0 offset)1070 (Character/toUpperCase ^Character (nth s offset)))1071 (subs s (inc offset))]1072 [s nil]))))1073 s)))))1075 (defn- capitalize-word-writer1076 "Returns a proxy that wraps writer, captializing all words"1077 [^java.io.Writer writer]1078 (let [last-was-whitespace? (ref true)]1079 (proxy [java.io.Writer] []1080 (close [] (.close writer))1081 (flush [] (.flush writer))1082 (write1083 ([^chars cbuf ^Integer off ^Integer len]1084 (.write writer cbuf off len))1085 ([x]1086 (condp = (class x)1087 String1088 (let [s ^String x]1089 (.write writer1090 ^String (capitalize-string (.toLowerCase s) @last-was-whitespace?))1091 (dosync1092 (ref-set last-was-whitespace?1093 (Character/isWhitespace1094 ^Character (nth s (dec (count s)))))))1096 Integer1097 (let [c (char x)]1098 (let [mod-c (if @last-was-whitespace? (Character/toUpperCase ^Character (char x)) c)]1099 (.write writer (int mod-c))1100 (dosync (ref-set last-was-whitespace? (Character/isWhitespace ^Character (char x))))))))))))1102 (defn- init-cap-writer1103 "Returns a proxy that wraps writer, capitalizing the first word"1104 [^java.io.Writer writer]1105 (let [capped (ref false)]1106 (proxy [java.io.Writer] []1107 (close [] (.close writer))1108 (flush [] (.flush writer))1109 (write ([^chars cbuf ^Integer off ^Integer len]1110 (.write writer cbuf off len))1111 ([x]1112 (condp = (class x)1113 String1114 (let [s (.toLowerCase ^String x)]1115 (if (not @capped)1116 (let [m (re-matcher #"\S" s)1117 match (re-find m)1118 offset (and match (.start m))]1119 (if offset1120 (do (.write writer1121 (str (subs s 0 offset)1122 (Character/toUpperCase ^Character (nth s offset))1123 (.toLowerCase ^String (subs s (inc offset)))))1124 (dosync (ref-set capped true)))1125 (.write writer s)))1126 (.write writer (.toLowerCase s))))1128 Integer1129 (let [c ^Character (char x)]1130 (if (and (not @capped) (Character/isLetter c))1131 (do1132 (dosync (ref-set capped true))1133 (.write writer (int (Character/toUpperCase c))))1134 (.write writer (int (Character/toLowerCase c)))))))))))1136 (defn- modify-case [make-writer params navigator offsets]1137 (let [clause (first (:clauses params))]1138 (binding [*out* (make-writer *out*)]1139 (execute-sub-format clause navigator (:base-args params)))))1141 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;1142 ;;; If necessary, wrap the writer in a PrettyWriter object1143 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;1145 (defn get-pretty-writer1146 "Returns the java.io.Writer passed in wrapped in a pretty writer proxy, unless it's1147 already a pretty writer. Generally, it is unneccesary to call this function, since pprint,1148 write, and cl-format all call it if they need to. However if you want the state to be1149 preserved across calls, you will want to wrap them with this.1151 For example, when you want to generate column-aware output with multiple calls to cl-format,1152 do it like in this example:1154 (defn print-table [aseq column-width]1155 (binding [*out* (get-pretty-writer *out*)]1156 (doseq [row aseq]1157 (doseq [col row]1158 (cl-format true \"~4D~7,vT\" col column-width))1159 (prn))))1161 Now when you run:1163 user> (print-table (map #(vector % (* % %) (* % % %)) (range 1 11)) 8)1165 It prints a table of squares and cubes for the numbers from 1 to 10:1167 1 1 11168 2 4 81169 3 9 271170 4 16 641171 5 25 1251172 6 36 2161173 7 49 3431174 8 64 5121175 9 81 7291176 10 100 1000"1177 {:added "1.2"}1178 [writer]1179 (if (pretty-writer? writer)1180 writer1181 (pretty-writer writer *print-right-margin* *print-miser-width*)))1183 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;1184 ;;; Support for column-aware operations ~&, ~T1185 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;1187 (defn fresh-line1188 "Make a newline if *out* is not already at the beginning of the line. If *out* is1189 not a pretty writer (which keeps track of columns), this function always outputs a newline."1190 {:added "1.2"}1191 []1192 (if (instance? clojure.lang.IDeref *out*)1193 (if (not (= 0 (get-column (:base @@*out*))))1194 (prn))1195 (prn)))1197 (defn- absolute-tabulation [params navigator offsets]1198 (let [colnum (:colnum params)1199 colinc (:colinc params)1200 current (get-column (:base @@*out*))1201 space-count (cond1202 (< current colnum) (- colnum current)1203 (= colinc 0) 01204 :else (- colinc (rem (- current colnum) colinc)))]1205 (print (apply str (repeat space-count \space))))1206 navigator)1208 (defn- relative-tabulation [params navigator offsets]1209 (let [colrel (:colnum params)1210 colinc (:colinc params)1211 start-col (+ colrel (get-column (:base @@*out*)))1212 offset (if (pos? colinc) (rem start-col colinc) 0)1213 space-count (+ colrel (if (= 0 offset) 0 (- colinc offset)))]1214 (print (apply str (repeat space-count \space))))1215 navigator)1218 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;1219 ;;; Support for accessing the pretty printer from a format1220 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;1222 ;; TODO: support ~@; per-line-prefix separator1223 ;; TODO: get the whole format wrapped so we can start the lb at any column1224 (defn- format-logical-block [params navigator offsets]1225 (let [clauses (:clauses params)1226 clause-count (count clauses)1227 prefix (cond1228 (> clause-count 1) (:string (:params (first (first clauses))))1229 (:colon params) "(")1230 body (nth clauses (if (> clause-count 1) 1 0))1231 suffix (cond1232 (> clause-count 2) (:string (:params (first (nth clauses 2))))1233 (:colon params) ")")1234 [arg navigator] (next-arg navigator)]1235 (pprint-logical-block :prefix prefix :suffix suffix1236 (execute-sub-format1237 body1238 (init-navigator arg)1239 (:base-args params)))1240 navigator))1242 (defn- set-indent [params navigator offsets]1243 (let [relative-to (if (:colon params) :current :block)]1244 (pprint-indent relative-to (:n params))1245 navigator))1247 ;;; TODO: support ~:T section options for ~T1249 (defn- conditional-newline [params navigator offsets]1250 (let [kind (if (:colon params)1251 (if (:at params) :mandatory :fill)1252 (if (:at params) :miser :linear))]1253 (pprint-newline kind)1254 navigator))1256 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;1257 ;;; The table of directives we support, each with its params,1258 ;;; properties, and the compilation function1259 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;1261 ;; We start with a couple of helpers1262 (defn- process-directive-table-element [ [ char params flags bracket-info & generator-fn ] ]1263 [char,1264 {:directive char,1265 :params `(array-map ~@params),1266 :flags flags,1267 :bracket-info bracket-info,1268 :generator-fn (concat '(fn [ params offset]) generator-fn) }])1270 (defmacro ^{:private true}1271 defdirectives1272 [ & directives ]1273 `(def ^{:private true}1274 directive-table (hash-map ~@(mapcat process-directive-table-element directives))))1276 (defdirectives1277 (\A1278 [ :mincol [0 Integer] :colinc [1 Integer] :minpad [0 Integer] :padchar [\space Character] ]1279 #{ :at :colon :both} {}1280 #(format-ascii print-str %1 %2 %3))1282 (\S1283 [ :mincol [0 Integer] :colinc [1 Integer] :minpad [0 Integer] :padchar [\space Character] ]1284 #{ :at :colon :both} {}1285 #(format-ascii pr-str %1 %2 %3))1287 (\D1288 [ :mincol [0 Integer] :padchar [\space Character] :commachar [\, Character]1289 :commainterval [ 3 Integer]]1290 #{ :at :colon :both } {}1291 #(format-integer 10 %1 %2 %3))1293 (\B1294 [ :mincol [0 Integer] :padchar [\space Character] :commachar [\, Character]1295 :commainterval [ 3 Integer]]1296 #{ :at :colon :both } {}1297 #(format-integer 2 %1 %2 %3))1299 (\O1300 [ :mincol [0 Integer] :padchar [\space Character] :commachar [\, Character]1301 :commainterval [ 3 Integer]]1302 #{ :at :colon :both } {}1303 #(format-integer 8 %1 %2 %3))1305 (\X1306 [ :mincol [0 Integer] :padchar [\space Character] :commachar [\, Character]1307 :commainterval [ 3 Integer]]1308 #{ :at :colon :both } {}1309 #(format-integer 16 %1 %2 %3))1311 (\R1312 [:base [nil Integer] :mincol [0 Integer] :padchar [\space Character] :commachar [\, Character]1313 :commainterval [ 3 Integer]]1314 #{ :at :colon :both } {}1315 (do1316 (cond ; ~R is overloaded with bizareness1317 (first (:base params)) #(format-integer (:base %1) %1 %2 %3)1318 (and (:at params) (:colon params)) #(format-old-roman %1 %2 %3)1319 (:at params) #(format-new-roman %1 %2 %3)1320 (:colon params) #(format-ordinal-english %1 %2 %3)1321 true #(format-cardinal-english %1 %2 %3))))1323 (\P1324 [ ]1325 #{ :at :colon :both } {}1326 (fn [params navigator offsets]1327 (let [navigator (if (:colon params) (relative-reposition navigator -1) navigator)1328 strs (if (:at params) ["y" "ies"] ["" "s"])1329 [arg navigator] (next-arg navigator)]1330 (print (if (= arg 1) (first strs) (second strs)))1331 navigator)))1333 (\C1334 [:char-format [nil Character]]1335 #{ :at :colon :both } {}1336 (cond1337 (:colon params) pretty-character1338 (:at params) readable-character1339 :else plain-character))1341 (\F1342 [ :w [nil Integer] :d [nil Integer] :k [0 Integer] :overflowchar [nil Character]1343 :padchar [\space Character] ]1344 #{ :at } {}1345 fixed-float)1347 (\E1348 [ :w [nil Integer] :d [nil Integer] :e [nil Integer] :k [1 Integer]1349 :overflowchar [nil Character] :padchar [\space Character]1350 :exponentchar [nil Character] ]1351 #{ :at } {}1352 exponential-float)1354 (\G1355 [ :w [nil Integer] :d [nil Integer] :e [nil Integer] :k [1 Integer]1356 :overflowchar [nil Character] :padchar [\space Character]1357 :exponentchar [nil Character] ]1358 #{ :at } {}1359 general-float)1361 (\$1362 [ :d [2 Integer] :n [1 Integer] :w [0 Integer] :padchar [\space Character]]1363 #{ :at :colon :both} {}1364 dollar-float)1366 (\%1367 [ :count [1 Integer] ]1368 #{ } {}1369 (fn [params arg-navigator offsets]1370 (dotimes [i (:count params)]1371 (prn))1372 arg-navigator))1374 (\&1375 [ :count [1 Integer] ]1376 #{ :pretty } {}1377 (fn [params arg-navigator offsets]1378 (let [cnt (:count params)]1379 (if (pos? cnt) (fresh-line))1380 (dotimes [i (dec cnt)]1381 (prn)))1382 arg-navigator))1384 (\|1385 [ :count [1 Integer] ]1386 #{ } {}1387 (fn [params arg-navigator offsets]1388 (dotimes [i (:count params)]1389 (print \formfeed))1390 arg-navigator))1392 (\~1393 [ :n [1 Integer] ]1394 #{ } {}1395 (fn [params arg-navigator offsets]1396 (let [n (:n params)]1397 (print (apply str (repeat n \~)))1398 arg-navigator)))1400 (\newline ;; Whitespace supression is handled in the compilation loop1401 [ ]1402 #{:colon :at} {}1403 (fn [params arg-navigator offsets]1404 (if (:at params)1405 (prn))1406 arg-navigator))1408 (\T1409 [ :colnum [1 Integer] :colinc [1 Integer] ]1410 #{ :at :pretty } {}1411 (if (:at params)1412 #(relative-tabulation %1 %2 %3)1413 #(absolute-tabulation %1 %2 %3)))1415 (\*1416 [ :n [1 Integer] ]1417 #{ :colon :at } {}1418 (fn [params navigator offsets]1419 (let [n (:n params)]1420 (if (:at params)1421 (absolute-reposition navigator n)1422 (relative-reposition navigator (if (:colon params) (- n) n)))1423 )))1425 (\?1426 [ ]1427 #{ :at } {}1428 (if (:at params)1429 (fn [params navigator offsets] ; args from main arg list1430 (let [[subformat navigator] (get-format-arg navigator)]1431 (execute-sub-format subformat navigator (:base-args params))))1432 (fn [params navigator offsets] ; args from sub-list1433 (let [[subformat navigator] (get-format-arg navigator)1434 [subargs navigator] (next-arg navigator)1435 sub-navigator (init-navigator subargs)]1436 (execute-sub-format subformat sub-navigator (:base-args params))1437 navigator))))1440 (\(1441 [ ]1442 #{ :colon :at :both} { :right \), :allows-separator nil, :else nil }1443 (let [mod-case-writer (cond1444 (and (:at params) (:colon params))1445 upcase-writer1447 (:colon params)1448 capitalize-word-writer1450 (:at params)1451 init-cap-writer1453 :else1454 downcase-writer)]1455 #(modify-case mod-case-writer %1 %2 %3)))1457 (\) [] #{} {} nil)1459 (\[1460 [ :selector [nil Integer] ]1461 #{ :colon :at } { :right \], :allows-separator true, :else :last }1462 (cond1463 (:colon params)1464 boolean-conditional1466 (:at params)1467 check-arg-conditional1469 true1470 choice-conditional))1472 (\; [:min-remaining [nil Integer] :max-columns [nil Integer]]1473 #{ :colon } { :separator true } nil)1475 (\] [] #{} {} nil)1477 (\{1478 [ :max-iterations [nil Integer] ]1479 #{ :colon :at :both} { :right \}, :allows-separator false }1480 (cond1481 (and (:at params) (:colon params))1482 iterate-main-sublists1484 (:colon params)1485 iterate-list-of-sublists1487 (:at params)1488 iterate-main-list1490 true1491 iterate-sublist))1494 (\} [] #{:colon} {} nil)1496 (\<1497 [:mincol [0 Integer] :colinc [1 Integer] :minpad [0 Integer] :padchar [\space Character]]1498 #{:colon :at :both :pretty} { :right \>, :allows-separator true, :else :first }1499 logical-block-or-justify)1501 (\> [] #{:colon} {} nil)1503 ;; TODO: detect errors in cases where colon not allowed1504 (\^ [:arg1 [nil Integer] :arg2 [nil Integer] :arg3 [nil Integer]]1505 #{:colon} {}1506 (fn [params navigator offsets]1507 (let [arg1 (:arg1 params)1508 arg2 (:arg2 params)1509 arg3 (:arg3 params)1510 exit (if (:colon params) :colon-up-arrow :up-arrow)]1511 (cond1512 (and arg1 arg2 arg3)1513 (if (<= arg1 arg2 arg3) [exit navigator] navigator)1515 (and arg1 arg2)1516 (if (= arg1 arg2) [exit navigator] navigator)1518 arg11519 (if (= arg1 0) [exit navigator] navigator)1521 true ; TODO: handle looking up the arglist stack for info1522 (if (if (:colon params)1523 (empty? (:rest (:base-args params)))1524 (empty? (:rest navigator)))1525 [exit navigator] navigator)))))1527 (\W1528 []1529 #{:at :colon :both} {}1530 (if (or (:at params) (:colon params))1531 (let [bindings (concat1532 (if (:at params) [:level nil :length nil] [])1533 (if (:colon params) [:pretty true] []))]1534 (fn [params navigator offsets]1535 (let [[arg navigator] (next-arg navigator)]1536 (if (apply write arg bindings)1537 [:up-arrow navigator]1538 navigator))))1539 (fn [params navigator offsets]1540 (let [[arg navigator] (next-arg navigator)]1541 (if (write-out arg)1542 [:up-arrow navigator]1543 navigator)))))1545 (\_1546 []1547 #{:at :colon :both} {}1548 conditional-newline)1550 (\I1551 [:n [0 Integer]]1552 #{:colon} {}1553 set-indent)1554 )1556 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;1557 ;;; Code to manage the parameters and flags associated with each1558 ;;; directive in the format string.1559 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;1561 (def ^{:private true}1562 param-pattern #"^([vV]|#|('.)|([+-]?\d+)|(?=,))")1563 (def ^{:private true}1564 special-params #{ :parameter-from-args :remaining-arg-count })1566 (defn- extract-param [[s offset saw-comma]]1567 (let [m (re-matcher param-pattern s)1568 param (re-find m)]1569 (if param1570 (let [token-str (first (re-groups m))1571 remainder (subs s (.end m))1572 new-offset (+ offset (.end m))]1573 (if (not (= \, (nth remainder 0)))1574 [ [token-str offset] [remainder new-offset false]]1575 [ [token-str offset] [(subs remainder 1) (inc new-offset) true]]))1576 (if saw-comma1577 (format-error "Badly formed parameters in format directive" offset)1578 [ nil [s offset]]))))1581 (defn- extract-params [s offset]1582 (consume extract-param [s offset false]))1584 (defn- translate-param1585 "Translate the string representation of a param to the internalized1586 representation"1587 [[^String p offset]]1588 [(cond1589 (= (.length p) 0) nil1590 (and (= (.length p) 1) (contains? #{\v \V} (nth p 0))) :parameter-from-args1591 (and (= (.length p) 1) (= \# (nth p 0))) :remaining-arg-count1592 (and (= (.length p) 2) (= \' (nth p 0))) (nth p 1)1593 true (new Integer p))1594 offset])1596 (def ^{:private true}1597 flag-defs { \: :colon, \@ :at })1599 (defn- extract-flags [s offset]1600 (consume1601 (fn [[s offset flags]]1602 (if (empty? s)1603 [nil [s offset flags]]1604 (let [flag (get flag-defs (first s))]1605 (if flag1606 (if (contains? flags flag)1607 (format-error1608 (str "Flag \"" (first s) "\" appears more than once in a directive")1609 offset)1610 [true [(subs s 1) (inc offset) (assoc flags flag [true offset])]])1611 [nil [s offset flags]]))))1612 [s offset {}]))1614 (defn- check-flags [def flags]1615 (let [allowed (:flags def)]1616 (if (and (not (:at allowed)) (:at flags))1617 (format-error (str "\"@\" is an illegal flag for format directive \"" (:directive def) "\"")1618 (nth (:at flags) 1)))1619 (if (and (not (:colon allowed)) (:colon flags))1620 (format-error (str "\":\" is an illegal flag for format directive \"" (:directive def) "\"")1621 (nth (:colon flags) 1)))1622 (if (and (not (:both allowed)) (:at flags) (:colon flags))1623 (format-error (str "Cannot combine \"@\" and \":\" flags for format directive \""1624 (:directive def) "\"")1625 (min (nth (:colon flags) 1) (nth (:at flags) 1))))))1627 (defn- map-params1628 "Takes a directive definition and the list of actual parameters and1629 a map of flags and returns a map of the parameters and flags with defaults1630 filled in. We check to make sure that there are the right types and number1631 of parameters as well."1632 [def params flags offset]1633 (check-flags def flags)1634 (if (> (count params) (count (:params def)))1635 (format-error1636 (cl-format1637 nil1638 "Too many parameters for directive \"~C\": ~D~:* ~[were~;was~:;were~] specified but only ~D~:* ~[are~;is~:;are~] allowed"1639 (:directive def) (count params) (count (:params def)))1640 (second (first params))))1641 (doall1642 (map #(let [val (first %1)]1643 (if (not (or (nil? val) (contains? special-params val)1644 (instance? (second (second %2)) val)))1645 (format-error (str "Parameter " (name (first %2))1646 " has bad type in directive \"" (:directive def) "\": "1647 (class val))1648 (second %1))) )1649 params (:params def)))1651 (merge ; create the result map1652 (into (array-map) ; start with the default values, make sure the order is right1653 (reverse (for [[name [default]] (:params def)] [name [default offset]])))1654 (reduce #(apply assoc %1 %2) {} (filter #(first (nth % 1)) (zipmap (keys (:params def)) params))) ; add the specified parameters, filtering out nils1655 flags)) ; and finally add the flags1657 (defn- compile-directive [s offset]1658 (let [[raw-params [rest offset]] (extract-params s offset)1659 [_ [rest offset flags]] (extract-flags rest offset)1660 directive (first rest)1661 def (get directive-table (Character/toUpperCase ^Character directive))1662 params (if def (map-params def (map translate-param raw-params) flags offset))]1663 (if (not directive)1664 (format-error "Format string ended in the middle of a directive" offset))1665 (if (not def)1666 (format-error (str "Directive \"" directive "\" is undefined") offset))1667 [(struct compiled-directive ((:generator-fn def) params offset) def params offset)1668 (let [remainder (subs rest 1)1669 offset (inc offset)1670 trim? (and (= \newline (:directive def))1671 (not (:colon params)))1672 trim-count (if trim? (prefix-count remainder [\space \tab]) 0)1673 remainder (subs remainder trim-count)1674 offset (+ offset trim-count)]1675 [remainder offset])]))1677 (defn- compile-raw-string [s offset]1678 (struct compiled-directive (fn [_ a _] (print s) a) nil { :string s } offset))1680 (defn- right-bracket [this] (:right (:bracket-info (:def this))))1681 (defn- separator? [this] (:separator (:bracket-info (:def this))))1682 (defn- else-separator? [this]1683 (and (:separator (:bracket-info (:def this)))1684 (:colon (:params this))))1687 (declare collect-clauses)1689 (defn- process-bracket [this remainder]1690 (let [[subex remainder] (collect-clauses (:bracket-info (:def this))1691 (:offset this) remainder)]1692 [(struct compiled-directive1693 (:func this) (:def this)1694 (merge (:params this) (tuple-map subex (:offset this)))1695 (:offset this))1696 remainder]))1698 (defn- process-clause [bracket-info offset remainder]1699 (consume1700 (fn [remainder]1701 (if (empty? remainder)1702 (format-error "No closing bracket found." offset)1703 (let [this (first remainder)1704 remainder (next remainder)]1705 (cond1706 (right-bracket this)1707 (process-bracket this remainder)1709 (= (:right bracket-info) (:directive (:def this)))1710 [ nil [:right-bracket (:params this) nil remainder]]1712 (else-separator? this)1713 [nil [:else nil (:params this) remainder]]1715 (separator? this)1716 [nil [:separator nil nil remainder]] ;; TODO: check to make sure that there are no params on ~;1718 true1719 [this remainder]))))1720 remainder))1722 (defn- collect-clauses [bracket-info offset remainder]1723 (second1724 (consume1725 (fn [[clause-map saw-else remainder]]1726 (let [[clause [type right-params else-params remainder]]1727 (process-clause bracket-info offset remainder)]1728 (cond1729 (= type :right-bracket)1730 [nil [(merge-with concat clause-map1731 {(if saw-else :else :clauses) [clause]1732 :right-params right-params})1733 remainder]]1735 (= type :else)1736 (cond1737 (:else clause-map)1738 (format-error "Two else clauses (\"~:;\") inside bracket construction." offset)1740 (not (:else bracket-info))1741 (format-error "An else clause (\"~:;\") is in a bracket type that doesn't support it."1742 offset)1744 (and (= :first (:else bracket-info)) (seq (:clauses clause-map)))1745 (format-error1746 "The else clause (\"~:;\") is only allowed in the first position for this directive."1747 offset)1749 true ; if the ~:; is in the last position, the else clause1750 ; is next, this was a regular clause1751 (if (= :first (:else bracket-info))1752 [true [(merge-with concat clause-map { :else [clause] :else-params else-params})1753 false remainder]]1754 [true [(merge-with concat clause-map { :clauses [clause] })1755 true remainder]]))1757 (= type :separator)1758 (cond1759 saw-else1760 (format-error "A plain clause (with \"~;\") follows an else clause (\"~:;\") inside bracket construction." offset)1762 (not (:allows-separator bracket-info))1763 (format-error "A separator (\"~;\") is in a bracket type that doesn't support it."1764 offset)1766 true1767 [true [(merge-with concat clause-map { :clauses [clause] })1768 false remainder]]))))1769 [{ :clauses [] } false remainder])))1771 (defn- process-nesting1772 "Take a linearly compiled format and process the bracket directives to give it1773 the appropriate tree structure"1774 [format]1775 (first1776 (consume1777 (fn [remainder]1778 (let [this (first remainder)1779 remainder (next remainder)1780 bracket (:bracket-info (:def this))]1781 (if (:right bracket)1782 (process-bracket this remainder)1783 [this remainder])))1784 format)))1786 (defn- compile-format1787 "Compiles format-str into a compiled format which can be used as an argument1788 to cl-format just like a plain format string. Use this function for improved1789 performance when you're using the same format string repeatedly"1790 [ format-str ]1791 ; (prlabel compiling format-str)1792 (binding [*format-str* format-str]1793 (process-nesting1794 (first1795 (consume1796 (fn [[^String s offset]]1797 (if (empty? s)1798 [nil s]1799 (let [tilde (.indexOf s (int \~))]1800 (cond1801 (neg? tilde) [(compile-raw-string s offset) ["" (+ offset (.length s))]]1802 (zero? tilde) (compile-directive (subs s 1) (inc offset))1803 true1804 [(compile-raw-string (subs s 0 tilde) offset) [(subs s tilde) (+ tilde offset)]]))))1805 [format-str 0])))))1807 (defn- needs-pretty1808 "determine whether a given compiled format has any directives that depend on the1809 column number or pretty printing"1810 [format]1811 (loop [format format]1812 (if (empty? format)1813 false1814 (if (or (:pretty (:flags (:def (first format))))1815 (some needs-pretty (first (:clauses (:params (first format)))))1816 (some needs-pretty (first (:else (:params (first format))))))1817 true1818 (recur (next format))))))1820 (defn- execute-format1821 "Executes the format with the arguments."1822 {:skip-wiki true}1823 ([stream format args]1824 (let [^java.io.Writer real-stream (cond1825 (not stream) (java.io.StringWriter.)1826 (true? stream) *out*1827 :else stream)1828 ^java.io.Writer wrapped-stream (if (and (needs-pretty format)1829 (not (pretty-writer? real-stream)))1830 (get-pretty-writer real-stream)1831 real-stream)]1832 (binding [*out* wrapped-stream]1833 (try1834 (execute-format format args)1835 (finally1836 (if-not (identical? real-stream wrapped-stream)1837 (.flush wrapped-stream))))1838 (if (not stream) (.toString real-stream)))))1839 ([format args]1840 (map-passing-context1841 (fn [element context]1842 (if (abort? context)1843 [nil context]1844 (let [[params args] (realize-parameter-list1845 (:params element) context)1846 [params offsets] (unzip-map params)1847 params (assoc params :base-args args)]1848 [nil (apply (:func element) [params args offsets])])))1849 args1850 format)1851 nil))1853 ;;; This is a bad idea, but it prevents us from leaking private symbols1854 ;;; This should all be replaced by really compiled formats anyway.1855 (def ^{:private true} cached-compile (memoize compile-format))1857 (defmacro formatter1858 "Makes a function which can directly run format-in. The function is1859 fn [stream & args] ... and returns nil unless the stream is nil (meaning1860 output to a string) in which case it returns the resulting string.1862 format-in can be either a control string or a previously compiled format."1863 {:added "1.2"}1864 [format-in]1865 `(let [format-in# ~format-in1866 my-c-c# (var-get (get (ns-interns (the-ns 'clojure.pprint))1867 '~'cached-compile))1868 my-e-f# (var-get (get (ns-interns (the-ns 'clojure.pprint))1869 '~'execute-format))1870 my-i-n# (var-get (get (ns-interns (the-ns 'clojure.pprint))1871 '~'init-navigator))1872 cf# (if (string? format-in#) (my-c-c# format-in#) format-in#)]1873 (fn [stream# & args#]1874 (let [navigator# (my-i-n# args#)]1875 (my-e-f# stream# cf# navigator#)))))1877 (defmacro formatter-out1878 "Makes a function which can directly run format-in. The function is1879 fn [& args] ... and returns nil. This version of the formatter macro is1880 designed to be used with *out* set to an appropriate Writer. In particular,1881 this is meant to be used as part of a pretty printer dispatch method.1883 format-in can be either a control string or a previously compiled format."1884 {:added "1.2"}1885 [format-in]1886 `(let [format-in# ~format-in1887 cf# (if (string? format-in#) (#'clojure.pprint/cached-compile format-in#) format-in#)]1888 (fn [& args#]1889 (let [navigator# (#'clojure.pprint/init-navigator args#)]1890 (#'clojure.pprint/execute-format cf# navigator#)))))