1
votes

après la marche pour évaluer l'expression arithmétique

J'essaie d'utiliser Instaparse pour créer un évaluateur d'expression arithmétique simple. L'analyseur semble fonctionner correctement mais je ne peux pas comprendre comment évaluer le vecteur imbriqué renvoyé. Actuellement, j'utilise postwalk, comme ceci

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

postwalk traverse le vecteur mais j'obtiens une liste imbriquée comme résultat, par exemple

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

p >


0 commentaires

3 Réponses :


2
votes

Utilisez org.clojure / core.match . En vous basant sur votre grammaire actuelle, vous pouvez écrire la fonction d'évaluation comme:

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

et l'évaluer avec:

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


0 commentaires

1
votes

C'est précisément ce problème qui explique pourquoi j'ai créé la bibliothèque de la forêt de Tupelo.

Veuillez consulter la présentation de Clojure Conj 2017 .

J'ai commencé quelques documents ici . Vous pouvez également voir des exemples en direct ici a >.


Mise à jour

Voici comment vous pouvez utiliser la bibliothèque de la forêt de Tupelo pour le faire:

Tout d'abord, définissez votre arbre de syntaxe abstraite (AST ) données au format Hiccup:

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

avec résultat:

  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)

Montrez comment walk-tree code > fonctionne en utilisant un "intercepteur d'affichage"

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}

avec résultat:

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

puis définissez les opérateurs et un intercepteur à transformer un sous-arbre comme (+ 2 3) => 5

(hid->bush root-hid) => 
[{:tag :rpc}
 [{:type :+, :tag :fn}
  [{:tag :value, :value 2}]
  [{:tag :value, :value 3}]]]

On peut alors afficher l'arbre AST modifié qui contient le résultat de ( + 2 3) :

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

Vous pouvez voir le code en direct ici .


0 commentaires

1
votes

Cela n'utilise ni Instaparse ni clojure.walk, mais voici quelque chose que j'avais pour évaluer les mathématiques infixes en utilisant uniquement reduce:

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

Cela nécessite la chaîne d'entrée pour représenter une liste Clojure valide. J'avais aussi cette fonction pour regrouper multiplication / division:

(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


1 commentaires

Merci, je l'apprécie. Je suis nouveau sur Clojure et j'ai toujours du mal avec tout ce qui dépasse les bases.