漫步以评估算术表达式

时间:2019-02-14 08:36:54

标签: 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+'"))


(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))

3 个答案:

答案 0 :(得分:2)

使用(defn eval-expr [expr] (match expr [:MultExpr e1 "*" e2] (* (eval-expr e1) (eval-expr e2)) [:MultExpr e1 "/" e2] (/ (eval-expr e1) (eval-expr e2)) [:AddExpr e1 "+" e2] (+ (eval-expr e1) (eval-expr e2)) [:AddExpr e1 "-" e2] (- (eval-expr e1) (eval-expr e2)) [:MultExpr e1] (eval-expr e1) [:AddExpr e1] (eval-expr e1) :else expr)) 。根据您当前的语法,您可以将评估函数编写为:

(-> "1 * 2 + 3"
    parse
    eval-expr)
;; => 5

并使用:

def swap(x,y):
  L[x],L[y]=L[y],L[x]
  L = input("Enter names separated by space: ")
  L = L.split(" ")
    for x in range(len(L)):
       for y in range(len(L)-1):
          if L[y] > L[y+1]:
            swap(y,y+1)
print(L)

答案 1 :(得分:1)

这个确切的问题是为什么我首先创建Tupelo Forest库。

请参阅talk from Clojure Conj 2017

我已经开始some docs here。您还可以看到live examples here


更新

这是您可以使用Tupelo Forest库执行的操作:

首先,使用打ic格式定义抽象语法树(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}]]]

使用“显示拦截器”显示walk-tree的工作原理

  disp-interceptor {:leave (fn [path]
                             (let [curr-hid  (xlast path)
                                   curr-node (hid->node curr-hid)]
                               (spyx curr-node)))}
  >>        (do
              (println "Display walk-tree processing:")
              (walk-tree root-hid disp-interceptor))

结果:

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}

然后定义运算符和拦截器以转换子树,例如(+ 2 3) => 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)

然后我们可以显示包含(+ 2 3)的结果的修改后的AST树:

(hid->bush root-hid) => 
[{:tag :rpc} 
  [{:tag :value, :value 5}]]

您可以see the live code here

答案 2 :(得分:1)

这不使用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)