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