(defn- make-infix-renderer
"Base function for infix renderers. This is meant to be specialized via
options for the treatment desired. Returns a rendering function. The options are:
- `precedence-map`: a map from (symbol or keyword) to numbers. Higher numbers
mean higher precedence. This guides parenthesization.
- `juxtapose-multiply`: a string that will be placed between factors in a
product. Defaults to `*`.
- `infix?` A function mapping symbols to boolean, used to decide if a function
application should be written as `x f y` or `f(x, y)`.
- `render-primitive` is a function used to render symbols, numeric constants
etc. into string form.
- `parenthesize` is a function used to wrap parens around objects when
needed. It defaults to the obvious thing.
- `special-handlers` is a map from symbol to a function of operator and
arguments, used to provide custom rendering for things like exponentiation
which might not be rendered either as infix or prefix.
- `rename-functions` is a map supplying replacement function names to be used
just before the expression is written."
[& {:keys [juxtapose-multiply special-handlers infix? render-primitive
rename-functions parenthesize precedence-map rewrite-trig-squares]
:or {special-handlers {}
parenthesize #(str "(" % ")")
juxtapose-multiply " * "
rewrite-trig-squares false
rename-functions {}
infix? {}}}]
(letfn [(ratio-expr? [op [num denom]]
(and (= '/ op)
(v/integral? num)
(or (nil? denom)
(v/integral? denom))))
(precedence [op] (or (precedence-map op)
(cond (seq? op)
(cond (and (= 3 (count op))
(= 'expt (first op))) (recur (second op))
(= 'partial (first op)) (precedence-map 'D)
:else (precedence-map :apply))
(symbol? op) (precedence-map :apply)
:else 0)))
(precedence> [a b] (> (precedence a) (precedence b)))
(precedence<= [a b] (not (precedence> a b)))
(parenthesize-if [b x]
(if b (parenthesize x) x))
(maybe-rename-function [f]
(or (rename-functions f) f))
(maybe-rewrite-negation [loc]
(let [result (rewrite-negation (z/node loc))]
(if (identical? loc result)
loc
(z/replace loc result))))
(maybe-rewrite-trig-squares [loc]
(if-let [result (and rewrite-trig-squares
(rewrite-trig-powers
(z/node loc)))]
(z/replace loc result)
loc))
(render-unary-node [op arg upper-op]
(case op
(+ *) (str arg)
u- (if (= upper-op '+)
{:hint :unary-minus :term arg}
(str "- " arg))
/ (if (v/integral? arg)
(str "1/" arg)
(str "1 / " arg))
(str op " " arg)))
(render-loc [loc]
(if (z/branch? loc)
(let [fn-loc (-> loc maybe-rewrite-negation maybe-rewrite-trig-squares z/next)
arg-loc (loop [a (-> fn-loc z/right)]
(let [a' (z/replace a (render-loc a))]
(if-let [r (z/right a')]
(recur r)
(z/up a'))))
[op & args] (z/node arg-loc)
upper-op (and (z/up arg-loc)
(-> arg-loc z/leftmost z/node))]
(if (infix? op)
(parenthesize-if
(and (infix? upper-op)
(and (precedence<= op upper-op)
(not (or (and (= op '*) (= upper-op 'u-))
(ratio-expr? op args)))))
(or (when-let [handler (special-handlers op)]
(handler args))
(cond
(= (count args) 1)
(render-unary-node op (first args) upper-op)
(= op '+)
(let [u-term (fn [t] (let [{:keys [hint term]} t]
(if hint
[(if (= hint :unary-minus) "-" "+") term]
["+" t])))
[t & terms] (map u-term args)
terms (cons (cond
(= (first t) "+")
(subvec t 1)
(= (first t) "-")
(assoc t 0 "- ")
:else t)
(for [[pm t] terms] [(str " " pm " ") t]))]
(transduce cat str terms))
:else
(let [sep (case op
* (or juxtapose-multiply " * ")
expt "^"
(str " " op " "))]
(transduce (interpose sep) str args)))))
(parenthesize-if
(and upper-op
(infix? upper-op)
(precedence<= op upper-op))
(or (and (special-handlers op)
((special-handlers op) args))
(str (parenthesize-if (and (z/branch? fn-loc)
(precedence> :apply (z/node (z/next fn-loc))))
(maybe-rename-function (render-loc (z/next arg-loc))))
(parenthesize-if (or (precedence<= op :apply)
(> (count args) 1)
(z/branch? (z/right fn-loc)))
(s/join ", " args)))))))
(let [n (z/node loc)]
(or (and render-primitive (render-primitive n))
n))))]
(fn [expr]
(let [result (-> (g/freeze expr)
(z/seq-zip)
(render-loc))]
(if (string? result)
result
(str result))))))