Clojure 计算算术表达式的postwalk

Clojure 计算算术表达式的postwalk,clojure,eval,interpreter,instaparse,Clojure,Eval,Interpreter,Instaparse,我正在尝试使用Instaparse制作一个简单的算术表达式计算器。解析器似乎工作正常,但我不知道如何计算返回的嵌套向量。目前我正在使用postwalk,如下所示 (ns test5.core (:require [instaparse.core :as insta]) (:require [clojure.walk :refer [postwalk]]) (:gen-class)) (def WS (insta/parser "WS = #'\\s+'")) (

我正在尝试使用Instaparse制作一个简单的算术表达式计算器。解析器似乎工作正常,但我不知道如何计算返回的嵌套向量。目前我正在使用postwalk,如下所示

(ns test5.core
  (:require [instaparse.core :as insta])
  (:require [clojure.walk  :refer [postwalk]])
  (:gen-class))


(def WS
  (insta/parser
    "WS = #'\\s+'"))


(def transform-options
  {:IntLiteral read-string})


(def parser
  (insta/parser
    "AddExpr = AddExpr '+' MultExpr
      | AddExpr '-' MultExpr
      | MultExpr

     MultExpr = MultExpr '*' IntLiteral
      | MultExpr '/' IntLiteral
      | IntLiteral

     IntLiteral = #'[0-9]+'"

    :auto-whitespace WS))


(defn parse[input]
  (->> (parser input)
       (insta/transform transform-options)))


(defn visit [node]
  (println node)
  (cond
    (number? node) node
    (string? node) (resolve (symbol node))
    (vector? node)
      (cond
        (= :MultExpr (first node)) (visit (rest node))
        (= :AddExpr (first node)) (visit (rest node))
        :else node)
    :else node))


(defn evaluate [tree]
  (println tree)
  (postwalk visit tree))


(defn -main
  [& args]
  (evaluate (parse "1 * 2 + 3")))
postwalk确实遍历了向量,但我得到了一个嵌套列表作为结果,例如

((((1) #'clojure.core/* 2)) #'clojure.core/+ (3))

使用
org.clojure/core.match
。根据当前语法,可以将求值函数编写为:

(defn eval expr[expr]
(匹配表达式)
[:MultExpr e1“*”e2](*(评估表达式e1)
(评估表e2)
[:MultExpr e1”/“e2](/(eval expr e1)
(评估表e2)
[:加法器e1“+”e2](+(求值表达式e1)
(评估表e2)
[:加法器e1“-”e2]((计算表达式e1)
(评估表e2)
[:MultExpr e1](评估表达式e1)
[:加法器e1](计算表达式e1)
:else expr)
并通过以下方式进行评估:

(>“1*2+3”
作语法分析
评估(出口)
;; => 5.

使用
org.clojure/core.match
。根据当前语法,可以将求值函数编写为:

(defn eval expr[expr]
(匹配表达式)
[:MultExpr e1“*”e2](*(评估表达式e1)
(评估表e2)
[:MultExpr e1”/“e2](/(eval expr e1)
(评估表e2)
[:加法器e1“+”e2](+(求值表达式e1)
(评估表e2)
[:加法器e1“-”e2]((计算表达式e1)
(评估表e2)
[:MultExpr e1](评估表达式e1)
[:加法器e1](计算表达式e1)
:else expr)
并通过以下方式进行评估:

(>“1*2+3”
作语法分析
评估(出口)
;; => 5.

这正是我第一次创建Tupelo森林库的原因

请看地图

我已经开始了。你也可以看到


更新 以下是如何使用Tupelo Forest库完成此操作:

首先,使用Hiccup格式定义抽象语法树(AST)数据:

  (with-forest (new-forest)
    (let [data-hiccup      [:rpc
                            [:fn {:type :+}
                             [:value 2]
                             [:value 3]]]
          root-hid         (add-tree-hiccup data-hiccup)
结果:

(hid->bush root-hid) => 
[{:tag :rpc}
 [{:type :+, :tag :fn}
  [{:tag :value, :value 2}]
  [{:tag :value, :value 3}]]]
Display walk-tree processing:
curr-node => {:tupelo.forest/khids [], :tag :value, :value 2}
curr-node => {:tupelo.forest/khids [], :tag :value, :value 3}
curr-node => {:tupelo.forest/khids [1037 1038], :type :+, :tag :fn}
curr-node => {:tupelo.forest/khids [1039], :tag :rpc}
使用“显示拦截器”显示行走树的工作方式

结果:

(hid->bush root-hid) => 
[{:tag :rpc}
 [{:type :+, :tag :fn}
  [{:tag :value, :value 2}]
  [{:tag :value, :value 3}]]]
Display walk-tree processing:
curr-node => {:tupelo.forest/khids [], :tag :value, :value 2}
curr-node => {:tupelo.forest/khids [], :tag :value, :value 3}
curr-node => {:tupelo.forest/khids [1037 1038], :type :+, :tag :fn}
curr-node => {:tupelo.forest/khids [1039], :tag :rpc}
然后定义操作符和拦截器来转换子树,如
(+23)
=>5

  op->fn           {:+ +
                    :* *}
  math-interceptor {:leave (fn [path]
                             (let [curr-hid  (xlast path)
                                   curr-node (hid->node curr-hid)
                                   curr-tag  (grab :tag curr-node)]
                               (when (= :fn curr-tag)
                                 (let [curr-op    (grab :type curr-node)
                                       curr-fn    (grab curr-op op->fn)
                                       kid-hids   (hid->kids curr-hid)
                                       kid-values (mapv hid->value kid-hids)                                           
                                       result-val (apply curr-fn kid-values)]
                                   (set-node curr-hid {:tag :value :value result-val} [])))))}  
  ]  ; end of let form

; imperative step replaces old nodes with result of math op
(walk-tree root-hid math-interceptor)
然后,我们可以显示修改后的AST树,其中包含
(+23)


您可以。

这正是我第一次创建Tupelo森林库的原因

请看地图

我已经开始了。你也可以看到


更新 以下是如何使用Tupelo Forest库完成此操作:

首先,使用Hiccup格式定义抽象语法树(AST)数据:

  (with-forest (new-forest)
    (let [data-hiccup      [:rpc
                            [:fn {:type :+}
                             [:value 2]
                             [:value 3]]]
          root-hid         (add-tree-hiccup data-hiccup)
结果:

(hid->bush root-hid) => 
[{:tag :rpc}
 [{:type :+, :tag :fn}
  [{:tag :value, :value 2}]
  [{:tag :value, :value 3}]]]
Display walk-tree processing:
curr-node => {:tupelo.forest/khids [], :tag :value, :value 2}
curr-node => {:tupelo.forest/khids [], :tag :value, :value 3}
curr-node => {:tupelo.forest/khids [1037 1038], :type :+, :tag :fn}
curr-node => {:tupelo.forest/khids [1039], :tag :rpc}
使用“显示拦截器”显示行走树的工作方式

结果:

(hid->bush root-hid) => 
[{:tag :rpc}
 [{:type :+, :tag :fn}
  [{:tag :value, :value 2}]
  [{:tag :value, :value 3}]]]
Display walk-tree processing:
curr-node => {:tupelo.forest/khids [], :tag :value, :value 2}
curr-node => {:tupelo.forest/khids [], :tag :value, :value 3}
curr-node => {:tupelo.forest/khids [1037 1038], :type :+, :tag :fn}
curr-node => {:tupelo.forest/khids [1039], :tag :rpc}
然后定义操作符和拦截器来转换子树,如
(+23)
=>5

  op->fn           {:+ +
                    :* *}
  math-interceptor {:leave (fn [path]
                             (let [curr-hid  (xlast path)
                                   curr-node (hid->node curr-hid)
                                   curr-tag  (grab :tag curr-node)]
                               (when (= :fn curr-tag)
                                 (let [curr-op    (grab :type curr-node)
                                       curr-fn    (grab curr-op op->fn)
                                       kid-hids   (hid->kids curr-hid)
                                       kid-values (mapv hid->value kid-hids)                                           
                                       result-val (apply curr-fn kid-values)]
                                   (set-node curr-hid {:tag :value :value result-val} [])))))}  
  ]  ; end of let form

; imperative step replaces old nodes with result of math op
(walk-tree root-hid math-interceptor)
然后,我们可以显示修改后的AST树,其中包含
(+23)


您可以。

这没有使用Instaparse或clojure.walk,但我有一个东西,可以仅使用
reduce
评估中缀数学:

(defn evaluate
  "Evaluates an infix arithmetic form e.g. (1 + 1 * 2)."
  [e]
  (let [eval-op (fn [op a b]
                  (let [f (resolve op)]
                    (f a b)))]
    (reduce
      (fn [[v op] elem]
        (cond
          (coll? elem)
          (if op
            [(eval-op op v (first (evaluate elem))) nil]
            [(first (evaluate elem)) nil])

          (and op (number? elem))
          [(eval-op op v elem) nil]

          (number? elem)
          [elem nil]

          (symbol? elem)
          [v elem]

          :else
          (throw (ex-info "Invalid evaluation" {:v v :op op :elem (type elem)}))))
      [0 nil]
      e)))

(first (evaluate (clojure.edn/read-string "(1 * 2 + 3)")))
=> 5
(first (evaluate (clojure.edn/read-string "(1 * 2 + (3 * 5))")))
=> 17
这需要输入字符串来表示有效的Clojure列表。我还有一个分组乘法/除法的功能:

(defn pemdas
  "Groups division/multiplication operations in e into lists."
  [e]
  (loop [out []
         rem e]
    (if (empty? rem)
      (seq out)
      (let [curr (first rem)
            next' (second rem)]
        (if (contains? #{'/ '*} next')
          (recur (conj out (list curr next' (nth rem 2)))
                 (drop 3 rem))
          (recur (conj out curr) (rest rem)))))))

(pemdas '(9.87 + 4 / 3 * 0.41))
=> (9.87 + (4 / 3) * 0.41)

这不使用Instaparse或clojure.walk,但我只使用
reduce
来评估中缀数学:

(defn evaluate
  "Evaluates an infix arithmetic form e.g. (1 + 1 * 2)."
  [e]
  (let [eval-op (fn [op a b]
                  (let [f (resolve op)]
                    (f a b)))]
    (reduce
      (fn [[v op] elem]
        (cond
          (coll? elem)
          (if op
            [(eval-op op v (first (evaluate elem))) nil]
            [(first (evaluate elem)) nil])

          (and op (number? elem))
          [(eval-op op v elem) nil]

          (number? elem)
          [elem nil]

          (symbol? elem)
          [v elem]

          :else
          (throw (ex-info "Invalid evaluation" {:v v :op op :elem (type elem)}))))
      [0 nil]
      e)))

(first (evaluate (clojure.edn/read-string "(1 * 2 + 3)")))
=> 5
(first (evaluate (clojure.edn/read-string "(1 * 2 + (3 * 5))")))
=> 17
这需要输入字符串来表示有效的Clojure列表。我还有一个分组乘法/除法的功能:

(defn pemdas
  "Groups division/multiplication operations in e into lists."
  [e]
  (loop [out []
         rem e]
    (if (empty? rem)
      (seq out)
      (let [curr (first rem)
            next' (second rem)]
        (if (contains? #{'/ '*} next')
          (recur (conj out (list curr next' (nth rem 2)))
                 (drop 3 rem))
          (recur (conj out curr) (rest rem)))))))

(pemdas '(9.87 + 4 / 3 * 0.41))
=> (9.87 + (4 / 3) * 0.41)

谢谢,我很感激。我是Clojure的新手,仍然在为基础之外的事情挣扎。谢谢,我很感激。我对Clojure还不熟悉,但仍在为基础之外的事情而挣扎。