postwalk для оценки арифметического выражения - PullRequest
0 голосов
/ 14 февраля 2019

Я пытаюсь использовать 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 голосов
/ 14 февраля 2019

Именно поэтому я впервые создал библиотеку Tupelo Forest.

Пожалуйста, смотрите доклад Clojure Conj 2017 .

Я начал некоторые документы здесь .Вы также можете увидеть живых примеров здесь .


Обновление

Вот как вы можете использовать библиотеку Tupelo Forest для этого:

Сначала определите данные в дереве абстрактного синтаксиса (AST), используя формат Hiccup:

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

Затем мы можем отобразитьмодифицированное дерево AST, которое содержит результат (+ 2 3):

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

Вы можете увидеть живой код здесь .

0 голосов
/ 14 февраля 2019

Это не использует 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)
0 голосов
/ 14 февраля 2019

Используйте org.clojure/core.match.Основываясь на текущей грамматике, вы можете написать функцию оценки в виде:

(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

...