ludus/out/cljs/pprint.cljs
2023-11-16 13:22:15 -05:00

3326 lines
125 KiB
Clojure

; Copyright (c) Rich Hickey. All rights reserved.
; The use and distribution terms for this software are covered by the
; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
; which can be found in the file epl-v10.html at the root of this distribution.
; By using this software in any fashion, you are agreeing to be bound by
; the terms of this license.
; You must not remove this notice, or any other, from this software.
(ns cljs.pprint
(:refer-clojure :exclude [deftype print println pr prn float?])
(:require-macros
[cljs.pprint :as m :refer [with-pretty-writer getf setf deftype
pprint-logical-block print-length-loop
defdirectives formatter-out]])
(:require
[cljs.core :refer [IWriter IDeref]]
[clojure.string :as string]
[goog.string :as gstring])
(:import [goog.string StringBuffer]))
;;======================================================================
;; override print fns to use *out*
;;======================================================================
(defn- print [& more]
(-write *out* (apply print-str more)))
(defn- println [& more]
(apply print more)
(-write *out* \newline))
(defn- print-char [c]
(-write *out* (condp = c
\backspace "\\backspace"
\space "\\space"
\tab "\\tab"
\newline "\\newline"
\formfeed "\\formfeed"
\return "\\return"
\" "\\\""
\\ "\\\\"
(str "\\" c))))
(defn- ^:dynamic pr [& more]
(-write *out* (apply pr-str more)))
(defn- prn [& more]
(apply pr more)
(-write *out* \newline))
;;======================================================================
;; cljs specific utils
;;======================================================================
(defn float?
"Returns true if n is an float."
[n]
(and (number? n)
(not ^boolean (js/isNaN n))
(not (identical? n js/Infinity))
(not (== (js/parseFloat n) (js/parseInt n 10)))))
(defn char-code
"Convert char to int"
[c]
(cond
(number? c) c
(and (string? c) (== (.-length c) 1)) (.charCodeAt c 0)
:else (throw (js/Error. "Argument to char must be a character or number"))))
;;======================================================================
;; Utilities
;;======================================================================
(defn- map-passing-context [func initial-context lis]
(loop [context initial-context
lis lis
acc []]
(if (empty? lis)
[acc context]
(let [this (first lis)
remainder (next lis)
[result new-context] (apply func [this context])]
(recur new-context remainder (conj acc result))))))
(defn- consume [func initial-context]
(loop [context initial-context
acc []]
(let [[result new-context] (apply func [context])]
(if (not result)
[acc new-context]
(recur new-context (conj acc result))))))
(defn- consume-while [func initial-context]
(loop [context initial-context
acc []]
(let [[result continue new-context] (apply func [context])]
(if (not continue)
[acc context]
(recur new-context (conj acc result))))))
(defn- unzip-map [m]
"Take a map that has pairs in the value slots and produce a pair of maps,
the first having all the first elements of the pairs and the second all
the second elements of the pairs"
[(into {} (for [[k [v1 v2]] m] [k v1]))
(into {} (for [[k [v1 v2]] m] [k v2]))])
(defn- tuple-map [m v1]
"For all the values, v, in the map, replace them with [v v1]"
(into {} (for [[k v] m] [k [v v1]])))
(defn- rtrim [s c]
"Trim all instances of c from the end of sequence s"
(let [len (count s)]
(if (and (pos? len) (= (nth s (dec (count s))) c))
(loop [n (dec len)]
(cond
(neg? n) ""
(not (= (nth s n) c)) (subs s 0 (inc n))
true (recur (dec n))))
s)))
(defn- ltrim [s c]
"Trim all instances of c from the beginning of sequence s"
(let [len (count s)]
(if (and (pos? len) (= (nth s 0) c))
(loop [n 0]
(if (or (= n len) (not (= (nth s n) c)))
(subs s n)
(recur (inc n))))
s)))
(defn- prefix-count [aseq val]
"Return the number of times that val occurs at the start of sequence aseq,
if val is a seq itself, count the number of times any element of val occurs at the
beginning of aseq"
(let [test (if (coll? val) (set val) #{val})]
(loop [pos 0]
(if (or (= pos (count aseq)) (not (test (nth aseq pos))))
pos
(recur (inc pos))))))
;; Flush the pretty-print buffer without flushing the underlying stream
(defprotocol IPrettyFlush
(-ppflush [pp]))
;;======================================================================
;; column_writer.clj
;;======================================================================
(def ^:dynamic ^{:private true} *default-page-width* 72)
(defn- get-field [this sym]
(sym @@this))
(defn- set-field [this sym new-val]
(swap! @this assoc sym new-val))
(defn- get-column [this]
(get-field this :cur))
(defn- get-line [this]
(get-field this :line))
(defn- get-max-column [this]
(get-field this :max))
(defn- set-max-column [this new-max]
(set-field this :max new-max)
nil)
(defn- get-writer [this]
(get-field this :base))
;; Why is the c argument an integer?
(defn- c-write-char [this c]
(if (= c \newline)
(do
(set-field this :cur 0)
(set-field this :line (inc (get-field this :line))))
(set-field this :cur (inc (get-field this :cur))))
(-write (get-field this :base) c))
(defn- column-writer
([writer] (column-writer writer *default-page-width*))
([writer max-columns]
(let [fields (atom {:max max-columns, :cur 0, :line 0 :base writer})]
(reify
IDeref
(-deref [_] fields)
IWriter
(-flush [_]
(-flush writer))
(-write
;;-write isn't multi-arity, so need different way to do this
#_([this ^chars cbuf ^Number off ^Number len]
(let [writer (get-field this :base)]
(-write writer cbuf off len)))
[this x]
(condp = (type x)
js/String
(let [s x
nl (.lastIndexOf s \newline)]
(if (neg? nl)
(set-field this :cur (+ (get-field this :cur) (count s)))
(do
(set-field this :cur (- (count s) nl 1))
(set-field this :line (+ (get-field this :line)
(count (filter #(= % \newline) s))))))
(-write (get-field this :base) s))
js/Number
(c-write-char this x)))))))
;;======================================================================
;; pretty_writer.clj
;;======================================================================
;;======================================================================
;; Forward declarations
;;======================================================================
(declare ^{:arglists '([this])} get-miser-width)
;;======================================================================
;; The data structures used by pretty-writer
;;======================================================================
(defrecord ^{:private true} logical-block
[parent section start-col indent
done-nl intra-block-nl
prefix per-line-prefix suffix
logical-block-callback])
(defn- ancestor? [parent child]
(loop [child (:parent child)]
(cond
(nil? child) false
(identical? parent child) true
:else (recur (:parent child)))))
(defn- buffer-length [l]
(let [l (seq l)]
(if l
(- (:end-pos (last l)) (:start-pos (first l)))
0)))
;; A blob of characters (aka a string)
(deftype buffer-blob :data :trailing-white-space :start-pos :end-pos)
;; A newline
(deftype nl-t :type :logical-block :start-pos :end-pos)
(deftype start-block-t :logical-block :start-pos :end-pos)
(deftype end-block-t :logical-block :start-pos :end-pos)
(deftype indent-t :logical-block :relative-to :offset :start-pos :end-pos)
(def ^:private pp-newline (fn [] "\n"))
(declare emit-nl)
(defmulti ^{:private true} write-token #(:type-tag %2))
(defmethod write-token :start-block-t [this token]
(when-let [cb (getf :logical-block-callback)] (cb :start))
(let [lb (:logical-block token)]
(when-let [prefix (:prefix lb)]
(-write (getf :base) prefix))
(let [col (get-column (getf :base))]
(reset! (:start-col lb) col)
(reset! (:indent lb) col))))
(defmethod write-token :end-block-t [this token]
(when-let [cb (getf :logical-block-callback)] (cb :end))
(when-let [suffix (:suffix (:logical-block token))]
(-write (getf :base) suffix)))
(defmethod write-token :indent-t [this token]
(let [lb (:logical-block token)]
(reset! (:indent lb)
(+ (:offset token)
(condp = (:relative-to token)
:block @(:start-col lb)
:current (get-column (getf :base)))))))
(defmethod write-token :buffer-blob [this token]
(-write (getf :base) (:data token)))
(defmethod write-token :nl-t [this token]
(if (or (= (:type token) :mandatory)
(and (not (= (:type token) :fill))
@(:done-nl (:logical-block token))))
(emit-nl this token)
(if-let [tws (getf :trailing-white-space)]
(-write (getf :base) tws)))
(setf :trailing-white-space nil))
(defn- write-tokens [this tokens force-trailing-whitespace]
(doseq [token tokens]
(if-not (= (:type-tag token) :nl-t)
(if-let [tws (getf :trailing-white-space)]
(-write (getf :base) tws)))
(write-token this token)
(setf :trailing-white-space (:trailing-white-space token))
(let [tws (getf :trailing-white-space)]
(when (and force-trailing-whitespace tws)
(-write (getf :base) tws)
(setf :trailing-white-space nil)))))
;;======================================================================
;; emit-nl? method defs for each type of new line. This makes
;; the decision about whether to print this type of new line.
;;======================================================================
(defn- tokens-fit? [this tokens]
(let [maxcol (get-max-column (getf :base))]
(or
(nil? maxcol)
(< (+ (get-column (getf :base)) (buffer-length tokens)) maxcol))))
(defn- linear-nl? [this lb section]
(or @(:done-nl lb)
(not (tokens-fit? this section))))
(defn- miser-nl? [this lb section]
(let [miser-width (get-miser-width this)
maxcol (get-max-column (getf :base))]
(and miser-width maxcol
(>= @(:start-col lb) (- maxcol miser-width))
(linear-nl? this lb section))))
(defmulti ^{:private true} emit-nl? (fn [t _ _ _] (:type t)))
(defmethod emit-nl? :linear [newl this section _]
(let [lb (:logical-block newl)]
(linear-nl? this lb section)))
(defmethod emit-nl? :miser [newl this section _]
(let [lb (:logical-block newl)]
(miser-nl? this lb section)))
(defmethod emit-nl? :fill [newl this section subsection]
(let [lb (:logical-block newl)]
(or @(:intra-block-nl lb)
(not (tokens-fit? this subsection))
(miser-nl? this lb section))))
(defmethod emit-nl? :mandatory [_ _ _ _]
true)
;;======================================================================
;; Various support functions
;;======================================================================
(defn- get-section [buffer]
(let [nl (first buffer)
lb (:logical-block nl)
section (seq (take-while #(not (and (nl-t? %) (ancestor? (:logical-block %) lb)))
(next buffer)))]
[section (seq (drop (inc (count section)) buffer))]))
(defn- get-sub-section [buffer]
(let [nl (first buffer)
lb (:logical-block nl)
section (seq (take-while #(let [nl-lb (:logical-block %)]
(not (and (nl-t? %) (or (= nl-lb lb) (ancestor? nl-lb lb)))))
(next buffer)))]
section))
(defn- update-nl-state [lb]
(reset! (:intra-block-nl lb) true)
(reset! (:done-nl lb) true)
(loop [lb (:parent lb)]
(if lb
(do (reset! (:done-nl lb) true)
(reset! (:intra-block-nl lb) true)
(recur (:parent lb))))))
(defn- emit-nl [this nl]
(-write (getf :base) (pp-newline))
(setf :trailing-white-space nil)
(let [lb (:logical-block nl)
prefix (:per-line-prefix lb)]
(if prefix
(-write (getf :base) prefix))
(let [istr (apply str (repeat (- @(:indent lb) (count prefix)) \space))]
(-write (getf :base) istr))
(update-nl-state lb)))
(defn- split-at-newline [tokens]
(let [pre (seq (take-while #(not (nl-t? %)) tokens))]
[pre (seq (drop (count pre) tokens))]))
;; write-token-string is called when the set of tokens in the buffer
;; is long than the available space on the line
(defn- write-token-string [this tokens]
(let [[a b] (split-at-newline tokens)]
(if a (write-tokens this a false))
(if b
(let [[section remainder] (get-section b)
newl (first b)]
(let [do-nl (emit-nl? newl this section (get-sub-section b))
result (if do-nl
(do
(emit-nl this newl)
(next b))
b)
long-section (not (tokens-fit? this result))
result (if long-section
(let [rem2 (write-token-string this section)]
(if (= rem2 section)
(do ; If that didn't produce any output, it has no nls
; so we'll force it
(write-tokens this section false)
remainder)
(into [] (concat rem2 remainder))))
result)]
result)))))
(defn- write-line [this]
(loop [buffer (getf :buffer)]
(setf :buffer (into [] buffer))
(if (not (tokens-fit? this buffer))
(let [new-buffer (write-token-string this buffer)]
(if-not (identical? buffer new-buffer)
(recur new-buffer))))))
;; Add a buffer token to the buffer and see if it's time to start
;; writing
(defn- add-to-buffer [this token]
(setf :buffer (conj (getf :buffer) token))
(if (not (tokens-fit? this (getf :buffer)))
(write-line this)))
;; Write all the tokens that have been buffered
(defn- write-buffered-output [this]
(write-line this)
(if-let [buf (getf :buffer)]
(do
(write-tokens this buf true)
(setf :buffer []))))
(defn- write-white-space [this]
(when-let [tws (getf :trailing-white-space)]
(-write (getf :base) tws)
(setf :trailing-white-space nil)))
;;; If there are newlines in the string, print the lines up until the last newline,
;;; making the appropriate adjustments. Return the remainder of the string
(defn- write-initial-lines
[^Writer this ^String s]
(let [lines (string/split s "\n" -1)]
(if (= (count lines) 1)
s
(let [^String prefix (:per-line-prefix (first (getf :logical-blocks)))
^String l (first lines)]
(if (= :buffering (getf :mode))
(let [oldpos (getf :pos)
newpos (+ oldpos (count l))]
(setf :pos newpos)
(add-to-buffer this (make-buffer-blob l nil oldpos newpos))
(write-buffered-output this))
(do
(write-white-space this)
(-write (getf :base) l)))
(-write (getf :base) \newline)
(doseq [^String l (next (butlast lines))]
(-write (getf :base) l)
(-write (getf :base) (pp-newline))
(if prefix
(-write (getf :base) prefix)))
(setf :buffering :writing)
(last lines)))))
(defn- p-write-char [this c]
(if (= (getf :mode) :writing)
(do
(write-white-space this)
(-write (getf :base) c))
(if (= c \newline)
(write-initial-lines this \newline)
(let [oldpos (getf :pos)
newpos (inc oldpos)]
(setf :pos newpos)
(add-to-buffer this (make-buffer-blob (char c) nil oldpos newpos))))))
;;======================================================================
;; Initialize the pretty-writer instance
;;======================================================================
(defn- pretty-writer [writer max-columns miser-width]
(let [lb (logical-block. nil nil (atom 0) (atom 0) (atom false) (atom false)
nil nil nil nil)
; NOTE: may want to just `specify!` #js { ... fields ... } with the protocols
fields (atom {:pretty-writer true
:base (column-writer writer max-columns)
:logical-blocks lb
:sections nil
:mode :writing
:buffer []
:buffer-block lb
:buffer-level 1
:miser-width miser-width
:trailing-white-space nil
:pos 0})]
(reify
IDeref
(-deref [_] fields)
IWriter
(-write [this x]
(condp = (type x)
js/String
(let [s0 (write-initial-lines this x)
s (string/replace-first s0 #"\s+$" "")
white-space (subs s0 (count s))
mode (getf :mode)]
(if (= mode :writing)
(do
(write-white-space this)
(-write (getf :base) s)
(setf :trailing-white-space white-space))
(let [oldpos (getf :pos)
newpos (+ oldpos (count s0))]
(setf :pos newpos)
(add-to-buffer this (make-buffer-blob s white-space oldpos newpos)))))
js/Number
(p-write-char this x)))
(-flush [this]
(-ppflush this)
(-flush (getf :base)))
IPrettyFlush
(-ppflush [this]
(if (= (getf :mode) :buffering)
(do
(write-tokens this (getf :buffer) true)
(setf :buffer []))
(write-white-space this)))
)))
;;======================================================================
;; Methods for pretty-writer
;;======================================================================
(defn- start-block
[this prefix per-line-prefix suffix]
(let [lb (logical-block. (getf :logical-blocks) nil (atom 0) (atom 0)
(atom false) (atom false)
prefix per-line-prefix suffix nil)]
(setf :logical-blocks lb)
(if (= (getf :mode) :writing)
(do
(write-white-space this)
(when-let [cb (getf :logical-block-callback)] (cb :start))
(if prefix
(-write (getf :base) prefix))
(let [col (get-column (getf :base))]
(reset! (:start-col lb) col)
(reset! (:indent lb) col)))
(let [oldpos (getf :pos)
newpos (+ oldpos (if prefix (count prefix) 0))]
(setf :pos newpos)
(add-to-buffer this (make-start-block-t lb oldpos newpos))))))
(defn- end-block [this]
(let [lb (getf :logical-blocks)
suffix (:suffix lb)]
(if (= (getf :mode) :writing)
(do
(write-white-space this)
(if suffix
(-write (getf :base) suffix))
(when-let [cb (getf :logical-block-callback)] (cb :end)))
(let [oldpos (getf :pos)
newpos (+ oldpos (if suffix (count suffix) 0))]
(setf :pos newpos)
(add-to-buffer this (make-end-block-t lb oldpos newpos))))
(setf :logical-blocks (:parent lb))))
(defn- nl [this type]
(setf :mode :buffering)
(let [pos (getf :pos)]
(add-to-buffer this (make-nl-t type (getf :logical-blocks) pos pos))))
(defn- indent [this relative-to offset]
(let [lb (getf :logical-blocks)]
(if (= (getf :mode) :writing)
(do
(write-white-space this)
(reset! (:indent lb)
(+ offset (condp = relative-to
:block @(:start-col lb)
:current (get-column (getf :base))))))
(let [pos (getf :pos)]
(add-to-buffer this (make-indent-t lb relative-to offset pos pos))))))
(defn- get-miser-width [this]
(getf :miser-width))
;;======================================================================
;; pprint_base.clj
;;======================================================================
;;======================================================================
;; Variables that control the pretty printer
;;======================================================================
;; *print-length*, *print-level*, *print-namespace-maps* and *print-dup* are defined in cljs.core
(def ^:dynamic
^{:doc "Bind to true if you want write to use pretty printing"}
*print-pretty* true)
(defonce ^:dynamic
^{:doc "The pretty print dispatch function. Use with-pprint-dispatch or
set-pprint-dispatch to modify."
:added "1.2"}
*print-pprint-dispatch* nil)
(def ^:dynamic
^{:doc "Pretty printing will try to avoid anything going beyond this column.
Set it to nil to have pprint let the line be arbitrarily long. This will ignore all
non-mandatory newlines.",
:added "1.2"}
*print-right-margin* 72)
(def ^:dynamic
^{:doc "The column at which to enter miser style. Depending on the dispatch table,
miser style add newlines in more places to try to keep lines short allowing for further
levels of nesting.",
:added "1.2"}
*print-miser-width* 40)
;;; TODO implement output limiting
(def ^:dynamic
^{:private true,
:doc "Maximum number of lines to print in a pretty print instance (N.B. This is not yet used)"}
*print-lines* nil)
;;; TODO: implement circle and shared
(def ^:dynamic
^{:private true,
:doc "Mark circular structures (N.B. This is not yet used)"}
*print-circle* nil)
;;; TODO: should we just use *print-dup* here?
(def ^:dynamic
^{:private true,
:doc "Mark repeated structures rather than repeat them (N.B. This is not yet used)"}
*print-shared* nil)
(def ^:dynamic
^{:doc "Don't print namespaces with symbols. This is particularly useful when
pretty printing the results of macro expansions"
:added "1.2"}
*print-suppress-namespaces* nil)
;;; TODO: support print-base and print-radix in cl-format
;;; TODO: support print-base and print-radix in rationals
(def ^:dynamic
^{:doc "Print a radix specifier in front of integers and rationals. If *print-base* is 2, 8,
or 16, then the radix specifier used is #b, #o, or #x, respectively. Otherwise the
radix specifier is in the form #XXr where XX is the decimal value of *print-base* "
:added "1.2"}
*print-radix* nil)
(def ^:dynamic
^{:doc "The base to use for printing integers and rationals."
:added "1.2"}
*print-base* 10)
;;======================================================================
;; Internal variables that keep track of where we are in the
;; structure
;;======================================================================
(def ^:dynamic ^{:private true} *current-level* 0)
(def ^:dynamic ^{:private true} *current-length* nil)
;;======================================================================
;; Support for the write function
;;======================================================================
(declare ^{:arglists '([n])} format-simple-number)
;; This map causes var metadata to be included in the compiled output, even
;; in advanced compilation. See CLJS-1853 - António Monteiro
;; (def ^{:private true} write-option-table
;; {;:array *print-array*
;; :base #'cljs.pprint/*print-base*,
;; ;;:case *print-case*,
;; :circle #'cljs.pprint/*print-circle*,
;; ;;:escape *print-escape*,
;; ;;:gensym *print-gensym*,
;; :length #'cljs.core/*print-length*,
;; :level #'cljs.core/*print-level*,
;; :lines #'cljs.pprint/*print-lines*,
;; :miser-width #'cljs.pprint/*print-miser-width*,
;; :dispatch #'cljs.pprint/*print-pprint-dispatch*,
;; :pretty #'cljs.pprint/*print-pretty*,
;; :radix #'cljs.pprint/*print-radix*,
;; :readably #'cljs.core/*print-readably*,
;; :right-margin #'cljs.pprint/*print-right-margin*,
;; :suppress-namespaces #'cljs.pprint/*print-suppress-namespaces*})
(defn- table-ize [t m]
(apply hash-map (mapcat
#(when-let [v (get t (key %))] [v (val %)])
m)))
(defn- pretty-writer?
"Return true iff x is a PrettyWriter"
[x] (and (satisfies? IDeref x) (:pretty-writer @@x)))
(defn- make-pretty-writer
"Wrap base-writer in a PrettyWriter with the specified right-margin and miser-width"
[base-writer right-margin miser-width]
(pretty-writer base-writer right-margin miser-width))
(defn write-out
"Write an object to *out* subject to the current bindings of the printer control
variables. Use the kw-args argument to override individual variables for this call (and
any recursive calls).
*out* must be a PrettyWriter if pretty printing is enabled. This is the responsibility
of the caller.
This method is primarily intended for use by pretty print dispatch functions that
already know that the pretty printer will have set up their environment appropriately.
Normal library clients should use the standard \"write\" interface. "
[object]
(let [length-reached (and *current-length*
*print-length*
(>= *current-length* *print-length*))]
(if-not *print-pretty*
(pr object)
(if length-reached
(-write *out* "...") ;;TODO could this (incorrectly) print ... on the next line?
(do
(if *current-length* (set! *current-length* (inc *current-length*)))
(*print-pprint-dispatch* object))))
length-reached))
(defn write
"Write an object subject to the current bindings of the printer control variables.
Use the kw-args argument to override individual variables for this call (and any
recursive calls). Returns the string result if :stream is nil or nil otherwise.
The following keyword arguments can be passed with values:
Keyword Meaning Default value
:stream Writer for output or nil true (indicates *out*)
:base Base to use for writing rationals Current value of *print-base*
:circle* If true, mark circular structures Current value of *print-circle*
:length Maximum elements to show in sublists Current value of *print-length*
:level Maximum depth Current value of *print-level*
:lines* Maximum lines of output Current value of *print-lines*
:miser-width Width to enter miser mode Current value of *print-miser-width*
:dispatch The pretty print dispatch function Current value of *print-pprint-dispatch*
:pretty If true, do pretty printing Current value of *print-pretty*
:radix If true, prepend a radix specifier Current value of *print-radix*
:readably* If true, print readably Current value of *print-readably*
:right-margin The column for the right margin Current value of *print-right-margin*
:suppress-namespaces If true, no namespaces in symbols Current value of *print-suppress-namespaces*
* = not yet supported
"
[object & kw-args]
(let [options (merge {:stream true} (apply hash-map kw-args))]
;;TODO rewrite this as a macro
(binding [cljs.pprint/*print-base* (:base options cljs.pprint/*print-base*)
;;:case *print-case*,
cljs.pprint/*print-circle* (:circle options cljs.pprint/*print-circle*)
;;:escape *print-escape*
;;:gensym *print-gensym*
cljs.core/*print-length* (:length options cljs.core/*print-length*)
cljs.core/*print-level* (:level options cljs.core/*print-level*)
cljs.pprint/*print-lines* (:lines options cljs.pprint/*print-lines*)
cljs.pprint/*print-miser-width* (:miser-width options cljs.pprint/*print-miser-width*)
cljs.pprint/*print-pprint-dispatch* (:dispatch options cljs.pprint/*print-pprint-dispatch*)
cljs.pprint/*print-pretty* (:pretty options cljs.pprint/*print-pretty*)
cljs.pprint/*print-radix* (:radix options cljs.pprint/*print-radix*)
cljs.core/*print-readably* (:readably options cljs.core/*print-readably*)
cljs.pprint/*print-right-margin* (:right-margin options cljs.pprint/*print-right-margin*)
cljs.pprint/*print-suppress-namespaces* (:suppress-namespaces options cljs.pprint/*print-suppress-namespaces*)]
;;TODO enable printing base
#_[bindings (if (or (not (= *print-base* 10)) *print-radix*)
{#'pr pr-with-base}
{})]
(binding []
(let [sb (StringBuffer.)
optval (if (contains? options :stream)
(:stream options)
true)
base-writer (if (or (true? optval) (nil? optval))
(StringBufferWriter. sb)
optval)]
(if *print-pretty*
(with-pretty-writer base-writer
(write-out object))
(binding [*out* base-writer]
(pr object)))
(if (true? optval)
(string-print (str sb)))
(if (nil? optval)
(str sb)))))))
(defn pprint
([object]
(let [sb (StringBuffer.)]
(binding [*out* (StringBufferWriter. sb)]
(pprint object *out*)
(string-print (str sb)))))
([object writer]
(with-pretty-writer writer
(binding [*print-pretty* true]
(write-out object))
(if (not (= 0 (get-column *out*)))
(-write *out* \newline)))))
(defn set-pprint-dispatch
[function]
(set! *print-pprint-dispatch* function)
nil)
;;======================================================================
;; Support for the functional interface to the pretty printer
;;======================================================================
(defn- check-enumerated-arg [arg choices]
(if-not (choices arg)
;; TODO clean up choices string
(throw (js/Error. (str "Bad argument: " arg ". It must be one of " choices)))))
(defn- level-exceeded []
(and *print-level* (>= *current-level* *print-level*)))
(defn pprint-newline
"Print a conditional newline to a pretty printing stream. kind specifies if the
newline is :linear, :miser, :fill, or :mandatory.
This function is intended for use when writing custom dispatch functions.
Output is sent to *out* which must be a pretty printing writer."
[kind]
(check-enumerated-arg kind #{:linear :miser :fill :mandatory})
(nl *out* kind))
(defn pprint-indent
"Create an indent at this point in the pretty printing stream. This defines how
following lines are indented. relative-to can be either :block or :current depending
whether the indent should be computed relative to the start of the logical block or
the current column position. n is an offset.
This function is intended for use when writing custom dispatch functions.
Output is sent to *out* which must be a pretty printing writer."
[relative-to n]
(check-enumerated-arg relative-to #{:block :current})
(indent *out* relative-to n))
;; TODO a real implementation for pprint-tab
(defn pprint-tab
"Tab at this point in the pretty printing stream. kind specifies whether the tab
is :line, :section, :line-relative, or :section-relative.
Colnum and colinc specify the target column and the increment to move the target
forward if the output is already past the original target.
This function is intended for use when writing custom dispatch functions.
Output is sent to *out* which must be a pretty printing writer.
THIS FUNCTION IS NOT YET IMPLEMENTED."
{:added "1.2"}
[kind colnum colinc]
(check-enumerated-arg kind #{:line :section :line-relative :section-relative})
(throw (js/Error. "pprint-tab is not yet implemented")))
;;======================================================================
;; cl_format.clj
;;======================================================================
;; Forward references
(declare ^{:arglists '([format-str])} compile-format)
(declare ^{:arglists '([stream format args] [format args])} execute-format)
(declare ^{:arglists '([s])} init-navigator)
;; End forward references
(defn cl-format
"An implementation of a Common Lisp compatible format function. cl-format formats its
arguments to an output stream or string based on the format control string given. It
supports sophisticated formatting of structured data.
Writer satisfies IWriter, true to output via *print-fn* or nil to output
to a string, format-in is the format control string and the remaining arguments
are the data to be formatted.
The format control string is a string to be output with embedded 'format directives'
describing how to format the various arguments passed in.
If writer is nil, cl-format returns the formatted result string. Otherwise, cl-format
returns nil.
For example:
(let [results [46 38 22]]
(cl-format true \"There ~[are~;is~:;are~]~:* ~d result~:p: ~{~d~^, ~}~%\"
(count results) results))
Prints via *print-fn*:
There are 3 results: 46, 38, 22
Detailed documentation on format control strings is available in the \"Common Lisp the
Language, 2nd edition\", Chapter 22 (available online at:
http://www.cs.cmu.edu/afs/cs.cmu.edu/project/ai-repository/ai/html/cltl/clm/node200.html#SECTION002633000000000000000)
and in the Common Lisp HyperSpec at
http://www.lispworks.com/documentation/HyperSpec/Body/22_c.htm"
{:see-also [["http://www.cs.cmu.edu/afs/cs.cmu.edu/project/ai-repository/ai/html/cltl/clm/node200.html#SECTION002633000000000000000"
"Common Lisp the Language"]
["http://www.lispworks.com/documentation/HyperSpec/Body/22_c.htm"
"Common Lisp HyperSpec"]]}
[writer format-in & args]
(let [compiled-format (if (string? format-in) (compile-format format-in) format-in)
navigator (init-navigator args)]
(execute-format writer compiled-format navigator)))
(def ^:dynamic ^{:private true} *format-str* nil)
(defn- format-error [message offset]
(let [full-message (str message \newline *format-str* \newline
(apply str (repeat offset \space)) "^" \newline)]
(throw (js/Error full-message))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Argument navigators manage the argument list
;; as the format statement moves through the list
;; (possibly going forwards and backwards as it does so)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defrecord ^{:private true}
arg-navigator [seq rest pos])
(defn- init-navigator
"Create a new arg-navigator from the sequence with the position set to 0"
{:skip-wiki true}
[s]
(let [s (seq s)]
(arg-navigator. s s 0)))
;; TODO call format-error with offset
(defn- next-arg [navigator]
(let [rst (:rest navigator)]
(if rst
[(first rst) (arg-navigator. (:seq navigator) (next rst) (inc (:pos navigator)))]
(throw (js/Error "Not enough arguments for format definition")))))
(defn- next-arg-or-nil [navigator]
(let [rst (:rest navigator)]
(if rst
[(first rst) (arg-navigator. (:seq navigator) (next rst) (inc (:pos navigator)))]
[nil navigator])))
;; Get an argument off the arg list and compile it if it's not already compiled
(defn- get-format-arg [navigator]
(let [[raw-format navigator] (next-arg navigator)
compiled-format (if (string? raw-format)
(compile-format raw-format)
raw-format)]
[compiled-format navigator]))
(declare relative-reposition)
(defn- absolute-reposition [navigator position]
(if (>= position (:pos navigator))
(relative-reposition navigator (- (:pos navigator) position))
(arg-navigator. (:seq navigator) (drop position (:seq navigator)) position)))
(defn- relative-reposition [navigator position]
(let [newpos (+ (:pos navigator) position)]
(if (neg? position)
(absolute-reposition navigator newpos)
(arg-navigator. (:seq navigator) (drop position (:rest navigator)) newpos))))
(defrecord ^{:private true}
compiled-directive [func def params offset])
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; When looking at the parameter list, we may need to manipulate
;; the argument list as well (for 'V' and '#' parameter types).
;; We hide all of this behind a function, but clients need to
;; manage changing arg navigator
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; TODO: validate parameters when they come from arg list
(defn- realize-parameter [[param [raw-val offset]] navigator]
(let [[real-param new-navigator]
(cond
(contains? #{:at :colon} param) ;pass flags through unchanged - this really isn't necessary
[raw-val navigator]
(= raw-val :parameter-from-args)
(next-arg navigator)
(= raw-val :remaining-arg-count)
[(count (:rest navigator)) navigator]
true
[raw-val navigator])]
[[param [real-param offset]] new-navigator]))
(defn- realize-parameter-list [parameter-map navigator]
(let [[pairs new-navigator]
(map-passing-context realize-parameter navigator parameter-map)]
[(into {} pairs) new-navigator]))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Functions that support individual directives
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Common handling code for ~A and ~S
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(declare ^{:arglists '([base val])} opt-base-str)
(def ^{:private true}
special-radix-markers {2 "#b" 8 "#o" 16 "#x"})
(defn- format-simple-number [n]
(cond
(integer? n) (if (= *print-base* 10)
(str n (if *print-radix* "."))
(str
(if *print-radix* (or (get special-radix-markers *print-base*) (str "#" *print-base* "r")))
(opt-base-str *print-base* n)))
;;(ratio? n) ;;no ratio support
:else nil))
(defn- format-ascii [print-func params arg-navigator offsets]
(let [[arg arg-navigator] (next-arg arg-navigator)
base-output (or (format-simple-number arg) (print-func arg))
base-width (.-length base-output)
min-width (+ base-width (:minpad params))
width (if (>= min-width (:mincol params))
min-width
(+ min-width
(* (+ (quot (- (:mincol params) min-width 1)
(:colinc params))
1)
(:colinc params))))
chars (apply str (repeat (- width base-width) (:padchar params)))]
(if (:at params)
(print (str chars base-output))
(print (str base-output chars)))
arg-navigator))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Support for the integer directives ~D, ~X, ~O, ~B and some
;; of ~R
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn- integral?
"returns true if a number is actually an integer (that is, has no fractional part)"
[x]
(cond
(integer? x) true
;;(decimal? x) ;;no decimal support
(float? x) (= x (Math/floor x))
;;(ratio? x) ;;no ratio support
:else false))
(defn- remainders
"Return the list of remainders (essentially the 'digits') of val in the given base"
[base val]
(reverse
(first
(consume #(if (pos? %)
[(rem % base) (quot % base)]
[nil nil])
val))))
;; TODO: xlated-val does not seem to be used here.
;; NB
(defn- base-str
"Return val as a string in the given base"
[base val]
(if (zero? val)
"0"
(let [xlated-val (cond
;(float? val) (bigdec val) ;;No bigdec
;(ratio? val) nil ;;No ratio
:else val)]
(apply str
(map
#(if (< % 10) (char (+ (char-code \0) %)) (char (+ (char-code \a) (- % 10))))
(remainders base val))))))
;;Not sure if this is accurate or necessary
(def ^{:private true}
javascript-base-formats {8 "%o", 10 "%d", 16 "%x"})
(defn- opt-base-str
"Return val as a string in the given base. No cljs format, so no improved performance."
[base val]
(base-str base val))
(defn- group-by* [unit lis]
(reverse
(first
(consume (fn [x] [(seq (reverse (take unit x))) (seq (drop unit x))]) (reverse lis)))))
(defn- format-integer [base params arg-navigator offsets]
(let [[arg arg-navigator] (next-arg arg-navigator)]
(if (integral? arg)
(let [neg (neg? arg)
pos-arg (if neg (- arg) arg)
raw-str (opt-base-str base pos-arg)
group-str (if (:colon params)
(let [groups (map #(apply str %) (group-by* (:commainterval params) raw-str))
commas (repeat (count groups) (:commachar params))]
(apply str (next (interleave commas groups))))
raw-str)
signed-str (cond
neg (str "-" group-str)
(:at params) (str "+" group-str)
true group-str)
padded-str (if (< (.-length signed-str) (:mincol params))
(str (apply str (repeat (- (:mincol params) (.-length signed-str))
(:padchar params)))
signed-str)
signed-str)]
(print padded-str))
(format-ascii print-str {:mincol (:mincol params) :colinc 1 :minpad 0
:padchar (:padchar params) :at true}
(init-navigator [arg]) nil))
arg-navigator))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Support for english formats (~R and ~:R)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(def ^{:private true}
english-cardinal-units
["zero" "one" "two" "three" "four" "five" "six" "seven" "eight" "nine"
"ten" "eleven" "twelve" "thirteen" "fourteen"
"fifteen" "sixteen" "seventeen" "eighteen" "nineteen"])
(def ^{:private true}
english-ordinal-units
["zeroth" "first" "second" "third" "fourth" "fifth" "sixth" "seventh" "eighth" "ninth"
"tenth" "eleventh" "twelfth" "thirteenth" "fourteenth"
"fifteenth" "sixteenth" "seventeenth" "eighteenth" "nineteenth"])
(def ^{:private true}
english-cardinal-tens
["" "" "twenty" "thirty" "forty" "fifty" "sixty" "seventy" "eighty" "ninety"])
(def ^{:private true}
english-ordinal-tens
["" "" "twentieth" "thirtieth" "fortieth" "fiftieth"
"sixtieth" "seventieth" "eightieth" "ninetieth"])
;; We use "short scale" for our units (see http://en.wikipedia.org/wiki/Long_and_short_scales)
;; Number names from http://www.jimloy.com/math/billion.htm
;; We follow the rules for writing numbers from the Blue Book
;; (http://www.grammarbook.com/numbers/numbers.asp)
(def ^{:private true}
english-scale-numbers
["" "thousand" "million" "billion" "trillion" "quadrillion" "quintillion"
"sextillion" "septillion" "octillion" "nonillion" "decillion"
"undecillion" "duodecillion" "tredecillion" "quattuordecillion"
"quindecillion" "sexdecillion" "septendecillion"
"octodecillion" "novemdecillion" "vigintillion"])
(defn- format-simple-cardinal
"Convert a number less than 1000 to a cardinal english string"
[num]
(let [hundreds (quot num 100)
tens (rem num 100)]
(str
(if (pos? hundreds) (str (nth english-cardinal-units hundreds) " hundred"))
(if (and (pos? hundreds) (pos? tens)) " ")
(if (pos? tens)
(if (< tens 20)
(nth english-cardinal-units tens)
(let [ten-digit (quot tens 10)
unit-digit (rem tens 10)]
(str
(if (pos? ten-digit) (nth english-cardinal-tens ten-digit))
(if (and (pos? ten-digit) (pos? unit-digit)) "-")
(if (pos? unit-digit) (nth english-cardinal-units unit-digit)))))))))
(defn- add-english-scales
"Take a sequence of parts, add scale numbers (e.g., million) and combine into a string
offset is a factor of 10^3 to multiply by"
[parts offset]
(let [cnt (count parts)]
(loop [acc []
pos (dec cnt)
this (first parts)
remainder (next parts)]
(if (nil? remainder)
(str (apply str (interpose ", " acc))
(if (and (not (empty? this)) (not (empty? acc))) ", ")
this
(if (and (not (empty? this)) (pos? (+ pos offset)))
(str " " (nth english-scale-numbers (+ pos offset)))))
(recur
(if (empty? this)
acc
(conj acc (str this " " (nth english-scale-numbers (+ pos offset)))))
(dec pos)
(first remainder)
(next remainder))))))
(defn- format-cardinal-english [params navigator offsets]
(let [[arg navigator] (next-arg navigator)]
(if (= 0 arg)
(print "zero")
(let [abs-arg (if (neg? arg) (- arg) arg) ; some numbers are too big for Math/abs (is this true?)
parts (remainders 1000 abs-arg)]
(if (<= (count parts) (count english-scale-numbers))
(let [parts-strs (map format-simple-cardinal parts)
full-str (add-english-scales parts-strs 0)]
(print (str (if (neg? arg) "minus ") full-str)))
(format-integer ;; for numbers > 10^63, we fall back on ~D
10
{:mincol 0, :padchar \space, :commachar \, :commainterval 3, :colon true}
(init-navigator [arg])
{:mincol 0, :padchar 0, :commachar 0 :commainterval 0}))))
navigator))
(defn- format-simple-ordinal
"Convert a number less than 1000 to a ordinal english string
Note this should only be used for the last one in the sequence"
[num]
(let [hundreds (quot num 100)
tens (rem num 100)]
(str
(if (pos? hundreds) (str (nth english-cardinal-units hundreds) " hundred"))
(if (and (pos? hundreds) (pos? tens)) " ")
(if (pos? tens)
(if (< tens 20)
(nth english-ordinal-units tens)
(let [ten-digit (quot tens 10)
unit-digit (rem tens 10)]
(if (and (pos? ten-digit) (not (pos? unit-digit)))
(nth english-ordinal-tens ten-digit)
(str
(if (pos? ten-digit) (nth english-cardinal-tens ten-digit))
(if (and (pos? ten-digit) (pos? unit-digit)) "-")
(if (pos? unit-digit) (nth english-ordinal-units unit-digit))))))
(if (pos? hundreds) "th")))))
(defn- format-ordinal-english [params navigator offsets]
(let [[arg navigator] (next-arg navigator)]
(if (= 0 arg)
(print "zeroth")
(let [abs-arg (if (neg? arg) (- arg) arg) ; some numbers are too big for Math/abs (is this true?)
parts (remainders 1000 abs-arg)]
(if (<= (count parts) (count english-scale-numbers))
(let [parts-strs (map format-simple-cardinal (drop-last parts))
head-str (add-english-scales parts-strs 1)
tail-str (format-simple-ordinal (last parts))]
(print (str (if (neg? arg) "minus ")
(cond
(and (not (empty? head-str)) (not (empty? tail-str)))
(str head-str ", " tail-str)
(not (empty? head-str)) (str head-str "th")
:else tail-str))))
(do (format-integer ;for numbers > 10^63, we fall back on ~D
10
{:mincol 0, :padchar \space, :commachar \, :commainterval 3, :colon true}
(init-navigator [arg])
{:mincol 0, :padchar 0, :commachar 0 :commainterval 0})
(let [low-two-digits (rem arg 100)
not-teens (or (< 11 low-two-digits) (> 19 low-two-digits))
low-digit (rem low-two-digits 10)]
(print (cond
(and (== low-digit 1) not-teens) "st"
(and (== low-digit 2) not-teens) "nd"
(and (== low-digit 3) not-teens) "rd"
:else "th")))))))
navigator))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Support for roman numeral formats (~@R and ~@:R)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(def ^{:private true}
old-roman-table
[[ "I" "II" "III" "IIII" "V" "VI" "VII" "VIII" "VIIII"]
[ "X" "XX" "XXX" "XXXX" "L" "LX" "LXX" "LXXX" "LXXXX"]
[ "C" "CC" "CCC" "CCCC" "D" "DC" "DCC" "DCCC" "DCCCC"]
[ "M" "MM" "MMM"]])
(def ^{:private true}
new-roman-table
[[ "I" "II" "III" "IV" "V" "VI" "VII" "VIII" "IX"]
[ "X" "XX" "XXX" "XL" "L" "LX" "LXX" "LXXX" "XC"]
[ "C" "CC" "CCC" "CD" "D" "DC" "DCC" "DCCC" "CM"]
[ "M" "MM" "MMM"]])
(defn- format-roman
"Format a roman numeral using the specified look-up table"
[table params navigator offsets]
(let [[arg navigator] (next-arg navigator)]
(if (and (number? arg) (> arg 0) (< arg 4000))
(let [digits (remainders 10 arg)]
(loop [acc []
pos (dec (count digits))
digits digits]
(if (empty? digits)
(print (apply str acc))
(let [digit (first digits)]
(recur (if (= 0 digit)
acc
(conj acc (nth (nth table pos) (dec digit))))
(dec pos)
(next digits))))))
(format-integer ; for anything <= 0 or > 3999, we fall back on ~D
10
{:mincol 0, :padchar \space, :commachar \, :commainterval 3, :colon true}
(init-navigator [arg])
{:mincol 0, :padchar 0, :commachar 0 :commainterval 0}))
navigator))
(defn- format-old-roman [params navigator offsets]
(format-roman old-roman-table params navigator offsets))
(defn- format-new-roman [params navigator offsets]
(format-roman new-roman-table params navigator offsets))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Support for character formats (~C)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(def ^{:private true}
special-chars {8 "Backspace", 9 "Tab", 10 "Newline", 13 "Return", 32 "Space"})
(defn- pretty-character [params navigator offsets]
(let [[c navigator] (next-arg navigator)
as-int (char-code c)
base-char (bit-and as-int 127)
meta (bit-and as-int 128)
special (get special-chars base-char)]
(if (> meta 0) (print "Meta-"))
(print (cond
special special
(< base-char 32) (str "Control-" (char (+ base-char 64)))
(= base-char 127) "Control-?"
:else (char base-char)))
navigator))
(defn- readable-character [params navigator offsets]
(let [[c navigator] (next-arg navigator)]
(condp = (:char-format params)
\o (cl-format true "\\o~3,'0o" (char-code c))
\u (cl-format true "\\u~4,'0x" (char-code c))
nil (print-char c))
navigator))
(defn- plain-character [params navigator offsets]
(let [[char navigator] (next-arg navigator)]
(print char)
navigator))
;; Check to see if a result is an abort (~^) construct
;; TODO: move these funcs somewhere more appropriate
(defn- abort? [context]
(let [token (first context)]
(or (= :up-arrow token) (= :colon-up-arrow token))))
;; Handle the execution of "sub-clauses" in bracket constructions
(defn- execute-sub-format [format args base-args]
(second
(map-passing-context
(fn [element context]
(if (abort? context)
[nil context] ; just keep passing it along
(let [[params args] (realize-parameter-list (:params element) context)
[params offsets] (unzip-map params)
params (assoc params :base-args base-args)]
[nil (apply (:func element) [params args offsets])])))
args
format)))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Support for real number formats
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; TODO - return exponent as int to eliminate double conversion
(defn- float-parts-base
"Produce string parts for the mantissa (normalize 1-9) and exponent"
[f]
(let [s (string/lower-case (str f))
exploc (.indexOf s \e)
dotloc (.indexOf s \.)]
(if (neg? exploc)
(if (neg? dotloc)
[s (str (dec (count s)))]
[(str (subs s 0 dotloc) (subs s (inc dotloc))) (str (dec dotloc))])
(if (neg? dotloc)
[(subs s 0 exploc) (subs s (inc exploc))]
[(str (subs s 0 1) (subs s 2 exploc)) (subs s (inc exploc))]))))
(defn- float-parts
"Take care of leading and trailing zeros in decomposed floats"
[f]
(let [[m e] (float-parts-base f)
m1 (rtrim m \0)
m2 (ltrim m1 \0)
delta (- (count m1) (count m2))
e (if (and (pos? (count e)) (= (nth e 0) \+)) (subs e 1) e)]
(if (empty? m2)
["0" 0]
[m2 (- (js/parseInt e 10) delta)])))
(defn- inc-s
"Assumption: The input string consists of one or more decimal digits,
and no other characters. Return a string containing one or more
decimal digits containing a decimal number one larger than the input
string. The output string will always be the same length as the input
string, or one character longer."
[s]
(let [len-1 (dec (count s))]
(loop [i (int len-1)]
(cond
(neg? i) (apply str "1" (repeat (inc len-1) "0"))
(= \9 (.charAt s i)) (recur (dec i))
:else (apply str (subs s 0 i)
(char (inc (char-code (.charAt s i))))
(repeat (- len-1 i) "0"))))))
(defn- round-str [m e d w]
(if (or d w)
(let [len (count m)
;; Every formatted floating point number should include at
;; least one decimal digit and a decimal point.
w (if w (max 2 w)
;;NB: if w doesn't exist, it won't ever be used because d will
;; satisfy the cond below. cljs gives a compilation warning if
;; we don't provide a value here.
0)
round-pos (cond
;; If d was given, that forces the rounding
;; position, regardless of any width that may
;; have been specified.
d (+ e d 1)
;; Otherwise w was specified, so pick round-pos
;; based upon that.
;; If e>=0, then abs value of number is >= 1.0,
;; and e+1 is number of decimal digits before the
;; decimal point when the number is written
;; without scientific notation. Never round the
;; number before the decimal point.
(>= e 0) (max (inc e) (dec w))
;; e < 0, so number abs value < 1.0
:else (+ w e))
[m1 e1 round-pos len] (if (= round-pos 0)
[(str "0" m) (inc e) 1 (inc len)]
[m e round-pos len])]
(if round-pos
(if (neg? round-pos)
["0" 0 false]
(if (> len round-pos)
(let [round-char (nth m1 round-pos)
result (subs m1 0 round-pos)]
(if (>= (char-code round-char) (char-code \5))
(let [round-up-result (inc-s result)
expanded (> (count round-up-result) (count result))]
[(if expanded
(subs round-up-result 0 (dec (count round-up-result)))
round-up-result)
e1 expanded])
[result e1 false]))
[m e false]))
[m e false]))
[m e false]))
(defn- expand-fixed [m e d]
(let [[m1 e1] (if (neg? e)
[(str (apply str (repeat (dec (- e)) \0)) m) -1]
[m e])
len (count m1)
target-len (if d (+ e1 d 1) (inc e1))]
(if (< len target-len)
(str m1 (apply str (repeat (- target-len len) \0)))
m1)))
(defn- insert-decimal
"Insert the decimal point at the right spot in the number to match an exponent"
[m e]
(if (neg? e)
(str "." m)
(let [loc (inc e)]
(str (subs m 0 loc) "." (subs m loc)))))
(defn- get-fixed [m e d]
(insert-decimal (expand-fixed m e d) e))
(defn- insert-scaled-decimal
"Insert the decimal point at the right spot in the number to match an exponent"
[m k]
(if (neg? k)
(str "." m)
(str (subs m 0 k) "." (subs m k))))
;;TODO: No ratio, so not sure what to do here
(defn- convert-ratio [x]
x)
;; the function to render ~F directives
;; TODO: support rationals. Back off to ~D/~A in the appropriate cases
(defn- fixed-float [params navigator offsets]
(let [w (:w params)
d (:d params)
[arg navigator] (next-arg navigator)
[sign abs] (if (neg? arg) ["-" (- arg)] ["+" arg])
abs (convert-ratio abs)
[mantissa exp] (float-parts abs)
scaled-exp (+ exp (:k params))
add-sign (or (:at params) (neg? arg))
append-zero (and (not d) (<= (dec (count mantissa)) scaled-exp))
[rounded-mantissa scaled-exp expanded] (round-str mantissa scaled-exp
d (if w (- w (if add-sign 1 0))))
fixed-repr (get-fixed rounded-mantissa (if expanded (inc scaled-exp) scaled-exp) d)
fixed-repr (if (and w d
(>= d 1)
(= (.charAt fixed-repr 0) \0)
(= (.charAt fixed-repr 1) \.)
(> (count fixed-repr) (- w (if add-sign 1 0))))
(subs fixed-repr 1) ;chop off leading 0
fixed-repr)
prepend-zero (= (first fixed-repr) \.)]
(if w
(let [len (count fixed-repr)
signed-len (if add-sign (inc len) len)
prepend-zero (and prepend-zero (not (>= signed-len w)))
append-zero (and append-zero (not (>= signed-len w)))
full-len (if (or prepend-zero append-zero)
(inc signed-len)
signed-len)]
(if (and (> full-len w) (:overflowchar params))
(print (apply str (repeat w (:overflowchar params))))
(print (str
(apply str (repeat (- w full-len) (:padchar params)))
(if add-sign sign)
(if prepend-zero "0")
fixed-repr
(if append-zero "0")))))
(print (str
(if add-sign sign)
(if prepend-zero "0")
fixed-repr
(if append-zero "0"))))
navigator))
;; the function to render ~E directives
;; TODO: support rationals. Back off to ~D/~A in the appropriate cases
;; TODO: define ~E representation for Infinity
(defn- exponential-float [params navigator offset]
(let [[arg navigator] (next-arg navigator)
arg (convert-ratio arg)]
(loop [[mantissa exp] (float-parts (if (neg? arg) (- arg) arg))]
(let [w (:w params)
d (:d params)
e (:e params)
k (:k params)
expchar (or (:exponentchar params) \E)
add-sign (or (:at params) (neg? arg))
prepend-zero (<= k 0)
scaled-exp (- exp (dec k))
scaled-exp-str (str (Math/abs scaled-exp))
scaled-exp-str (str expchar (if (neg? scaled-exp) \- \+)
(if e (apply str
(repeat
(- e
(count scaled-exp-str))
\0)))
scaled-exp-str)
exp-width (count scaled-exp-str)
base-mantissa-width (count mantissa)
scaled-mantissa (str (apply str (repeat (- k) \0))
mantissa
(if d
(apply str
(repeat
(- d (dec base-mantissa-width)
(if (neg? k) (- k) 0)) \0))))
w-mantissa (if w (- w exp-width))
[rounded-mantissa _ incr-exp] (round-str
scaled-mantissa 0
(cond
(= k 0) (dec d)
(pos? k) d
(neg? k) (dec d))
(if w-mantissa
(- w-mantissa (if add-sign 1 0))))
full-mantissa (insert-scaled-decimal rounded-mantissa k)
append-zero (and (= k (count rounded-mantissa)) (nil? d))]
(if (not incr-exp)
(if w
(let [len (+ (count full-mantissa) exp-width)
signed-len (if add-sign (inc len) len)
prepend-zero (and prepend-zero (not (= signed-len w)))
full-len (if prepend-zero (inc signed-len) signed-len)
append-zero (and append-zero (< full-len w))]
(if (and (or (> full-len w) (and e (> (- exp-width 2) e)))
(:overflowchar params))
(print (apply str (repeat w (:overflowchar params))))
(print (str
(apply str
(repeat
(- w full-len (if append-zero 1 0))
(:padchar params)))
(if add-sign (if (neg? arg) \- \+))
(if prepend-zero "0")
full-mantissa
(if append-zero "0")
scaled-exp-str))))
(print (str
(if add-sign (if (neg? arg) \- \+))
(if prepend-zero "0")
full-mantissa
(if append-zero "0")
scaled-exp-str)))
(recur [rounded-mantissa (inc exp)]))))
navigator))
;; the function to render ~G directives
;; This just figures out whether to pass the request off to ~F or ~E based
;; on the algorithm in CLtL.
;; TODO: support rationals. Back off to ~D/~A in the appropriate cases
;; TODO: refactor so that float-parts isn't called twice
(defn- general-float [params navigator offsets]
(let [[arg _] (next-arg navigator)
arg (convert-ratio arg)
[mantissa exp] (float-parts (if (neg? arg) (- arg) arg))
w (:w params)
d (:d params)
e (:e params)
n (if (= arg 0.0) 0 (inc exp))
ee (if e (+ e 2) 4)
ww (if w (- w ee))
d (if d d (max (count mantissa) (min n 7)))
dd (- d n)]
(if (<= 0 dd d)
(let [navigator (fixed-float {:w ww, :d dd, :k 0,
:overflowchar (:overflowchar params),
:padchar (:padchar params), :at (:at params)}
navigator offsets)]
(print (apply str (repeat ee \space)))
navigator)
(exponential-float params navigator offsets))))
;; the function to render ~$ directives
;; TODO: support rationals. Back off to ~D/~A in the appropriate cases
(defn- dollar-float [params navigator offsets]
(let [[arg navigator] (next-arg navigator)
[mantissa exp] (float-parts (Math/abs arg))
d (:d params) ; digits after the decimal
n (:n params) ; minimum digits before the decimal
w (:w params) ; minimum field width
add-sign (or (:at params) (neg? arg))
[rounded-mantissa scaled-exp expanded] (round-str mantissa exp d nil)
fixed-repr (get-fixed rounded-mantissa (if expanded (inc scaled-exp) scaled-exp) d)
full-repr (str (apply str (repeat (- n (.indexOf fixed-repr \.)) \0)) fixed-repr)
full-len (+ (count full-repr) (if add-sign 1 0))]
(print (str
(if (and (:colon params) add-sign) (if (neg? arg) \- \+))
(apply str (repeat (- w full-len) (:padchar params)))
(if (and (not (:colon params)) add-sign) (if (neg? arg) \- \+))
full-repr))
navigator))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Support for the '~[...~]' conditional construct in its
;; different flavors
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; ~[...~] without any modifiers chooses one of the clauses based on the param or
;; next argument
;; TODO check arg is positive int
(defn- choice-conditional [params arg-navigator offsets]
(let [arg (:selector params)
[arg navigator] (if arg [arg arg-navigator] (next-arg arg-navigator))
clauses (:clauses params)
clause (if (or (neg? arg) (>= arg (count clauses)))
(first (:else params))
(nth clauses arg))]
(if clause
(execute-sub-format clause navigator (:base-args params))
navigator)))
;; ~:[...~] with the colon reads the next argument treating it as a truth value
(defn- boolean-conditional [params arg-navigator offsets]
(let [[arg navigator] (next-arg arg-navigator)
clauses (:clauses params)
clause (if arg
(second clauses)
(first clauses))]
(if clause
(execute-sub-format clause navigator (:base-args params))
navigator)))
;; ~@[...~] with the at sign executes the conditional if the next arg is not
;; nil/false without consuming the arg
(defn- check-arg-conditional [params arg-navigator offsets]
(let [[arg navigator] (next-arg arg-navigator)
clauses (:clauses params)
clause (if arg (first clauses))]
(if arg
(if clause
(execute-sub-format clause arg-navigator (:base-args params))
arg-navigator)
navigator)))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Support for the '~{...~}' iteration construct in its
;; different flavors
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; ~{...~} without any modifiers uses the next argument as an argument list that
;; is consumed by all the iterations
(defn- iterate-sublist [params navigator offsets]
(let [max-count (:max-iterations params)
param-clause (first (:clauses params))
[clause navigator] (if (empty? param-clause)
(get-format-arg navigator)
[param-clause navigator])
[arg-list navigator] (next-arg navigator)
args (init-navigator arg-list)]
(loop [count 0
args args
last-pos (int -1)]
(if (and (not max-count) (= (:pos args) last-pos) (> count 1))
;; TODO get the offset in here and call format exception
(throw (js/Error "%{ construct not consuming any arguments: Infinite loop!")))
(if (or (and (empty? (:rest args))
(or (not (:colon (:right-params params))) (> count 0)))
(and max-count (>= count max-count)))
navigator
(let [iter-result (execute-sub-format clause args (:base-args params))]
(if (= :up-arrow (first iter-result))
navigator
(recur (inc count) iter-result (:pos args))))))))
;; ~:{...~} with the colon treats the next argument as a list of sublists. Each of the
;; sublists is used as the arglist for a single iteration.
(defn- iterate-list-of-sublists [params navigator offsets]
(let [max-count (:max-iterations params)
param-clause (first (:clauses params))
[clause navigator] (if (empty? param-clause)
(get-format-arg navigator)
[param-clause navigator])
[arg-list navigator] (next-arg navigator)]
(loop [count 0
arg-list arg-list]
(if (or (and (empty? arg-list)
(or (not (:colon (:right-params params))) (> count 0)))
(and max-count (>= count max-count)))
navigator
(let [iter-result (execute-sub-format
clause
(init-navigator (first arg-list))
(init-navigator (next arg-list)))]
(if (= :colon-up-arrow (first iter-result))
navigator
(recur (inc count) (next arg-list))))))))
;; ~@{...~} with the at sign uses the main argument list as the arguments to the iterations
;; is consumed by all the iterations
(defn- iterate-main-list [params navigator offsets]
(let [max-count (:max-iterations params)
param-clause (first (:clauses params))
[clause navigator] (if (empty? param-clause)
(get-format-arg navigator)
[param-clause navigator])]
(loop [count 0
navigator navigator
last-pos (int -1)]
(if (and (not max-count) (= (:pos navigator) last-pos) (> count 1))
;; TODO get the offset in here and call format exception
(throw (js/Error "%@{ construct not consuming any arguments: Infinite loop!")))
(if (or (and (empty? (:rest navigator))
(or (not (:colon (:right-params params))) (> count 0)))
(and max-count (>= count max-count)))
navigator
(let [iter-result (execute-sub-format clause navigator (:base-args params))]
(if (= :up-arrow (first iter-result))
(second iter-result)
(recur
(inc count) iter-result (:pos navigator))))))))
;; ~@:{...~} with both colon and at sign uses the main argument list as a set of sublists, one
;; of which is consumed with each iteration
(defn- iterate-main-sublists [params navigator offsets]
(let [max-count (:max-iterations params)
param-clause (first (:clauses params))
[clause navigator] (if (empty? param-clause)
(get-format-arg navigator)
[param-clause navigator])]
(loop [count 0
navigator navigator]
(if (or (and (empty? (:rest navigator))
(or (not (:colon (:right-params params))) (> count 0)))
(and max-count (>= count max-count)))
navigator
(let [[sublist navigator] (next-arg-or-nil navigator)
iter-result (execute-sub-format clause (init-navigator sublist) navigator)]
(if (= :colon-up-arrow (first iter-result))
navigator
(recur (inc count) navigator)))))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; The '~< directive has two completely different meanings
;; in the '~<...~>' form it does justification, but with
;; ~<...~:>' it represents the logical block operation of the
;; pretty printer.
;;
;; Unfortunately, the current architecture decides what function
;; to call at form parsing time before the sub-clauses have been
;; folded, so it is left to run-time to make the decision.
;;
;; TODO: make it possible to make these decisions at compile-time.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(declare ^{:arglists '([params navigator offsets])} format-logical-block)
(declare ^{:arglists '([params navigator offsets])} justify-clauses)
(defn- logical-block-or-justify [params navigator offsets]
(if (:colon (:right-params params))
(format-logical-block params navigator offsets)
(justify-clauses params navigator offsets)))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Support for the '~<...~>' justification directive
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn- render-clauses [clauses navigator base-navigator]
(loop [clauses clauses
acc []
navigator navigator]
(if (empty? clauses)
[acc navigator]
(let [clause (first clauses)
[iter-result result-str] (let [sb (StringBuffer.)]
(binding [*out* (StringBufferWriter. sb)]
[(execute-sub-format clause navigator base-navigator)
(str sb)]))]
(if (= :up-arrow (first iter-result))
[acc (second iter-result)]
(recur (next clauses) (conj acc result-str) iter-result))))))
;; TODO support for ~:; constructions
(defn- justify-clauses [params navigator offsets]
(let [[[eol-str] new-navigator] (when-let [else (:else params)]
(render-clauses else navigator (:base-args params)))
navigator (or new-navigator navigator)
[else-params new-navigator] (when-let [p (:else-params params)]
(realize-parameter-list p navigator))
navigator (or new-navigator navigator)
min-remaining (or (first (:min-remaining else-params)) 0)
max-columns (or (first (:max-columns else-params))
(get-max-column *out*))
clauses (:clauses params)
[strs navigator] (render-clauses clauses navigator (:base-args params))
slots (max 1
(+ (dec (count strs)) (if (:colon params) 1 0) (if (:at params) 1 0)))
chars (reduce + (map count strs))
mincol (:mincol params)
minpad (:minpad params)
colinc (:colinc params)
minout (+ chars (* slots minpad))
result-columns (if (<= minout mincol)
mincol
(+ mincol (* colinc
(+ 1 (quot (- minout mincol 1) colinc)))))
total-pad (- result-columns chars)
pad (max minpad (quot total-pad slots))
extra-pad (- total-pad (* pad slots))
pad-str (apply str (repeat pad (:padchar params)))]
(if (and eol-str (> (+ (get-column (:base @@*out*)) min-remaining result-columns)
max-columns))
(print eol-str))
(loop [slots slots
extra-pad extra-pad
strs strs
pad-only (or (:colon params)
(and (= (count strs) 1) (not (:at params))))]
(if (seq strs)
(do
(print (str (if (not pad-only) (first strs))
(if (or pad-only (next strs) (:at params)) pad-str)
(if (pos? extra-pad) (:padchar params))))
(recur
(dec slots)
(dec extra-pad)
(if pad-only strs (next strs))
false))))
navigator))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; Support for case modification with ~(...~).
;;; We do this by wrapping the underlying writer with
;;; a special writer to do the appropriate modification. This
;;; allows us to support arbitrary-sized output and sources
;;; that may block.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn- downcase-writer
"Returns a proxy that wraps writer, converting all characters to lower case"
[writer]
(reify
IWriter
(-flush [_] (-flush writer))
(-write
;;no multi-arity, not sure of importance
#_([^chars cbuf ^Integer off ^Integer len]
(.write writer cbuf off len))
[this x]
(condp = (type x)
js/String
(let [s x]
(-write writer (string/lower-case s)))
js/Number
(let [c x]
;;TODO need to enforce integers only?
(-write writer (string/lower-case (char c))))))))
(defn- upcase-writer
"Returns a proxy that wraps writer, converting all characters to upper case"
[writer]
(reify
IWriter
(-flush [_] (-flush writer))
(-write
;;no multi-arity, not sure of importance
#_([^chars cbuf ^Integer off ^Integer len]
(.write writer cbuf off len))
[this x]
(condp = (type x)
js/String
(let [s x]
(-write writer (string/upper-case s)))
js/Number
(let [c x]
;;TODO need to enforce integers only?
(-write writer (string/upper-case (char c))))))))
(defn- capitalize-string
"Capitalizes the words in a string. If first? is false, don't capitalize the
first character of the string even if it's a letter."
[s first?]
(let [f (first s)
s (if (and first? f (gstring/isUnicodeChar f))
(str (string/upper-case f) (subs s 1))
s)]
(apply str
(first
(consume
(fn [s]
(if (empty? s)
[nil nil]
(let [m (.exec (js/RegExp "\\W\\w" "g") s)
offset (and m (inc (.-index m)))]
(if offset
[(str (subs s 0 offset)
(string/upper-case (nth s offset)))
(subs s (inc offset))]
[s nil]))))
s)))))
(defn- capitalize-word-writer
"Returns a proxy that wraps writer, capitalizing all words"
[writer]
(let [last-was-whitespace? (atom true)]
(reify
IWriter
(-flush [_] (-flush writer))
(-write
;;no multi-arity
#_([^chars cbuf ^Integer off ^Integer len]
(.write writer cbuf off len))
[this x]
(condp = (type x)
js/String
(let [s x]
(-write writer
(capitalize-string (.toLowerCase s) @last-was-whitespace?))
(when (pos? (.-length s))
(reset! last-was-whitespace? (gstring/isEmptyOrWhitespace (nth s (dec (count s)))))))
js/Number
(let [c (char x)]
(let [mod-c (if @last-was-whitespace? (string/upper-case c) c)]
(-write writer mod-c)
(reset! last-was-whitespace? (gstring/isEmptyOrWhitespace c)))))))))
(defn- init-cap-writer
"Returns a proxy that wraps writer, capitalizing the first word"
[writer]
(let [capped (atom false)]
(reify
IWriter
(-flush [_] (-flush writer))
(-write
;;no multi-arity
#_([^chars cbuf ^Integer off ^Integer len]
(.write writer cbuf off len))
[this x]
(condp = (type x)
js/String
(let [s (string/lower-case x)]
(if (not @capped)
(let [m (.exec (js/RegExp "\\S" "g") s)
offset (and m (.-index m))]
(if offset
(do (-write writer
(str (subs s 0 offset)
(string/upper-case (nth s offset))
(string/lower-case (subs s (inc offset)))))
(reset! capped true))
(-write writer s)))
(-write writer (string/lower-case s))))
js/Number
(let [c (char x)]
(if (and (not @capped) (gstring/isUnicodeChar c))
(do
(reset! capped true)
(-write writer (string/upper-case c)))
(-write writer (string/lower-case c)))))))))
(defn- modify-case [make-writer params navigator offsets]
(let [clause (first (:clauses params))]
(binding [*out* (make-writer *out*)]
(execute-sub-format clause navigator (:base-args params)))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; If necessary, wrap the writer in a PrettyWriter object
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; TODO update this doc string to show correct way to print
(defn get-pretty-writer
"Returns the IWriter passed in wrapped in a pretty writer proxy, unless it's
already a pretty writer. Generally, it is unnecessary to call this function, since pprint,
write, and cl-format all call it if they need to. However if you want the state to be
preserved across calls, you will want to wrap them with this.
For example, when you want to generate column-aware output with multiple calls to cl-format,
do it like in this example:
(defn print-table [aseq column-width]
(binding [*out* (get-pretty-writer *out*)]
(doseq [row aseq]
(doseq [col row]
(cl-format true \"~4D~7,vT\" col column-width))
(prn))))
Now when you run:
user> (print-table (map #(vector % (* % %) (* % % %)) (range 1 11)) 8)
It prints a table of squares and cubes for the numbers from 1 to 10:
1 1 1
2 4 8
3 9 27
4 16 64
5 25 125
6 36 216
7 49 343
8 64 512
9 81 729
10 100 1000"
[writer]
(if (pretty-writer? writer)
writer
(pretty-writer writer *print-right-margin* *print-miser-width*)))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; Support for column-aware operations ~&, ~T
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn fresh-line
"Make a newline if *out* is not already at the beginning of the line. If *out* is
not a pretty writer (which keeps track of columns), this function always outputs a newline."
[]
(if (satisfies? IDeref *out*)
(if (not (= 0 (get-column (:base @@*out*))))
(prn))
(prn)))
(defn- absolute-tabulation [params navigator offsets]
(let [colnum (:colnum params)
colinc (:colinc params)
current (get-column (:base @@*out*))
space-count (cond
(< current colnum) (- colnum current)
(= colinc 0) 0
:else (- colinc (rem (- current colnum) colinc)))]
(print (apply str (repeat space-count \space))))
navigator)
(defn- relative-tabulation [params navigator offsets]
(let [colrel (:colnum params)
colinc (:colinc params)
start-col (+ colrel (get-column (:base @@*out*)))
offset (if (pos? colinc) (rem start-col colinc) 0)
space-count (+ colrel (if (= 0 offset) 0 (- colinc offset)))]
(print (apply str (repeat space-count \space))))
navigator)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; Support for accessing the pretty printer from a format
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; TODO: support ~@; per-line-prefix separator
;; TODO: get the whole format wrapped so we can start the lb at any column
(defn- format-logical-block [params navigator offsets]
(let [clauses (:clauses params)
clause-count (count clauses)
prefix (cond
(> clause-count 1) (:string (:params (first (first clauses))))
(:colon params) "(")
body (nth clauses (if (> clause-count 1) 1 0))
suffix (cond
(> clause-count 2) (:string (:params (first (nth clauses 2))))
(:colon params) ")")
[arg navigator] (next-arg navigator)]
(pprint-logical-block :prefix prefix :suffix suffix
(execute-sub-format
body
(init-navigator arg)
(:base-args params)))
navigator))
(defn- set-indent [params navigator offsets]
(let [relative-to (if (:colon params) :current :block)]
(pprint-indent relative-to (:n params))
navigator))
;;; TODO: support ~:T section options for ~T
(defn- conditional-newline [params navigator offsets]
(let [kind (if (:colon params)
(if (:at params) :mandatory :fill)
(if (:at params) :miser :linear))]
(pprint-newline kind)
navigator))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; The table of directives we support, each with its params,
;;; properties, and the compilation function
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defdirectives
(\A
[:mincol [0 js/Number] :colinc [1 js/Number] :minpad [0 js/Number] :padchar [\space js/String]]
#{:at :colon :both} {}
#(format-ascii print-str %1 %2 %3))
(\S
[:mincol [0 js/Number] :colinc [1 js/Number] :minpad [0 js/Number] :padchar [\space js/String]]
#{:at :colon :both} {}
#(format-ascii pr-str %1 %2 %3))
(\D
[:mincol [0 js/Number] :padchar [\space js/String] :commachar [\, js/String]
:commainterval [3 js/Number]]
#{:at :colon :both} {}
#(format-integer 10 %1 %2 %3))
(\B
[:mincol [0 js/Number] :padchar [\space js/String] :commachar [\, js/String]
:commainterval [3 js/Number]]
#{:at :colon :both} {}
#(format-integer 2 %1 %2 %3))
(\O
[:mincol [0 js/Number] :padchar [\space js/String] :commachar [\, js/String]
:commainterval [3 js/Number]]
#{:at :colon :both} {}
#(format-integer 8 %1 %2 %3))
(\X
[:mincol [0 js/Number] :padchar [\space js/String] :commachar [\, js/String]
:commainterval [3 js/Number]]
#{:at :colon :both} {}
#(format-integer 16 %1 %2 %3))
(\R
[:base [nil js/Number] :mincol [0 js/Number] :padchar [\space js/String] :commachar [\, js/String]
:commainterval [3 js/Number]]
#{:at :colon :both} {}
(do
(cond ; ~R is overloaded with bizareness
(first (:base params)) #(format-integer (:base %1) %1 %2 %3)
(and (:at params) (:colon params)) #(format-old-roman %1 %2 %3)
(:at params) #(format-new-roman %1 %2 %3)
(:colon params) #(format-ordinal-english %1 %2 %3)
true #(format-cardinal-english %1 %2 %3))))
(\P
[]
#{:at :colon :both} {}
(fn [params navigator offsets]
(let [navigator (if (:colon params) (relative-reposition navigator -1) navigator)
strs (if (:at params) ["y" "ies"] ["" "s"])
[arg navigator] (next-arg navigator)]
(print (if (= arg 1) (first strs) (second strs)))
navigator)))
(\C
[:char-format [nil js/String]]
#{:at :colon :both} {}
(cond
(:colon params) pretty-character
(:at params) readable-character
:else plain-character))
(\F
[:w [nil js/Number] :d [nil js/Number] :k [0 js/Number] :overflowchar [nil js/String]
:padchar [\space js/String]]
#{:at} {}
fixed-float)
(\E
[:w [nil js/Number] :d [nil js/Number] :e [nil js/Number] :k [1 js/Number]
:overflowchar [nil js/String] :padchar [\space js/String]
:exponentchar [nil js/String]]
#{:at} {}
exponential-float)
(\G
[:w [nil js/Number] :d [nil js/Number] :e [nil js/Number] :k [1 js/Number]
:overflowchar [nil js/String] :padchar [\space js/String]
:exponentchar [nil js/String]]
#{:at} {}
general-float)
(\$
[:d [2 js/Number] :n [1 js/Number] :w [0 js/Number] :padchar [\space js/String]]
#{:at :colon :both} {}
dollar-float)
(\%
[:count [1 js/Number]]
#{} {}
(fn [params arg-navigator offsets]
(dotimes [i (:count params)]
(prn))
arg-navigator))
(\&
[:count [1 js/Number]]
#{:pretty} {}
(fn [params arg-navigator offsets]
(let [cnt (:count params)]
(if (pos? cnt) (fresh-line))
(dotimes [i (dec cnt)]
(prn)))
arg-navigator))
(\|
[:count [1 js/Number]]
#{} {}
(fn [params arg-navigator offsets]
(dotimes [i (:count params)]
(print \formfeed))
arg-navigator))
(\~
[:n [1 js/Number]]
#{} {}
(fn [params arg-navigator offsets]
(let [n (:n params)]
(print (apply str (repeat n \~)))
arg-navigator)))
(\newline ;; Whitespace supression is handled in the compilation loop
[]
#{:colon :at} {}
(fn [params arg-navigator offsets]
(if (:at params)
(prn))
arg-navigator))
(\T
[:colnum [1 js/Number] :colinc [1 js/Number]]
#{:at :pretty} {}
(if (:at params)
#(relative-tabulation %1 %2 %3)
#(absolute-tabulation %1 %2 %3)))
(\*
[:n [1 js/Number]]
#{:colon :at} {}
(fn [params navigator offsets]
(let [n (:n params)]
(if (:at params)
(absolute-reposition navigator n)
(relative-reposition navigator (if (:colon params) (- n) n))))))
(\?
[]
#{:at} {}
(if (:at params)
(fn [params navigator offsets] ; args from main arg list
(let [[subformat navigator] (get-format-arg navigator)]
(execute-sub-format subformat navigator (:base-args params))))
(fn [params navigator offsets] ; args from sub-list
(let [[subformat navigator] (get-format-arg navigator)
[subargs navigator] (next-arg navigator)
sub-navigator (init-navigator subargs)]
(execute-sub-format subformat sub-navigator (:base-args params))
navigator))))
(\(
[]
#{:colon :at :both} {:right \), :allows-separator nil, :else nil}
(let [mod-case-writer (cond
(and (:at params) (:colon params))
upcase-writer
(:colon params)
capitalize-word-writer
(:at params)
init-cap-writer
:else
downcase-writer)]
#(modify-case mod-case-writer %1 %2 %3)))
(\) [] #{} {} nil)
(\[
[:selector [nil js/Number]]
#{:colon :at} {:right \], :allows-separator true, :else :last}
(cond
(:colon params)
boolean-conditional
(:at params)
check-arg-conditional
true
choice-conditional))
(\; [:min-remaining [nil js/Number] :max-columns [nil js/Number]]
#{:colon} {:separator true} nil)
(\] [] #{} {} nil)
(\{
[:max-iterations [nil js/Number]]
#{:colon :at :both} {:right \}, :allows-separator false}
(cond
(and (:at params) (:colon params))
iterate-main-sublists
(:colon params)
iterate-list-of-sublists
(:at params)
iterate-main-list
true
iterate-sublist))
(\} [] #{:colon} {} nil)
(\<
[:mincol [0 js/Number] :colinc [1 js/Number] :minpad [0 js/Number] :padchar [\space js/String]]
#{:colon :at :both :pretty} {:right \>, :allows-separator true, :else :first}
logical-block-or-justify)
(\> [] #{:colon} {} nil)
;; TODO: detect errors in cases where colon not allowed
(\^ [:arg1 [nil js/Number] :arg2 [nil js/Number] :arg3 [nil js/Number]]
#{:colon} {}
(fn [params navigator offsets]
(let [arg1 (:arg1 params)
arg2 (:arg2 params)
arg3 (:arg3 params)
exit (if (:colon params) :colon-up-arrow :up-arrow)]
(cond
(and arg1 arg2 arg3)
(if (<= arg1 arg2 arg3) [exit navigator] navigator)
(and arg1 arg2)
(if (= arg1 arg2) [exit navigator] navigator)
arg1
(if (= arg1 0) [exit navigator] navigator)
true ; TODO: handle looking up the arglist stack for info
(if (if (:colon params)
(empty? (:rest (:base-args params)))
(empty? (:rest navigator)))
[exit navigator] navigator)))))
(\W
[]
#{:at :colon :both :pretty} {}
(if (or (:at params) (:colon params))
(let [bindings (concat
(if (:at params) [:level nil :length nil] [])
(if (:colon params) [:pretty true] []))]
(fn [params navigator offsets]
(let [[arg navigator] (next-arg navigator)]
(if (apply write arg bindings)
[:up-arrow navigator]
navigator))))
(fn [params navigator offsets]
(let [[arg navigator] (next-arg navigator)]
(if (write-out arg)
[:up-arrow navigator]
navigator)))))
(\_
[]
#{:at :colon :both} {}
conditional-newline)
(\I
[:n [0 js/Number]]
#{:colon} {}
set-indent)
)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Code to manage the parameters and flags associated with each
;; directive in the format string.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(def ^{:private true}
param-pattern #"^([vV]|#|('.)|([+-]?\d+)|(?=,))")
(def ^{:private true}
special-params #{:parameter-from-args :remaining-arg-count})
(defn- extract-param [[s offset saw-comma]]
(let [m (js/RegExp. (.-source param-pattern) "g")
param (.exec m s)]
(if param
(let [token-str (first param)
remainder (subs s (.-lastIndex m))
new-offset (+ offset (.-lastIndex m))]
(if (not (= \, (nth remainder 0)))
[[token-str offset] [remainder new-offset false]]
[[token-str offset] [(subs remainder 1) (inc new-offset) true]]))
(if saw-comma
(format-error "Badly formed parameters in format directive" offset)
[nil [s offset]]))))
(defn- extract-params [s offset]
(consume extract-param [s offset false]))
(defn- translate-param
"Translate the string representation of a param to the internalized
representation"
[[p offset]]
[(cond
(= (.-length p) 0) nil
(and (= (.-length p) 1) (contains? #{\v \V} (nth p 0))) :parameter-from-args
(and (= (.-length p) 1) (= \# (nth p 0))) :remaining-arg-count
(and (= (.-length p) 2) (= \' (nth p 0))) (nth p 1)
true (js/parseInt p 10))
offset])
(def ^{:private true}
flag-defs {\: :colon, \@ :at})
(defn- extract-flags [s offset]
(consume
(fn [[s offset flags]]
(if (empty? s)
[nil [s offset flags]]
(let [flag (get flag-defs (first s))]
(if flag
(if (contains? flags flag)
(format-error
(str "Flag \"" (first s) "\" appears more than once in a directive")
offset)
[true [(subs s 1) (inc offset) (assoc flags flag [true offset])]])
[nil [s offset flags]]))))
[s offset {}]))
(defn- check-flags [def flags]
(let [allowed (:flags def)]
(if (and (not (:at allowed)) (:at flags))
(format-error (str "\"@\" is an illegal flag for format directive \"" (:directive def) "\"")
(nth (:at flags) 1)))
(if (and (not (:colon allowed)) (:colon flags))
(format-error (str "\":\" is an illegal flag for format directive \"" (:directive def) "\"")
(nth (:colon flags) 1)))
(if (and (not (:both allowed)) (:at flags) (:colon flags))
(format-error (str "Cannot combine \"@\" and \":\" flags for format directive \""
(:directive def) "\"")
(min (nth (:colon flags) 1) (nth (:at flags) 1))))))
(defn- map-params
"Takes a directive definition and the list of actual parameters and
a map of flags and returns a map of the parameters and flags with defaults
filled in. We check to make sure that there are the right types and number
of parameters as well."
[def params flags offset]
(check-flags def flags)
(if (> (count params) (count (:params def)))
(format-error
(cl-format
nil
"Too many parameters for directive \"~C\": ~D~:* ~[were~;was~:;were~] specified but only ~D~:* ~[are~;is~:;are~] allowed"
(:directive def) (count params) (count (:params def)))
(second (first params))))
(doall
(map #(let [val (first %1)]
(if (not (or (nil? val) (contains? special-params val)
(= (second (second %2)) (type val))))
(format-error (str "Parameter " (name (first %2))
" has bad type in directive \"" (:directive def) "\": "
(type val))
(second %1))) )
params (:params def)))
(merge ; create the result map
(into (array-map) ; start with the default values, make sure the order is right
(reverse (for [[name [default]] (:params def)] [name [default offset]])))
(reduce #(apply assoc %1 %2) {} (filter #(first (nth % 1)) (zipmap (keys (:params def)) params))) ; add the specified parameters, filtering out nils
flags)); and finally add the flags
(defn- compile-directive [s offset]
(let [[raw-params [rest offset]] (extract-params s offset)
[_ [rest offset flags]] (extract-flags rest offset)
directive (first rest)
def (get directive-table (string/upper-case directive))
params (if def (map-params def (map translate-param raw-params) flags offset))]
(if (not directive)
(format-error "Format string ended in the middle of a directive" offset))
(if (not def)
(format-error (str "Directive \"" directive "\" is undefined") offset))
[(compiled-directive. ((:generator-fn def) params offset) def params offset)
(let [remainder (subs rest 1)
offset (inc offset)
trim? (and (= \newline (:directive def))
(not (:colon params)))
trim-count (if trim? (prefix-count remainder [\space \tab]) 0)
remainder (subs remainder trim-count)
offset (+ offset trim-count)]
[remainder offset])]))
(defn- compile-raw-string [s offset]
(compiled-directive. (fn [_ a _] (print s) a) nil {:string s} offset))
(defn- right-bracket [this] (:right (:bracket-info (:def this))))
(defn- separator? [this] (:separator (:bracket-info (:def this))))
(defn- else-separator? [this]
(and (:separator (:bracket-info (:def this)))
(:colon (:params this))))
(declare ^{:arglists '([bracket-info offset remainder])} collect-clauses)
(defn- process-bracket [this remainder]
(let [[subex remainder] (collect-clauses (:bracket-info (:def this))
(:offset this) remainder)]
[(compiled-directive.
(:func this) (:def this)
(merge (:params this) (tuple-map subex (:offset this)))
(:offset this))
remainder]))
(defn- process-clause [bracket-info offset remainder]
(consume
(fn [remainder]
(if (empty? remainder)
(format-error "No closing bracket found." offset)
(let [this (first remainder)
remainder (next remainder)]
(cond
(right-bracket this)
(process-bracket this remainder)
(= (:right bracket-info) (:directive (:def this)))
[ nil [:right-bracket (:params this) nil remainder]]
(else-separator? this)
[nil [:else nil (:params this) remainder]]
(separator? this)
[nil [:separator nil nil remainder]] ;; TODO: check to make sure that there are no params on ~;
true
[this remainder]))))
remainder))
(defn- collect-clauses [bracket-info offset remainder]
(second
(consume
(fn [[clause-map saw-else remainder]]
(let [[clause [type right-params else-params remainder]]
(process-clause bracket-info offset remainder)]
(cond
(= type :right-bracket)
[nil [(merge-with concat clause-map
{(if saw-else :else :clauses) [clause]
:right-params right-params})
remainder]]
(= type :else)
(cond
(:else clause-map)
(format-error "Two else clauses (\"~:;\") inside bracket construction." offset)
(not (:else bracket-info))
(format-error "An else clause (\"~:;\") is in a bracket type that doesn't support it."
offset)
(and (= :first (:else bracket-info)) (seq (:clauses clause-map)))
(format-error
"The else clause (\"~:;\") is only allowed in the first position for this directive."
offset)
true ; if the ~:; is in the last position, the else clause
; is next, this was a regular clause
(if (= :first (:else bracket-info))
[true [(merge-with concat clause-map {:else [clause] :else-params else-params})
false remainder]]
[true [(merge-with concat clause-map {:clauses [clause]})
true remainder]]))
(= type :separator)
(cond
saw-else
(format-error "A plain clause (with \"~;\") follows an else clause (\"~:;\") inside bracket construction." offset)
(not (:allows-separator bracket-info))
(format-error "A separator (\"~;\") is in a bracket type that doesn't support it."
offset)
true
[true [(merge-with concat clause-map {:clauses [clause]})
false remainder]]))))
[{:clauses []} false remainder])))
(defn- process-nesting
"Take a linearly compiled format and process the bracket directives to give it
the appropriate tree structure"
[format]
(first
(consume
(fn [remainder]
(let [this (first remainder)
remainder (next remainder)
bracket (:bracket-info (:def this))]
(if (:right bracket)
(process-bracket this remainder)
[this remainder])))
format)))
(defn- compile-format
"Compiles format-str into a compiled format which can be used as an argument
to cl-format just like a plain format string. Use this function for improved
performance when you're using the same format string repeatedly"
[format-str]
(binding [*format-str* format-str]
(process-nesting
(first
(consume
(fn [[s offset]]
(if (empty? s)
[nil s]
(let [tilde (.indexOf s \~)]
(cond
(neg? tilde) [(compile-raw-string s offset) ["" (+ offset (.-length s))]]
(zero? tilde) (compile-directive (subs s 1) (inc offset))
true
[(compile-raw-string (subs s 0 tilde) offset) [(subs s tilde) (+ tilde offset)]]))))
[format-str 0])))))
(defn- needs-pretty
"determine whether a given compiled format has any directives that depend on the
column number or pretty printing"
[format]
(loop [format format]
(if (empty? format)
false
(if (or (:pretty (:flags (:def (first format))))
(some needs-pretty (first (:clauses (:params (first format)))))
(some needs-pretty (first (:else (:params (first format))))))
true
(recur (next format))))))
;;NB We depart from the original api. In clj, if execute-format is called multiple times with the same stream or
;; called on *out*, the results are different than if the same calls are made with different streams or printing
;; to a string. The reason is that mutating the underlying stream changes the result by changing spacing.
;;
;; clj:
;; * stream => "1 2 3"
;; * true (prints to *out*) => "1 2 3"
;; * nil (prints to string) => "1 2 3"
;; cljs:
;; * stream => "1 2 3"
;; * true (prints via *print-fn*) => "1 2 3"
;; * nil (prints to string) => "1 2 3"
(defn- execute-format
"Executes the format with the arguments."
{:skip-wiki true}
([stream format args]
(let [sb (StringBuffer.)
real-stream (if (or (not stream) (true? stream))
(StringBufferWriter. sb)
stream)
wrapped-stream (if (and (needs-pretty format)
(not (pretty-writer? real-stream)))
(get-pretty-writer real-stream)
real-stream)]
(binding [*out* wrapped-stream]
(try
(execute-format format args)
(finally
(if-not (identical? real-stream wrapped-stream)
(-flush wrapped-stream))))
(cond
(not stream) (str sb)
(true? stream) (string-print (str sb))
:else nil))))
([format args]
(map-passing-context
(fn [element context]
(if (abort? context)
[nil context]
(let [[params args] (realize-parameter-list
(:params element) context)
[params offsets] (unzip-map params)
params (assoc params :base-args args)]
[nil (apply (:func element) [params args offsets])])))
args
format)
nil))
;;; This is a bad idea, but it prevents us from leaking private symbols
;;; This should all be replaced by really compiled formats anyway.
(def ^{:private true} cached-compile (memoize compile-format))
;;======================================================================
;; dispatch.clj
;;======================================================================
(defn- use-method
"Installs a function as a new method of multimethod associated with dispatch-value. "
[multifn dispatch-val func]
(-add-method multifn dispatch-val func))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Implementations of specific dispatch table entries
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; Handle forms that can be "back-translated" to reader macros
;;; Not all reader macros can be dealt with this way or at all.
;;; Macros that we can't deal with at all are:
;;; ; - The comment character is absorbed by the reader and never is part of the form
;;; ` - Is fully processed at read time into a lisp expression (which will contain concats
;;; and regular quotes).
;;; ~@ - Also fully eaten by the processing of ` and can't be used outside.
;;; , - is whitespace and is lost (like all other whitespace). Formats can generate commas
;;; where they deem them useful to help readability.
;;; ^ - Adding metadata completely disappears at read time and the data appears to be
;;; completely lost.
;;;
;;; Most other syntax stuff is dealt with directly by the formats (like (), [], {}, and #{})
;;; or directly by printing the objects using Clojure's built-in print functions (like
;;; :keyword, \char, or ""). The notable exception is #() which is special-cased.
(def ^{:private true} reader-macros
{'quote "'"
'var "#'"
'clojure.core/deref "@",
'clojure.core/unquote "~"
'cljs.core/deref "@",
'cljs.core/unquote "~"})
(defn- pprint-reader-macro [alis]
(let [macro-char (reader-macros (first alis))]
(when (and macro-char (= 2 (count alis)))
(-write *out* macro-char)
(write-out (second alis))
true)))
;;======================================================================
;; Dispatch for the basic data types when interpreted
;; as data (as opposed to code).
;;======================================================================
;;; TODO: inline these formatter statements into funcs so that we
;;; are a little easier on the stack. (Or, do "real" compilation, a
;;; la Common Lisp)
;;; (def pprint-simple-list (formatter-out "~:<~@{~w~^ ~_~}~:>"))
(defn- pprint-simple-list [alis]
(pprint-logical-block :prefix "(" :suffix ")"
(print-length-loop [alis (seq alis)]
(when alis
(write-out (first alis))
(when (next alis)
(-write *out* " ")
(pprint-newline :linear)
(recur (next alis)))))))
(defn- pprint-list [alis]
(if-not (pprint-reader-macro alis)
(pprint-simple-list alis)))
;;; (def pprint-vector (formatter-out "~<[~;~@{~w~^ ~_~}~;]~:>"))
(defn- pprint-vector [avec]
(pprint-logical-block :prefix "[" :suffix "]"
(print-length-loop [aseq (seq avec)]
(when aseq
(write-out (first aseq))
(when (next aseq)
(-write *out* " ")
(pprint-newline :linear)
(recur (next aseq)))))))
(def ^{:private true} pprint-array (formatter-out "~<[~;~@{~w~^, ~:_~}~;]~:>"))
;;; (def pprint-map (formatter-out "~<{~;~@{~<~w~^ ~_~w~:>~^, ~_~}~;}~:>"))
(defn- pprint-map [amap]
(let [[ns lift-map] (when (not (record? amap))
(#'cljs.core/lift-ns amap))
amap (or lift-map amap)
prefix (if ns (str "#:" ns "{") "{")]
(pprint-logical-block :prefix prefix :suffix "}"
(print-length-loop [aseq (seq amap)]
(when aseq
;;compiler gets confused with nested macro if it isn't namespaced
;;it tries to use clojure.pprint/pprint-logical-block for some reason
(m/pprint-logical-block
(write-out (ffirst aseq))
(-write *out* " ")
(pprint-newline :linear)
(set! *current-length* 0) ;always print both parts of the [k v] pair
(write-out (fnext (first aseq))))
(when (next aseq)
(-write *out* ", ")
(pprint-newline :linear)
(recur (next aseq))))))))
(defn- pprint-simple-default [obj]
;;TODO: Update to handle arrays (?) and suppressing namespaces
(-write *out* (pr-str obj)))
(def pprint-set (formatter-out "~<#{~;~@{~w~^ ~:_~}~;}~:>"))
(def ^{:private true}
type-map {"core$future_call" "Future",
"core$promise" "Promise"})
(defn- map-ref-type
"Map ugly type names to something simpler"
[name]
(or (when-let [match (re-find #"^[^$]+\$[^$]+" name)]
(type-map match))
name))
(defn- pprint-ideref [o]
(let [prefix (str "#<" (map-ref-type (.-name (type o)))
"@" (goog/getUid o) ": ")]
(pprint-logical-block :prefix prefix :suffix ">"
(pprint-indent :block (-> (count prefix) (- 2) -))
(pprint-newline :linear)
(write-out
(if (and (satisfies? IPending o) (not (-realized? o)))
:not-delivered
@o)))))
(def ^{:private true} pprint-pqueue (formatter-out "~<<-(~;~@{~w~^ ~_~}~;)-<~:>"))
(defn- type-dispatcher [obj]
(cond
(instance? PersistentQueue obj) :queue
(satisfies? IDeref obj) :deref
(symbol? obj) :symbol
(seq? obj) :list
(map? obj) :map
(vector? obj) :vector
(set? obj) :set
(nil? obj) nil
:default :default))
(defmulti simple-dispatch
"The pretty print dispatch function for simple data structure format."
type-dispatcher)
(use-method simple-dispatch :list pprint-list)
(use-method simple-dispatch :vector pprint-vector)
(use-method simple-dispatch :map pprint-map)
(use-method simple-dispatch :set pprint-set)
(use-method simple-dispatch nil #(-write *out* (pr-str nil)))
(use-method simple-dispatch :default pprint-simple-default)
(set-pprint-dispatch simple-dispatch)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; Dispatch for the code table
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(declare ^{:arglists '([alis])} pprint-simple-code-list)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; Format the namespace ("ns") macro. This is quite complicated because of all the
;;; different forms supported and because programmers can choose lists or vectors
;;; in various places.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn- brackets
"Figure out which kind of brackets to use"
[form]
(if (vector? form)
["[" "]"]
["(" ")"]))
(defn- pprint-ns-reference
"Pretty print a single reference (import, use, etc.) from a namespace decl"
[reference]
(if (sequential? reference)
(let [[start end] (brackets reference)
[keyw & args] reference]
(pprint-logical-block :prefix start :suffix end
((formatter-out "~w~:i") keyw)
(loop [args args]
(when (seq args)
((formatter-out " "))
(let [arg (first args)]
(if (sequential? arg)
(let [[start end] (brackets arg)]
(pprint-logical-block :prefix start :suffix end
(if (and (= (count arg) 3) (keyword? (second arg)))
(let [[ns kw lis] arg]
((formatter-out "~w ~w ") ns kw)
(if (sequential? lis)
((formatter-out (if (vector? lis)
"~<[~;~@{~w~^ ~:_~}~;]~:>"
"~<(~;~@{~w~^ ~:_~}~;)~:>"))
lis)
(write-out lis)))
(apply (formatter-out "~w ~:i~@{~w~^ ~:_~}") arg)))
(when (next args)
((formatter-out "~_"))))
(do
(write-out arg)
(when (next args)
((formatter-out "~:_"))))))
(recur (next args))))))
(write-out reference)))
(defn- pprint-ns
"The pretty print dispatch chunk for the ns macro"
[alis]
(if (next alis)
(let [[ns-sym ns-name & stuff] alis
[doc-str stuff] (if (string? (first stuff))
[(first stuff) (next stuff)]
[nil stuff])
[attr-map references] (if (map? (first stuff))
[(first stuff) (next stuff)]
[nil stuff])]
(pprint-logical-block :prefix "(" :suffix ")"
((formatter-out "~w ~1I~@_~w") ns-sym ns-name)
(when (or doc-str attr-map (seq references))
((formatter-out "~@:_")))
(when doc-str
(cl-format true "\"~a\"~:[~;~:@_~]" doc-str (or attr-map (seq references))))
(when attr-map
((formatter-out "~w~:[~;~:@_~]") attr-map (seq references)))
(loop [references references]
(pprint-ns-reference (first references))
(when-let [references (next references)]
(pprint-newline :linear)
(recur references)))))
(write-out alis)))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; Format something that looks like a simple def (sans metadata, since the reader
;;; won't give it to us now).
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(def ^{:private true} pprint-hold-first (formatter-out "~:<~w~^ ~@_~w~^ ~_~@{~w~^ ~_~}~:>"))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; Format something that looks like a defn or defmacro
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; Format the params and body of a defn with a single arity
(defn- single-defn [alis has-doc-str?]
(if (seq alis)
(do
(if has-doc-str?
((formatter-out " ~_"))
((formatter-out " ~@_")))
((formatter-out "~{~w~^ ~_~}") alis))))
;;; Format the param and body sublists of a defn with multiple arities
(defn- multi-defn [alis has-doc-str?]
(if (seq alis)
((formatter-out " ~_~{~w~^ ~_~}") alis)))
;;; TODO: figure out how to support capturing metadata in defns (we might need a
;;; special reader)
(defn- pprint-defn [alis]
(if (next alis)
(let [[defn-sym defn-name & stuff] alis
[doc-str stuff] (if (string? (first stuff))
[(first stuff) (next stuff)]
[nil stuff])
[attr-map stuff] (if (map? (first stuff))
[(first stuff) (next stuff)]
[nil stuff])]
(pprint-logical-block :prefix "(" :suffix ")"
((formatter-out "~w ~1I~@_~w") defn-sym defn-name)
(if doc-str
((formatter-out " ~_~w") doc-str))
(if attr-map
((formatter-out " ~_~w") attr-map))
;; Note: the multi-defn case will work OK for malformed defns too
(cond
(vector? (first stuff)) (single-defn stuff (or doc-str attr-map))
:else (multi-defn stuff (or doc-str attr-map)))))
(pprint-simple-code-list alis)))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; Format something with a binding form
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn- pprint-binding-form [binding-vec]
(pprint-logical-block :prefix "[" :suffix "]"
(print-length-loop [binding binding-vec]
(when (seq binding)
(pprint-logical-block binding
(write-out (first binding))
(when (next binding)
(-write *out* " ")
(pprint-newline :miser)
(write-out (second binding))))
(when (next (rest binding))
(-write *out* " ")
(pprint-newline :linear)
(recur (next (rest binding))))))))
(defn- pprint-let [alis]
(let [base-sym (first alis)]
(pprint-logical-block :prefix "(" :suffix ")"
(if (and (next alis) (vector? (second alis)))
(do
((formatter-out "~w ~1I~@_") base-sym)
(pprint-binding-form (second alis))
((formatter-out " ~_~{~w~^ ~_~}") (next (rest alis))))
(pprint-simple-code-list alis)))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; Format something that looks like "if"
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(def ^{:private true} pprint-if (formatter-out "~:<~1I~w~^ ~@_~w~@{ ~_~w~}~:>"))
(defn- pprint-cond [alis]
(pprint-logical-block :prefix "(" :suffix ")"
(pprint-indent :block 1)
(write-out (first alis))
(when (next alis)
(-write *out* " ")
(pprint-newline :linear)
(print-length-loop [alis (next alis)]
(when alis
(pprint-logical-block alis
(write-out (first alis))
(when (next alis)
(-write *out* " ")
(pprint-newline :miser)
(write-out (second alis))))
(when (next (rest alis))
(-write *out* " ")
(pprint-newline :linear)
(recur (next (rest alis)))))))))
(defn- pprint-condp [alis]
(if (> (count alis) 3)
(pprint-logical-block :prefix "(" :suffix ")"
(pprint-indent :block 1)
(apply (formatter-out "~w ~@_~w ~@_~w ~_") alis)
(print-length-loop [alis (seq (drop 3 alis))]
(when alis
(pprint-logical-block alis
(write-out (first alis))
(when (next alis)
(-write *out* " ")
(pprint-newline :miser)
(write-out (second alis))))
(when (next (rest alis))
(-write *out* " ")
(pprint-newline :linear)
(recur (next (rest alis)))))))
(pprint-simple-code-list alis)))
;;; The map of symbols that are defined in an enclosing #() anonymous function
(def ^:dynamic ^{:private true} *symbol-map* {})
(defn- pprint-anon-func [alis]
(let [args (second alis)
nlis (first (rest (rest alis)))]
(if (vector? args)
(binding [*symbol-map* (if (= 1 (count args))
{(first args) "%"}
(into {}
(map
#(vector %1 (str \% %2))
args
(range 1 (inc (count args))))))]
((formatter-out "~<#(~;~@{~w~^ ~_~}~;)~:>") nlis))
(pprint-simple-code-list alis))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; The master definitions for formatting lists in code (that is, (fn args...) or
;;; special forms).
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; This is the equivalent of (formatter-out "~:<~1I~@{~w~^ ~_~}~:>"), but is
;;; easier on the stack.
(defn- pprint-simple-code-list [alis]
(pprint-logical-block :prefix "(" :suffix ")"
(pprint-indent :block 1)
(print-length-loop [alis (seq alis)]
(when alis
(write-out (first alis))
(when (next alis)
(-write *out* " ")
(pprint-newline :linear)
(recur (next alis)))))))
;;; Take a map with symbols as keys and add versions with no namespace.
;;; That is, if ns/sym->val is in the map, add sym->val to the result.
(defn- two-forms [amap]
(into {}
(mapcat
identity
(for [x amap]
[x [(symbol (name (first x))) (second x)]]))))
(defn- add-core-ns [amap]
(let [core "clojure.core"]
(into {}
(map #(let [[s f] %]
(if (not (or (namespace s) (special-symbol? s)))
[(symbol core (name s)) f]
%))
amap))))
(def ^:dynamic ^{:private true} *code-table*
(two-forms
(add-core-ns
{'def pprint-hold-first, 'defonce pprint-hold-first,
'defn pprint-defn, 'defn- pprint-defn, 'defmacro pprint-defn, 'fn pprint-defn,
'let pprint-let, 'loop pprint-let, 'binding pprint-let,
'with-local-vars pprint-let, 'with-open pprint-let, 'when-let pprint-let,
'if-let pprint-let, 'doseq pprint-let, 'dotimes pprint-let,
'when-first pprint-let,
'if pprint-if, 'if-not pprint-if, 'when pprint-if, 'when-not pprint-if,
'cond pprint-cond, 'condp pprint-condp,
'fn* pprint-anon-func,
'. pprint-hold-first, '.. pprint-hold-first, '-> pprint-hold-first,
'locking pprint-hold-first, 'struct pprint-hold-first,
'struct-map pprint-hold-first, 'ns pprint-ns
})))
(defn- pprint-code-list [alis]
(if-not (pprint-reader-macro alis)
(if-let [special-form (*code-table* (first alis))]
(special-form alis)
(pprint-simple-code-list alis))))
(defn- pprint-code-symbol [sym]
(if-let [arg-num (sym *symbol-map*)]
(print arg-num)
(if *print-suppress-namespaces*
(print (name sym))
(pr sym))))
(defmulti
code-dispatch
"The pretty print dispatch function for pretty printing Clojure code."
{:added "1.2" :arglists '[[object]]}
type-dispatcher)
(use-method code-dispatch :list pprint-code-list)
(use-method code-dispatch :symbol pprint-code-symbol)
;; The following are all exact copies of simple-dispatch
(use-method code-dispatch :vector pprint-vector)
(use-method code-dispatch :map pprint-map)
(use-method code-dispatch :set pprint-set)
(use-method code-dispatch :queue pprint-pqueue)
(use-method code-dispatch :deref pprint-ideref)
(use-method code-dispatch nil pr)
(use-method code-dispatch :default pprint-simple-default)
(set-pprint-dispatch simple-dispatch)
;;; For testing
(comment
(with-pprint-dispatch code-dispatch
(pprint
'(defn cl-format
"An implementation of a Common Lisp compatible format function"
[stream format-in & args]
(let [compiled-format (if (string? format-in) (compile-format format-in) format-in)
navigator (init-navigator args)]
(execute-format stream compiled-format navigator)))))
(with-pprint-dispatch code-dispatch
(pprint
'(defn cl-format
[stream format-in & args]
(let [compiled-format (if (string? format-in) (compile-format format-in) format-in)
navigator (init-navigator args)]
(execute-format stream compiled-format navigator)))))
(with-pprint-dispatch code-dispatch
(pprint
'(defn- -write
([this x]
(condp = (class x)
String
(let [s0 (write-initial-lines this x)
s (.replaceFirst s0 "\\s+$" "")
white-space (.substring s0 (count s))
mode (getf :mode)]
(if (= mode :writing)
(dosync
(write-white-space this)
(.col_write this s)
(setf :trailing-white-space white-space))
(add-to-buffer this (make-buffer-blob s white-space))))
Integer
(let [c ^Character x]
(if (= (getf :mode) :writing)
(do
(write-white-space this)
(.col_write this x))
(if (= c (int \newline))
(write-initial-lines this "\n")
(add-to-buffer this (make-buffer-blob (str (char c)) nil))))))))))
(with-pprint-dispatch code-dispatch
(pprint
'(defn pprint-defn [writer alis]
(if (next alis)
(let [[defn-sym defn-name & stuff] alis
[doc-str stuff] (if (string? (first stuff))
[(first stuff) (next stuff)]
[nil stuff])
[attr-map stuff] (if (map? (first stuff))
[(first stuff) (next stuff)]
[nil stuff])]
(pprint-logical-block writer :prefix "(" :suffix ")"
(cl-format true "~w ~1I~@_~w" defn-sym defn-name)
(if doc-str
(cl-format true " ~_~w" doc-str))
(if attr-map
(cl-format true " ~_~w" attr-map))
;; Note: the multi-defn case will work OK for malformed defns too
(cond
(vector? (first stuff)) (single-defn stuff (or doc-str attr-map))
:else (multi-defn stuff (or doc-str attr-map)))))
(pprint-simple-code-list writer alis)))))
)
;;======================================================================
;; print_table.clj
;;======================================================================
(defn- add-padding [width s]
(let [padding (max 0 (- width (count s)))]
(apply str (clojure.string/join (repeat padding \space)) s)))
(defn print-table
"Prints a collection of maps in a textual table. Prints table headings
ks, and then a line of output for each row, corresponding to the keys
in ks. If ks are not specified, use the keys of the first item in rows."
{:added "1.3"}
([ks rows]
(when (seq rows)
(let [widths (map
(fn [k]
(apply max (count (str k)) (map #(count (str (get % k))) rows)))
ks)
spacers (map #(apply str (repeat % "-")) widths)
fmt-row (fn [leader divider trailer row]
(str leader
(apply str (interpose divider
(for [[col width] (map vector (map #(get row %) ks) widths)]
(add-padding width (str col)))))
trailer))]
(cljs.core/println)
(cljs.core/println (fmt-row "| " " | " " |" (zipmap ks ks)))
(cljs.core/println (fmt-row "|-" "-+-" "-|" (zipmap ks spacers)))
(doseq [row rows]
(cljs.core/println (fmt-row "| " " | " " |" row))))))
([rows] (print-table (keys (first rows)) rows)))