Mercurial > lasercutter
view src/clojure/contrib/pprint/cl_format.clj @ 10:ef7dbbd6452c
added clojure source goodness
author | Robert McIntyre <rlm@mit.edu> |
---|---|
date | Sat, 21 Aug 2010 06:25:44 -0400 |
parents | |
children |
line wrap: on
line source
1 ;;; cl_format.clj -- part of the pretty printer for Clojure3 ;; by Tom Faulhaber4 ;; April 3, 20096 ; Copyright (c) Tom Faulhaber, Dec 2008. All rights reserved.7 ; The use and distribution terms for this software are covered by the8 ; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)9 ; which can be found in the file epl-v10.html at the root of this distribution.10 ; By using this software in any fashion, you are agreeing to be bound by11 ; the terms of this license.12 ; You must not remove this notice, or any other, from this software.14 ;; This module implements the Common Lisp compatible format function as documented15 ;; in "Common Lisp the Language, 2nd edition", Chapter 22 (available online at:16 ;; http://www.cs.cmu.edu/afs/cs.cmu.edu/project/ai-repository/ai/html/cltl/clm/node200.html#SECTION002633000000000000000)18 (in-ns 'clojure.contrib.pprint)20 ;;; Forward references21 (declare compile-format)22 (declare execute-format)23 (declare init-navigator)24 ;;; End forward references26 (defn cl-format27 "An implementation of a Common Lisp compatible format function. cl-format formats its28 arguments to an output stream or string based on the format control string given. It29 supports sophisticated formatting of structured data.31 Writer is an instance of java.io.Writer, true to output to *out* or nil to output32 to a string, format-in is the format control string and the remaining arguments33 are the data to be formatted.35 The format control string is a string to be output with embedded 'format directives'36 describing how to format the various arguments passed in.38 If writer is nil, cl-format returns the formatted result string. Otherwise, cl-format39 returns nil.41 For example:42 (let [results [46 38 22]]43 (cl-format true \"There ~[are~;is~:;are~]~:* ~d result~:p: ~{~d~^, ~}~%\"44 (count results) results))46 Prints to *out*:47 There are 3 results: 46, 38, 2249 Detailed documentation on format control strings is available in the \"Common Lisp the50 Language, 2nd edition\", Chapter 22 (available online at:51 http://www.cs.cmu.edu/afs/cs.cmu.edu/project/ai-repository/ai/html/cltl/clm/node200.html#SECTION002633000000000000000)52 and in the Common Lisp HyperSpec at53 http://www.lispworks.com/documentation/HyperSpec/Body/22_c.htm54 "55 {:see-also [["http://www.cs.cmu.edu/afs/cs.cmu.edu/project/ai-repository/ai/html/cltl/clm/node200.html#SECTION002633000000000000000"56 "Common Lisp the Language"]57 ["http://www.lispworks.com/documentation/HyperSpec/Body/22_c.htm"58 "Common Lisp HyperSpec"]]}59 [writer format-in & args]60 (let [compiled-format (if (string? format-in) (compile-format format-in) format-in)61 navigator (init-navigator args)]62 (execute-format writer compiled-format navigator)))64 (def ^{:private true} *format-str* nil)66 (defn- format-error [message offset]67 (let [full-message (str message \newline *format-str* \newline68 (apply str (repeat offset \space)) "^" \newline)]69 (throw (RuntimeException. full-message))))71 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;72 ;;; Argument navigators manage the argument list73 ;;; as the format statement moves through the list74 ;;; (possibly going forwards and backwards as it does so)75 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;77 (defstruct ^{:private true}78 arg-navigator :seq :rest :pos )80 (defn init-navigator81 "Create a new arg-navigator from the sequence with the position set to 0"82 {:skip-wiki true}83 [s]84 (let [s (seq s)]85 (struct arg-navigator s s 0)))87 ;; TODO call format-error with offset88 (defn- next-arg [ navigator ]89 (let [ rst (:rest navigator) ]90 (if rst91 [(first rst) (struct arg-navigator (:seq navigator ) (next rst) (inc (:pos navigator)))]92 (throw (new Exception "Not enough arguments for format definition")))))94 (defn- next-arg-or-nil [navigator]95 (let [rst (:rest navigator)]96 (if rst97 [(first rst) (struct arg-navigator (:seq navigator ) (next rst) (inc (:pos navigator)))]98 [nil navigator])))100 ;; Get an argument off the arg list and compile it if it's not already compiled101 (defn- get-format-arg [navigator]102 (let [[raw-format navigator] (next-arg navigator)103 compiled-format (if (instance? String raw-format)104 (compile-format raw-format)105 raw-format)]106 [compiled-format navigator]))108 (declare relative-reposition)110 (defn- absolute-reposition [navigator position]111 (if (>= position (:pos navigator))112 (relative-reposition navigator (- (:pos navigator) position))113 (struct arg-navigator (:seq navigator) (drop position (:seq navigator)) position)))115 (defn- relative-reposition [navigator position]116 (let [newpos (+ (:pos navigator) position)]117 (if (neg? position)118 (absolute-reposition navigator newpos)119 (struct arg-navigator (:seq navigator) (drop position (:rest navigator)) newpos))))121 (defstruct ^{:private true}122 compiled-directive :func :def :params :offset)124 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;125 ;;; When looking at the parameter list, we may need to manipulate126 ;;; the argument list as well (for 'V' and '#' parameter types).127 ;;; We hide all of this behind a function, but clients need to128 ;;; manage changing arg navigator129 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;131 ;; TODO: validate parameters when they come from arg list132 (defn- realize-parameter [[param [raw-val offset]] navigator]133 (let [[real-param new-navigator]134 (cond135 (contains? #{ :at :colon } param) ;pass flags through unchanged - this really isn't necessary136 [raw-val navigator]138 (= raw-val :parameter-from-args)139 (next-arg navigator)141 (= raw-val :remaining-arg-count)142 [(count (:rest navigator)) navigator]144 true145 [raw-val navigator])]146 [[param [real-param offset]] new-navigator]))148 (defn- realize-parameter-list [parameter-map navigator]149 (let [[pairs new-navigator]150 (map-passing-context realize-parameter navigator parameter-map)]151 [(into {} pairs) new-navigator]))153 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;154 ;;; Functions that support individual directives155 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;157 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;158 ;;; Common handling code for ~A and ~S159 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;161 (declare opt-base-str)163 (def ^{:private true}164 special-radix-markers {2 "#b" 8 "#o", 16 "#x"})166 (defn- format-simple-number [n]167 (cond168 (integer? n) (if (= *print-base* 10)169 (str n (if *print-radix* "."))170 (str171 (if *print-radix* (or (get special-radix-markers *print-base*) (str "#" *print-base* "r")))172 (opt-base-str *print-base* n)))173 (ratio? n) (str174 (if *print-radix* (or (get special-radix-markers *print-base*) (str "#" *print-base* "r")))175 (opt-base-str *print-base* (.numerator n))176 "/"177 (opt-base-str *print-base* (.denominator n)))178 :else nil))180 (defn- format-ascii [print-func params arg-navigator offsets]181 (let [ [arg arg-navigator] (next-arg arg-navigator)182 ^String base-output (or (format-simple-number arg) (print-func arg))183 base-width (.length base-output)184 min-width (+ base-width (:minpad params))185 width (if (>= min-width (:mincol params))186 min-width187 (+ min-width188 (* (+ (quot (- (:mincol params) min-width 1)189 (:colinc params) )190 1)191 (:colinc params))))192 chars (apply str (repeat (- width base-width) (:padchar params)))]193 (if (:at params)194 (print (str chars base-output))195 (print (str base-output chars)))196 arg-navigator))198 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;199 ;;; Support for the integer directives ~D, ~X, ~O, ~B and some200 ;;; of ~R201 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;203 (defn- integral?204 "returns true if a number is actually an integer (that is, has no fractional part)"205 [x]206 (cond207 (integer? x) true208 (decimal? x) (>= (.ulp (.stripTrailingZeros (bigdec 0))) 1) ; true iff no fractional part209 (float? x) (= x (Math/floor x))210 (ratio? x) (let [^clojure.lang.Ratio r x]211 (= 0 (rem (.numerator r) (.denominator r))))212 :else false))214 (defn- remainders215 "Return the list of remainders (essentially the 'digits') of val in the given base"216 [base val]217 (reverse218 (first219 (consume #(if (pos? %)220 [(rem % base) (quot % base)]221 [nil nil])222 val))))224 ;;; TODO: xlated-val does not seem to be used here.225 (defn- base-str226 "Return val as a string in the given base"227 [base val]228 (if (zero? val)229 "0"230 (let [xlated-val (cond231 (float? val) (bigdec val)232 (ratio? val) (let [^clojure.lang.Ratio r val]233 (/ (.numerator r) (.denominator r)))234 :else val)]235 (apply str236 (map237 #(if (< % 10) (char (+ (int \0) %)) (char (+ (int \a) (- % 10))))238 (remainders base val))))))240 (def ^{:private true}241 java-base-formats {8 "%o", 10 "%d", 16 "%x"})243 (defn- opt-base-str244 "Return val as a string in the given base, using clojure.core/format if supported245 for improved performance"246 [base val]247 (let [format-str (get java-base-formats base)]248 (if (and format-str (integer? val) (-> val class .getName (.startsWith "java.")))249 (clojure.core/format format-str val)250 (base-str base val))))252 (defn- group-by* [unit lis]253 (reverse254 (first255 (consume (fn [x] [(seq (reverse (take unit x))) (seq (drop unit x))]) (reverse lis)))))257 (defn- format-integer [base params arg-navigator offsets]258 (let [[arg arg-navigator] (next-arg arg-navigator)]259 (if (integral? arg)260 (let [neg (neg? arg)261 pos-arg (if neg (- arg) arg)262 raw-str (opt-base-str base pos-arg)263 group-str (if (:colon params)264 (let [groups (map #(apply str %) (group-by* (:commainterval params) raw-str))265 commas (repeat (count groups) (:commachar params))]266 (apply str (next (interleave commas groups))))267 raw-str)268 ^String signed-str (cond269 neg (str "-" group-str)270 (:at params) (str "+" group-str)271 true group-str)272 padded-str (if (< (.length signed-str) (:mincol params))273 (str (apply str (repeat (- (:mincol params) (.length signed-str))274 (:padchar params)))275 signed-str)276 signed-str)]277 (print padded-str))278 (format-ascii print-str {:mincol (:mincol params) :colinc 1 :minpad 0279 :padchar (:padchar params) :at true}280 (init-navigator [arg]) nil))281 arg-navigator))283 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;284 ;;; Support for english formats (~R and ~:R)285 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;287 (def ^{:private true}288 english-cardinal-units289 ["zero" "one" "two" "three" "four" "five" "six" "seven" "eight" "nine"290 "ten" "eleven" "twelve" "thirteen" "fourteen"291 "fifteen" "sixteen" "seventeen" "eighteen" "nineteen"])293 (def ^{:private true}294 english-ordinal-units295 ["zeroth" "first" "second" "third" "fourth" "fifth" "sixth" "seventh" "eighth" "ninth"296 "tenth" "eleventh" "twelfth" "thirteenth" "fourteenth"297 "fifteenth" "sixteenth" "seventeenth" "eighteenth" "nineteenth"])299 (def ^{:private true}300 english-cardinal-tens301 ["" "" "twenty" "thirty" "forty" "fifty" "sixty" "seventy" "eighty" "ninety"])303 (def ^{:private true}304 english-ordinal-tens305 ["" "" "twentieth" "thirtieth" "fortieth" "fiftieth"306 "sixtieth" "seventieth" "eightieth" "ninetieth"])308 ;; We use "short scale" for our units (see http://en.wikipedia.org/wiki/Long_and_short_scales)309 ;; Number names from http://www.jimloy.com/math/billion.htm310 ;; We follow the rules for writing numbers from the Blue Book311 ;; (http://www.grammarbook.com/numbers/numbers.asp)312 (def ^{:private true}313 english-scale-numbers314 ["" "thousand" "million" "billion" "trillion" "quadrillion" "quintillion"315 "sextillion" "septillion" "octillion" "nonillion" "decillion"316 "undecillion" "duodecillion" "tredecillion" "quattuordecillion"317 "quindecillion" "sexdecillion" "septendecillion"318 "octodecillion" "novemdecillion" "vigintillion"])320 (defn- format-simple-cardinal321 "Convert a number less than 1000 to a cardinal english string"322 [num]323 (let [hundreds (quot num 100)324 tens (rem num 100)]325 (str326 (if (pos? hundreds) (str (nth english-cardinal-units hundreds) " hundred"))327 (if (and (pos? hundreds) (pos? tens)) " ")328 (if (pos? tens)329 (if (< tens 20)330 (nth english-cardinal-units tens)331 (let [ten-digit (quot tens 10)332 unit-digit (rem tens 10)]333 (str334 (if (pos? ten-digit) (nth english-cardinal-tens ten-digit))335 (if (and (pos? ten-digit) (pos? unit-digit)) "-")336 (if (pos? unit-digit) (nth english-cardinal-units unit-digit)))))))))338 (defn- add-english-scales339 "Take a sequence of parts, add scale numbers (e.g., million) and combine into a string340 offset is a factor of 10^3 to multiply by"341 [parts offset]342 (let [cnt (count parts)]343 (loop [acc []344 pos (dec cnt)345 this (first parts)346 remainder (next parts)]347 (if (nil? remainder)348 (str (apply str (interpose ", " acc))349 (if (and (not (empty? this)) (not (empty? acc))) ", ")350 this351 (if (and (not (empty? this)) (pos? (+ pos offset)))352 (str " " (nth english-scale-numbers (+ pos offset)))))353 (recur354 (if (empty? this)355 acc356 (conj acc (str this " " (nth english-scale-numbers (+ pos offset)))))357 (dec pos)358 (first remainder)359 (next remainder))))))361 (defn- format-cardinal-english [params navigator offsets]362 (let [[arg navigator] (next-arg navigator)]363 (if (= 0 arg)364 (print "zero")365 (let [abs-arg (if (neg? arg) (- arg) arg) ; some numbers are too big for Math/abs366 parts (remainders 1000 abs-arg)]367 (if (<= (count parts) (count english-scale-numbers))368 (let [parts-strs (map format-simple-cardinal parts)369 full-str (add-english-scales parts-strs 0)]370 (print (str (if (neg? arg) "minus ") full-str)))371 (format-integer ;; for numbers > 10^63, we fall back on ~D372 10373 { :mincol 0, :padchar \space, :commachar \, :commainterval 3, :colon true}374 (init-navigator [arg])375 { :mincol 0, :padchar 0, :commachar 0 :commainterval 0}))))376 navigator))378 (defn- format-simple-ordinal379 "Convert a number less than 1000 to a ordinal english string380 Note this should only be used for the last one in the sequence"381 [num]382 (let [hundreds (quot num 100)383 tens (rem num 100)]384 (str385 (if (pos? hundreds) (str (nth english-cardinal-units hundreds) " hundred"))386 (if (and (pos? hundreds) (pos? tens)) " ")387 (if (pos? tens)388 (if (< tens 20)389 (nth english-ordinal-units tens)390 (let [ten-digit (quot tens 10)391 unit-digit (rem tens 10)]392 (if (and (pos? ten-digit) (not (pos? unit-digit)))393 (nth english-ordinal-tens ten-digit)394 (str395 (if (pos? ten-digit) (nth english-cardinal-tens ten-digit))396 (if (and (pos? ten-digit) (pos? unit-digit)) "-")397 (if (pos? unit-digit) (nth english-ordinal-units unit-digit))))))398 (if (pos? hundreds) "th")))))400 (defn- format-ordinal-english [params navigator offsets]401 (let [[arg navigator] (next-arg navigator)]402 (if (= 0 arg)403 (print "zeroth")404 (let [abs-arg (if (neg? arg) (- arg) arg) ; some numbers are too big for Math/abs405 parts (remainders 1000 abs-arg)]406 (if (<= (count parts) (count english-scale-numbers))407 (let [parts-strs (map format-simple-cardinal (drop-last parts))408 head-str (add-english-scales parts-strs 1)409 tail-str (format-simple-ordinal (last parts))]410 (print (str (if (neg? arg) "minus ")411 (cond412 (and (not (empty? head-str)) (not (empty? tail-str)))413 (str head-str ", " tail-str)415 (not (empty? head-str)) (str head-str "th")416 :else tail-str))))417 (do (format-integer ;; for numbers > 10^63, we fall back on ~D418 10419 { :mincol 0, :padchar \space, :commachar \, :commainterval 3, :colon true}420 (init-navigator [arg])421 { :mincol 0, :padchar 0, :commachar 0 :commainterval 0})422 (let [low-two-digits (rem arg 100)423 not-teens (or (< 11 low-two-digits) (> 19 low-two-digits))424 low-digit (rem low-two-digits 10)]425 (print (cond426 (and (== low-digit 1) not-teens) "st"427 (and (== low-digit 2) not-teens) "nd"428 (and (== low-digit 3) not-teens) "rd"429 :else "th")))))))430 navigator))433 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;434 ;;; Support for roman numeral formats (~@R and ~@:R)435 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;437 (def ^{:private true}438 old-roman-table439 [[ "I" "II" "III" "IIII" "V" "VI" "VII" "VIII" "VIIII"]440 [ "X" "XX" "XXX" "XXXX" "L" "LX" "LXX" "LXXX" "LXXXX"]441 [ "C" "CC" "CCC" "CCCC" "D" "DC" "DCC" "DCCC" "DCCCC"]442 [ "M" "MM" "MMM"]])444 (def ^{:private true}445 new-roman-table446 [[ "I" "II" "III" "IV" "V" "VI" "VII" "VIII" "IX"]447 [ "X" "XX" "XXX" "XL" "L" "LX" "LXX" "LXXX" "XC"]448 [ "C" "CC" "CCC" "CD" "D" "DC" "DCC" "DCCC" "CM"]449 [ "M" "MM" "MMM"]])451 (defn- format-roman452 "Format a roman numeral using the specified look-up table"453 [table params navigator offsets]454 (let [[arg navigator] (next-arg navigator)]455 (if (and (number? arg) (> arg 0) (< arg 4000))456 (let [digits (remainders 10 arg)]457 (loop [acc []458 pos (dec (count digits))459 digits digits]460 (if (empty? digits)461 (print (apply str acc))462 (let [digit (first digits)]463 (recur (if (= 0 digit)464 acc465 (conj acc (nth (nth table pos) (dec digit))))466 (dec pos)467 (next digits))))))468 (format-integer ;; for anything <= 0 or > 3999, we fall back on ~D469 10470 { :mincol 0, :padchar \space, :commachar \, :commainterval 3, :colon true}471 (init-navigator [arg])472 { :mincol 0, :padchar 0, :commachar 0 :commainterval 0}))473 navigator))475 (defn- format-old-roman [params navigator offsets]476 (format-roman old-roman-table params navigator offsets))478 (defn- format-new-roman [params navigator offsets]479 (format-roman new-roman-table params navigator offsets))481 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;482 ;;; Support for character formats (~C)483 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;485 (def ^{:private true}486 special-chars { 8 "Backspace", 9 "Tab", 10 "Newline", 13 "Return", 32 "Space"})488 (defn- pretty-character [params navigator offsets]489 (let [[c navigator] (next-arg navigator)490 as-int (int c)491 base-char (bit-and as-int 127)492 meta (bit-and as-int 128)493 special (get special-chars base-char)]494 (if (> meta 0) (print "Meta-"))495 (print (cond496 special special497 (< base-char 32) (str "Control-" (char (+ base-char 64)))498 (= base-char 127) "Control-?"499 :else (char base-char)))500 navigator))502 (defn- readable-character [params navigator offsets]503 (let [[c navigator] (next-arg navigator)]504 (condp = (:char-format params)505 \o (cl-format true "\\o~3,'0o" (int c))506 \u (cl-format true "\\u~4,'0x" (int c))507 nil (pr c))508 navigator))510 (defn- plain-character [params navigator offsets]511 (let [[char navigator] (next-arg navigator)]512 (print char)513 navigator))515 ;; Check to see if a result is an abort (~^) construct516 ;; TODO: move these funcs somewhere more appropriate517 (defn- abort? [context]518 (let [token (first context)]519 (or (= :up-arrow token) (= :colon-up-arrow token))))521 ;; Handle the execution of "sub-clauses" in bracket constructions522 (defn- execute-sub-format [format args base-args]523 (second524 (map-passing-context525 (fn [element context]526 (if (abort? context)527 [nil context] ; just keep passing it along528 (let [[params args] (realize-parameter-list (:params element) context)529 [params offsets] (unzip-map params)530 params (assoc params :base-args base-args)]531 [nil (apply (:func element) [params args offsets])])))532 args533 format)))535 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;536 ;;; Support for real number formats537 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;539 ;; TODO - return exponent as int to eliminate double conversion540 (defn- float-parts-base541 "Produce string parts for the mantissa (normalized 1-9) and exponent"542 [^Object f]543 (let [^String s (.toLowerCase (.toString f))544 exploc (.indexOf s (int \e))]545 (if (neg? exploc)546 (let [dotloc (.indexOf s (int \.))]547 (if (neg? dotloc)548 [s (str (dec (count s)))]549 [(str (subs s 0 dotloc) (subs s (inc dotloc))) (str (dec dotloc))]))550 [(str (subs s 0 1) (subs s 2 exploc)) (subs s (inc exploc))])))553 (defn- float-parts554 "Take care of leading and trailing zeros in decomposed floats"555 [f]556 (let [[m ^String e] (float-parts-base f)557 m1 (rtrim m \0)558 m2 (ltrim m1 \0)559 delta (- (count m1) (count m2))560 ^String e (if (and (pos? (count e)) (= (nth e 0) \+)) (subs e 1) e)]561 (if (empty? m2)562 ["0" 0]563 [m2 (- (Integer/valueOf e) delta)])))565 (defn- round-str [m e d w]566 (if (or d w)567 (let [len (count m)568 round-pos (if d (+ e d 1))569 round-pos (if (and w (< (inc e) (dec w))570 (or (nil? round-pos) (< (dec w) round-pos)))571 (dec w)572 round-pos)573 [m1 e1 round-pos len] (if (= round-pos 0)574 [(str "0" m) (inc e) 1 (inc len)]575 [m e round-pos len])]576 (if round-pos577 (if (neg? round-pos)578 ["0" 0 false]579 (if (> len round-pos)580 (let [round-char (nth m1 round-pos)581 ^String result (subs m1 0 round-pos)]582 (if (>= (int round-char) (int \5))583 (let [result-val (Integer/valueOf result)584 leading-zeros (subs result 0 (min (prefix-count result \0) (- round-pos 1)))585 round-up-result (str leading-zeros586 (String/valueOf (+ result-val587 (if (neg? result-val) -1 1))))588 expanded (> (count round-up-result) (count result))]589 [round-up-result e1 expanded])590 [result e1 false]))591 [m e false]))592 [m e false]))593 [m e false]))595 (defn- expand-fixed [m e d]596 (let [m1 (if (neg? e) (str (apply str (repeat (dec (- e)) \0)) m) m)597 len (count m1)598 target-len (if d (+ e d 1) (inc e))]599 (if (< len target-len)600 (str m1 (apply str (repeat (- target-len len) \0)))601 m1)))603 (defn- insert-decimal604 "Insert the decimal point at the right spot in the number to match an exponent"605 [m e]606 (if (neg? e)607 (str "." m)608 (let [loc (inc e)]609 (str (subs m 0 loc) "." (subs m loc)))))611 (defn- get-fixed [m e d]612 (insert-decimal (expand-fixed m e d) e))614 (defn- insert-scaled-decimal615 "Insert the decimal point at the right spot in the number to match an exponent"616 [m k]617 (if (neg? k)618 (str "." m)619 (str (subs m 0 k) "." (subs m k))))621 ;; the function to render ~F directives622 ;; TODO: support rationals. Back off to ~D/~A is the appropriate cases623 (defn- fixed-float [params navigator offsets]624 (let [w (:w params)625 d (:d params)626 [arg navigator] (next-arg navigator)627 [sign abs] (if (neg? arg) ["-" (- arg)] ["+" arg])628 [mantissa exp] (float-parts abs)629 scaled-exp (+ exp (:k params))630 add-sign (or (:at params) (neg? arg))631 append-zero (and (not d) (<= (dec (count mantissa)) scaled-exp))632 [rounded-mantissa scaled-exp expanded] (round-str mantissa scaled-exp633 d (if w (- w (if add-sign 1 0))))634 fixed-repr (get-fixed rounded-mantissa (if expanded (inc scaled-exp) scaled-exp) d)635 prepend-zero (= (first fixed-repr) \.)]636 (if w637 (let [len (count fixed-repr)638 signed-len (if add-sign (inc len) len)639 prepend-zero (and prepend-zero (not (>= signed-len w)))640 append-zero (and append-zero (not (>= signed-len w)))641 full-len (if (or prepend-zero append-zero)642 (inc signed-len)643 signed-len)]644 (if (and (> full-len w) (:overflowchar params))645 (print (apply str (repeat w (:overflowchar params))))646 (print (str647 (apply str (repeat (- w full-len) (:padchar params)))648 (if add-sign sign)649 (if prepend-zero "0")650 fixed-repr651 (if append-zero "0")))))652 (print (str653 (if add-sign sign)654 (if prepend-zero "0")655 fixed-repr656 (if append-zero "0"))))657 navigator))660 ;; the function to render ~E directives661 ;; TODO: support rationals. Back off to ~D/~A is the appropriate cases662 ;; TODO: define ~E representation for Infinity663 (defn- exponential-float [params navigator offsets]664 (let [[arg navigator] (next-arg navigator)]665 (loop [[mantissa exp] (float-parts (if (neg? arg) (- arg) arg))]666 (let [w (:w params)667 d (:d params)668 e (:e params)669 k (:k params)670 expchar (or (:exponentchar params) \E)671 add-sign (or (:at params) (neg? arg))672 prepend-zero (<= k 0)673 ^Integer scaled-exp (- exp (dec k))674 scaled-exp-str (str (Math/abs scaled-exp))675 scaled-exp-str (str expchar (if (neg? scaled-exp) \- \+)676 (if e (apply str677 (repeat678 (- e679 (count scaled-exp-str))680 \0)))681 scaled-exp-str)682 exp-width (count scaled-exp-str)683 base-mantissa-width (count mantissa)684 scaled-mantissa (str (apply str (repeat (- k) \0))685 mantissa686 (if d687 (apply str688 (repeat689 (- d (dec base-mantissa-width)690 (if (neg? k) (- k) 0)) \0))))691 w-mantissa (if w (- w exp-width))692 [rounded-mantissa _ incr-exp] (round-str693 scaled-mantissa 0694 (cond695 (= k 0) (dec d)696 (pos? k) d697 (neg? k) (dec d))698 (if w-mantissa699 (- w-mantissa (if add-sign 1 0))))700 full-mantissa (insert-scaled-decimal rounded-mantissa k)701 append-zero (and (= k (count rounded-mantissa)) (nil? d))]702 (if (not incr-exp)703 (if w704 (let [len (+ (count full-mantissa) exp-width)705 signed-len (if add-sign (inc len) len)706 prepend-zero (and prepend-zero (not (= signed-len w)))707 full-len (if prepend-zero (inc signed-len) signed-len)708 append-zero (and append-zero (< full-len w))]709 (if (and (or (> full-len w) (and e (> (- exp-width 2) e)))710 (:overflowchar params))711 (print (apply str (repeat w (:overflowchar params))))712 (print (str713 (apply str714 (repeat715 (- w full-len (if append-zero 1 0) )716 (:padchar params)))717 (if add-sign (if (neg? arg) \- \+))718 (if prepend-zero "0")719 full-mantissa720 (if append-zero "0")721 scaled-exp-str))))722 (print (str723 (if add-sign (if (neg? arg) \- \+))724 (if prepend-zero "0")725 full-mantissa726 (if append-zero "0")727 scaled-exp-str)))728 (recur [rounded-mantissa (inc exp)]))))729 navigator))731 ;; the function to render ~G directives732 ;; This just figures out whether to pass the request off to ~F or ~E based733 ;; on the algorithm in CLtL.734 ;; TODO: support rationals. Back off to ~D/~A is the appropriate cases735 ;; TODO: refactor so that float-parts isn't called twice736 (defn- general-float [params navigator offsets]737 (let [[arg _] (next-arg navigator)738 [mantissa exp] (float-parts (if (neg? arg) (- arg) arg))739 w (:w params)740 d (:d params)741 e (:e params)742 n (if (= arg 0.0) 0 (inc exp))743 ee (if e (+ e 2) 4)744 ww (if w (- w ee))745 d (if d d (max (count mantissa) (min n 7)))746 dd (- d n)]747 (if (<= 0 dd d)748 (let [navigator (fixed-float {:w ww, :d dd, :k 0,749 :overflowchar (:overflowchar params),750 :padchar (:padchar params), :at (:at params)}751 navigator offsets)]752 (print (apply str (repeat ee \space)))753 navigator)754 (exponential-float params navigator offsets))))756 ;; the function to render ~$ directives757 ;; TODO: support rationals. Back off to ~D/~A is the appropriate cases758 (defn- dollar-float [params navigator offsets]759 (let [[^Double arg navigator] (next-arg navigator)760 [mantissa exp] (float-parts (Math/abs arg))761 d (:d params) ; digits after the decimal762 n (:n params) ; minimum digits before the decimal763 w (:w params) ; minimum field width764 add-sign (or (:at params) (neg? arg))765 [rounded-mantissa scaled-exp expanded] (round-str mantissa exp d nil)766 ^String fixed-repr (get-fixed rounded-mantissa (if expanded (inc scaled-exp) scaled-exp) d)767 full-repr (str (apply str (repeat (- n (.indexOf fixed-repr (int \.))) \0)) fixed-repr)768 full-len (+ (count full-repr) (if add-sign 1 0))]769 (print (str770 (if (and (:colon params) add-sign) (if (neg? arg) \- \+))771 (apply str (repeat (- w full-len) (:padchar params)))772 (if (and (not (:colon params)) add-sign) (if (neg? arg) \- \+))773 full-repr))774 navigator))776 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;777 ;;; Support for the '~[...~]' conditional construct in its778 ;;; different flavors779 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;781 ;; ~[...~] without any modifiers chooses one of the clauses based on the param or782 ;; next argument783 ;; TODO check arg is positive int784 (defn- choice-conditional [params arg-navigator offsets]785 (let [arg (:selector params)786 [arg navigator] (if arg [arg arg-navigator] (next-arg arg-navigator))787 clauses (:clauses params)788 clause (if (or (neg? arg) (>= arg (count clauses)))789 (first (:else params))790 (nth clauses arg))]791 (if clause792 (execute-sub-format clause navigator (:base-args params))793 navigator)))795 ;; ~:[...~] with the colon reads the next argument treating it as a truth value796 (defn- boolean-conditional [params arg-navigator offsets]797 (let [[arg navigator] (next-arg arg-navigator)798 clauses (:clauses params)799 clause (if arg800 (second clauses)801 (first clauses))]802 (if clause803 (execute-sub-format clause navigator (:base-args params))804 navigator)))806 ;; ~@[...~] with the at sign executes the conditional if the next arg is not807 ;; nil/false without consuming the arg808 (defn- check-arg-conditional [params arg-navigator offsets]809 (let [[arg navigator] (next-arg arg-navigator)810 clauses (:clauses params)811 clause (if arg (first clauses))]812 (if arg813 (if clause814 (execute-sub-format clause arg-navigator (:base-args params))815 arg-navigator)816 navigator)))819 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;820 ;;; Support for the '~{...~}' iteration construct in its821 ;;; different flavors822 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;825 ;; ~{...~} without any modifiers uses the next argument as an argument list that826 ;; is consumed by all the iterations827 (defn- iterate-sublist [params navigator offsets]828 (let [max-count (:max-iterations params)829 param-clause (first (:clauses params))830 [clause navigator] (if (empty? param-clause)831 (get-format-arg navigator)832 [param-clause navigator])833 [arg-list navigator] (next-arg navigator)834 args (init-navigator arg-list)]835 (loop [count 0836 args args837 last-pos (num -1)]838 (if (and (not max-count) (= (:pos args) last-pos) (> count 1))839 ;; TODO get the offset in here and call format exception840 (throw (RuntimeException. "%{ construct not consuming any arguments: Infinite loop!")))841 (if (or (and (empty? (:rest args))842 (or (not (:colon (:right-params params))) (> count 0)))843 (and max-count (>= count max-count)))844 navigator845 (let [iter-result (execute-sub-format clause args (:base-args params))]846 (if (= :up-arrow (first iter-result))847 navigator848 (recur (inc count) iter-result (:pos args))))))))850 ;; ~:{...~} with the colon treats the next argument as a list of sublists. Each of the851 ;; sublists is used as the arglist for a single iteration.852 (defn- iterate-list-of-sublists [params navigator offsets]853 (let [max-count (:max-iterations params)854 param-clause (first (:clauses params))855 [clause navigator] (if (empty? param-clause)856 (get-format-arg navigator)857 [param-clause navigator])858 [arg-list navigator] (next-arg navigator)]859 (loop [count 0860 arg-list arg-list]861 (if (or (and (empty? arg-list)862 (or (not (:colon (:right-params params))) (> count 0)))863 (and max-count (>= count max-count)))864 navigator865 (let [iter-result (execute-sub-format866 clause867 (init-navigator (first arg-list))868 (init-navigator (next arg-list)))]869 (if (= :colon-up-arrow (first iter-result))870 navigator871 (recur (inc count) (next arg-list))))))))873 ;; ~@{...~} with the at sign uses the main argument list as the arguments to the iterations874 ;; is consumed by all the iterations875 (defn- iterate-main-list [params navigator offsets]876 (let [max-count (:max-iterations params)877 param-clause (first (:clauses params))878 [clause navigator] (if (empty? param-clause)879 (get-format-arg navigator)880 [param-clause navigator])]881 (loop [count 0882 navigator navigator883 last-pos (num -1)]884 (if (and (not max-count) (= (:pos navigator) last-pos) (> count 1))885 ;; TODO get the offset in here and call format exception886 (throw (RuntimeException. "%@{ construct not consuming any arguments: Infinite loop!")))887 (if (or (and (empty? (:rest navigator))888 (or (not (:colon (:right-params params))) (> count 0)))889 (and max-count (>= count max-count)))890 navigator891 (let [iter-result (execute-sub-format clause navigator (:base-args params))]892 (if (= :up-arrow (first iter-result))893 (second iter-result)894 (recur895 (inc count) iter-result (:pos navigator))))))))897 ;; ~@:{...~} with both colon and at sign uses the main argument list as a set of sublists, one898 ;; of which is consumed with each iteration899 (defn- iterate-main-sublists [params navigator offsets]900 (let [max-count (:max-iterations params)901 param-clause (first (:clauses params))902 [clause navigator] (if (empty? param-clause)903 (get-format-arg navigator)904 [param-clause navigator])905 ]906 (loop [count 0907 navigator navigator]908 (if (or (and (empty? (:rest navigator))909 (or (not (:colon (:right-params params))) (> count 0)))910 (and max-count (>= count max-count)))911 navigator912 (let [[sublist navigator] (next-arg-or-nil navigator)913 iter-result (execute-sub-format clause (init-navigator sublist) navigator)]914 (if (= :colon-up-arrow (first iter-result))915 navigator916 (recur (inc count) navigator)))))))918 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;919 ;;; The '~< directive has two completely different meanings920 ;;; in the '~<...~>' form it does justification, but with921 ;;; ~<...~:>' it represents the logical block operation of the922 ;;; pretty printer.923 ;;;924 ;;; Unfortunately, the current architecture decides what function925 ;;; to call at form parsing time before the sub-clauses have been926 ;;; folded, so it is left to run-time to make the decision.927 ;;;928 ;;; TODO: make it possible to make these decisions at compile-time.929 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;931 (declare format-logical-block)932 (declare justify-clauses)934 (defn- logical-block-or-justify [params navigator offsets]935 (if (:colon (:right-params params))936 (format-logical-block params navigator offsets)937 (justify-clauses params navigator offsets)))939 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;940 ;;; Support for the '~<...~>' justification directive941 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;943 (defn- render-clauses [clauses navigator base-navigator]944 (loop [clauses clauses945 acc []946 navigator navigator]947 (if (empty? clauses)948 [acc navigator]949 (let [clause (first clauses)950 [iter-result result-str] (binding [*out* (java.io.StringWriter.)]951 [(execute-sub-format clause navigator base-navigator)952 (.toString *out*)])]953 (if (= :up-arrow (first iter-result))954 [acc (second iter-result)]955 (recur (next clauses) (conj acc result-str) iter-result))))))957 ;; TODO support for ~:; constructions958 (defn- justify-clauses [params navigator offsets]959 (let [[[eol-str] new-navigator] (when-let [else (:else params)]960 (render-clauses else navigator (:base-args params)))961 navigator (or new-navigator navigator)962 [else-params new-navigator] (when-let [p (:else-params params)]963 (realize-parameter-list p navigator))964 navigator (or new-navigator navigator)965 min-remaining (or (first (:min-remaining else-params)) 0)966 max-columns (or (first (:max-columns else-params))967 (get-max-column *out*))968 clauses (:clauses params)969 [strs navigator] (render-clauses clauses navigator (:base-args params))970 slots (max 1971 (+ (dec (count strs)) (if (:colon params) 1 0) (if (:at params) 1 0)))972 chars (reduce + (map count strs))973 mincol (:mincol params)974 minpad (:minpad params)975 colinc (:colinc params)976 minout (+ chars (* slots minpad))977 result-columns (if (<= minout mincol)978 mincol979 (+ mincol (* colinc980 (+ 1 (quot (- minout mincol 1) colinc)))))981 total-pad (- result-columns chars)982 pad (max minpad (quot total-pad slots))983 extra-pad (- total-pad (* pad slots))984 pad-str (apply str (repeat pad (:padchar params)))]985 (if (and eol-str (> (+ (get-column (:base @@*out*)) min-remaining result-columns)986 max-columns))987 (print eol-str))988 (loop [slots slots989 extra-pad extra-pad990 strs strs991 pad-only (or (:colon params)992 (and (= (count strs) 1) (not (:at params))))]993 (if (seq strs)994 (do995 (print (str (if (not pad-only) (first strs))996 (if (or pad-only (next strs) (:at params)) pad-str)997 (if (pos? extra-pad) (:padchar params))))998 (recur999 (dec slots)1000 (dec extra-pad)1001 (if pad-only strs (next strs))1002 false))))1003 navigator))1005 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;1006 ;;; Support for case modification with ~(...~).1007 ;;; We do this by wrapping the underlying writer with1008 ;;; a special writer to do the appropriate modification. This1009 ;;; allows us to support arbitrary-sized output and sources1010 ;;; that may block.1011 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;1013 (defn- downcase-writer1014 "Returns a proxy that wraps writer, converting all characters to lower case"1015 [^java.io.Writer writer]1016 (proxy [java.io.Writer] []1017 (close [] (.close writer))1018 (flush [] (.flush writer))1019 (write ([^chars cbuf ^Integer off ^Integer len]1020 (.write writer cbuf off len))1021 ([x]1022 (condp = (class x)1023 String1024 (let [s ^String x]1025 (.write writer (.toLowerCase s)))1027 Integer1028 (let [c ^Character x]1029 (.write writer (int (Character/toLowerCase (char c))))))))))1031 (defn- upcase-writer1032 "Returns a proxy that wraps writer, converting all characters to upper case"1033 [^java.io.Writer writer]1034 (proxy [java.io.Writer] []1035 (close [] (.close writer))1036 (flush [] (.flush writer))1037 (write ([^chars cbuf ^Integer off ^Integer len]1038 (.write writer cbuf off len))1039 ([x]1040 (condp = (class x)1041 String1042 (let [s ^String x]1043 (.write writer (.toUpperCase s)))1045 Integer1046 (let [c ^Character x]1047 (.write writer (int (Character/toUpperCase (char c))))))))))1049 (defn- capitalize-string1050 "Capitalizes the words in a string. If first? is false, don't capitalize the1051 first character of the string even if it's a letter."1052 [s first?]1053 (let [^Character f (first s)1054 s (if (and first? f (Character/isLetter f))1055 (str (Character/toUpperCase f) (subs s 1))1056 s)]1057 (apply str1058 (first1059 (consume1060 (fn [s]1061 (if (empty? s)1062 [nil nil]1063 (let [m (re-matcher #"\W\w" s)1064 match (re-find m)1065 offset (and match (inc (.start m)))]1066 (if offset1067 [(str (subs s 0 offset)1068 (Character/toUpperCase ^Character (nth s offset)))1069 (subs s (inc offset))]1070 [s nil]))))1071 s)))))1073 (defn- capitalize-word-writer1074 "Returns a proxy that wraps writer, captializing all words"1075 [^java.io.Writer writer]1076 (let [last-was-whitespace? (ref true)]1077 (proxy [java.io.Writer] []1078 (close [] (.close writer))1079 (flush [] (.flush writer))1080 (write1081 ([^chars cbuf ^Integer off ^Integer len]1082 (.write writer cbuf off len))1083 ([x]1084 (condp = (class x)1085 String1086 (let [s ^String x]1087 (.write writer1088 ^String (capitalize-string (.toLowerCase s) @last-was-whitespace?))1089 (dosync1090 (ref-set last-was-whitespace?1091 (Character/isWhitespace1092 ^Character (nth s (dec (count s)))))))1094 Integer1095 (let [c (char x)]1096 (let [mod-c (if @last-was-whitespace? (Character/toUpperCase ^Character (char x)) c)]1097 (.write writer (int mod-c))1098 (dosync (ref-set last-was-whitespace? (Character/isWhitespace ^Character (char x))))))))))))1100 (defn- init-cap-writer1101 "Returns a proxy that wraps writer, capitalizing the first word"1102 [^java.io.Writer writer]1103 (let [capped (ref false)]1104 (proxy [java.io.Writer] []1105 (close [] (.close writer))1106 (flush [] (.flush writer))1107 (write ([^chars cbuf ^Integer off ^Integer len]1108 (.write writer cbuf off len))1109 ([x]1110 (condp = (class x)1111 String1112 (let [s (.toLowerCase ^String x)]1113 (if (not @capped)1114 (let [m (re-matcher #"\S" s)1115 match (re-find m)1116 offset (and match (.start m))]1117 (if offset1118 (do (.write writer1119 (str (subs s 0 offset)1120 (Character/toUpperCase ^Character (nth s offset))1121 (.toLowerCase ^String (subs s (inc offset)))))1122 (dosync (ref-set capped true)))1123 (.write writer s)))1124 (.write writer (.toLowerCase s))))1126 Integer1127 (let [c ^Character (char x)]1128 (if (and (not @capped) (Character/isLetter c))1129 (do1130 (dosync (ref-set capped true))1131 (.write writer (int (Character/toUpperCase c))))1132 (.write writer (int (Character/toLowerCase c)))))))))))1134 (defn- modify-case [make-writer params navigator offsets]1135 (let [clause (first (:clauses params))]1136 (binding [*out* (make-writer *out*)]1137 (execute-sub-format clause navigator (:base-args params)))))1139 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;1140 ;;; If necessary, wrap the writer in a PrettyWriter object1141 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;1143 (defn get-pretty-writer [writer]1144 (if (pretty-writer? writer)1145 writer1146 (pretty-writer writer *print-right-margin* *print-miser-width*)))1148 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;1149 ;;; Support for column-aware operations ~&, ~T1150 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;1152 ;; TODO: make an automatic newline for non-ColumnWriters1153 (defn fresh-line1154 "Make a newline if the Writer is not already at the beginning of the line.1155 N.B. Only works on ColumnWriters right now."1156 []1157 (if (not (= 0 (get-column (:base @@*out*))))1158 (prn)))1160 (defn- absolute-tabulation [params navigator offsets]1161 (let [colnum (:colnum params)1162 colinc (:colinc params)1163 current (get-column (:base @@*out*))1164 space-count (cond1165 (< current colnum) (- colnum current)1166 (= colinc 0) 01167 :else (- colinc (rem (- current colnum) colinc)))]1168 (print (apply str (repeat space-count \space))))1169 navigator)1171 (defn- relative-tabulation [params navigator offsets]1172 (let [colrel (:colnum params)1173 colinc (:colinc params)1174 start-col (+ colrel (get-column (:base @@*out*)))1175 offset (if (pos? colinc) (rem start-col colinc) 0)1176 space-count (+ colrel (if (= 0 offset) 0 (- colinc offset)))]1177 (print (apply str (repeat space-count \space))))1178 navigator)1181 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;1182 ;;; Support for accessing the pretty printer from a format1183 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;1185 ;; TODO: support ~@; per-line-prefix separator1186 ;; TODO: get the whole format wrapped so we can start the lb at any column1187 (defn- format-logical-block [params navigator offsets]1188 (let [clauses (:clauses params)1189 clause-count (count clauses)1190 prefix (cond1191 (> clause-count 1) (:string (:params (first (first clauses))))1192 (:colon params) "(")1193 body (nth clauses (if (> clause-count 1) 1 0))1194 suffix (cond1195 (> clause-count 2) (:string (:params (first (nth clauses 2))))1196 (:colon params) ")")1197 [arg navigator] (next-arg navigator)]1198 (pprint-logical-block :prefix prefix :suffix suffix1199 (execute-sub-format1200 body1201 (init-navigator arg)1202 (:base-args params)))1203 navigator))1205 (defn- set-indent [params navigator offsets]1206 (let [relative-to (if (:colon params) :current :block)]1207 (pprint-indent relative-to (:n params))1208 navigator))1210 ;;; TODO: support ~:T section options for ~T1212 (defn- conditional-newline [params navigator offsets]1213 (let [kind (if (:colon params)1214 (if (:at params) :mandatory :fill)1215 (if (:at params) :miser :linear))]1216 (pprint-newline kind)1217 navigator))1219 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;1220 ;;; The table of directives we support, each with its params,1221 ;;; properties, and the compilation function1222 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;1224 ;; We start with a couple of helpers1225 (defn- process-directive-table-element [ [ char params flags bracket-info & generator-fn ] ]1226 [char,1227 {:directive char,1228 :params `(array-map ~@params),1229 :flags flags,1230 :bracket-info bracket-info,1231 :generator-fn (concat '(fn [ params offset]) generator-fn) }])1233 (defmacro ^{:private true}1234 defdirectives1235 [ & directives ]1236 `(def ^{:private true}1237 directive-table (hash-map ~@(mapcat process-directive-table-element directives))))1239 (defdirectives1240 (\A1241 [ :mincol [0 Integer] :colinc [1 Integer] :minpad [0 Integer] :padchar [\space Character] ]1242 #{ :at :colon :both} {}1243 #(format-ascii print-str %1 %2 %3))1245 (\S1246 [ :mincol [0 Integer] :colinc [1 Integer] :minpad [0 Integer] :padchar [\space Character] ]1247 #{ :at :colon :both} {}1248 #(format-ascii pr-str %1 %2 %3))1250 (\D1251 [ :mincol [0 Integer] :padchar [\space Character] :commachar [\, Character]1252 :commainterval [ 3 Integer]]1253 #{ :at :colon :both } {}1254 #(format-integer 10 %1 %2 %3))1256 (\B1257 [ :mincol [0 Integer] :padchar [\space Character] :commachar [\, Character]1258 :commainterval [ 3 Integer]]1259 #{ :at :colon :both } {}1260 #(format-integer 2 %1 %2 %3))1262 (\O1263 [ :mincol [0 Integer] :padchar [\space Character] :commachar [\, Character]1264 :commainterval [ 3 Integer]]1265 #{ :at :colon :both } {}1266 #(format-integer 8 %1 %2 %3))1268 (\X1269 [ :mincol [0 Integer] :padchar [\space Character] :commachar [\, Character]1270 :commainterval [ 3 Integer]]1271 #{ :at :colon :both } {}1272 #(format-integer 16 %1 %2 %3))1274 (\R1275 [:base [nil Integer] :mincol [0 Integer] :padchar [\space Character] :commachar [\, Character]1276 :commainterval [ 3 Integer]]1277 #{ :at :colon :both } {}1278 (do1279 (cond ; ~R is overloaded with bizareness1280 (first (:base params)) #(format-integer (:base %1) %1 %2 %3)1281 (and (:at params) (:colon params)) #(format-old-roman %1 %2 %3)1282 (:at params) #(format-new-roman %1 %2 %3)1283 (:colon params) #(format-ordinal-english %1 %2 %3)1284 true #(format-cardinal-english %1 %2 %3))))1286 (\P1287 [ ]1288 #{ :at :colon :both } {}1289 (fn [params navigator offsets]1290 (let [navigator (if (:colon params) (relative-reposition navigator -1) navigator)1291 strs (if (:at params) ["y" "ies"] ["" "s"])1292 [arg navigator] (next-arg navigator)]1293 (print (if (= arg 1) (first strs) (second strs)))1294 navigator)))1296 (\C1297 [:char-format [nil Character]]1298 #{ :at :colon :both } {}1299 (cond1300 (:colon params) pretty-character1301 (:at params) readable-character1302 :else plain-character))1304 (\F1305 [ :w [nil Integer] :d [nil Integer] :k [0 Integer] :overflowchar [nil Character]1306 :padchar [\space Character] ]1307 #{ :at } {}1308 fixed-float)1310 (\E1311 [ :w [nil Integer] :d [nil Integer] :e [nil Integer] :k [1 Integer]1312 :overflowchar [nil Character] :padchar [\space Character]1313 :exponentchar [nil Character] ]1314 #{ :at } {}1315 exponential-float)1317 (\G1318 [ :w [nil Integer] :d [nil Integer] :e [nil Integer] :k [1 Integer]1319 :overflowchar [nil Character] :padchar [\space Character]1320 :exponentchar [nil Character] ]1321 #{ :at } {}1322 general-float)1324 (\$1325 [ :d [2 Integer] :n [1 Integer] :w [0 Integer] :padchar [\space Character]]1326 #{ :at :colon :both} {}1327 dollar-float)1329 (\%1330 [ :count [1 Integer] ]1331 #{ } {}1332 (fn [params arg-navigator offsets]1333 (dotimes [i (:count params)]1334 (prn))1335 arg-navigator))1337 (\&1338 [ :count [1 Integer] ]1339 #{ :pretty } {}1340 (fn [params arg-navigator offsets]1341 (let [cnt (:count params)]1342 (if (pos? cnt) (fresh-line))1343 (dotimes [i (dec cnt)]1344 (prn)))1345 arg-navigator))1347 (\|1348 [ :count [1 Integer] ]1349 #{ } {}1350 (fn [params arg-navigator offsets]1351 (dotimes [i (:count params)]1352 (print \formfeed))1353 arg-navigator))1355 (\~1356 [ :n [1 Integer] ]1357 #{ } {}1358 (fn [params arg-navigator offsets]1359 (let [n (:n params)]1360 (print (apply str (repeat n \~)))1361 arg-navigator)))1363 (\newline ;; Whitespace supression is handled in the compilation loop1364 [ ]1365 #{:colon :at} {}1366 (fn [params arg-navigator offsets]1367 (if (:at params)1368 (prn))1369 arg-navigator))1371 (\T1372 [ :colnum [1 Integer] :colinc [1 Integer] ]1373 #{ :at :pretty } {}1374 (if (:at params)1375 #(relative-tabulation %1 %2 %3)1376 #(absolute-tabulation %1 %2 %3)))1378 (\*1379 [ :n [1 Integer] ]1380 #{ :colon :at } {}1381 (fn [params navigator offsets]1382 (let [n (:n params)]1383 (if (:at params)1384 (absolute-reposition navigator n)1385 (relative-reposition navigator (if (:colon params) (- n) n)))1386 )))1388 (\?1389 [ ]1390 #{ :at } {}1391 (if (:at params)1392 (fn [params navigator offsets] ; args from main arg list1393 (let [[subformat navigator] (get-format-arg navigator)]1394 (execute-sub-format subformat navigator (:base-args params))))1395 (fn [params navigator offsets] ; args from sub-list1396 (let [[subformat navigator] (get-format-arg navigator)1397 [subargs navigator] (next-arg navigator)1398 sub-navigator (init-navigator subargs)]1399 (execute-sub-format subformat sub-navigator (:base-args params))1400 navigator))))1403 (\(1404 [ ]1405 #{ :colon :at :both} { :right \), :allows-separator nil, :else nil }1406 (let [mod-case-writer (cond1407 (and (:at params) (:colon params))1408 upcase-writer1410 (:colon params)1411 capitalize-word-writer1413 (:at params)1414 init-cap-writer1416 :else1417 downcase-writer)]1418 #(modify-case mod-case-writer %1 %2 %3)))1420 (\) [] #{} {} nil)1422 (\[1423 [ :selector [nil Integer] ]1424 #{ :colon :at } { :right \], :allows-separator true, :else :last }1425 (cond1426 (:colon params)1427 boolean-conditional1429 (:at params)1430 check-arg-conditional1432 true1433 choice-conditional))1435 (\; [:min-remaining [nil Integer] :max-columns [nil Integer]]1436 #{ :colon } { :separator true } nil)1438 (\] [] #{} {} nil)1440 (\{1441 [ :max-iterations [nil Integer] ]1442 #{ :colon :at :both} { :right \}, :allows-separator false }1443 (cond1444 (and (:at params) (:colon params))1445 iterate-main-sublists1447 (:colon params)1448 iterate-list-of-sublists1450 (:at params)1451 iterate-main-list1453 true1454 iterate-sublist))1457 (\} [] #{:colon} {} nil)1459 (\<1460 [:mincol [0 Integer] :colinc [1 Integer] :minpad [0 Integer] :padchar [\space Character]]1461 #{:colon :at :both :pretty} { :right \>, :allows-separator true, :else :first }1462 logical-block-or-justify)1464 (\> [] #{:colon} {} nil)1466 ;; TODO: detect errors in cases where colon not allowed1467 (\^ [:arg1 [nil Integer] :arg2 [nil Integer] :arg3 [nil Integer]]1468 #{:colon} {}1469 (fn [params navigator offsets]1470 (let [arg1 (:arg1 params)1471 arg2 (:arg2 params)1472 arg3 (:arg3 params)1473 exit (if (:colon params) :colon-up-arrow :up-arrow)]1474 (cond1475 (and arg1 arg2 arg3)1476 (if (<= arg1 arg2 arg3) [exit navigator] navigator)1478 (and arg1 arg2)1479 (if (= arg1 arg2) [exit navigator] navigator)1481 arg11482 (if (= arg1 0) [exit navigator] navigator)1484 true ; TODO: handle looking up the arglist stack for info1485 (if (if (:colon params)1486 (empty? (:rest (:base-args params)))1487 (empty? (:rest navigator)))1488 [exit navigator] navigator)))))1490 (\W1491 []1492 #{:at :colon :both} {}1493 (if (or (:at params) (:colon params))1494 (let [bindings (concat1495 (if (:at params) [:level nil :length nil] [])1496 (if (:colon params) [:pretty true] []))]1497 (fn [params navigator offsets]1498 (let [[arg navigator] (next-arg navigator)]1499 (if (apply write arg bindings)1500 [:up-arrow navigator]1501 navigator))))1502 (fn [params navigator offsets]1503 (let [[arg navigator] (next-arg navigator)]1504 (if (write-out arg)1505 [:up-arrow navigator]1506 navigator)))))1508 (\_1509 []1510 #{:at :colon :both} {}1511 conditional-newline)1513 (\I1514 [:n [0 Integer]]1515 #{:colon} {}1516 set-indent)1517 )1519 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;1520 ;;; Code to manage the parameters and flags associated with each1521 ;;; directive in the format string.1522 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;1524 (def ^{:private true}1525 param-pattern #"^([vV]|#|('.)|([+-]?\d+)|(?=,))")1526 (def ^{:private true}1527 special-params #{ :parameter-from-args :remaining-arg-count })1529 (defn- extract-param [[s offset saw-comma]]1530 (let [m (re-matcher param-pattern s)1531 param (re-find m)]1532 (if param1533 (let [token-str (first (re-groups m))1534 remainder (subs s (.end m))1535 new-offset (+ offset (.end m))]1536 (if (not (= \, (nth remainder 0)))1537 [ [token-str offset] [remainder new-offset false]]1538 [ [token-str offset] [(subs remainder 1) (inc new-offset) true]]))1539 (if saw-comma1540 (format-error "Badly formed parameters in format directive" offset)1541 [ nil [s offset]]))))1544 (defn- extract-params [s offset]1545 (consume extract-param [s offset false]))1547 (defn- translate-param1548 "Translate the string representation of a param to the internalized1549 representation"1550 [[^String p offset]]1551 [(cond1552 (= (.length p) 0) nil1553 (and (= (.length p) 1) (contains? #{\v \V} (nth p 0))) :parameter-from-args1554 (and (= (.length p) 1) (= \# (nth p 0))) :remaining-arg-count1555 (and (= (.length p) 2) (= \' (nth p 0))) (nth p 1)1556 true (new Integer p))1557 offset])1559 (def ^{:private true}1560 flag-defs { \: :colon, \@ :at })1562 (defn- extract-flags [s offset]1563 (consume1564 (fn [[s offset flags]]1565 (if (empty? s)1566 [nil [s offset flags]]1567 (let [flag (get flag-defs (first s))]1568 (if flag1569 (if (contains? flags flag)1570 (format-error1571 (str "Flag \"" (first s) "\" appears more than once in a directive")1572 offset)1573 [true [(subs s 1) (inc offset) (assoc flags flag [true offset])]])1574 [nil [s offset flags]]))))1575 [s offset {}]))1577 (defn- check-flags [def flags]1578 (let [allowed (:flags def)]1579 (if (and (not (:at allowed)) (:at flags))1580 (format-error (str "\"@\" is an illegal flag for format directive \"" (:directive def) "\"")1581 (nth (:at flags) 1)))1582 (if (and (not (:colon allowed)) (:colon flags))1583 (format-error (str "\":\" is an illegal flag for format directive \"" (:directive def) "\"")1584 (nth (:colon flags) 1)))1585 (if (and (not (:both allowed)) (:at flags) (:colon flags))1586 (format-error (str "Cannot combine \"@\" and \":\" flags for format directive \""1587 (:directive def) "\"")1588 (min (nth (:colon flags) 1) (nth (:at flags) 1))))))1590 (defn- map-params1591 "Takes a directive definition and the list of actual parameters and1592 a map of flags and returns a map of the parameters and flags with defaults1593 filled in. We check to make sure that there are the right types and number1594 of parameters as well."1595 [def params flags offset]1596 (check-flags def flags)1597 (if (> (count params) (count (:params def)))1598 (format-error1599 (cl-format1600 nil1601 "Too many parameters for directive \"~C\": ~D~:* ~[were~;was~:;were~] specified but only ~D~:* ~[are~;is~:;are~] allowed"1602 (:directive def) (count params) (count (:params def)))1603 (second (first params))))1604 (doall1605 (map #(let [val (first %1)]1606 (if (not (or (nil? val) (contains? special-params val)1607 (instance? (second (second %2)) val)))1608 (format-error (str "Parameter " (name (first %2))1609 " has bad type in directive \"" (:directive def) "\": "1610 (class val))1611 (second %1))) )1612 params (:params def)))1614 (merge ; create the result map1615 (into (array-map) ; start with the default values, make sure the order is right1616 (reverse (for [[name [default]] (:params def)] [name [default offset]])))1617 (reduce #(apply assoc %1 %2) {} (filter #(first (nth % 1)) (zipmap (keys (:params def)) params))) ; add the specified parameters, filtering out nils1618 flags)) ; and finally add the flags1620 (defn- compile-directive [s offset]1621 (let [[raw-params [rest offset]] (extract-params s offset)1622 [_ [rest offset flags]] (extract-flags rest offset)1623 directive (first rest)1624 def (get directive-table (Character/toUpperCase ^Character directive))1625 params (if def (map-params def (map translate-param raw-params) flags offset))]1626 (if (not directive)1627 (format-error "Format string ended in the middle of a directive" offset))1628 (if (not def)1629 (format-error (str "Directive \"" directive "\" is undefined") offset))1630 [(struct compiled-directive ((:generator-fn def) params offset) def params offset)1631 (let [remainder (subs rest 1)1632 offset (inc offset)1633 trim? (and (= \newline (:directive def))1634 (not (:colon params)))1635 trim-count (if trim? (prefix-count remainder [\space \tab]) 0)1636 remainder (subs remainder trim-count)1637 offset (+ offset trim-count)]1638 [remainder offset])]))1640 (defn- compile-raw-string [s offset]1641 (struct compiled-directive (fn [_ a _] (print s) a) nil { :string s } offset))1643 (defn- right-bracket [this] (:right (:bracket-info (:def this))))1644 (defn- separator? [this] (:separator (:bracket-info (:def this))))1645 (defn- else-separator? [this]1646 (and (:separator (:bracket-info (:def this)))1647 (:colon (:params this))))1650 (declare collect-clauses)1652 (defn- process-bracket [this remainder]1653 (let [[subex remainder] (collect-clauses (:bracket-info (:def this))1654 (:offset this) remainder)]1655 [(struct compiled-directive1656 (:func this) (:def this)1657 (merge (:params this) (tuple-map subex (:offset this)))1658 (:offset this))1659 remainder]))1661 (defn- process-clause [bracket-info offset remainder]1662 (consume1663 (fn [remainder]1664 (if (empty? remainder)1665 (format-error "No closing bracket found." offset)1666 (let [this (first remainder)1667 remainder (next remainder)]1668 (cond1669 (right-bracket this)1670 (process-bracket this remainder)1672 (= (:right bracket-info) (:directive (:def this)))1673 [ nil [:right-bracket (:params this) nil remainder]]1675 (else-separator? this)1676 [nil [:else nil (:params this) remainder]]1678 (separator? this)1679 [nil [:separator nil nil remainder]] ;; TODO: check to make sure that there are no params on ~;1681 true1682 [this remainder]))))1683 remainder))1685 (defn- collect-clauses [bracket-info offset remainder]1686 (second1687 (consume1688 (fn [[clause-map saw-else remainder]]1689 (let [[clause [type right-params else-params remainder]]1690 (process-clause bracket-info offset remainder)]1691 (cond1692 (= type :right-bracket)1693 [nil [(merge-with concat clause-map1694 {(if saw-else :else :clauses) [clause]1695 :right-params right-params})1696 remainder]]1698 (= type :else)1699 (cond1700 (:else clause-map)1701 (format-error "Two else clauses (\"~:;\") inside bracket construction." offset)1703 (not (:else bracket-info))1704 (format-error "An else clause (\"~:;\") is in a bracket type that doesn't support it."1705 offset)1707 (and (= :first (:else bracket-info)) (seq (:clauses clause-map)))1708 (format-error1709 "The else clause (\"~:;\") is only allowed in the first position for this directive."1710 offset)1712 true ; if the ~:; is in the last position, the else clause1713 ; is next, this was a regular clause1714 (if (= :first (:else bracket-info))1715 [true [(merge-with concat clause-map { :else [clause] :else-params else-params})1716 false remainder]]1717 [true [(merge-with concat clause-map { :clauses [clause] })1718 true remainder]]))1720 (= type :separator)1721 (cond1722 saw-else1723 (format-error "A plain clause (with \"~;\") follows an else clause (\"~:;\") inside bracket construction." offset)1725 (not (:allows-separator bracket-info))1726 (format-error "A separator (\"~;\") is in a bracket type that doesn't support it."1727 offset)1729 true1730 [true [(merge-with concat clause-map { :clauses [clause] })1731 false remainder]]))))1732 [{ :clauses [] } false remainder])))1734 (defn- process-nesting1735 "Take a linearly compiled format and process the bracket directives to give it1736 the appropriate tree structure"1737 [format]1738 (first1739 (consume1740 (fn [remainder]1741 (let [this (first remainder)1742 remainder (next remainder)1743 bracket (:bracket-info (:def this))]1744 (if (:right bracket)1745 (process-bracket this remainder)1746 [this remainder])))1747 format)))1749 (defn compile-format1750 "Compiles format-str into a compiled format which can be used as an argument1751 to cl-format just like a plain format string. Use this function for improved1752 performance when you're using the same format string repeatedly"1753 [ format-str ]1754 ; (prlabel compiling format-str)1755 (binding [*format-str* format-str]1756 (process-nesting1757 (first1758 (consume1759 (fn [[^String s offset]]1760 (if (empty? s)1761 [nil s]1762 (let [tilde (.indexOf s (int \~))]1763 (cond1764 (neg? tilde) [(compile-raw-string s offset) ["" (+ offset (.length s))]]1765 (zero? tilde) (compile-directive (subs s 1) (inc offset))1766 true1767 [(compile-raw-string (subs s 0 tilde) offset) [(subs s tilde) (+ tilde offset)]]))))1768 [format-str 0])))))1770 (defn- needs-pretty1771 "determine whether a given compiled format has any directives that depend on the1772 column number or pretty printing"1773 [format]1774 (loop [format format]1775 (if (empty? format)1776 false1777 (if (or (:pretty (:flags (:def (first format))))1778 (some needs-pretty (first (:clauses (:params (first format)))))1779 (some needs-pretty (first (:else (:params (first format))))))1780 true1781 (recur (next format))))))1783 (defn execute-format1784 "Executes the format with the arguments. This should never be used directly, but is public1785 because the formatter macro uses it."1786 {:skip-wiki true}1787 ([stream format args]1788 (let [^java.io.Writer real-stream (cond1789 (not stream) (java.io.StringWriter.)1790 (true? stream) *out*1791 :else stream)1792 ^java.io.Writer wrapped-stream (if (and (needs-pretty format)1793 (not (pretty-writer? real-stream)))1794 (get-pretty-writer real-stream)1795 real-stream)]1796 (binding [*out* wrapped-stream]1797 (try1798 (execute-format format args)1799 (finally1800 (if-not (identical? real-stream wrapped-stream)1801 (.flush wrapped-stream))))1802 (if (not stream) (.toString real-stream)))))1803 ([format args]1804 (map-passing-context1805 (fn [element context]1806 (if (abort? context)1807 [nil context]1808 (let [[params args] (realize-parameter-list1809 (:params element) context)1810 [params offsets] (unzip-map params)1811 params (assoc params :base-args args)]1812 [nil (apply (:func element) [params args offsets])])))1813 args1814 format)))1817 (defmacro formatter1818 "Makes a function which can directly run format-in. The function is1819 fn [stream & args] ... and returns nil unless the stream is nil (meaning1820 output to a string) in which case it returns the resulting string.1822 format-in can be either a control string or a previously compiled format."1823 [format-in]1824 (let [cf (gensym "compiled-format")]1825 `(let [format-in# ~format-in]1826 (do (defonce ~cf (if (string? format-in#) (compile-format format-in#) format-in#))1827 (fn [stream# & args#]1828 (let [navigator# (init-navigator args#)]1829 (execute-format stream# ~cf navigator#)))))))1831 (defmacro formatter-out1832 "Makes a function which can directly run format-in. The function is1833 fn [& args] ... and returns nil. This version of the formatter macro is1834 designed to be used with *out* set to an appropriate Writer. In particular,1835 this is meant to be used as part of a pretty printer dispatch method.1837 format-in can be either a control string or a previously compiled format."1838 [format-in]1839 (let [cf (gensym "compiled-format")]1840 `(let [format-in# ~format-in]1841 (do (defonce ~cf (if (string? format-in#) (compile-format format-in#) format-in#))1842 (fn [& args#]1843 (let [navigator# (init-navigator args#)]1844 (execute-format ~cf navigator#)))))))