I'm trying to write a Clojure macro that creates a prefix notation list for evaluation from a simple infix notation list, say (2 * 3 + 4 * 2)
to an evaluated(+ (* 2 3) (*4 2))
(resulting in 14 being returned).
I have written the following code:
(defmacro infix [op inlist]
(let [[i1 i2 & i3] inlist
last-group? (nil? (second i3))]
(if last-group?
`(if (= ~op ~i2)
(~i2 ~i1 ~(first i3)) ; return unevaluated prefix list
(~i1 ~i2 ~(first i3))) ; return unevaluated infix list
`(if (= ~op ~i2)
; recur with prefix list in i1 position
(infix ~op ~(conj (rest i3) (list i2 i1 (first i3)) ))
; return as list: i1 and i2, recur i3 (probably wrong)
(~i1 ~i2 (infix ~op ~i3))
))))
With the intention of enforcing operator precedence by calling the macro recursively with different op
(operator function) parameters:
(infix + (infix * (2 * 3 + 4 * 2)))
Above, I'm just using it with two *
and +
, but ultimately I'd want to call the macro for all (or at least for the sake of this exercise, / * + -) operators.
When I execute the above nested macro call, I get the following error:
CompilerException java.lang.RuntimeException: Can't take value of a macro: #'cbat.ch7.ex2/infix, compiling:(/tmp/form-init4661580047453041691.clj:1:1)
Calling the macro for a single operator and a list of the same operator (i.e. (infix * (2 * 3 * 4))
) works as expected. If I call the macro with a single (i1 i2 i3)
list, if op
differs from i2
, it tries to (understandably) return the unevaluated infix list with the error:
ClassCastException java.lang.Long cannot be cast to clojure.lang.IFn cbat.ch7.ex2/eval3003 (form-init4661580047453041691.clj:1)
I was hoping calling the macro recursively would mean that I could process the unevaluated infix list before the entire line was evaluated, but this doesn't seem to work.
I'm pretty sure the else branch of the latter, inner if
(i.e. (~i1 ~i2 (infix ~op ~i3))
) is incorrect and I may just need the inner infix call, but I'm more concerned with getting the nested macro calls for the different operators working prior to evaluation.
I know that this isn't the usual way of converting infix to prefix notation, and have since found out about Dijkstra's shunting-yard algorithm, but please could someone kindly enlighten me as to:
- whether such nested macro calls are possible?
- whether my logic is reasonable, and not too far from a solution? If so...
- ... what changes I need to make to get things running?
I'm really focused on learning Clojure, so any thorough explanation (where possible) will be most welcome.