I am trying to evaluate an infixed expression in a string.
Some sample data to evaluate my code against:
(def data {:Location "US-NY-Location1"
:Priority 3})
(def qual "(Location = \"US\")")
I would like the qual
string to be converted to something like this form and evaluated by clojure:
(= (:Location data) "US")
I wrote the following macro to achieve this:
(defmacro parse-qual [[data-key op val] data-map]
`(~op ((keyword (str (quote ~data-key))) ~data-map) ~val))
and a helper function:
(defn eval-qual [qual-str data]
(eval `(parse-qual ~(clojure.edn/read-string qual-str) ~data)))
(eval-qual qual data)
provides me with the expected result
This is the first macro I have written and I am still trying to wrap my head around all the quoting and unquoting.
I want to know if there is a more efficient way to achieve the above? (Or even without the need for a macro at all)
How can I expand the macro to deal with nested expressions. To handle an expression like
((Location = "US") or (Priority > 2))
. Any pointers would be appreciated. I am currently trying to play withtree-seq
to solve this.How can I make this more robust and be more graceful in case of an invalid
qual
string.
I also wrote a second iteration of the parse-qual
macro as follows:
(defmacro parse-qual-2 [qual-str data-map]
(let [[data-key op val] (clojure.edn/read-string qual-str)]
`(~op ((keyword (str (quote ~data-key))) ~data-map) ~val)))
and on macroexpand
throws the following:
playfield.core> (macroexpand `(parse-qual-2 qual data))
java.lang.ClassCastException: clojure.lang.Symbol cannot be cast to java.lang.String
And I am at a loss on how to debug this!
Some extra information:
macroexpand
of parse-qual on the REPL gives me the following:
playfield.core> (macroexpand
`(parse-qual ~(clojure.edn/read-string qual) data))
(= ((clojure.core/keyword (clojure.core/str (quote Location))) playfield.core/data) "US")
Thank you @Alan Thompson, I was able to write this as a functions as follows, this also allows for nested expressions to be evaluated.
(def qual "(Location = \"US\")")
(def qual2 "((Location = \"US\") or (Priority > 2))")
(def qual3 "(Priority > 2)")
(def qual4 "(((Location = \"US\") or (Priority > 2)) and (Active = true))")
(defn eval-qual-2 [qual-str data]
(let [[l op r] (clojure.edn/read-string qual-str)]
(cond
(and (seq? l)
(seq? r)) (eval (list op (list eval-qual-2 (str l) data) (list eval-qual-2 (str r) data)))
(seq? l) (eval (list op (list eval-qual-2 (str l) data) r))
(seq? r) (eval (list op (list (keyword l) data) (list eval-qual-2 (str r) data)))
:else (eval (list op (list (keyword l) data) r)))))
(eval-qual-2 qual data) ; => false
(eval-qual-2 qual2 data) ; => true
(eval-qual-2 qual3 data) ; => true
(eval-qual-2 qual3 data) ; => true