Clojure 计算算术表达式的postwalk
我正在尝试使用Instaparse制作一个简单的算术表达式计算器。解析器似乎工作正常,但我不知道如何计算返回的嵌套向量。目前我正在使用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+'")) (
(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还不熟悉,但仍在为基础之外的事情而挣扎。