How to convert instaparse output to a function that can be evaluated?

I use instaparse to parse a simple query language used by an end user that evaluates a boolean result, for example, "(AGE> 35) AND (GENDER =" MALE "), this query should then be applied to many thousands of rows of data to decide whether each line matches the expression or not.

My question is the best way to convert instaparse output to a function, which is subsequently evaluated on each line? For example, the above query will be converted to something like

fn [AGE GENDER] (AND (= AGE 35) (= GENDER "MAN"))

Please note that I am Clojure noob ...

+4
source share
2 answers

You can write a small compiler for the query language using instaparse to create a parsing tree, regular Clojure functions to convert it to Clojure code, and finally evalto create a Clojure function, which you can then apply to your records.

The initial call evalwill be somewhat expensive, but the resulting function will be equivalent to the manually recorded entry in the source file, and will not incur a performance penalty. In fact, this is one of the rare valid use cases eval- creating a function whose code is built really dynamically, which will then be called a large number of times.

, , , , .

, , :

(def p (insta/parser "

expr = and-expr | pred
and-expr = <'('> expr <')'> ws? <'AND'> ws? <'('> expr <')'>
pred = (atom ws? rel ws? atom)
rel = '<' | '>' | '='
atom = symbol | number | string
symbol = #'[A-Z]+'
string = <'\"'> #'[A-Za-z0-9]+' <'\"'>
number = #'\\d+'
<ws> = <#'\\s+'>

"))

:

[:expr
 [:and-expr
  [:expr
   [:pred [:atom [:symbol "AGE"]] [:rel ">"] [:atom [:number "35"]]]]
  [:expr
   [:pred
    [:atom [:symbol "GENDER"]]
    [:rel "="]
    [:atom [:string "MALE"]]]]]]

, Clojure ; ctx , , :

(defmulti expr-to-sexp (fn [expr ctx] (first expr)))

(defmethod expr-to-sexp :symbol [[_ name] ctx]
  (let [name (clojure.string/lower-case name)
        sym  (symbol name)]
    (swap! ctx conj sym)
    sym))

(defmethod expr-to-sexp :string [[_ s] ctx]
  s)

(defmethod expr-to-sexp :number [[_ n] ctx]
  (Long/parseLong n))

(defmethod expr-to-sexp :atom [[_ a] ctx]
  (expr-to-sexp a ctx))

(defmethod expr-to-sexp :rel [[_ name] ctx]
  (symbol "clojure.core" name))

(defmethod expr-to-sexp :pred [[_ left rel right] ctx]
  (doall (map #(expr-to-sexp % ctx) [rel left right])))

(defmethod expr-to-sexp :and-expr [[_ left right] ctx]
  `(and ~(expr-to-sexp left ctx) ~(expr-to-sexp right ctx)))

(defmethod expr-to-sexp :expr [[_ child] ctx]
  (expr-to-sexp child ctx))

:

(expr-to-sexp (p "(AGE > 35) AND (GENDER = \"MALE\")") (atom #{}))
;= (clojure.core/and (clojure.core/> age 35) (clojure.core/= gender "MALE"))

(let [ctx (atom #{})]
  (expr-to-sexp (p "(AGE > 35) AND (GENDER = \"MALE\")") ctx)
  @ctx)
;= #{age gender}

, , Clojure:

(defn compile-expr [expr-string]
  (let [expr (p expr-string)
        ctx  (atom #{})
        body (expr-to-sexp expr ctx)]
    (eval `(fn [{:keys ~(vec @ctx)}] ~body))))

:

(def valid? (compile-expr "(AGE > 35) AND (GENDER = \"MALE\")"))

(valid? {:gender "MALE" :age 36})
;= true

(valid? {:gender "FEMALE" :age 36})
;= false
+7

, . , :)

clojure "filter" . instaparse, ():

(def ls  [{:age 10, :gender "m"} {:age 15 :gender "fm"}] )

, "ls" - . , "m", , 5, :

(filter (fn [a] (if (and (= (:gender a) "m") (> (:age a) 5)) a)) ls)

:

 ({:age 10, :gender "m"})

"fn" :

(filter #(if (and (= (:gender %1) "m") (> (:age %1) 5)) %1) ls)

, . ! , " ", :)

+1

All Articles