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