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 Clojure
3 ; Copyright (c) Rich Hickey. All rights reserved.
4 ; The use and distribution terms for this software are covered by the
5 ; 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 by
8 ; the terms of this license.
9 ; You must not remove this notice, or any other, from this software.
11 ;; Author: Tom Faulhaber
12 ;; April 3, 2009
15 ;; This module implements the Common Lisp compatible format function as documented
16 ;; 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 references
22 (declare compile-format)
23 (declare execute-format)
24 (declare init-navigator)
25 ;;; End forward references
27 (defn cl-format
28 "An implementation of a Common Lisp compatible format function. cl-format formats its
29 arguments to an output stream or string based on the format control string given. It
30 supports sophisticated formatting of structured data.
32 Writer is an instance of java.io.Writer, true to output to *out* or nil to output
33 to a string, format-in is the format control string and the remaining arguments
34 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-format
40 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, 22
50 Detailed documentation on format control strings is available in the \"Common Lisp the
51 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 at
54 http://www.lispworks.com/documentation/HyperSpec/Body/22_c.htm
55 "
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* \newline
70 (apply str (repeat offset \space)) "^" \newline)]
71 (throw (RuntimeException. full-message))))
73 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
74 ;;; Argument navigators manage the argument list
75 ;;; as the format statement moves through the list
76 ;;; (possibly going forwards and backwards as it does so)
77 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
79 (defstruct ^{:private true}
80 arg-navigator :seq :rest :pos )
82 (defn- init-navigator
83 "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 offset
90 (defn- next-arg [ navigator ]
91 (let [ rst (:rest navigator) ]
92 (if rst
93 [(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 rst
99 [(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 compiled
103 (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 manipulate
128 ;;; the argument list as well (for 'V' and '#' parameter types).
129 ;;; We hide all of this behind a function, but clients need to
130 ;;; manage changing arg navigator
131 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
133 ;; TODO: validate parameters when they come from arg list
134 (defn- realize-parameter [[param [raw-val offset]] navigator]
135 (let [[real-param new-navigator]
136 (cond
137 (contains? #{ :at :colon } param) ;pass flags through unchanged - this really isn't necessary
138 [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 true
147 [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 directives
157 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
159 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
160 ;;; Common handling code for ~A and ~S
161 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
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 (cond
170 (integer? n) (if (= *print-base* 10)
171 (str n (if *print-radix* "."))
172 (str
173 (if *print-radix* (or (get special-radix-markers *print-base*) (str "#" *print-base* "r")))
174 (opt-base-str *print-base* n)))
175 (ratio? n) (str
176 (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-width
189 (+ min-width
190 (* (+ (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 some
202 ;;; of ~R
203 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
205 (defn- integral?
206 "returns true if a number is actually an integer (that is, has no fractional part)"
207 [x]
208 (cond
209 (integer? x) true
210 (decimal? x) (>= (.ulp (.stripTrailingZeros (bigdec 0))) 1) ; true iff no fractional part
211 (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- remainders
217 "Return the list of remainders (essentially the 'digits') of val in the given base"
218 [base val]
219 (reverse
220 (first
221 (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-str
228 "Return val as a string in the given base"
229 [base val]
230 (if (zero? val)
231 "0"
232 (let [xlated-val (cond
233 (float? val) (bigdec val)
234 (ratio? val) (let [^clojure.lang.Ratio r val]
235 (/ (.numerator r) (.denominator r)))
236 :else val)]
237 (apply str
238 (map
239 #(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-str
246 "Return val as a string in the given base, using clojure.core/format if supported
247 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 (reverse
256 (first
257 (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 (cond
271 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 0
281 :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-units
291 ["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-units
297 ["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-tens
303 ["" "" "twenty" "thirty" "forty" "fifty" "sixty" "seventy" "eighty" "ninety"])
305 (def ^{:private true}
306 english-ordinal-tens
307 ["" "" "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.htm
312 ;; We follow the rules for writing numbers from the Blue Book
313 ;; (http://www.grammarbook.com/numbers/numbers.asp)
314 (def ^{:private true}
315 english-scale-numbers
316 ["" "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-cardinal
323 "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 (str
328 (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 (str
336 (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-scales
341 "Take a sequence of parts, add scale numbers (e.g., million) and combine into a string
342 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 this
353 (if (and (not (empty? this)) (pos? (+ pos offset)))
354 (str " " (nth english-scale-numbers (+ pos offset)))))
355 (recur
356 (if (empty? this)
357 acc
358 (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/abs
368 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 ~D
374 10
375 { :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-ordinal
381 "Convert a number less than 1000 to a ordinal english string
382 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 (str
387 (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 (str
397 (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/abs
407 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 (cond
414 (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 ~D
420 10
421 { :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 (cond
428 (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-table
441 [[ "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-table
448 [[ "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-roman
454 "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 acc
467 (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 ~D
471 10
472 { :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 (cond
498 special special
499 (< 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 (~^) construct
518 ;; TODO: move these funcs somewhere more appropriate
519 (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 constructions
524 (defn- execute-sub-format [format args base-args]
525 (second
526 (map-passing-context
527 (fn [element context]
528 (if (abort? context)
529 [nil context] ; just keep passing it along
530 (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 args
535 format)))
537 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
538 ;;; Support for real number formats
539 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
541 ;; TODO - return exponent as int to eliminate double conversion
542 (defn- float-parts-base
543 "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-parts
556 "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-pos
579 (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-zeros
588 (String/valueOf (+ result-val
589 (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-decimal
606 "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-decimal
617 "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 directives
624 ;; TODO: support rationals. Back off to ~D/~A is the appropriate cases
625 (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-exp
635 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 w
639 (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 (str
649 (apply str (repeat (- w full-len) (:padchar params)))
650 (if add-sign sign)
651 (if prepend-zero "0")
652 fixed-repr
653 (if append-zero "0")))))
654 (print (str
655 (if add-sign sign)
656 (if prepend-zero "0")
657 fixed-repr
658 (if append-zero "0"))))
659 navigator))
662 ;; the function to render ~E directives
663 ;; TODO: support rationals. Back off to ~D/~A is the appropriate cases
664 ;; TODO: define ~E representation for Infinity
665 (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 str
679 (repeat
680 (- e
681 (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 mantissa
688 (if d
689 (apply str
690 (repeat
691 (- 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-str
695 scaled-mantissa 0
696 (cond
697 (= k 0) (dec d)
698 (pos? k) d
699 (neg? k) (dec d))
700 (if w-mantissa
701 (- 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 w
706 (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 (str
715 (apply str
716 (repeat
717 (- 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-mantissa
722 (if append-zero "0")
723 scaled-exp-str))))
724 (print (str
725 (if add-sign (if (neg? arg) \- \+))
726 (if prepend-zero "0")
727 full-mantissa
728 (if append-zero "0")
729 scaled-exp-str)))
730 (recur [rounded-mantissa (inc exp)]))))
731 navigator))
733 ;; the function to render ~G directives
734 ;; This just figures out whether to pass the request off to ~F or ~E based
735 ;; on the algorithm in CLtL.
736 ;; TODO: support rationals. Back off to ~D/~A is the appropriate cases
737 ;; TODO: refactor so that float-parts isn't called twice
738 (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 ~$ directives
759 ;; TODO: support rationals. Back off to ~D/~A is the appropriate cases
760 (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 decimal
764 n (:n params) ; minimum digits before the decimal
765 w (:w params) ; minimum field width
766 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 (str
772 (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 its
780 ;;; different flavors
781 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
783 ;; ~[...~] without any modifiers chooses one of the clauses based on the param or
784 ;; next argument
785 ;; TODO check arg is positive int
786 (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 clause
794 (execute-sub-format clause navigator (:base-args params))
795 navigator)))
797 ;; ~:[...~] with the colon reads the next argument treating it as a truth value
798 (defn- boolean-conditional [params arg-navigator offsets]
799 (let [[arg navigator] (next-arg arg-navigator)
800 clauses (:clauses params)
801 clause (if arg
802 (second clauses)
803 (first clauses))]
804 (if clause
805 (execute-sub-format clause navigator (:base-args params))
806 navigator)))
808 ;; ~@[...~] with the at sign executes the conditional if the next arg is not
809 ;; nil/false without consuming the arg
810 (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 arg
815 (if clause
816 (execute-sub-format clause arg-navigator (:base-args params))
817 arg-navigator)
818 navigator)))
821 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
822 ;;; Support for the '~{...~}' iteration construct in its
823 ;;; different flavors
824 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
827 ;; ~{...~} without any modifiers uses the next argument as an argument list that
828 ;; is consumed by all the iterations
829 (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 0
838 args args
839 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 exception
842 (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 navigator
847 (let [iter-result (execute-sub-format clause args (:base-args params))]
848 (if (= :up-arrow (first iter-result))
849 navigator
850 (recur (inc count) iter-result (:pos args))))))))
852 ;; ~:{...~} with the colon treats the next argument as a list of sublists. Each of the
853 ;; 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 0
862 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 navigator
867 (let [iter-result (execute-sub-format
868 clause
869 (init-navigator (first arg-list))
870 (init-navigator (next arg-list)))]
871 (if (= :colon-up-arrow (first iter-result))
872 navigator
873 (recur (inc count) (next arg-list))))))))
875 ;; ~@{...~} with the at sign uses the main argument list as the arguments to the iterations
876 ;; is consumed by all the iterations
877 (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 0
884 navigator navigator
885 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 exception
888 (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 navigator
893 (let [iter-result (execute-sub-format clause navigator (:base-args params))]
894 (if (= :up-arrow (first iter-result))
895 (second iter-result)
896 (recur
897 (inc count) iter-result (:pos navigator))))))))
899 ;; ~@:{...~} with both colon and at sign uses the main argument list as a set of sublists, one
900 ;; of which is consumed with each iteration
901 (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 0
909 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 navigator
914 (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 navigator
918 (recur (inc count) navigator)))))))
920 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
921 ;;; The '~< directive has two completely different meanings
922 ;;; in the '~<...~>' form it does justification, but with
923 ;;; ~<...~:>' it represents the logical block operation of the
924 ;;; pretty printer.
925 ;;;
926 ;;; Unfortunately, the current architecture decides what function
927 ;;; to call at form parsing time before the sub-clauses have been
928 ;;; 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 directive
943 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
945 (defn- render-clauses [clauses navigator base-navigator]
946 (loop [clauses clauses
947 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 ~:; constructions
960 (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 1
973 (+ (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 mincol
981 (+ mincol (* colinc
982 (+ 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 slots
991 extra-pad extra-pad
992 strs strs
993 pad-only (or (:colon params)
994 (and (= (count strs) 1) (not (:at params))))]
995 (if (seq strs)
996 (do
997 (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 (recur
1001 (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 with
1010 ;;; a special writer to do the appropriate modification. This
1011 ;;; allows us to support arbitrary-sized output and sources
1012 ;;; that may block.
1013 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
1015 (defn- downcase-writer
1016 "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 String
1026 (let [s ^String x]
1027 (.write writer (.toLowerCase s)))
1029 Integer
1030 (let [c ^Character x]
1031 (.write writer (int (Character/toLowerCase (char c))))))))))
1033 (defn- upcase-writer
1034 "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 String
1044 (let [s ^String x]
1045 (.write writer (.toUpperCase s)))
1047 Integer
1048 (let [c ^Character x]
1049 (.write writer (int (Character/toUpperCase (char c))))))))))
1051 (defn- capitalize-string
1052 "Capitalizes the words in a string. If first? is false, don't capitalize the
1053 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 str
1060 (first
1061 (consume
1062 (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 offset
1069 [(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-writer
1076 "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 (write
1083 ([^chars cbuf ^Integer off ^Integer len]
1084 (.write writer cbuf off len))
1085 ([x]
1086 (condp = (class x)
1087 String
1088 (let [s ^String x]
1089 (.write writer
1090 ^String (capitalize-string (.toLowerCase s) @last-was-whitespace?))
1091 (dosync
1092 (ref-set last-was-whitespace?
1093 (Character/isWhitespace
1094 ^Character (nth s (dec (count s)))))))
1096 Integer
1097 (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-writer
1103 "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 String
1114 (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 offset
1120 (do (.write writer
1121 (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 Integer
1129 (let [c ^Character (char x)]
1130 (if (and (not @capped) (Character/isLetter c))
1131 (do
1132 (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 object
1143 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
1145 (defn get-pretty-writer
1146 "Returns the java.io.Writer passed in wrapped in a pretty writer proxy, unless it's
1147 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 be
1149 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 1
1168 2 4 8
1169 3 9 27
1170 4 16 64
1171 5 25 125
1172 6 36 216
1173 7 49 343
1174 8 64 512
1175 9 81 729
1176 10 100 1000"
1177 {:added "1.2"}
1178 [writer]
1179 (if (pretty-writer? writer)
1180 writer
1181 (pretty-writer writer *print-right-margin* *print-miser-width*)))
1183 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
1184 ;;; Support for column-aware operations ~&, ~T
1185 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
1187 (defn fresh-line
1188 "Make a newline if *out* is not already at the beginning of the line. If *out* is
1189 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 (cond
1202 (< current colnum) (- colnum current)
1203 (= colinc 0) 0
1204 :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 format
1220 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
1222 ;; TODO: support ~@; per-line-prefix separator
1223 ;; TODO: get the whole format wrapped so we can start the lb at any column
1224 (defn- format-logical-block [params navigator offsets]
1225 (let [clauses (:clauses params)
1226 clause-count (count clauses)
1227 prefix (cond
1228 (> clause-count 1) (:string (:params (first (first clauses))))
1229 (:colon params) "(")
1230 body (nth clauses (if (> clause-count 1) 1 0))
1231 suffix (cond
1232 (> 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 suffix
1236 (execute-sub-format
1237 body
1238 (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 ~T
1249 (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 function
1259 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
1261 ;; We start with a couple of helpers
1262 (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 defdirectives
1272 [ & directives ]
1273 `(def ^{:private true}
1274 directive-table (hash-map ~@(mapcat process-directive-table-element directives))))
1276 (defdirectives
1277 (\A
1278 [ :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 (\S
1283 [ :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 (\D
1288 [ :mincol [0 Integer] :padchar [\space Character] :commachar [\, Character]
1289 :commainterval [ 3 Integer]]
1290 #{ :at :colon :both } {}
1291 #(format-integer 10 %1 %2 %3))
1293 (\B
1294 [ :mincol [0 Integer] :padchar [\space Character] :commachar [\, Character]
1295 :commainterval [ 3 Integer]]
1296 #{ :at :colon :both } {}
1297 #(format-integer 2 %1 %2 %3))
1299 (\O
1300 [ :mincol [0 Integer] :padchar [\space Character] :commachar [\, Character]
1301 :commainterval [ 3 Integer]]
1302 #{ :at :colon :both } {}
1303 #(format-integer 8 %1 %2 %3))
1305 (\X
1306 [ :mincol [0 Integer] :padchar [\space Character] :commachar [\, Character]
1307 :commainterval [ 3 Integer]]
1308 #{ :at :colon :both } {}
1309 #(format-integer 16 %1 %2 %3))
1311 (\R
1312 [:base [nil Integer] :mincol [0 Integer] :padchar [\space Character] :commachar [\, Character]
1313 :commainterval [ 3 Integer]]
1314 #{ :at :colon :both } {}
1315 (do
1316 (cond ; ~R is overloaded with bizareness
1317 (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 (\P
1324 [ ]
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 (\C
1334 [:char-format [nil Character]]
1335 #{ :at :colon :both } {}
1336 (cond
1337 (:colon params) pretty-character
1338 (:at params) readable-character
1339 :else plain-character))
1341 (\F
1342 [ :w [nil Integer] :d [nil Integer] :k [0 Integer] :overflowchar [nil Character]
1343 :padchar [\space Character] ]
1344 #{ :at } {}
1345 fixed-float)
1347 (\E
1348 [ :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 (\G
1355 [ :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 loop
1401 [ ]
1402 #{:colon :at} {}
1403 (fn [params arg-navigator offsets]
1404 (if (:at params)
1405 (prn))
1406 arg-navigator))
1408 (\T
1409 [ :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 list
1430 (let [[subformat navigator] (get-format-arg navigator)]
1431 (execute-sub-format subformat navigator (:base-args params))))
1432 (fn [params navigator offsets] ; args from sub-list
1433 (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 (cond
1444 (and (:at params) (:colon params))
1445 upcase-writer
1447 (:colon params)
1448 capitalize-word-writer
1450 (:at params)
1451 init-cap-writer
1453 :else
1454 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 (cond
1463 (:colon params)
1464 boolean-conditional
1466 (:at params)
1467 check-arg-conditional
1469 true
1470 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 (cond
1481 (and (:at params) (:colon params))
1482 iterate-main-sublists
1484 (:colon params)
1485 iterate-list-of-sublists
1487 (:at params)
1488 iterate-main-list
1490 true
1491 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 allowed
1504 (\^ [: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 (cond
1512 (and arg1 arg2 arg3)
1513 (if (<= arg1 arg2 arg3) [exit navigator] navigator)
1515 (and arg1 arg2)
1516 (if (= arg1 arg2) [exit navigator] navigator)
1518 arg1
1519 (if (= arg1 0) [exit navigator] navigator)
1521 true ; TODO: handle looking up the arglist stack for info
1522 (if (if (:colon params)
1523 (empty? (:rest (:base-args params)))
1524 (empty? (:rest navigator)))
1525 [exit navigator] navigator)))))
1527 (\W
1528 []
1529 #{:at :colon :both} {}
1530 (if (or (:at params) (:colon params))
1531 (let [bindings (concat
1532 (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 (\I
1551 [:n [0 Integer]]
1552 #{:colon} {}
1553 set-indent)
1556 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
1557 ;;; Code to manage the parameters and flags associated with each
1558 ;;; 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 param
1570 (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-comma
1577 (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-param
1585 "Translate the string representation of a param to the internalized
1586 representation"
1587 [[^String p offset]]
1588 [(cond
1589 (= (.length p) 0) nil
1590 (and (= (.length p) 1) (contains? #{\v \V} (nth p 0))) :parameter-from-args
1591 (and (= (.length p) 1) (= \# (nth p 0))) :remaining-arg-count
1592 (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 (consume
1601 (fn [[s offset flags]]
1602 (if (empty? s)
1603 [nil [s offset flags]]
1604 (let [flag (get flag-defs (first s))]
1605 (if flag
1606 (if (contains? flags flag)
1607 (format-error
1608 (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-params
1628 "Takes a directive definition and the list of actual parameters and
1629 a map of flags and returns a map of the parameters and flags with defaults
1630 filled in. We check to make sure that there are the right types and number
1631 of parameters as well."
1632 [def params flags offset]
1633 (check-flags def flags)
1634 (if (> (count params) (count (:params def)))
1635 (format-error
1636 (cl-format
1637 nil
1638 "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 (doall
1642 (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 map
1652 (into (array-map) ; start with the default values, make sure the order is right
1653 (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 nils
1655 flags)) ; and finally add the flags
1657 (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-directive
1693 (: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 (consume
1700 (fn [remainder]
1701 (if (empty? remainder)
1702 (format-error "No closing bracket found." offset)
1703 (let [this (first remainder)
1704 remainder (next remainder)]
1705 (cond
1706 (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 true
1719 [this remainder]))))
1720 remainder))
1722 (defn- collect-clauses [bracket-info offset remainder]
1723 (second
1724 (consume
1725 (fn [[clause-map saw-else remainder]]
1726 (let [[clause [type right-params else-params remainder]]
1727 (process-clause bracket-info offset remainder)]
1728 (cond
1729 (= type :right-bracket)
1730 [nil [(merge-with concat clause-map
1731 {(if saw-else :else :clauses) [clause]
1732 :right-params right-params})
1733 remainder]]
1735 (= type :else)
1736 (cond
1737 (: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-error
1746 "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 clause
1750 ; is next, this was a regular clause
1751 (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 (cond
1759 saw-else
1760 (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 true
1767 [true [(merge-with concat clause-map { :clauses [clause] })
1768 false remainder]]))))
1769 [{ :clauses [] } false remainder])))
1771 (defn- process-nesting
1772 "Take a linearly compiled format and process the bracket directives to give it
1773 the appropriate tree structure"
1774 [format]
1775 (first
1776 (consume
1777 (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-format
1787 "Compiles format-str into a compiled format which can be used as an argument
1788 to cl-format just like a plain format string. Use this function for improved
1789 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-nesting
1794 (first
1795 (consume
1796 (fn [[^String s offset]]
1797 (if (empty? s)
1798 [nil s]
1799 (let [tilde (.indexOf s (int \~))]
1800 (cond
1801 (neg? tilde) [(compile-raw-string s offset) ["" (+ offset (.length s))]]
1802 (zero? tilde) (compile-directive (subs s 1) (inc offset))
1803 true
1804 [(compile-raw-string (subs s 0 tilde) offset) [(subs s tilde) (+ tilde offset)]]))))
1805 [format-str 0])))))
1807 (defn- needs-pretty
1808 "determine whether a given compiled format has any directives that depend on the
1809 column number or pretty printing"
1810 [format]
1811 (loop [format format]
1812 (if (empty? format)
1813 false
1814 (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 true
1818 (recur (next format))))))
1820 (defn- execute-format
1821 "Executes the format with the arguments."
1822 {:skip-wiki true}
1823 ([stream format args]
1824 (let [^java.io.Writer real-stream (cond
1825 (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 (try
1834 (execute-format format args)
1835 (finally
1836 (if-not (identical? real-stream wrapped-stream)
1837 (.flush wrapped-stream))))
1838 (if (not stream) (.toString real-stream)))))
1839 ([format args]
1840 (map-passing-context
1841 (fn [element context]
1842 (if (abort? context)
1843 [nil context]
1844 (let [[params args] (realize-parameter-list
1845 (: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 args
1850 format)
1851 nil))
1853 ;;; This is a bad idea, but it prevents us from leaking private symbols
1854 ;;; This should all be replaced by really compiled formats anyway.
1855 (def ^{:private true} cached-compile (memoize compile-format))
1857 (defmacro formatter
1858 "Makes a function which can directly run format-in. The function is
1859 fn [stream & args] ... and returns nil unless the stream is nil (meaning
1860 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-in
1866 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-out
1878 "Makes a function which can directly run format-in. The function is
1879 fn [& args] ... and returns nil. This version of the formatter macro is
1880 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-in
1887 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#)))))