view src/clojure/contrib/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 ;; by Tom Faulhaber
4 ;; April 3, 2009
6 ; Copyright (c) Tom Faulhaber, Dec 2008. All rights reserved.
7 ; The use and distribution terms for this software are covered by the
8 ; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
9 ; which can be found in the file epl-v10.html at the root of this distribution.
10 ; By using this software in any fashion, you are agreeing to be bound by
11 ; the terms of this license.
12 ; You must not remove this notice, or any other, from this software.
14 ;; This module implements the Common Lisp compatible format function as documented
15 ;; in "Common Lisp the Language, 2nd edition", Chapter 22 (available online at:
16 ;; http://www.cs.cmu.edu/afs/cs.cmu.edu/project/ai-repository/ai/html/cltl/clm/node200.html#SECTION002633000000000000000)
18 (in-ns 'clojure.contrib.pprint)
20 ;;; Forward references
21 (declare compile-format)
22 (declare execute-format)
23 (declare init-navigator)
24 ;;; End forward references
26 (defn cl-format
27 "An implementation of a Common Lisp compatible format function. cl-format formats its
28 arguments to an output stream or string based on the format control string given. It
29 supports sophisticated formatting of structured data.
31 Writer is an instance of java.io.Writer, true to output to *out* or nil to output
32 to a string, format-in is the format control string and the remaining arguments
33 are the data to be formatted.
35 The format control string is a string to be output with embedded 'format directives'
36 describing how to format the various arguments passed in.
38 If writer is nil, cl-format returns the formatted result string. Otherwise, cl-format
39 returns nil.
41 For example:
42 (let [results [46 38 22]]
43 (cl-format true \"There ~[are~;is~:;are~]~:* ~d result~:p: ~{~d~^, ~}~%\"
44 (count results) results))
46 Prints to *out*:
47 There are 3 results: 46, 38, 22
49 Detailed documentation on format control strings is available in the \"Common Lisp the
50 Language, 2nd edition\", Chapter 22 (available online at:
51 http://www.cs.cmu.edu/afs/cs.cmu.edu/project/ai-repository/ai/html/cltl/clm/node200.html#SECTION002633000000000000000)
52 and in the Common Lisp HyperSpec at
53 http://www.lispworks.com/documentation/HyperSpec/Body/22_c.htm
54 "
55 {:see-also [["http://www.cs.cmu.edu/afs/cs.cmu.edu/project/ai-repository/ai/html/cltl/clm/node200.html#SECTION002633000000000000000"
56 "Common Lisp the Language"]
57 ["http://www.lispworks.com/documentation/HyperSpec/Body/22_c.htm"
58 "Common Lisp HyperSpec"]]}
59 [writer format-in & args]
60 (let [compiled-format (if (string? format-in) (compile-format format-in) format-in)
61 navigator (init-navigator args)]
62 (execute-format writer compiled-format navigator)))
64 (def ^{:private true} *format-str* nil)
66 (defn- format-error [message offset]
67 (let [full-message (str message \newline *format-str* \newline
68 (apply str (repeat offset \space)) "^" \newline)]
69 (throw (RuntimeException. full-message))))
71 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
72 ;;; Argument navigators manage the argument list
73 ;;; as the format statement moves through the list
74 ;;; (possibly going forwards and backwards as it does so)
75 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
77 (defstruct ^{:private true}
78 arg-navigator :seq :rest :pos )
80 (defn init-navigator
81 "Create a new arg-navigator from the sequence with the position set to 0"
82 {:skip-wiki true}
83 [s]
84 (let [s (seq s)]
85 (struct arg-navigator s s 0)))
87 ;; TODO call format-error with offset
88 (defn- next-arg [ navigator ]
89 (let [ rst (:rest navigator) ]
90 (if rst
91 [(first rst) (struct arg-navigator (:seq navigator ) (next rst) (inc (:pos navigator)))]
92 (throw (new Exception "Not enough arguments for format definition")))))
94 (defn- next-arg-or-nil [navigator]
95 (let [rst (:rest navigator)]
96 (if rst
97 [(first rst) (struct arg-navigator (:seq navigator ) (next rst) (inc (:pos navigator)))]
98 [nil navigator])))
100 ;; Get an argument off the arg list and compile it if it's not already compiled
101 (defn- get-format-arg [navigator]
102 (let [[raw-format navigator] (next-arg navigator)
103 compiled-format (if (instance? String raw-format)
104 (compile-format raw-format)
105 raw-format)]
106 [compiled-format navigator]))
108 (declare relative-reposition)
110 (defn- absolute-reposition [navigator position]
111 (if (>= position (:pos navigator))
112 (relative-reposition navigator (- (:pos navigator) position))
113 (struct arg-navigator (:seq navigator) (drop position (:seq navigator)) position)))
115 (defn- relative-reposition [navigator position]
116 (let [newpos (+ (:pos navigator) position)]
117 (if (neg? position)
118 (absolute-reposition navigator newpos)
119 (struct arg-navigator (:seq navigator) (drop position (:rest navigator)) newpos))))
121 (defstruct ^{:private true}
122 compiled-directive :func :def :params :offset)
124 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
125 ;;; When looking at the parameter list, we may need to manipulate
126 ;;; the argument list as well (for 'V' and '#' parameter types).
127 ;;; We hide all of this behind a function, but clients need to
128 ;;; manage changing arg navigator
129 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
131 ;; TODO: validate parameters when they come from arg list
132 (defn- realize-parameter [[param [raw-val offset]] navigator]
133 (let [[real-param new-navigator]
134 (cond
135 (contains? #{ :at :colon } param) ;pass flags through unchanged - this really isn't necessary
136 [raw-val navigator]
138 (= raw-val :parameter-from-args)
139 (next-arg navigator)
141 (= raw-val :remaining-arg-count)
142 [(count (:rest navigator)) navigator]
144 true
145 [raw-val navigator])]
146 [[param [real-param offset]] new-navigator]))
148 (defn- realize-parameter-list [parameter-map navigator]
149 (let [[pairs new-navigator]
150 (map-passing-context realize-parameter navigator parameter-map)]
151 [(into {} pairs) new-navigator]))
153 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
154 ;;; Functions that support individual directives
155 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
157 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
158 ;;; Common handling code for ~A and ~S
159 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
161 (declare opt-base-str)
163 (def ^{:private true}
164 special-radix-markers {2 "#b" 8 "#o", 16 "#x"})
166 (defn- format-simple-number [n]
167 (cond
168 (integer? n) (if (= *print-base* 10)
169 (str n (if *print-radix* "."))
170 (str
171 (if *print-radix* (or (get special-radix-markers *print-base*) (str "#" *print-base* "r")))
172 (opt-base-str *print-base* n)))
173 (ratio? n) (str
174 (if *print-radix* (or (get special-radix-markers *print-base*) (str "#" *print-base* "r")))
175 (opt-base-str *print-base* (.numerator n))
176 "/"
177 (opt-base-str *print-base* (.denominator n)))
178 :else nil))
180 (defn- format-ascii [print-func params arg-navigator offsets]
181 (let [ [arg arg-navigator] (next-arg arg-navigator)
182 ^String base-output (or (format-simple-number arg) (print-func arg))
183 base-width (.length base-output)
184 min-width (+ base-width (:minpad params))
185 width (if (>= min-width (:mincol params))
186 min-width
187 (+ min-width
188 (* (+ (quot (- (:mincol params) min-width 1)
189 (:colinc params) )
190 1)
191 (:colinc params))))
192 chars (apply str (repeat (- width base-width) (:padchar params)))]
193 (if (:at params)
194 (print (str chars base-output))
195 (print (str base-output chars)))
196 arg-navigator))
198 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
199 ;;; Support for the integer directives ~D, ~X, ~O, ~B and some
200 ;;; of ~R
201 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
203 (defn- integral?
204 "returns true if a number is actually an integer (that is, has no fractional part)"
205 [x]
206 (cond
207 (integer? x) true
208 (decimal? x) (>= (.ulp (.stripTrailingZeros (bigdec 0))) 1) ; true iff no fractional part
209 (float? x) (= x (Math/floor x))
210 (ratio? x) (let [^clojure.lang.Ratio r x]
211 (= 0 (rem (.numerator r) (.denominator r))))
212 :else false))
214 (defn- remainders
215 "Return the list of remainders (essentially the 'digits') of val in the given base"
216 [base val]
217 (reverse
218 (first
219 (consume #(if (pos? %)
220 [(rem % base) (quot % base)]
221 [nil nil])
222 val))))
224 ;;; TODO: xlated-val does not seem to be used here.
225 (defn- base-str
226 "Return val as a string in the given base"
227 [base val]
228 (if (zero? val)
229 "0"
230 (let [xlated-val (cond
231 (float? val) (bigdec val)
232 (ratio? val) (let [^clojure.lang.Ratio r val]
233 (/ (.numerator r) (.denominator r)))
234 :else val)]
235 (apply str
236 (map
237 #(if (< % 10) (char (+ (int \0) %)) (char (+ (int \a) (- % 10))))
238 (remainders base val))))))
240 (def ^{:private true}
241 java-base-formats {8 "%o", 10 "%d", 16 "%x"})
243 (defn- opt-base-str
244 "Return val as a string in the given base, using clojure.core/format if supported
245 for improved performance"
246 [base val]
247 (let [format-str (get java-base-formats base)]
248 (if (and format-str (integer? val) (-> val class .getName (.startsWith "java.")))
249 (clojure.core/format format-str val)
250 (base-str base val))))
252 (defn- group-by* [unit lis]
253 (reverse
254 (first
255 (consume (fn [x] [(seq (reverse (take unit x))) (seq (drop unit x))]) (reverse lis)))))
257 (defn- format-integer [base params arg-navigator offsets]
258 (let [[arg arg-navigator] (next-arg arg-navigator)]
259 (if (integral? arg)
260 (let [neg (neg? arg)
261 pos-arg (if neg (- arg) arg)
262 raw-str (opt-base-str base pos-arg)
263 group-str (if (:colon params)
264 (let [groups (map #(apply str %) (group-by* (:commainterval params) raw-str))
265 commas (repeat (count groups) (:commachar params))]
266 (apply str (next (interleave commas groups))))
267 raw-str)
268 ^String signed-str (cond
269 neg (str "-" group-str)
270 (:at params) (str "+" group-str)
271 true group-str)
272 padded-str (if (< (.length signed-str) (:mincol params))
273 (str (apply str (repeat (- (:mincol params) (.length signed-str))
274 (:padchar params)))
275 signed-str)
276 signed-str)]
277 (print padded-str))
278 (format-ascii print-str {:mincol (:mincol params) :colinc 1 :minpad 0
279 :padchar (:padchar params) :at true}
280 (init-navigator [arg]) nil))
281 arg-navigator))
283 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
284 ;;; Support for english formats (~R and ~:R)
285 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
287 (def ^{:private true}
288 english-cardinal-units
289 ["zero" "one" "two" "three" "four" "five" "six" "seven" "eight" "nine"
290 "ten" "eleven" "twelve" "thirteen" "fourteen"
291 "fifteen" "sixteen" "seventeen" "eighteen" "nineteen"])
293 (def ^{:private true}
294 english-ordinal-units
295 ["zeroth" "first" "second" "third" "fourth" "fifth" "sixth" "seventh" "eighth" "ninth"
296 "tenth" "eleventh" "twelfth" "thirteenth" "fourteenth"
297 "fifteenth" "sixteenth" "seventeenth" "eighteenth" "nineteenth"])
299 (def ^{:private true}
300 english-cardinal-tens
301 ["" "" "twenty" "thirty" "forty" "fifty" "sixty" "seventy" "eighty" "ninety"])
303 (def ^{:private true}
304 english-ordinal-tens
305 ["" "" "twentieth" "thirtieth" "fortieth" "fiftieth"
306 "sixtieth" "seventieth" "eightieth" "ninetieth"])
308 ;; We use "short scale" for our units (see http://en.wikipedia.org/wiki/Long_and_short_scales)
309 ;; Number names from http://www.jimloy.com/math/billion.htm
310 ;; We follow the rules for writing numbers from the Blue Book
311 ;; (http://www.grammarbook.com/numbers/numbers.asp)
312 (def ^{:private true}
313 english-scale-numbers
314 ["" "thousand" "million" "billion" "trillion" "quadrillion" "quintillion"
315 "sextillion" "septillion" "octillion" "nonillion" "decillion"
316 "undecillion" "duodecillion" "tredecillion" "quattuordecillion"
317 "quindecillion" "sexdecillion" "septendecillion"
318 "octodecillion" "novemdecillion" "vigintillion"])
320 (defn- format-simple-cardinal
321 "Convert a number less than 1000 to a cardinal english string"
322 [num]
323 (let [hundreds (quot num 100)
324 tens (rem num 100)]
325 (str
326 (if (pos? hundreds) (str (nth english-cardinal-units hundreds) " hundred"))
327 (if (and (pos? hundreds) (pos? tens)) " ")
328 (if (pos? tens)
329 (if (< tens 20)
330 (nth english-cardinal-units tens)
331 (let [ten-digit (quot tens 10)
332 unit-digit (rem tens 10)]
333 (str
334 (if (pos? ten-digit) (nth english-cardinal-tens ten-digit))
335 (if (and (pos? ten-digit) (pos? unit-digit)) "-")
336 (if (pos? unit-digit) (nth english-cardinal-units unit-digit)))))))))
338 (defn- add-english-scales
339 "Take a sequence of parts, add scale numbers (e.g., million) and combine into a string
340 offset is a factor of 10^3 to multiply by"
341 [parts offset]
342 (let [cnt (count parts)]
343 (loop [acc []
344 pos (dec cnt)
345 this (first parts)
346 remainder (next parts)]
347 (if (nil? remainder)
348 (str (apply str (interpose ", " acc))
349 (if (and (not (empty? this)) (not (empty? acc))) ", ")
350 this
351 (if (and (not (empty? this)) (pos? (+ pos offset)))
352 (str " " (nth english-scale-numbers (+ pos offset)))))
353 (recur
354 (if (empty? this)
355 acc
356 (conj acc (str this " " (nth english-scale-numbers (+ pos offset)))))
357 (dec pos)
358 (first remainder)
359 (next remainder))))))
361 (defn- format-cardinal-english [params navigator offsets]
362 (let [[arg navigator] (next-arg navigator)]
363 (if (= 0 arg)
364 (print "zero")
365 (let [abs-arg (if (neg? arg) (- arg) arg) ; some numbers are too big for Math/abs
366 parts (remainders 1000 abs-arg)]
367 (if (<= (count parts) (count english-scale-numbers))
368 (let [parts-strs (map format-simple-cardinal parts)
369 full-str (add-english-scales parts-strs 0)]
370 (print (str (if (neg? arg) "minus ") full-str)))
371 (format-integer ;; for numbers > 10^63, we fall back on ~D
372 10
373 { :mincol 0, :padchar \space, :commachar \, :commainterval 3, :colon true}
374 (init-navigator [arg])
375 { :mincol 0, :padchar 0, :commachar 0 :commainterval 0}))))
376 navigator))
378 (defn- format-simple-ordinal
379 "Convert a number less than 1000 to a ordinal english string
380 Note this should only be used for the last one in the sequence"
381 [num]
382 (let [hundreds (quot num 100)
383 tens (rem num 100)]
384 (str
385 (if (pos? hundreds) (str (nth english-cardinal-units hundreds) " hundred"))
386 (if (and (pos? hundreds) (pos? tens)) " ")
387 (if (pos? tens)
388 (if (< tens 20)
389 (nth english-ordinal-units tens)
390 (let [ten-digit (quot tens 10)
391 unit-digit (rem tens 10)]
392 (if (and (pos? ten-digit) (not (pos? unit-digit)))
393 (nth english-ordinal-tens ten-digit)
394 (str
395 (if (pos? ten-digit) (nth english-cardinal-tens ten-digit))
396 (if (and (pos? ten-digit) (pos? unit-digit)) "-")
397 (if (pos? unit-digit) (nth english-ordinal-units unit-digit))))))
398 (if (pos? hundreds) "th")))))
400 (defn- format-ordinal-english [params navigator offsets]
401 (let [[arg navigator] (next-arg navigator)]
402 (if (= 0 arg)
403 (print "zeroth")
404 (let [abs-arg (if (neg? arg) (- arg) arg) ; some numbers are too big for Math/abs
405 parts (remainders 1000 abs-arg)]
406 (if (<= (count parts) (count english-scale-numbers))
407 (let [parts-strs (map format-simple-cardinal (drop-last parts))
408 head-str (add-english-scales parts-strs 1)
409 tail-str (format-simple-ordinal (last parts))]
410 (print (str (if (neg? arg) "minus ")
411 (cond
412 (and (not (empty? head-str)) (not (empty? tail-str)))
413 (str head-str ", " tail-str)
415 (not (empty? head-str)) (str head-str "th")
416 :else tail-str))))
417 (do (format-integer ;; for numbers > 10^63, we fall back on ~D
418 10
419 { :mincol 0, :padchar \space, :commachar \, :commainterval 3, :colon true}
420 (init-navigator [arg])
421 { :mincol 0, :padchar 0, :commachar 0 :commainterval 0})
422 (let [low-two-digits (rem arg 100)
423 not-teens (or (< 11 low-two-digits) (> 19 low-two-digits))
424 low-digit (rem low-two-digits 10)]
425 (print (cond
426 (and (== low-digit 1) not-teens) "st"
427 (and (== low-digit 2) not-teens) "nd"
428 (and (== low-digit 3) not-teens) "rd"
429 :else "th")))))))
430 navigator))
433 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
434 ;;; Support for roman numeral formats (~@R and ~@:R)
435 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
437 (def ^{:private true}
438 old-roman-table
439 [[ "I" "II" "III" "IIII" "V" "VI" "VII" "VIII" "VIIII"]
440 [ "X" "XX" "XXX" "XXXX" "L" "LX" "LXX" "LXXX" "LXXXX"]
441 [ "C" "CC" "CCC" "CCCC" "D" "DC" "DCC" "DCCC" "DCCCC"]
442 [ "M" "MM" "MMM"]])
444 (def ^{:private true}
445 new-roman-table
446 [[ "I" "II" "III" "IV" "V" "VI" "VII" "VIII" "IX"]
447 [ "X" "XX" "XXX" "XL" "L" "LX" "LXX" "LXXX" "XC"]
448 [ "C" "CC" "CCC" "CD" "D" "DC" "DCC" "DCCC" "CM"]
449 [ "M" "MM" "MMM"]])
451 (defn- format-roman
452 "Format a roman numeral using the specified look-up table"
453 [table params navigator offsets]
454 (let [[arg navigator] (next-arg navigator)]
455 (if (and (number? arg) (> arg 0) (< arg 4000))
456 (let [digits (remainders 10 arg)]
457 (loop [acc []
458 pos (dec (count digits))
459 digits digits]
460 (if (empty? digits)
461 (print (apply str acc))
462 (let [digit (first digits)]
463 (recur (if (= 0 digit)
464 acc
465 (conj acc (nth (nth table pos) (dec digit))))
466 (dec pos)
467 (next digits))))))
468 (format-integer ;; for anything <= 0 or > 3999, we fall back on ~D
469 10
470 { :mincol 0, :padchar \space, :commachar \, :commainterval 3, :colon true}
471 (init-navigator [arg])
472 { :mincol 0, :padchar 0, :commachar 0 :commainterval 0}))
473 navigator))
475 (defn- format-old-roman [params navigator offsets]
476 (format-roman old-roman-table params navigator offsets))
478 (defn- format-new-roman [params navigator offsets]
479 (format-roman new-roman-table params navigator offsets))
481 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
482 ;;; Support for character formats (~C)
483 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
485 (def ^{:private true}
486 special-chars { 8 "Backspace", 9 "Tab", 10 "Newline", 13 "Return", 32 "Space"})
488 (defn- pretty-character [params navigator offsets]
489 (let [[c navigator] (next-arg navigator)
490 as-int (int c)
491 base-char (bit-and as-int 127)
492 meta (bit-and as-int 128)
493 special (get special-chars base-char)]
494 (if (> meta 0) (print "Meta-"))
495 (print (cond
496 special special
497 (< base-char 32) (str "Control-" (char (+ base-char 64)))
498 (= base-char 127) "Control-?"
499 :else (char base-char)))
500 navigator))
502 (defn- readable-character [params navigator offsets]
503 (let [[c navigator] (next-arg navigator)]
504 (condp = (:char-format params)
505 \o (cl-format true "\\o~3,'0o" (int c))
506 \u (cl-format true "\\u~4,'0x" (int c))
507 nil (pr c))
508 navigator))
510 (defn- plain-character [params navigator offsets]
511 (let [[char navigator] (next-arg navigator)]
512 (print char)
513 navigator))
515 ;; Check to see if a result is an abort (~^) construct
516 ;; TODO: move these funcs somewhere more appropriate
517 (defn- abort? [context]
518 (let [token (first context)]
519 (or (= :up-arrow token) (= :colon-up-arrow token))))
521 ;; Handle the execution of "sub-clauses" in bracket constructions
522 (defn- execute-sub-format [format args base-args]
523 (second
524 (map-passing-context
525 (fn [element context]
526 (if (abort? context)
527 [nil context] ; just keep passing it along
528 (let [[params args] (realize-parameter-list (:params element) context)
529 [params offsets] (unzip-map params)
530 params (assoc params :base-args base-args)]
531 [nil (apply (:func element) [params args offsets])])))
532 args
533 format)))
535 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
536 ;;; Support for real number formats
537 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
539 ;; TODO - return exponent as int to eliminate double conversion
540 (defn- float-parts-base
541 "Produce string parts for the mantissa (normalized 1-9) and exponent"
542 [^Object f]
543 (let [^String s (.toLowerCase (.toString f))
544 exploc (.indexOf s (int \e))]
545 (if (neg? exploc)
546 (let [dotloc (.indexOf s (int \.))]
547 (if (neg? dotloc)
548 [s (str (dec (count s)))]
549 [(str (subs s 0 dotloc) (subs s (inc dotloc))) (str (dec dotloc))]))
550 [(str (subs s 0 1) (subs s 2 exploc)) (subs s (inc exploc))])))
553 (defn- float-parts
554 "Take care of leading and trailing zeros in decomposed floats"
555 [f]
556 (let [[m ^String e] (float-parts-base f)
557 m1 (rtrim m \0)
558 m2 (ltrim m1 \0)
559 delta (- (count m1) (count m2))
560 ^String e (if (and (pos? (count e)) (= (nth e 0) \+)) (subs e 1) e)]
561 (if (empty? m2)
562 ["0" 0]
563 [m2 (- (Integer/valueOf e) delta)])))
565 (defn- round-str [m e d w]
566 (if (or d w)
567 (let [len (count m)
568 round-pos (if d (+ e d 1))
569 round-pos (if (and w (< (inc e) (dec w))
570 (or (nil? round-pos) (< (dec w) round-pos)))
571 (dec w)
572 round-pos)
573 [m1 e1 round-pos len] (if (= round-pos 0)
574 [(str "0" m) (inc e) 1 (inc len)]
575 [m e round-pos len])]
576 (if round-pos
577 (if (neg? round-pos)
578 ["0" 0 false]
579 (if (> len round-pos)
580 (let [round-char (nth m1 round-pos)
581 ^String result (subs m1 0 round-pos)]
582 (if (>= (int round-char) (int \5))
583 (let [result-val (Integer/valueOf result)
584 leading-zeros (subs result 0 (min (prefix-count result \0) (- round-pos 1)))
585 round-up-result (str leading-zeros
586 (String/valueOf (+ result-val
587 (if (neg? result-val) -1 1))))
588 expanded (> (count round-up-result) (count result))]
589 [round-up-result e1 expanded])
590 [result e1 false]))
591 [m e false]))
592 [m e false]))
593 [m e false]))
595 (defn- expand-fixed [m e d]
596 (let [m1 (if (neg? e) (str (apply str (repeat (dec (- e)) \0)) m) m)
597 len (count m1)
598 target-len (if d (+ e d 1) (inc e))]
599 (if (< len target-len)
600 (str m1 (apply str (repeat (- target-len len) \0)))
601 m1)))
603 (defn- insert-decimal
604 "Insert the decimal point at the right spot in the number to match an exponent"
605 [m e]
606 (if (neg? e)
607 (str "." m)
608 (let [loc (inc e)]
609 (str (subs m 0 loc) "." (subs m loc)))))
611 (defn- get-fixed [m e d]
612 (insert-decimal (expand-fixed m e d) e))
614 (defn- insert-scaled-decimal
615 "Insert the decimal point at the right spot in the number to match an exponent"
616 [m k]
617 (if (neg? k)
618 (str "." m)
619 (str (subs m 0 k) "." (subs m k))))
621 ;; the function to render ~F directives
622 ;; TODO: support rationals. Back off to ~D/~A is the appropriate cases
623 (defn- fixed-float [params navigator offsets]
624 (let [w (:w params)
625 d (:d params)
626 [arg navigator] (next-arg navigator)
627 [sign abs] (if (neg? arg) ["-" (- arg)] ["+" arg])
628 [mantissa exp] (float-parts abs)
629 scaled-exp (+ exp (:k params))
630 add-sign (or (:at params) (neg? arg))
631 append-zero (and (not d) (<= (dec (count mantissa)) scaled-exp))
632 [rounded-mantissa scaled-exp expanded] (round-str mantissa scaled-exp
633 d (if w (- w (if add-sign 1 0))))
634 fixed-repr (get-fixed rounded-mantissa (if expanded (inc scaled-exp) scaled-exp) d)
635 prepend-zero (= (first fixed-repr) \.)]
636 (if w
637 (let [len (count fixed-repr)
638 signed-len (if add-sign (inc len) len)
639 prepend-zero (and prepend-zero (not (>= signed-len w)))
640 append-zero (and append-zero (not (>= signed-len w)))
641 full-len (if (or prepend-zero append-zero)
642 (inc signed-len)
643 signed-len)]
644 (if (and (> full-len w) (:overflowchar params))
645 (print (apply str (repeat w (:overflowchar params))))
646 (print (str
647 (apply str (repeat (- w full-len) (:padchar params)))
648 (if add-sign sign)
649 (if prepend-zero "0")
650 fixed-repr
651 (if append-zero "0")))))
652 (print (str
653 (if add-sign sign)
654 (if prepend-zero "0")
655 fixed-repr
656 (if append-zero "0"))))
657 navigator))
660 ;; the function to render ~E directives
661 ;; TODO: support rationals. Back off to ~D/~A is the appropriate cases
662 ;; TODO: define ~E representation for Infinity
663 (defn- exponential-float [params navigator offsets]
664 (let [[arg navigator] (next-arg navigator)]
665 (loop [[mantissa exp] (float-parts (if (neg? arg) (- arg) arg))]
666 (let [w (:w params)
667 d (:d params)
668 e (:e params)
669 k (:k params)
670 expchar (or (:exponentchar params) \E)
671 add-sign (or (:at params) (neg? arg))
672 prepend-zero (<= k 0)
673 ^Integer scaled-exp (- exp (dec k))
674 scaled-exp-str (str (Math/abs scaled-exp))
675 scaled-exp-str (str expchar (if (neg? scaled-exp) \- \+)
676 (if e (apply str
677 (repeat
678 (- e
679 (count scaled-exp-str))
680 \0)))
681 scaled-exp-str)
682 exp-width (count scaled-exp-str)
683 base-mantissa-width (count mantissa)
684 scaled-mantissa (str (apply str (repeat (- k) \0))
685 mantissa
686 (if d
687 (apply str
688 (repeat
689 (- d (dec base-mantissa-width)
690 (if (neg? k) (- k) 0)) \0))))
691 w-mantissa (if w (- w exp-width))
692 [rounded-mantissa _ incr-exp] (round-str
693 scaled-mantissa 0
694 (cond
695 (= k 0) (dec d)
696 (pos? k) d
697 (neg? k) (dec d))
698 (if w-mantissa
699 (- w-mantissa (if add-sign 1 0))))
700 full-mantissa (insert-scaled-decimal rounded-mantissa k)
701 append-zero (and (= k (count rounded-mantissa)) (nil? d))]
702 (if (not incr-exp)
703 (if w
704 (let [len (+ (count full-mantissa) exp-width)
705 signed-len (if add-sign (inc len) len)
706 prepend-zero (and prepend-zero (not (= signed-len w)))
707 full-len (if prepend-zero (inc signed-len) signed-len)
708 append-zero (and append-zero (< full-len w))]
709 (if (and (or (> full-len w) (and e (> (- exp-width 2) e)))
710 (:overflowchar params))
711 (print (apply str (repeat w (:overflowchar params))))
712 (print (str
713 (apply str
714 (repeat
715 (- w full-len (if append-zero 1 0) )
716 (:padchar params)))
717 (if add-sign (if (neg? arg) \- \+))
718 (if prepend-zero "0")
719 full-mantissa
720 (if append-zero "0")
721 scaled-exp-str))))
722 (print (str
723 (if add-sign (if (neg? arg) \- \+))
724 (if prepend-zero "0")
725 full-mantissa
726 (if append-zero "0")
727 scaled-exp-str)))
728 (recur [rounded-mantissa (inc exp)]))))
729 navigator))
731 ;; the function to render ~G directives
732 ;; This just figures out whether to pass the request off to ~F or ~E based
733 ;; on the algorithm in CLtL.
734 ;; TODO: support rationals. Back off to ~D/~A is the appropriate cases
735 ;; TODO: refactor so that float-parts isn't called twice
736 (defn- general-float [params navigator offsets]
737 (let [[arg _] (next-arg navigator)
738 [mantissa exp] (float-parts (if (neg? arg) (- arg) arg))
739 w (:w params)
740 d (:d params)
741 e (:e params)
742 n (if (= arg 0.0) 0 (inc exp))
743 ee (if e (+ e 2) 4)
744 ww (if w (- w ee))
745 d (if d d (max (count mantissa) (min n 7)))
746 dd (- d n)]
747 (if (<= 0 dd d)
748 (let [navigator (fixed-float {:w ww, :d dd, :k 0,
749 :overflowchar (:overflowchar params),
750 :padchar (:padchar params), :at (:at params)}
751 navigator offsets)]
752 (print (apply str (repeat ee \space)))
753 navigator)
754 (exponential-float params navigator offsets))))
756 ;; the function to render ~$ directives
757 ;; TODO: support rationals. Back off to ~D/~A is the appropriate cases
758 (defn- dollar-float [params navigator offsets]
759 (let [[^Double arg navigator] (next-arg navigator)
760 [mantissa exp] (float-parts (Math/abs arg))
761 d (:d params) ; digits after the decimal
762 n (:n params) ; minimum digits before the decimal
763 w (:w params) ; minimum field width
764 add-sign (or (:at params) (neg? arg))
765 [rounded-mantissa scaled-exp expanded] (round-str mantissa exp d nil)
766 ^String fixed-repr (get-fixed rounded-mantissa (if expanded (inc scaled-exp) scaled-exp) d)
767 full-repr (str (apply str (repeat (- n (.indexOf fixed-repr (int \.))) \0)) fixed-repr)
768 full-len (+ (count full-repr) (if add-sign 1 0))]
769 (print (str
770 (if (and (:colon params) add-sign) (if (neg? arg) \- \+))
771 (apply str (repeat (- w full-len) (:padchar params)))
772 (if (and (not (:colon params)) add-sign) (if (neg? arg) \- \+))
773 full-repr))
774 navigator))
776 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
777 ;;; Support for the '~[...~]' conditional construct in its
778 ;;; different flavors
779 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
781 ;; ~[...~] without any modifiers chooses one of the clauses based on the param or
782 ;; next argument
783 ;; TODO check arg is positive int
784 (defn- choice-conditional [params arg-navigator offsets]
785 (let [arg (:selector params)
786 [arg navigator] (if arg [arg arg-navigator] (next-arg arg-navigator))
787 clauses (:clauses params)
788 clause (if (or (neg? arg) (>= arg (count clauses)))
789 (first (:else params))
790 (nth clauses arg))]
791 (if clause
792 (execute-sub-format clause navigator (:base-args params))
793 navigator)))
795 ;; ~:[...~] with the colon reads the next argument treating it as a truth value
796 (defn- boolean-conditional [params arg-navigator offsets]
797 (let [[arg navigator] (next-arg arg-navigator)
798 clauses (:clauses params)
799 clause (if arg
800 (second clauses)
801 (first clauses))]
802 (if clause
803 (execute-sub-format clause navigator (:base-args params))
804 navigator)))
806 ;; ~@[...~] with the at sign executes the conditional if the next arg is not
807 ;; nil/false without consuming the arg
808 (defn- check-arg-conditional [params arg-navigator offsets]
809 (let [[arg navigator] (next-arg arg-navigator)
810 clauses (:clauses params)
811 clause (if arg (first clauses))]
812 (if arg
813 (if clause
814 (execute-sub-format clause arg-navigator (:base-args params))
815 arg-navigator)
816 navigator)))
819 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
820 ;;; Support for the '~{...~}' iteration construct in its
821 ;;; different flavors
822 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
825 ;; ~{...~} without any modifiers uses the next argument as an argument list that
826 ;; is consumed by all the iterations
827 (defn- iterate-sublist [params navigator offsets]
828 (let [max-count (:max-iterations params)
829 param-clause (first (:clauses params))
830 [clause navigator] (if (empty? param-clause)
831 (get-format-arg navigator)
832 [param-clause navigator])
833 [arg-list navigator] (next-arg navigator)
834 args (init-navigator arg-list)]
835 (loop [count 0
836 args args
837 last-pos (num -1)]
838 (if (and (not max-count) (= (:pos args) last-pos) (> count 1))
839 ;; TODO get the offset in here and call format exception
840 (throw (RuntimeException. "%{ construct not consuming any arguments: Infinite loop!")))
841 (if (or (and (empty? (:rest args))
842 (or (not (:colon (:right-params params))) (> count 0)))
843 (and max-count (>= count max-count)))
844 navigator
845 (let [iter-result (execute-sub-format clause args (:base-args params))]
846 (if (= :up-arrow (first iter-result))
847 navigator
848 (recur (inc count) iter-result (:pos args))))))))
850 ;; ~:{...~} with the colon treats the next argument as a list of sublists. Each of the
851 ;; sublists is used as the arglist for a single iteration.
852 (defn- iterate-list-of-sublists [params navigator offsets]
853 (let [max-count (:max-iterations params)
854 param-clause (first (:clauses params))
855 [clause navigator] (if (empty? param-clause)
856 (get-format-arg navigator)
857 [param-clause navigator])
858 [arg-list navigator] (next-arg navigator)]
859 (loop [count 0
860 arg-list arg-list]
861 (if (or (and (empty? arg-list)
862 (or (not (:colon (:right-params params))) (> count 0)))
863 (and max-count (>= count max-count)))
864 navigator
865 (let [iter-result (execute-sub-format
866 clause
867 (init-navigator (first arg-list))
868 (init-navigator (next arg-list)))]
869 (if (= :colon-up-arrow (first iter-result))
870 navigator
871 (recur (inc count) (next arg-list))))))))
873 ;; ~@{...~} with the at sign uses the main argument list as the arguments to the iterations
874 ;; is consumed by all the iterations
875 (defn- iterate-main-list [params navigator offsets]
876 (let [max-count (:max-iterations params)
877 param-clause (first (:clauses params))
878 [clause navigator] (if (empty? param-clause)
879 (get-format-arg navigator)
880 [param-clause navigator])]
881 (loop [count 0
882 navigator navigator
883 last-pos (num -1)]
884 (if (and (not max-count) (= (:pos navigator) last-pos) (> count 1))
885 ;; TODO get the offset in here and call format exception
886 (throw (RuntimeException. "%@{ construct not consuming any arguments: Infinite loop!")))
887 (if (or (and (empty? (:rest navigator))
888 (or (not (:colon (:right-params params))) (> count 0)))
889 (and max-count (>= count max-count)))
890 navigator
891 (let [iter-result (execute-sub-format clause navigator (:base-args params))]
892 (if (= :up-arrow (first iter-result))
893 (second iter-result)
894 (recur
895 (inc count) iter-result (:pos navigator))))))))
897 ;; ~@:{...~} with both colon and at sign uses the main argument list as a set of sublists, one
898 ;; of which is consumed with each iteration
899 (defn- iterate-main-sublists [params navigator offsets]
900 (let [max-count (:max-iterations params)
901 param-clause (first (:clauses params))
902 [clause navigator] (if (empty? param-clause)
903 (get-format-arg navigator)
904 [param-clause navigator])
905 ]
906 (loop [count 0
907 navigator navigator]
908 (if (or (and (empty? (:rest navigator))
909 (or (not (:colon (:right-params params))) (> count 0)))
910 (and max-count (>= count max-count)))
911 navigator
912 (let [[sublist navigator] (next-arg-or-nil navigator)
913 iter-result (execute-sub-format clause (init-navigator sublist) navigator)]
914 (if (= :colon-up-arrow (first iter-result))
915 navigator
916 (recur (inc count) navigator)))))))
918 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
919 ;;; The '~< directive has two completely different meanings
920 ;;; in the '~<...~>' form it does justification, but with
921 ;;; ~<...~:>' it represents the logical block operation of the
922 ;;; pretty printer.
923 ;;;
924 ;;; Unfortunately, the current architecture decides what function
925 ;;; to call at form parsing time before the sub-clauses have been
926 ;;; folded, so it is left to run-time to make the decision.
927 ;;;
928 ;;; TODO: make it possible to make these decisions at compile-time.
929 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
931 (declare format-logical-block)
932 (declare justify-clauses)
934 (defn- logical-block-or-justify [params navigator offsets]
935 (if (:colon (:right-params params))
936 (format-logical-block params navigator offsets)
937 (justify-clauses params navigator offsets)))
939 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
940 ;;; Support for the '~<...~>' justification directive
941 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
943 (defn- render-clauses [clauses navigator base-navigator]
944 (loop [clauses clauses
945 acc []
946 navigator navigator]
947 (if (empty? clauses)
948 [acc navigator]
949 (let [clause (first clauses)
950 [iter-result result-str] (binding [*out* (java.io.StringWriter.)]
951 [(execute-sub-format clause navigator base-navigator)
952 (.toString *out*)])]
953 (if (= :up-arrow (first iter-result))
954 [acc (second iter-result)]
955 (recur (next clauses) (conj acc result-str) iter-result))))))
957 ;; TODO support for ~:; constructions
958 (defn- justify-clauses [params navigator offsets]
959 (let [[[eol-str] new-navigator] (when-let [else (:else params)]
960 (render-clauses else navigator (:base-args params)))
961 navigator (or new-navigator navigator)
962 [else-params new-navigator] (when-let [p (:else-params params)]
963 (realize-parameter-list p navigator))
964 navigator (or new-navigator navigator)
965 min-remaining (or (first (:min-remaining else-params)) 0)
966 max-columns (or (first (:max-columns else-params))
967 (get-max-column *out*))
968 clauses (:clauses params)
969 [strs navigator] (render-clauses clauses navigator (:base-args params))
970 slots (max 1
971 (+ (dec (count strs)) (if (:colon params) 1 0) (if (:at params) 1 0)))
972 chars (reduce + (map count strs))
973 mincol (:mincol params)
974 minpad (:minpad params)
975 colinc (:colinc params)
976 minout (+ chars (* slots minpad))
977 result-columns (if (<= minout mincol)
978 mincol
979 (+ mincol (* colinc
980 (+ 1 (quot (- minout mincol 1) colinc)))))
981 total-pad (- result-columns chars)
982 pad (max minpad (quot total-pad slots))
983 extra-pad (- total-pad (* pad slots))
984 pad-str (apply str (repeat pad (:padchar params)))]
985 (if (and eol-str (> (+ (get-column (:base @@*out*)) min-remaining result-columns)
986 max-columns))
987 (print eol-str))
988 (loop [slots slots
989 extra-pad extra-pad
990 strs strs
991 pad-only (or (:colon params)
992 (and (= (count strs) 1) (not (:at params))))]
993 (if (seq strs)
994 (do
995 (print (str (if (not pad-only) (first strs))
996 (if (or pad-only (next strs) (:at params)) pad-str)
997 (if (pos? extra-pad) (:padchar params))))
998 (recur
999 (dec slots)
1000 (dec extra-pad)
1001 (if pad-only strs (next strs))
1002 false))))
1003 navigator))
1005 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
1006 ;;; Support for case modification with ~(...~).
1007 ;;; We do this by wrapping the underlying writer with
1008 ;;; a special writer to do the appropriate modification. This
1009 ;;; allows us to support arbitrary-sized output and sources
1010 ;;; that may block.
1011 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
1013 (defn- downcase-writer
1014 "Returns a proxy that wraps writer, converting all characters to lower case"
1015 [^java.io.Writer writer]
1016 (proxy [java.io.Writer] []
1017 (close [] (.close writer))
1018 (flush [] (.flush writer))
1019 (write ([^chars cbuf ^Integer off ^Integer len]
1020 (.write writer cbuf off len))
1021 ([x]
1022 (condp = (class x)
1023 String
1024 (let [s ^String x]
1025 (.write writer (.toLowerCase s)))
1027 Integer
1028 (let [c ^Character x]
1029 (.write writer (int (Character/toLowerCase (char c))))))))))
1031 (defn- upcase-writer
1032 "Returns a proxy that wraps writer, converting all characters to upper case"
1033 [^java.io.Writer writer]
1034 (proxy [java.io.Writer] []
1035 (close [] (.close writer))
1036 (flush [] (.flush writer))
1037 (write ([^chars cbuf ^Integer off ^Integer len]
1038 (.write writer cbuf off len))
1039 ([x]
1040 (condp = (class x)
1041 String
1042 (let [s ^String x]
1043 (.write writer (.toUpperCase s)))
1045 Integer
1046 (let [c ^Character x]
1047 (.write writer (int (Character/toUpperCase (char c))))))))))
1049 (defn- capitalize-string
1050 "Capitalizes the words in a string. If first? is false, don't capitalize the
1051 first character of the string even if it's a letter."
1052 [s first?]
1053 (let [^Character f (first s)
1054 s (if (and first? f (Character/isLetter f))
1055 (str (Character/toUpperCase f) (subs s 1))
1056 s)]
1057 (apply str
1058 (first
1059 (consume
1060 (fn [s]
1061 (if (empty? s)
1062 [nil nil]
1063 (let [m (re-matcher #"\W\w" s)
1064 match (re-find m)
1065 offset (and match (inc (.start m)))]
1066 (if offset
1067 [(str (subs s 0 offset)
1068 (Character/toUpperCase ^Character (nth s offset)))
1069 (subs s (inc offset))]
1070 [s nil]))))
1071 s)))))
1073 (defn- capitalize-word-writer
1074 "Returns a proxy that wraps writer, captializing all words"
1075 [^java.io.Writer writer]
1076 (let [last-was-whitespace? (ref true)]
1077 (proxy [java.io.Writer] []
1078 (close [] (.close writer))
1079 (flush [] (.flush writer))
1080 (write
1081 ([^chars cbuf ^Integer off ^Integer len]
1082 (.write writer cbuf off len))
1083 ([x]
1084 (condp = (class x)
1085 String
1086 (let [s ^String x]
1087 (.write writer
1088 ^String (capitalize-string (.toLowerCase s) @last-was-whitespace?))
1089 (dosync
1090 (ref-set last-was-whitespace?
1091 (Character/isWhitespace
1092 ^Character (nth s (dec (count s)))))))
1094 Integer
1095 (let [c (char x)]
1096 (let [mod-c (if @last-was-whitespace? (Character/toUpperCase ^Character (char x)) c)]
1097 (.write writer (int mod-c))
1098 (dosync (ref-set last-was-whitespace? (Character/isWhitespace ^Character (char x))))))))))))
1100 (defn- init-cap-writer
1101 "Returns a proxy that wraps writer, capitalizing the first word"
1102 [^java.io.Writer writer]
1103 (let [capped (ref false)]
1104 (proxy [java.io.Writer] []
1105 (close [] (.close writer))
1106 (flush [] (.flush writer))
1107 (write ([^chars cbuf ^Integer off ^Integer len]
1108 (.write writer cbuf off len))
1109 ([x]
1110 (condp = (class x)
1111 String
1112 (let [s (.toLowerCase ^String x)]
1113 (if (not @capped)
1114 (let [m (re-matcher #"\S" s)
1115 match (re-find m)
1116 offset (and match (.start m))]
1117 (if offset
1118 (do (.write writer
1119 (str (subs s 0 offset)
1120 (Character/toUpperCase ^Character (nth s offset))
1121 (.toLowerCase ^String (subs s (inc offset)))))
1122 (dosync (ref-set capped true)))
1123 (.write writer s)))
1124 (.write writer (.toLowerCase s))))
1126 Integer
1127 (let [c ^Character (char x)]
1128 (if (and (not @capped) (Character/isLetter c))
1129 (do
1130 (dosync (ref-set capped true))
1131 (.write writer (int (Character/toUpperCase c))))
1132 (.write writer (int (Character/toLowerCase c)))))))))))
1134 (defn- modify-case [make-writer params navigator offsets]
1135 (let [clause (first (:clauses params))]
1136 (binding [*out* (make-writer *out*)]
1137 (execute-sub-format clause navigator (:base-args params)))))
1139 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
1140 ;;; If necessary, wrap the writer in a PrettyWriter object
1141 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
1143 (defn get-pretty-writer [writer]
1144 (if (pretty-writer? writer)
1145 writer
1146 (pretty-writer writer *print-right-margin* *print-miser-width*)))
1148 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
1149 ;;; Support for column-aware operations ~&, ~T
1150 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
1152 ;; TODO: make an automatic newline for non-ColumnWriters
1153 (defn fresh-line
1154 "Make a newline if the Writer is not already at the beginning of the line.
1155 N.B. Only works on ColumnWriters right now."
1156 []
1157 (if (not (= 0 (get-column (:base @@*out*))))
1158 (prn)))
1160 (defn- absolute-tabulation [params navigator offsets]
1161 (let [colnum (:colnum params)
1162 colinc (:colinc params)
1163 current (get-column (:base @@*out*))
1164 space-count (cond
1165 (< current colnum) (- colnum current)
1166 (= colinc 0) 0
1167 :else (- colinc (rem (- current colnum) colinc)))]
1168 (print (apply str (repeat space-count \space))))
1169 navigator)
1171 (defn- relative-tabulation [params navigator offsets]
1172 (let [colrel (:colnum params)
1173 colinc (:colinc params)
1174 start-col (+ colrel (get-column (:base @@*out*)))
1175 offset (if (pos? colinc) (rem start-col colinc) 0)
1176 space-count (+ colrel (if (= 0 offset) 0 (- colinc offset)))]
1177 (print (apply str (repeat space-count \space))))
1178 navigator)
1181 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
1182 ;;; Support for accessing the pretty printer from a format
1183 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
1185 ;; TODO: support ~@; per-line-prefix separator
1186 ;; TODO: get the whole format wrapped so we can start the lb at any column
1187 (defn- format-logical-block [params navigator offsets]
1188 (let [clauses (:clauses params)
1189 clause-count (count clauses)
1190 prefix (cond
1191 (> clause-count 1) (:string (:params (first (first clauses))))
1192 (:colon params) "(")
1193 body (nth clauses (if (> clause-count 1) 1 0))
1194 suffix (cond
1195 (> clause-count 2) (:string (:params (first (nth clauses 2))))
1196 (:colon params) ")")
1197 [arg navigator] (next-arg navigator)]
1198 (pprint-logical-block :prefix prefix :suffix suffix
1199 (execute-sub-format
1200 body
1201 (init-navigator arg)
1202 (:base-args params)))
1203 navigator))
1205 (defn- set-indent [params navigator offsets]
1206 (let [relative-to (if (:colon params) :current :block)]
1207 (pprint-indent relative-to (:n params))
1208 navigator))
1210 ;;; TODO: support ~:T section options for ~T
1212 (defn- conditional-newline [params navigator offsets]
1213 (let [kind (if (:colon params)
1214 (if (:at params) :mandatory :fill)
1215 (if (:at params) :miser :linear))]
1216 (pprint-newline kind)
1217 navigator))
1219 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
1220 ;;; The table of directives we support, each with its params,
1221 ;;; properties, and the compilation function
1222 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
1224 ;; We start with a couple of helpers
1225 (defn- process-directive-table-element [ [ char params flags bracket-info & generator-fn ] ]
1226 [char,
1227 {:directive char,
1228 :params `(array-map ~@params),
1229 :flags flags,
1230 :bracket-info bracket-info,
1231 :generator-fn (concat '(fn [ params offset]) generator-fn) }])
1233 (defmacro ^{:private true}
1234 defdirectives
1235 [ & directives ]
1236 `(def ^{:private true}
1237 directive-table (hash-map ~@(mapcat process-directive-table-element directives))))
1239 (defdirectives
1240 (\A
1241 [ :mincol [0 Integer] :colinc [1 Integer] :minpad [0 Integer] :padchar [\space Character] ]
1242 #{ :at :colon :both} {}
1243 #(format-ascii print-str %1 %2 %3))
1245 (\S
1246 [ :mincol [0 Integer] :colinc [1 Integer] :minpad [0 Integer] :padchar [\space Character] ]
1247 #{ :at :colon :both} {}
1248 #(format-ascii pr-str %1 %2 %3))
1250 (\D
1251 [ :mincol [0 Integer] :padchar [\space Character] :commachar [\, Character]
1252 :commainterval [ 3 Integer]]
1253 #{ :at :colon :both } {}
1254 #(format-integer 10 %1 %2 %3))
1256 (\B
1257 [ :mincol [0 Integer] :padchar [\space Character] :commachar [\, Character]
1258 :commainterval [ 3 Integer]]
1259 #{ :at :colon :both } {}
1260 #(format-integer 2 %1 %2 %3))
1262 (\O
1263 [ :mincol [0 Integer] :padchar [\space Character] :commachar [\, Character]
1264 :commainterval [ 3 Integer]]
1265 #{ :at :colon :both } {}
1266 #(format-integer 8 %1 %2 %3))
1268 (\X
1269 [ :mincol [0 Integer] :padchar [\space Character] :commachar [\, Character]
1270 :commainterval [ 3 Integer]]
1271 #{ :at :colon :both } {}
1272 #(format-integer 16 %1 %2 %3))
1274 (\R
1275 [:base [nil Integer] :mincol [0 Integer] :padchar [\space Character] :commachar [\, Character]
1276 :commainterval [ 3 Integer]]
1277 #{ :at :colon :both } {}
1278 (do
1279 (cond ; ~R is overloaded with bizareness
1280 (first (:base params)) #(format-integer (:base %1) %1 %2 %3)
1281 (and (:at params) (:colon params)) #(format-old-roman %1 %2 %3)
1282 (:at params) #(format-new-roman %1 %2 %3)
1283 (:colon params) #(format-ordinal-english %1 %2 %3)
1284 true #(format-cardinal-english %1 %2 %3))))
1286 (\P
1287 [ ]
1288 #{ :at :colon :both } {}
1289 (fn [params navigator offsets]
1290 (let [navigator (if (:colon params) (relative-reposition navigator -1) navigator)
1291 strs (if (:at params) ["y" "ies"] ["" "s"])
1292 [arg navigator] (next-arg navigator)]
1293 (print (if (= arg 1) (first strs) (second strs)))
1294 navigator)))
1296 (\C
1297 [:char-format [nil Character]]
1298 #{ :at :colon :both } {}
1299 (cond
1300 (:colon params) pretty-character
1301 (:at params) readable-character
1302 :else plain-character))
1304 (\F
1305 [ :w [nil Integer] :d [nil Integer] :k [0 Integer] :overflowchar [nil Character]
1306 :padchar [\space Character] ]
1307 #{ :at } {}
1308 fixed-float)
1310 (\E
1311 [ :w [nil Integer] :d [nil Integer] :e [nil Integer] :k [1 Integer]
1312 :overflowchar [nil Character] :padchar [\space Character]
1313 :exponentchar [nil Character] ]
1314 #{ :at } {}
1315 exponential-float)
1317 (\G
1318 [ :w [nil Integer] :d [nil Integer] :e [nil Integer] :k [1 Integer]
1319 :overflowchar [nil Character] :padchar [\space Character]
1320 :exponentchar [nil Character] ]
1321 #{ :at } {}
1322 general-float)
1324 (\$
1325 [ :d [2 Integer] :n [1 Integer] :w [0 Integer] :padchar [\space Character]]
1326 #{ :at :colon :both} {}
1327 dollar-float)
1329 (\%
1330 [ :count [1 Integer] ]
1331 #{ } {}
1332 (fn [params arg-navigator offsets]
1333 (dotimes [i (:count params)]
1334 (prn))
1335 arg-navigator))
1337 (\&
1338 [ :count [1 Integer] ]
1339 #{ :pretty } {}
1340 (fn [params arg-navigator offsets]
1341 (let [cnt (:count params)]
1342 (if (pos? cnt) (fresh-line))
1343 (dotimes [i (dec cnt)]
1344 (prn)))
1345 arg-navigator))
1347 (\|
1348 [ :count [1 Integer] ]
1349 #{ } {}
1350 (fn [params arg-navigator offsets]
1351 (dotimes [i (:count params)]
1352 (print \formfeed))
1353 arg-navigator))
1355 (\~
1356 [ :n [1 Integer] ]
1357 #{ } {}
1358 (fn [params arg-navigator offsets]
1359 (let [n (:n params)]
1360 (print (apply str (repeat n \~)))
1361 arg-navigator)))
1363 (\newline ;; Whitespace supression is handled in the compilation loop
1364 [ ]
1365 #{:colon :at} {}
1366 (fn [params arg-navigator offsets]
1367 (if (:at params)
1368 (prn))
1369 arg-navigator))
1371 (\T
1372 [ :colnum [1 Integer] :colinc [1 Integer] ]
1373 #{ :at :pretty } {}
1374 (if (:at params)
1375 #(relative-tabulation %1 %2 %3)
1376 #(absolute-tabulation %1 %2 %3)))
1378 (\*
1379 [ :n [1 Integer] ]
1380 #{ :colon :at } {}
1381 (fn [params navigator offsets]
1382 (let [n (:n params)]
1383 (if (:at params)
1384 (absolute-reposition navigator n)
1385 (relative-reposition navigator (if (:colon params) (- n) n)))
1386 )))
1388 (\?
1389 [ ]
1390 #{ :at } {}
1391 (if (:at params)
1392 (fn [params navigator offsets] ; args from main arg list
1393 (let [[subformat navigator] (get-format-arg navigator)]
1394 (execute-sub-format subformat navigator (:base-args params))))
1395 (fn [params navigator offsets] ; args from sub-list
1396 (let [[subformat navigator] (get-format-arg navigator)
1397 [subargs navigator] (next-arg navigator)
1398 sub-navigator (init-navigator subargs)]
1399 (execute-sub-format subformat sub-navigator (:base-args params))
1400 navigator))))
1403 (\(
1404 [ ]
1405 #{ :colon :at :both} { :right \), :allows-separator nil, :else nil }
1406 (let [mod-case-writer (cond
1407 (and (:at params) (:colon params))
1408 upcase-writer
1410 (:colon params)
1411 capitalize-word-writer
1413 (:at params)
1414 init-cap-writer
1416 :else
1417 downcase-writer)]
1418 #(modify-case mod-case-writer %1 %2 %3)))
1420 (\) [] #{} {} nil)
1422 (\[
1423 [ :selector [nil Integer] ]
1424 #{ :colon :at } { :right \], :allows-separator true, :else :last }
1425 (cond
1426 (:colon params)
1427 boolean-conditional
1429 (:at params)
1430 check-arg-conditional
1432 true
1433 choice-conditional))
1435 (\; [:min-remaining [nil Integer] :max-columns [nil Integer]]
1436 #{ :colon } { :separator true } nil)
1438 (\] [] #{} {} nil)
1440 (\{
1441 [ :max-iterations [nil Integer] ]
1442 #{ :colon :at :both} { :right \}, :allows-separator false }
1443 (cond
1444 (and (:at params) (:colon params))
1445 iterate-main-sublists
1447 (:colon params)
1448 iterate-list-of-sublists
1450 (:at params)
1451 iterate-main-list
1453 true
1454 iterate-sublist))
1457 (\} [] #{:colon} {} nil)
1459 (\<
1460 [:mincol [0 Integer] :colinc [1 Integer] :minpad [0 Integer] :padchar [\space Character]]
1461 #{:colon :at :both :pretty} { :right \>, :allows-separator true, :else :first }
1462 logical-block-or-justify)
1464 (\> [] #{:colon} {} nil)
1466 ;; TODO: detect errors in cases where colon not allowed
1467 (\^ [:arg1 [nil Integer] :arg2 [nil Integer] :arg3 [nil Integer]]
1468 #{:colon} {}
1469 (fn [params navigator offsets]
1470 (let [arg1 (:arg1 params)
1471 arg2 (:arg2 params)
1472 arg3 (:arg3 params)
1473 exit (if (:colon params) :colon-up-arrow :up-arrow)]
1474 (cond
1475 (and arg1 arg2 arg3)
1476 (if (<= arg1 arg2 arg3) [exit navigator] navigator)
1478 (and arg1 arg2)
1479 (if (= arg1 arg2) [exit navigator] navigator)
1481 arg1
1482 (if (= arg1 0) [exit navigator] navigator)
1484 true ; TODO: handle looking up the arglist stack for info
1485 (if (if (:colon params)
1486 (empty? (:rest (:base-args params)))
1487 (empty? (:rest navigator)))
1488 [exit navigator] navigator)))))
1490 (\W
1491 []
1492 #{:at :colon :both} {}
1493 (if (or (:at params) (:colon params))
1494 (let [bindings (concat
1495 (if (:at params) [:level nil :length nil] [])
1496 (if (:colon params) [:pretty true] []))]
1497 (fn [params navigator offsets]
1498 (let [[arg navigator] (next-arg navigator)]
1499 (if (apply write arg bindings)
1500 [:up-arrow navigator]
1501 navigator))))
1502 (fn [params navigator offsets]
1503 (let [[arg navigator] (next-arg navigator)]
1504 (if (write-out arg)
1505 [:up-arrow navigator]
1506 navigator)))))
1508 (\_
1509 []
1510 #{:at :colon :both} {}
1511 conditional-newline)
1513 (\I
1514 [:n [0 Integer]]
1515 #{:colon} {}
1516 set-indent)
1519 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
1520 ;;; Code to manage the parameters and flags associated with each
1521 ;;; directive in the format string.
1522 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
1524 (def ^{:private true}
1525 param-pattern #"^([vV]|#|('.)|([+-]?\d+)|(?=,))")
1526 (def ^{:private true}
1527 special-params #{ :parameter-from-args :remaining-arg-count })
1529 (defn- extract-param [[s offset saw-comma]]
1530 (let [m (re-matcher param-pattern s)
1531 param (re-find m)]
1532 (if param
1533 (let [token-str (first (re-groups m))
1534 remainder (subs s (.end m))
1535 new-offset (+ offset (.end m))]
1536 (if (not (= \, (nth remainder 0)))
1537 [ [token-str offset] [remainder new-offset false]]
1538 [ [token-str offset] [(subs remainder 1) (inc new-offset) true]]))
1539 (if saw-comma
1540 (format-error "Badly formed parameters in format directive" offset)
1541 [ nil [s offset]]))))
1544 (defn- extract-params [s offset]
1545 (consume extract-param [s offset false]))
1547 (defn- translate-param
1548 "Translate the string representation of a param to the internalized
1549 representation"
1550 [[^String p offset]]
1551 [(cond
1552 (= (.length p) 0) nil
1553 (and (= (.length p) 1) (contains? #{\v \V} (nth p 0))) :parameter-from-args
1554 (and (= (.length p) 1) (= \# (nth p 0))) :remaining-arg-count
1555 (and (= (.length p) 2) (= \' (nth p 0))) (nth p 1)
1556 true (new Integer p))
1557 offset])
1559 (def ^{:private true}
1560 flag-defs { \: :colon, \@ :at })
1562 (defn- extract-flags [s offset]
1563 (consume
1564 (fn [[s offset flags]]
1565 (if (empty? s)
1566 [nil [s offset flags]]
1567 (let [flag (get flag-defs (first s))]
1568 (if flag
1569 (if (contains? flags flag)
1570 (format-error
1571 (str "Flag \"" (first s) "\" appears more than once in a directive")
1572 offset)
1573 [true [(subs s 1) (inc offset) (assoc flags flag [true offset])]])
1574 [nil [s offset flags]]))))
1575 [s offset {}]))
1577 (defn- check-flags [def flags]
1578 (let [allowed (:flags def)]
1579 (if (and (not (:at allowed)) (:at flags))
1580 (format-error (str "\"@\" is an illegal flag for format directive \"" (:directive def) "\"")
1581 (nth (:at flags) 1)))
1582 (if (and (not (:colon allowed)) (:colon flags))
1583 (format-error (str "\":\" is an illegal flag for format directive \"" (:directive def) "\"")
1584 (nth (:colon flags) 1)))
1585 (if (and (not (:both allowed)) (:at flags) (:colon flags))
1586 (format-error (str "Cannot combine \"@\" and \":\" flags for format directive \""
1587 (:directive def) "\"")
1588 (min (nth (:colon flags) 1) (nth (:at flags) 1))))))
1590 (defn- map-params
1591 "Takes a directive definition and the list of actual parameters and
1592 a map of flags and returns a map of the parameters and flags with defaults
1593 filled in. We check to make sure that there are the right types and number
1594 of parameters as well."
1595 [def params flags offset]
1596 (check-flags def flags)
1597 (if (> (count params) (count (:params def)))
1598 (format-error
1599 (cl-format
1600 nil
1601 "Too many parameters for directive \"~C\": ~D~:* ~[were~;was~:;were~] specified but only ~D~:* ~[are~;is~:;are~] allowed"
1602 (:directive def) (count params) (count (:params def)))
1603 (second (first params))))
1604 (doall
1605 (map #(let [val (first %1)]
1606 (if (not (or (nil? val) (contains? special-params val)
1607 (instance? (second (second %2)) val)))
1608 (format-error (str "Parameter " (name (first %2))
1609 " has bad type in directive \"" (:directive def) "\": "
1610 (class val))
1611 (second %1))) )
1612 params (:params def)))
1614 (merge ; create the result map
1615 (into (array-map) ; start with the default values, make sure the order is right
1616 (reverse (for [[name [default]] (:params def)] [name [default offset]])))
1617 (reduce #(apply assoc %1 %2) {} (filter #(first (nth % 1)) (zipmap (keys (:params def)) params))) ; add the specified parameters, filtering out nils
1618 flags)) ; and finally add the flags
1620 (defn- compile-directive [s offset]
1621 (let [[raw-params [rest offset]] (extract-params s offset)
1622 [_ [rest offset flags]] (extract-flags rest offset)
1623 directive (first rest)
1624 def (get directive-table (Character/toUpperCase ^Character directive))
1625 params (if def (map-params def (map translate-param raw-params) flags offset))]
1626 (if (not directive)
1627 (format-error "Format string ended in the middle of a directive" offset))
1628 (if (not def)
1629 (format-error (str "Directive \"" directive "\" is undefined") offset))
1630 [(struct compiled-directive ((:generator-fn def) params offset) def params offset)
1631 (let [remainder (subs rest 1)
1632 offset (inc offset)
1633 trim? (and (= \newline (:directive def))
1634 (not (:colon params)))
1635 trim-count (if trim? (prefix-count remainder [\space \tab]) 0)
1636 remainder (subs remainder trim-count)
1637 offset (+ offset trim-count)]
1638 [remainder offset])]))
1640 (defn- compile-raw-string [s offset]
1641 (struct compiled-directive (fn [_ a _] (print s) a) nil { :string s } offset))
1643 (defn- right-bracket [this] (:right (:bracket-info (:def this))))
1644 (defn- separator? [this] (:separator (:bracket-info (:def this))))
1645 (defn- else-separator? [this]
1646 (and (:separator (:bracket-info (:def this)))
1647 (:colon (:params this))))
1650 (declare collect-clauses)
1652 (defn- process-bracket [this remainder]
1653 (let [[subex remainder] (collect-clauses (:bracket-info (:def this))
1654 (:offset this) remainder)]
1655 [(struct compiled-directive
1656 (:func this) (:def this)
1657 (merge (:params this) (tuple-map subex (:offset this)))
1658 (:offset this))
1659 remainder]))
1661 (defn- process-clause [bracket-info offset remainder]
1662 (consume
1663 (fn [remainder]
1664 (if (empty? remainder)
1665 (format-error "No closing bracket found." offset)
1666 (let [this (first remainder)
1667 remainder (next remainder)]
1668 (cond
1669 (right-bracket this)
1670 (process-bracket this remainder)
1672 (= (:right bracket-info) (:directive (:def this)))
1673 [ nil [:right-bracket (:params this) nil remainder]]
1675 (else-separator? this)
1676 [nil [:else nil (:params this) remainder]]
1678 (separator? this)
1679 [nil [:separator nil nil remainder]] ;; TODO: check to make sure that there are no params on ~;
1681 true
1682 [this remainder]))))
1683 remainder))
1685 (defn- collect-clauses [bracket-info offset remainder]
1686 (second
1687 (consume
1688 (fn [[clause-map saw-else remainder]]
1689 (let [[clause [type right-params else-params remainder]]
1690 (process-clause bracket-info offset remainder)]
1691 (cond
1692 (= type :right-bracket)
1693 [nil [(merge-with concat clause-map
1694 {(if saw-else :else :clauses) [clause]
1695 :right-params right-params})
1696 remainder]]
1698 (= type :else)
1699 (cond
1700 (:else clause-map)
1701 (format-error "Two else clauses (\"~:;\") inside bracket construction." offset)
1703 (not (:else bracket-info))
1704 (format-error "An else clause (\"~:;\") is in a bracket type that doesn't support it."
1705 offset)
1707 (and (= :first (:else bracket-info)) (seq (:clauses clause-map)))
1708 (format-error
1709 "The else clause (\"~:;\") is only allowed in the first position for this directive."
1710 offset)
1712 true ; if the ~:; is in the last position, the else clause
1713 ; is next, this was a regular clause
1714 (if (= :first (:else bracket-info))
1715 [true [(merge-with concat clause-map { :else [clause] :else-params else-params})
1716 false remainder]]
1717 [true [(merge-with concat clause-map { :clauses [clause] })
1718 true remainder]]))
1720 (= type :separator)
1721 (cond
1722 saw-else
1723 (format-error "A plain clause (with \"~;\") follows an else clause (\"~:;\") inside bracket construction." offset)
1725 (not (:allows-separator bracket-info))
1726 (format-error "A separator (\"~;\") is in a bracket type that doesn't support it."
1727 offset)
1729 true
1730 [true [(merge-with concat clause-map { :clauses [clause] })
1731 false remainder]]))))
1732 [{ :clauses [] } false remainder])))
1734 (defn- process-nesting
1735 "Take a linearly compiled format and process the bracket directives to give it
1736 the appropriate tree structure"
1737 [format]
1738 (first
1739 (consume
1740 (fn [remainder]
1741 (let [this (first remainder)
1742 remainder (next remainder)
1743 bracket (:bracket-info (:def this))]
1744 (if (:right bracket)
1745 (process-bracket this remainder)
1746 [this remainder])))
1747 format)))
1749 (defn compile-format
1750 "Compiles format-str into a compiled format which can be used as an argument
1751 to cl-format just like a plain format string. Use this function for improved
1752 performance when you're using the same format string repeatedly"
1753 [ format-str ]
1754 ; (prlabel compiling format-str)
1755 (binding [*format-str* format-str]
1756 (process-nesting
1757 (first
1758 (consume
1759 (fn [[^String s offset]]
1760 (if (empty? s)
1761 [nil s]
1762 (let [tilde (.indexOf s (int \~))]
1763 (cond
1764 (neg? tilde) [(compile-raw-string s offset) ["" (+ offset (.length s))]]
1765 (zero? tilde) (compile-directive (subs s 1) (inc offset))
1766 true
1767 [(compile-raw-string (subs s 0 tilde) offset) [(subs s tilde) (+ tilde offset)]]))))
1768 [format-str 0])))))
1770 (defn- needs-pretty
1771 "determine whether a given compiled format has any directives that depend on the
1772 column number or pretty printing"
1773 [format]
1774 (loop [format format]
1775 (if (empty? format)
1776 false
1777 (if (or (:pretty (:flags (:def (first format))))
1778 (some needs-pretty (first (:clauses (:params (first format)))))
1779 (some needs-pretty (first (:else (:params (first format))))))
1780 true
1781 (recur (next format))))))
1783 (defn execute-format
1784 "Executes the format with the arguments. This should never be used directly, but is public
1785 because the formatter macro uses it."
1786 {:skip-wiki true}
1787 ([stream format args]
1788 (let [^java.io.Writer real-stream (cond
1789 (not stream) (java.io.StringWriter.)
1790 (true? stream) *out*
1791 :else stream)
1792 ^java.io.Writer wrapped-stream (if (and (needs-pretty format)
1793 (not (pretty-writer? real-stream)))
1794 (get-pretty-writer real-stream)
1795 real-stream)]
1796 (binding [*out* wrapped-stream]
1797 (try
1798 (execute-format format args)
1799 (finally
1800 (if-not (identical? real-stream wrapped-stream)
1801 (.flush wrapped-stream))))
1802 (if (not stream) (.toString real-stream)))))
1803 ([format args]
1804 (map-passing-context
1805 (fn [element context]
1806 (if (abort? context)
1807 [nil context]
1808 (let [[params args] (realize-parameter-list
1809 (:params element) context)
1810 [params offsets] (unzip-map params)
1811 params (assoc params :base-args args)]
1812 [nil (apply (:func element) [params args offsets])])))
1813 args
1814 format)))
1817 (defmacro formatter
1818 "Makes a function which can directly run format-in. The function is
1819 fn [stream & args] ... and returns nil unless the stream is nil (meaning
1820 output to a string) in which case it returns the resulting string.
1822 format-in can be either a control string or a previously compiled format."
1823 [format-in]
1824 (let [cf (gensym "compiled-format")]
1825 `(let [format-in# ~format-in]
1826 (do (defonce ~cf (if (string? format-in#) (compile-format format-in#) format-in#))
1827 (fn [stream# & args#]
1828 (let [navigator# (init-navigator args#)]
1829 (execute-format stream# ~cf navigator#)))))))
1831 (defmacro formatter-out
1832 "Makes a function which can directly run format-in. The function is
1833 fn [& args] ... and returns nil. This version of the formatter macro is
1834 designed to be used with *out* set to an appropriate Writer. In particular,
1835 this is meant to be used as part of a pretty printer dispatch method.
1837 format-in can be either a control string or a previously compiled format."
1838 [format-in]
1839 (let [cf (gensym "compiled-format")]
1840 `(let [format-in# ~format-in]
1841 (do (defonce ~cf (if (string? format-in#) (compile-format format-in#) format-in#))
1842 (fn [& args#]
1843 (let [navigator# (init-navigator args#)]
1844 (execute-format ~cf navigator#)))))))