gpt4 book ai didi

parsing - 如何将 instaparse 输出转换为可以评估的函数?

转载 作者:行者123 更新时间:2023-12-02 16:43:53 25 4
gpt4 key购买 nike

我正在使用 instaparse 来解析最终用户使用的简单查询语言,该语言的计算结果为 bool 结果,例如“(AGE > 35) AND (GENDER = "MALE")”,然后需要将此查询应用于数千行数据来决定每一行是否满足表达式。

我的问题是将 instaparse 的输出转换为随后针对每一行进行评估的函数的最佳方法是什么?例如,上面的查询将被转换为类似

fn [年龄性别](AND(= 年龄 35)(= 性别“男性”))

请注意,我是 Clojure 菜鸟...

最佳答案

您可以为查询语言编写一个小型编译器,使用 instaparse 生成解析树,使用常规 Clojure 函数将其转换为 Clojure 代码,最后使用 eval 生成一个 Clojure 函数,然后您可以使用它适用于您的记录。

eval 的初始调用会有些昂贵,但生成的函数将相当于在源文件中手工编写的函数,并且不会带来性能损失。事实上,这是 eval 的罕见有效用例之一——生成一个函数,该函数的代码以真正动态的方式构建,然后将被多次调用。

显然,在采用这种方法时,您需要确保不会无意中允许不受信任的来源执行任意代码。

为了演示,这里有一个基于非常简单的语法的 instaparse 解析器,它只能解析您的示例查询:

(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

关于parsing - 如何将 instaparse 输出转换为可以评估的函数?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/21216991/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com