Mercurial > lasercutter
comparison 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 |
comparison
equal
deleted
inserted
replaced
9:35cf337adfcf | 10:ef7dbbd6452c |
---|---|
1 ;;; cl_format.clj -- part of the pretty printer for Clojure | |
2 | |
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. | |
10 | |
11 ;; Author: Tom Faulhaber | |
12 ;; April 3, 2009 | |
13 | |
14 | |
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) | |
18 | |
19 (in-ns 'clojure.pprint) | |
20 | |
21 ;;; Forward references | |
22 (declare compile-format) | |
23 (declare execute-format) | |
24 (declare init-navigator) | |
25 ;;; End forward references | |
26 | |
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. | |
31 | |
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. | |
35 | |
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. | |
38 | |
39 If writer is nil, cl-format returns the formatted result string. Otherwise, cl-format | |
40 returns nil. | |
41 | |
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)) | |
46 | |
47 Prints to *out*: | |
48 There are 3 results: 46, 38, 22 | |
49 | |
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))) | |
65 | |
66 (def ^{:private true} *format-str* nil) | |
67 | |
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)))) | |
72 | |
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 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |
78 | |
79 (defstruct ^{:private true} | |
80 arg-navigator :seq :rest :pos ) | |
81 | |
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))) | |
88 | |
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"))))) | |
95 | |
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]))) | |
101 | |
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])) | |
109 | |
110 (declare relative-reposition) | |
111 | |
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))) | |
116 | |
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)))) | |
122 | |
123 (defstruct ^{:private true} | |
124 compiled-directive :func :def :params :offset) | |
125 | |
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 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |
132 | |
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] | |
139 | |
140 (= raw-val :parameter-from-args) | |
141 (next-arg navigator) | |
142 | |
143 (= raw-val :remaining-arg-count) | |
144 [(count (:rest navigator)) navigator] | |
145 | |
146 true | |
147 [raw-val navigator])] | |
148 [[param [real-param offset]] new-navigator])) | |
149 | |
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])) | |
154 | |
155 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |
156 ;;; Functions that support individual directives | |
157 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |
158 | |
159 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |
160 ;;; Common handling code for ~A and ~S | |
161 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |
162 | |
163 (declare opt-base-str) | |
164 | |
165 (def ^{:private true} | |
166 special-radix-markers {2 "#b" 8 "#o", 16 "#x"}) | |
167 | |
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)) | |
181 | |
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)) | |
199 | |
200 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |
201 ;;; Support for the integer directives ~D, ~X, ~O, ~B and some | |
202 ;;; of ~R | |
203 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |
204 | |
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)) | |
215 | |
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)))) | |
225 | |
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)))))) | |
241 | |
242 (def ^{:private true} | |
243 java-base-formats {8 "%o", 10 "%d", 16 "%x"}) | |
244 | |
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)))) | |
253 | |
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))))) | |
258 | |
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)) | |
284 | |
285 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |
286 ;;; Support for english formats (~R and ~:R) | |
287 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |
288 | |
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"]) | |
294 | |
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"]) | |
300 | |
301 (def ^{:private true} | |
302 english-cardinal-tens | |
303 ["" "" "twenty" "thirty" "forty" "fifty" "sixty" "seventy" "eighty" "ninety"]) | |
304 | |
305 (def ^{:private true} | |
306 english-ordinal-tens | |
307 ["" "" "twentieth" "thirtieth" "fortieth" "fiftieth" | |
308 "sixtieth" "seventieth" "eightieth" "ninetieth"]) | |
309 | |
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"]) | |
321 | |
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))))))))) | |
339 | |
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)))))) | |
362 | |
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)) | |
379 | |
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"))))) | |
401 | |
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) | |
416 | |
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)) | |
433 | |
434 | |
435 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |
436 ;;; Support for roman numeral formats (~@R and ~@:R) | |
437 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |
438 | |
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"]]) | |
445 | |
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"]]) | |
452 | |
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)) | |
476 | |
477 (defn- format-old-roman [params navigator offsets] | |
478 (format-roman old-roman-table params navigator offsets)) | |
479 | |
480 (defn- format-new-roman [params navigator offsets] | |
481 (format-roman new-roman-table params navigator offsets)) | |
482 | |
483 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |
484 ;;; Support for character formats (~C) | |
485 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |
486 | |
487 (def ^{:private true} | |
488 special-chars { 8 "Backspace", 9 "Tab", 10 "Newline", 13 "Return", 32 "Space"}) | |
489 | |
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)) | |
503 | |
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)) | |
511 | |
512 (defn- plain-character [params navigator offsets] | |
513 (let [[char navigator] (next-arg navigator)] | |
514 (print char) | |
515 navigator)) | |
516 | |
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)))) | |
522 | |
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))) | |
536 | |
537 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |
538 ;;; Support for real number formats | |
539 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |
540 | |
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))]))) | |
553 | |
554 | |
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)]))) | |
566 | |
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])) | |
596 | |
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))) | |
604 | |
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))))) | |
612 | |
613 (defn- get-fixed [m e d] | |
614 (insert-decimal (expand-fixed m e d) e)) | |
615 | |
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)))) | |
622 | |
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)) | |
660 | |
661 | |
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)) | |
732 | |
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)))) | |
757 | |
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)) | |
777 | |
778 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |
779 ;;; Support for the '~[...~]' conditional construct in its | |
780 ;;; different flavors | |
781 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |
782 | |
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))) | |
796 | |
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))) | |
807 | |
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))) | |
819 | |
820 | |
821 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |
822 ;;; Support for the '~{...~}' iteration construct in its | |
823 ;;; different flavors | |
824 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |
825 | |
826 | |
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)))))))) | |
851 | |
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)))))))) | |
874 | |
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)))))))) | |
898 | |
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))))))) | |
919 | |
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 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |
932 | |
933 (declare format-logical-block) | |
934 (declare justify-clauses) | |
935 | |
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))) | |
940 | |
941 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |
942 ;;; Support for the '~<...~>' justification directive | |
943 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |
944 | |
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)))))) | |
958 | |
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)) | |
1006 | |
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 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |
1014 | |
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))) | |
1028 | |
1029 Integer | |
1030 (let [c ^Character x] | |
1031 (.write writer (int (Character/toLowerCase (char c)))))))))) | |
1032 | |
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))) | |
1046 | |
1047 Integer | |
1048 (let [c ^Character x] | |
1049 (.write writer (int (Character/toUpperCase (char c)))))))))) | |
1050 | |
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))))) | |
1074 | |
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))))))) | |
1095 | |
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)))))))))))) | |
1101 | |
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)))) | |
1127 | |
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))))))))))) | |
1135 | |
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))))) | |
1140 | |
1141 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |
1142 ;;; If necessary, wrap the writer in a PrettyWriter object | |
1143 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |
1144 | |
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. | |
1150 | |
1151 For example, when you want to generate column-aware output with multiple calls to cl-format, | |
1152 do it like in this example: | |
1153 | |
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)))) | |
1160 | |
1161 Now when you run: | |
1162 | |
1163 user> (print-table (map #(vector % (* % %) (* % % %)) (range 1 11)) 8) | |
1164 | |
1165 It prints a table of squares and cubes for the numbers from 1 to 10: | |
1166 | |
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*))) | |
1182 | |
1183 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |
1184 ;;; Support for column-aware operations ~&, ~T | |
1185 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |
1186 | |
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))) | |
1196 | |
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) | |
1207 | |
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) | |
1216 | |
1217 | |
1218 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |
1219 ;;; Support for accessing the pretty printer from a format | |
1220 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |
1221 | |
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)) | |
1241 | |
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)) | |
1246 | |
1247 ;;; TODO: support ~:T section options for ~T | |
1248 | |
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)) | |
1255 | |
1256 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |
1257 ;;; The table of directives we support, each with its params, | |
1258 ;;; properties, and the compilation function | |
1259 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |
1260 | |
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) }]) | |
1269 | |
1270 (defmacro ^{:private true} | |
1271 defdirectives | |
1272 [ & directives ] | |
1273 `(def ^{:private true} | |
1274 directive-table (hash-map ~@(mapcat process-directive-table-element directives)))) | |
1275 | |
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)) | |
1281 | |
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)) | |
1286 | |
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)) | |
1292 | |
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)) | |
1298 | |
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)) | |
1304 | |
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)) | |
1310 | |
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)))) | |
1322 | |
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))) | |
1332 | |
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)) | |
1340 | |
1341 (\F | |
1342 [ :w [nil Integer] :d [nil Integer] :k [0 Integer] :overflowchar [nil Character] | |
1343 :padchar [\space Character] ] | |
1344 #{ :at } {} | |
1345 fixed-float) | |
1346 | |
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) | |
1353 | |
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) | |
1360 | |
1361 (\$ | |
1362 [ :d [2 Integer] :n [1 Integer] :w [0 Integer] :padchar [\space Character]] | |
1363 #{ :at :colon :both} {} | |
1364 dollar-float) | |
1365 | |
1366 (\% | |
1367 [ :count [1 Integer] ] | |
1368 #{ } {} | |
1369 (fn [params arg-navigator offsets] | |
1370 (dotimes [i (:count params)] | |
1371 (prn)) | |
1372 arg-navigator)) | |
1373 | |
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)) | |
1383 | |
1384 (\| | |
1385 [ :count [1 Integer] ] | |
1386 #{ } {} | |
1387 (fn [params arg-navigator offsets] | |
1388 (dotimes [i (:count params)] | |
1389 (print \formfeed)) | |
1390 arg-navigator)) | |
1391 | |
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))) | |
1399 | |
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)) | |
1407 | |
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))) | |
1414 | |
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 ))) | |
1424 | |
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)))) | |
1438 | |
1439 | |
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 | |
1446 | |
1447 (:colon params) | |
1448 capitalize-word-writer | |
1449 | |
1450 (:at params) | |
1451 init-cap-writer | |
1452 | |
1453 :else | |
1454 downcase-writer)] | |
1455 #(modify-case mod-case-writer %1 %2 %3))) | |
1456 | |
1457 (\) [] #{} {} nil) | |
1458 | |
1459 (\[ | |
1460 [ :selector [nil Integer] ] | |
1461 #{ :colon :at } { :right \], :allows-separator true, :else :last } | |
1462 (cond | |
1463 (:colon params) | |
1464 boolean-conditional | |
1465 | |
1466 (:at params) | |
1467 check-arg-conditional | |
1468 | |
1469 true | |
1470 choice-conditional)) | |
1471 | |
1472 (\; [:min-remaining [nil Integer] :max-columns [nil Integer]] | |
1473 #{ :colon } { :separator true } nil) | |
1474 | |
1475 (\] [] #{} {} nil) | |
1476 | |
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 | |
1483 | |
1484 (:colon params) | |
1485 iterate-list-of-sublists | |
1486 | |
1487 (:at params) | |
1488 iterate-main-list | |
1489 | |
1490 true | |
1491 iterate-sublist)) | |
1492 | |
1493 | |
1494 (\} [] #{:colon} {} nil) | |
1495 | |
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) | |
1500 | |
1501 (\> [] #{:colon} {} nil) | |
1502 | |
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) | |
1514 | |
1515 (and arg1 arg2) | |
1516 (if (= arg1 arg2) [exit navigator] navigator) | |
1517 | |
1518 arg1 | |
1519 (if (= arg1 0) [exit navigator] navigator) | |
1520 | |
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))))) | |
1526 | |
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))))) | |
1544 | |
1545 (\_ | |
1546 [] | |
1547 #{:at :colon :both} {} | |
1548 conditional-newline) | |
1549 | |
1550 (\I | |
1551 [:n [0 Integer]] | |
1552 #{:colon} {} | |
1553 set-indent) | |
1554 ) | |
1555 | |
1556 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |
1557 ;;; Code to manage the parameters and flags associated with each | |
1558 ;;; directive in the format string. | |
1559 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |
1560 | |
1561 (def ^{:private true} | |
1562 param-pattern #"^([vV]|#|('.)|([+-]?\d+)|(?=,))") | |
1563 (def ^{:private true} | |
1564 special-params #{ :parameter-from-args :remaining-arg-count }) | |
1565 | |
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]])))) | |
1579 | |
1580 | |
1581 (defn- extract-params [s offset] | |
1582 (consume extract-param [s offset false])) | |
1583 | |
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]) | |
1595 | |
1596 (def ^{:private true} | |
1597 flag-defs { \: :colon, \@ :at }) | |
1598 | |
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 {}])) | |
1613 | |
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)))))) | |
1626 | |
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))) | |
1650 | |
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 | |
1656 | |
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])])) | |
1676 | |
1677 (defn- compile-raw-string [s offset] | |
1678 (struct compiled-directive (fn [_ a _] (print s) a) nil { :string s } offset)) | |
1679 | |
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)))) | |
1685 | |
1686 | |
1687 (declare collect-clauses) | |
1688 | |
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])) | |
1697 | |
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) | |
1708 | |
1709 (= (:right bracket-info) (:directive (:def this))) | |
1710 [ nil [:right-bracket (:params this) nil remainder]] | |
1711 | |
1712 (else-separator? this) | |
1713 [nil [:else nil (:params this) remainder]] | |
1714 | |
1715 (separator? this) | |
1716 [nil [:separator nil nil remainder]] ;; TODO: check to make sure that there are no params on ~; | |
1717 | |
1718 true | |
1719 [this remainder])))) | |
1720 remainder)) | |
1721 | |
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]] | |
1734 | |
1735 (= type :else) | |
1736 (cond | |
1737 (:else clause-map) | |
1738 (format-error "Two else clauses (\"~:;\") inside bracket construction." offset) | |
1739 | |
1740 (not (:else bracket-info)) | |
1741 (format-error "An else clause (\"~:;\") is in a bracket type that doesn't support it." | |
1742 offset) | |
1743 | |
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) | |
1748 | |
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]])) | |
1756 | |
1757 (= type :separator) | |
1758 (cond | |
1759 saw-else | |
1760 (format-error "A plain clause (with \"~;\") follows an else clause (\"~:;\") inside bracket construction." offset) | |
1761 | |
1762 (not (:allows-separator bracket-info)) | |
1763 (format-error "A separator (\"~;\") is in a bracket type that doesn't support it." | |
1764 offset) | |
1765 | |
1766 true | |
1767 [true [(merge-with concat clause-map { :clauses [clause] }) | |
1768 false remainder]])))) | |
1769 [{ :clauses [] } false remainder]))) | |
1770 | |
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))) | |
1785 | |
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]))))) | |
1806 | |
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)))))) | |
1819 | |
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)) | |
1852 | |
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)) | |
1856 | |
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. | |
1861 | |
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#))))) | |
1876 | |
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. | |
1882 | |
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#))))) |