Mercurial > lasercutter
comparison 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 |
comparison
equal
deleted
inserted
replaced
9:35cf337adfcf | 10:ef7dbbd6452c |
---|---|
1 ;;; cl_format.clj -- part of the pretty printer for Clojure | |
2 | |
3 ;; by Tom Faulhaber | |
4 ;; April 3, 2009 | |
5 | |
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. | |
13 | |
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) | |
17 | |
18 (in-ns 'clojure.contrib.pprint) | |
19 | |
20 ;;; Forward references | |
21 (declare compile-format) | |
22 (declare execute-format) | |
23 (declare init-navigator) | |
24 ;;; End forward references | |
25 | |
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. | |
30 | |
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. | |
34 | |
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. | |
37 | |
38 If writer is nil, cl-format returns the formatted result string. Otherwise, cl-format | |
39 returns nil. | |
40 | |
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)) | |
45 | |
46 Prints to *out*: | |
47 There are 3 results: 46, 38, 22 | |
48 | |
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))) | |
63 | |
64 (def ^{:private true} *format-str* nil) | |
65 | |
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)))) | |
70 | |
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 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |
76 | |
77 (defstruct ^{:private true} | |
78 arg-navigator :seq :rest :pos ) | |
79 | |
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))) | |
86 | |
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"))))) | |
93 | |
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]))) | |
99 | |
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])) | |
107 | |
108 (declare relative-reposition) | |
109 | |
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))) | |
114 | |
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)))) | |
120 | |
121 (defstruct ^{:private true} | |
122 compiled-directive :func :def :params :offset) | |
123 | |
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 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |
130 | |
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] | |
137 | |
138 (= raw-val :parameter-from-args) | |
139 (next-arg navigator) | |
140 | |
141 (= raw-val :remaining-arg-count) | |
142 [(count (:rest navigator)) navigator] | |
143 | |
144 true | |
145 [raw-val navigator])] | |
146 [[param [real-param offset]] new-navigator])) | |
147 | |
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])) | |
152 | |
153 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |
154 ;;; Functions that support individual directives | |
155 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |
156 | |
157 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |
158 ;;; Common handling code for ~A and ~S | |
159 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |
160 | |
161 (declare opt-base-str) | |
162 | |
163 (def ^{:private true} | |
164 special-radix-markers {2 "#b" 8 "#o", 16 "#x"}) | |
165 | |
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)) | |
179 | |
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)) | |
197 | |
198 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |
199 ;;; Support for the integer directives ~D, ~X, ~O, ~B and some | |
200 ;;; of ~R | |
201 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |
202 | |
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)) | |
213 | |
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)))) | |
223 | |
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)))))) | |
239 | |
240 (def ^{:private true} | |
241 java-base-formats {8 "%o", 10 "%d", 16 "%x"}) | |
242 | |
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)))) | |
251 | |
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))))) | |
256 | |
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)) | |
282 | |
283 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |
284 ;;; Support for english formats (~R and ~:R) | |
285 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |
286 | |
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"]) | |
292 | |
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"]) | |
298 | |
299 (def ^{:private true} | |
300 english-cardinal-tens | |
301 ["" "" "twenty" "thirty" "forty" "fifty" "sixty" "seventy" "eighty" "ninety"]) | |
302 | |
303 (def ^{:private true} | |
304 english-ordinal-tens | |
305 ["" "" "twentieth" "thirtieth" "fortieth" "fiftieth" | |
306 "sixtieth" "seventieth" "eightieth" "ninetieth"]) | |
307 | |
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"]) | |
319 | |
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))))))))) | |
337 | |
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)))))) | |
360 | |
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)) | |
377 | |
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"))))) | |
399 | |
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) | |
414 | |
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)) | |
431 | |
432 | |
433 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |
434 ;;; Support for roman numeral formats (~@R and ~@:R) | |
435 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |
436 | |
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"]]) | |
443 | |
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"]]) | |
450 | |
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)) | |
474 | |
475 (defn- format-old-roman [params navigator offsets] | |
476 (format-roman old-roman-table params navigator offsets)) | |
477 | |
478 (defn- format-new-roman [params navigator offsets] | |
479 (format-roman new-roman-table params navigator offsets)) | |
480 | |
481 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |
482 ;;; Support for character formats (~C) | |
483 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |
484 | |
485 (def ^{:private true} | |
486 special-chars { 8 "Backspace", 9 "Tab", 10 "Newline", 13 "Return", 32 "Space"}) | |
487 | |
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)) | |
501 | |
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)) | |
509 | |
510 (defn- plain-character [params navigator offsets] | |
511 (let [[char navigator] (next-arg navigator)] | |
512 (print char) | |
513 navigator)) | |
514 | |
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)))) | |
520 | |
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))) | |
534 | |
535 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |
536 ;;; Support for real number formats | |
537 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |
538 | |
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))]))) | |
551 | |
552 | |
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)]))) | |
564 | |
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])) | |
594 | |
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))) | |
602 | |
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))))) | |
610 | |
611 (defn- get-fixed [m e d] | |
612 (insert-decimal (expand-fixed m e d) e)) | |
613 | |
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)))) | |
620 | |
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)) | |
658 | |
659 | |
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)) | |
730 | |
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)))) | |
755 | |
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)) | |
775 | |
776 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |
777 ;;; Support for the '~[...~]' conditional construct in its | |
778 ;;; different flavors | |
779 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |
780 | |
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))) | |
794 | |
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))) | |
805 | |
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))) | |
817 | |
818 | |
819 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |
820 ;;; Support for the '~{...~}' iteration construct in its | |
821 ;;; different flavors | |
822 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |
823 | |
824 | |
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)))))))) | |
849 | |
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)))))))) | |
872 | |
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)))))))) | |
896 | |
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))))))) | |
917 | |
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 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |
930 | |
931 (declare format-logical-block) | |
932 (declare justify-clauses) | |
933 | |
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))) | |
938 | |
939 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |
940 ;;; Support for the '~<...~>' justification directive | |
941 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |
942 | |
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)))))) | |
956 | |
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)) | |
1004 | |
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 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |
1012 | |
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))) | |
1026 | |
1027 Integer | |
1028 (let [c ^Character x] | |
1029 (.write writer (int (Character/toLowerCase (char c)))))))))) | |
1030 | |
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))) | |
1044 | |
1045 Integer | |
1046 (let [c ^Character x] | |
1047 (.write writer (int (Character/toUpperCase (char c)))))))))) | |
1048 | |
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))))) | |
1072 | |
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))))))) | |
1093 | |
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)))))))))))) | |
1099 | |
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)))) | |
1125 | |
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))))))))))) | |
1133 | |
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))))) | |
1138 | |
1139 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |
1140 ;;; If necessary, wrap the writer in a PrettyWriter object | |
1141 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |
1142 | |
1143 (defn get-pretty-writer [writer] | |
1144 (if (pretty-writer? writer) | |
1145 writer | |
1146 (pretty-writer writer *print-right-margin* *print-miser-width*))) | |
1147 | |
1148 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |
1149 ;;; Support for column-aware operations ~&, ~T | |
1150 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |
1151 | |
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))) | |
1159 | |
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) | |
1170 | |
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) | |
1179 | |
1180 | |
1181 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |
1182 ;;; Support for accessing the pretty printer from a format | |
1183 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |
1184 | |
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)) | |
1204 | |
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)) | |
1209 | |
1210 ;;; TODO: support ~:T section options for ~T | |
1211 | |
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)) | |
1218 | |
1219 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |
1220 ;;; The table of directives we support, each with its params, | |
1221 ;;; properties, and the compilation function | |
1222 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |
1223 | |
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) }]) | |
1232 | |
1233 (defmacro ^{:private true} | |
1234 defdirectives | |
1235 [ & directives ] | |
1236 `(def ^{:private true} | |
1237 directive-table (hash-map ~@(mapcat process-directive-table-element directives)))) | |
1238 | |
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)) | |
1244 | |
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)) | |
1249 | |
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)) | |
1255 | |
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)) | |
1261 | |
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)) | |
1267 | |
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)) | |
1273 | |
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)))) | |
1285 | |
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))) | |
1295 | |
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)) | |
1303 | |
1304 (\F | |
1305 [ :w [nil Integer] :d [nil Integer] :k [0 Integer] :overflowchar [nil Character] | |
1306 :padchar [\space Character] ] | |
1307 #{ :at } {} | |
1308 fixed-float) | |
1309 | |
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) | |
1316 | |
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) | |
1323 | |
1324 (\$ | |
1325 [ :d [2 Integer] :n [1 Integer] :w [0 Integer] :padchar [\space Character]] | |
1326 #{ :at :colon :both} {} | |
1327 dollar-float) | |
1328 | |
1329 (\% | |
1330 [ :count [1 Integer] ] | |
1331 #{ } {} | |
1332 (fn [params arg-navigator offsets] | |
1333 (dotimes [i (:count params)] | |
1334 (prn)) | |
1335 arg-navigator)) | |
1336 | |
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)) | |
1346 | |
1347 (\| | |
1348 [ :count [1 Integer] ] | |
1349 #{ } {} | |
1350 (fn [params arg-navigator offsets] | |
1351 (dotimes [i (:count params)] | |
1352 (print \formfeed)) | |
1353 arg-navigator)) | |
1354 | |
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))) | |
1362 | |
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)) | |
1370 | |
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))) | |
1377 | |
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 ))) | |
1387 | |
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)))) | |
1401 | |
1402 | |
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 | |
1409 | |
1410 (:colon params) | |
1411 capitalize-word-writer | |
1412 | |
1413 (:at params) | |
1414 init-cap-writer | |
1415 | |
1416 :else | |
1417 downcase-writer)] | |
1418 #(modify-case mod-case-writer %1 %2 %3))) | |
1419 | |
1420 (\) [] #{} {} nil) | |
1421 | |
1422 (\[ | |
1423 [ :selector [nil Integer] ] | |
1424 #{ :colon :at } { :right \], :allows-separator true, :else :last } | |
1425 (cond | |
1426 (:colon params) | |
1427 boolean-conditional | |
1428 | |
1429 (:at params) | |
1430 check-arg-conditional | |
1431 | |
1432 true | |
1433 choice-conditional)) | |
1434 | |
1435 (\; [:min-remaining [nil Integer] :max-columns [nil Integer]] | |
1436 #{ :colon } { :separator true } nil) | |
1437 | |
1438 (\] [] #{} {} nil) | |
1439 | |
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 | |
1446 | |
1447 (:colon params) | |
1448 iterate-list-of-sublists | |
1449 | |
1450 (:at params) | |
1451 iterate-main-list | |
1452 | |
1453 true | |
1454 iterate-sublist)) | |
1455 | |
1456 | |
1457 (\} [] #{:colon} {} nil) | |
1458 | |
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) | |
1463 | |
1464 (\> [] #{:colon} {} nil) | |
1465 | |
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) | |
1477 | |
1478 (and arg1 arg2) | |
1479 (if (= arg1 arg2) [exit navigator] navigator) | |
1480 | |
1481 arg1 | |
1482 (if (= arg1 0) [exit navigator] navigator) | |
1483 | |
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))))) | |
1489 | |
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))))) | |
1507 | |
1508 (\_ | |
1509 [] | |
1510 #{:at :colon :both} {} | |
1511 conditional-newline) | |
1512 | |
1513 (\I | |
1514 [:n [0 Integer]] | |
1515 #{:colon} {} | |
1516 set-indent) | |
1517 ) | |
1518 | |
1519 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |
1520 ;;; Code to manage the parameters and flags associated with each | |
1521 ;;; directive in the format string. | |
1522 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |
1523 | |
1524 (def ^{:private true} | |
1525 param-pattern #"^([vV]|#|('.)|([+-]?\d+)|(?=,))") | |
1526 (def ^{:private true} | |
1527 special-params #{ :parameter-from-args :remaining-arg-count }) | |
1528 | |
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]])))) | |
1542 | |
1543 | |
1544 (defn- extract-params [s offset] | |
1545 (consume extract-param [s offset false])) | |
1546 | |
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]) | |
1558 | |
1559 (def ^{:private true} | |
1560 flag-defs { \: :colon, \@ :at }) | |
1561 | |
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 {}])) | |
1576 | |
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)))))) | |
1589 | |
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))) | |
1613 | |
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 | |
1619 | |
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])])) | |
1639 | |
1640 (defn- compile-raw-string [s offset] | |
1641 (struct compiled-directive (fn [_ a _] (print s) a) nil { :string s } offset)) | |
1642 | |
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)))) | |
1648 | |
1649 | |
1650 (declare collect-clauses) | |
1651 | |
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])) | |
1660 | |
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) | |
1671 | |
1672 (= (:right bracket-info) (:directive (:def this))) | |
1673 [ nil [:right-bracket (:params this) nil remainder]] | |
1674 | |
1675 (else-separator? this) | |
1676 [nil [:else nil (:params this) remainder]] | |
1677 | |
1678 (separator? this) | |
1679 [nil [:separator nil nil remainder]] ;; TODO: check to make sure that there are no params on ~; | |
1680 | |
1681 true | |
1682 [this remainder])))) | |
1683 remainder)) | |
1684 | |
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]] | |
1697 | |
1698 (= type :else) | |
1699 (cond | |
1700 (:else clause-map) | |
1701 (format-error "Two else clauses (\"~:;\") inside bracket construction." offset) | |
1702 | |
1703 (not (:else bracket-info)) | |
1704 (format-error "An else clause (\"~:;\") is in a bracket type that doesn't support it." | |
1705 offset) | |
1706 | |
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) | |
1711 | |
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]])) | |
1719 | |
1720 (= type :separator) | |
1721 (cond | |
1722 saw-else | |
1723 (format-error "A plain clause (with \"~;\") follows an else clause (\"~:;\") inside bracket construction." offset) | |
1724 | |
1725 (not (:allows-separator bracket-info)) | |
1726 (format-error "A separator (\"~;\") is in a bracket type that doesn't support it." | |
1727 offset) | |
1728 | |
1729 true | |
1730 [true [(merge-with concat clause-map { :clauses [clause] }) | |
1731 false remainder]])))) | |
1732 [{ :clauses [] } false remainder]))) | |
1733 | |
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))) | |
1748 | |
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]))))) | |
1769 | |
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)))))) | |
1782 | |
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))) | |
1815 | |
1816 | |
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. | |
1821 | |
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#))))))) | |
1830 | |
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. | |
1836 | |
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#))))))) |