clojureevalinterpreterinstaparse

postwalk to evaluate arithmetic expression


I am trying to use Instaparse to make a simple arithmetic expression evaluator. The parser seems to work fine but I cannot figure out how to evaluate the returned nested vector. Currently I am using postwalk, like this

(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 does traverse the vector but I get a nested list as the result, eg

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

Solution

  • Use org.clojure/core.match. Base on your current grammar, you can write the evaluation function as:

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

    and evaluate with:

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