Skip to content

Commit

Permalink
add boolean expressions
Browse files Browse the repository at this point in the history
  • Loading branch information
oakmac authored and cburgmer committed Apr 27, 2022
1 parent 894e7b4 commit 8eeb586
Show file tree
Hide file tree
Showing 4 changed files with 75 additions and 23 deletions.
2 changes: 1 addition & 1 deletion src/json_path/parser.clj
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
(cond
(some boolean-ops-strings expr) (parse-boolean-expr expr)
(some (set (keys comparator-ops)) expr) (parse-comparator-expr expr)
:else [:some (parse expr)]))
:else [:bool (parse expr)]))

(defn parse-indexer [remaining]
(let [next (first remaining)]
Expand Down
42 changes: 31 additions & 11 deletions src/json_path/walker.clj
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,34 @@

(declare walk eval-expr)

(defn eval-eq-expr [op-form context operands]
(apply op-form (map #(eval-expr % context) operands)))
(defn map-with-value? [m]
(and (map? m)
(contains? m :value)))

(defn eval-bool-expr [op-form context operands]
(boolean (apply op-form (map #(eval-expr % context) operands))))

(def boolean-ops
"expression operands that result in a boolean result"
{:eq =
:neq not=
:lt <
:lt-eq <=
:gt >
:gt-eq >=
;; NOTE: 'and' and 'or' are macros in Clojure, so we need to wrap them in functions here
:and #(and %1 %2)
:or #(or %1 %2)})

(defn eval-expr [[expr-type & operands :as expr] context]
(let [ops {:eq =, :neq not=, :lt <, :lt-eq <=, :gt >, :gt-eq >=, :and every?}]
(cond
(contains? ops expr-type) (eval-eq-expr (expr-type ops) context operands)
(= expr-type :some) (some? (:value (walk (first operands) context)))
(= expr-type :val) (first operands)
(= expr-type :path) (:value (walk expr context)))))
(cond
(contains? boolean-ops expr-type) (eval-bool-expr (get boolean-ops expr-type) context operands)
(= expr-type :bool) (let [inner-val (walk (first operands) context)]
(if (map-with-value? inner-val)
(:value inner-val)
inner-val))
(= expr-type :val) (first operands)
(= expr-type :path) (:value (walk expr context))))

(defn map# [func obj]
(if (seq? obj)
Expand Down Expand Up @@ -81,10 +99,12 @@
(filter (fn [[key val]] (eval-expr (second sel-expr) (assoc context :current (m/root val)))))
(map (fn [[key val]] (m/with-context key val (:current context))))))))

(defn walk [[opcode operand continuation] context]
(defn walk [[opcode operand continuation :as expr] context]
(let [down-obj (cond
(= opcode :path) (walk-path operand context)
(= opcode :selector) (walk-selector operand context))]
(= opcode :path) (walk-path operand context)
(= opcode :selector) (walk-selector operand context)
(= opcode :val) (eval-expr expr context)
:else nil)]
(if continuation
(map# #(walk continuation (assoc context :current %)) down-obj)
down-obj)))
12 changes: 6 additions & 6 deletions test/json_path/test/parser_test.clj
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,11 @@
(parse-expr '("\"" "bar" "\"" "=" "3.1415")) => [:eq [:val "bar"] [:val 3.1415]])

(facts "boolean expressions should be parseable"
(parse-expr '("\"" "bar" "\"" "&&" "\"" "bar" "\"")) => [:and [:some [:val "bar"]] [:some [:val "bar"]]]
(parse-expr '("\"" "bar" "\"" "&&" "true")) => [:and [:some [:val "bar"]] [:some [:val true]]]
(parse-expr '("false" "&&" "\"" "bar" "\"")) => [:and [:some [:val false]] [:some [:val "bar"]]]
(parse-expr '("\"" "bar" "\"" "||" "\"" "bar" "\"")) => [:or [:some [:val "bar"]] [:some [:val "bar"]]]
(parse-expr '("\"" "bar" "\"" "=" "42" "&&" "\"" "bar" "\"")) => [:and [:eq [:val "bar"] [:val 42]] [:some [:val "bar"]]])
(parse-expr '("\"" "bar" "\"" "&&" "\"" "bar" "\"")) => [:and [:bool [:val "bar"]] [:bool [:val "bar"]]]
(parse-expr '("\"" "bar" "\"" "&&" "true")) => [:and [:bool [:val "bar"]] [:bool [:val true]]]
(parse-expr '("false" "&&" "\"" "bar" "\"")) => [:and [:bool [:val false]] [:bool [:val "bar"]]]
(parse-expr '("\"" "bar" "\"" "||" "\"" "bar" "\"")) => [:or [:bool [:val "bar"]] [:bool [:val "bar"]]]
(parse-expr '("\"" "bar" "\"" "=" "42" "&&" "\"" "bar" "\"")) => [:and [:eq [:val "bar"] [:val 42]] [:bool [:val "bar"]]])

(fact
(parse-indexer '("*")) => [:index "*"]
Expand All @@ -51,7 +51,7 @@
(parse-path "$.foo[3]") => [:path [[:root] [:child] [:key "foo"]] [:selector [:index "3"]]]
(parse-path "foo[*]") => [:path [[:key "foo"]] [:selector [:index "*"]]]
(parse-path "$[?(@.baz)]") => [:path [[:root]]
[:selector [:filter [:some [:path [[:current]
[:selector [:filter [:bool [:path [[:current]
[:child]
[:key "baz"]]]]]]]
(parse-path "$[?(@.bar<2)]") => [:path [[:root]]
Expand Down
42 changes: 37 additions & 5 deletions test/json_path/test/walker_test.clj
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,50 @@

(unfinished)

(facts
(facts "evaluate expressions"
(eval-expr [:eq [:val "a"] [:val "b"]] {}) => falsey
(eval-expr [:eq [:val "a"] [:val "a"]] {}) => truthy
(eval-expr [:neq [:val "a"] [:val "b"]] {}) => truthy
(eval-expr [:lt [:val 10] [:val 11]] {}) => truthy
(eval-expr [:lt-eq [:val 10] [:val 10]] {}) => truthy
(eval-expr [:gt [:val 10] [:val 9]] {}) => truthy
(eval-expr [:gt-eq [:val 10] [:val 9]] {}) => truthy
(eval-expr [:gt-eq [:val 10] [:val 10]] {}) => truthy
(eval-expr [:path [[:key "foo"]]] {:current (m/root {:foo "bar"})}) => "bar"
(eval-expr [:some [:path [[:current] [:child] [:key "foo"]]]] {:current (m/root {:foo "bar"})}) => truthy
(eval-expr [:some [:path [[:current] [:child] [:key "foo"]]]] {:current (m/root {:foo nil})}) => falsey
(eval-expr [:eq [:path [[:key "foo"]]] [:val "bar"]] {:current (m/root {:foo "bar"})}) => truthy)
(eval-expr [:bool [:path [[:current] [:child] [:key "foo"]]]] {:current (m/root {:foo "bar"})}) => truthy
(eval-expr [:bool [:path [[:current] [:child] [:key "foo"]]]] {:current (m/root {:foo nil})}) => falsey
(eval-expr [:eq [:path [[:key "foo"]]] [:val "bar"]] {:current (m/root {:foo "bar"})}) => truthy

(eval-expr [:val true] {}) => truthy
(eval-expr [:val false] {}) => falsey
(eval-expr [:bool [:val true]] {}) => truthy
(eval-expr [:bool [:val false]] {}) => falsey

;; 'and' expressions
(eval-expr [:and [:bool [:val true]] [:bool [:val true]]] {}) => truthy
(eval-expr [:and [:bool [:val true]] [:bool [:val false]]] {}) => falsey
(eval-expr [:and [:bool [:val false]] [:bool [:val true]]] {}) => falsey
(eval-expr [:and [:bool [:val false]] [:bool [:val false]]] {}) => falsey
(eval-expr [:and [:bool [:val true]] [:bool [:val "bar"]]] {}) => truthy
(eval-expr [:and [:bool [:val "bar"]] [:bool [:val false]]] {}) => falsey
(eval-expr [:and [:bool [:val true]] [:lt [:val 10] [:val 11]]] {}) => truthy
(eval-expr [:and [:bool [:val true]]
[:path [[:key "foo"]]]] {:current (m/root {:foo "bar"}) => truthy})
(eval-expr [:and [:bool [:val true]]
[:path [[:key "foo"]]]] {:current (m/root {:foo nil}) => falsey})

;; 'or' expressions
(eval-expr [:or [:bool [:val true]] [:bool [:val true]]] {}) => truthy
(eval-expr [:or [:bool [:val true]] [:bool [:val false]]] {}) => truthy
(eval-expr [:or [:bool [:val false]] [:bool [:val true]]] {}) => truthy
(eval-expr [:or [:bool [:val false]] [:bool [:val false]]] {}) => falsey
(eval-expr [:or [:bool [:val true]] [:bool [:val "bar"]]] {}) => truthy
(eval-expr [:or [:bool [:val "bar"]] [:bool [:val false]]] {}) => truthy
(eval-expr [:or [:bool [:val true]] [:lt [:val 10] [:val 11]]] {}) => truthy
(eval-expr [:or [:bool [:val false]]
[:path [[:key "foo"]]]] {:current (m/root {:foo "bar"}) => truthy})
(eval-expr [:or [:bool [:val false]]
[:path [[:key "foo"]]]] {:current (m/root {:foo nil}) => falsey}))

(facts
(select-by [:key "hello"] (m/root {:hello "world"})) => (m/create "world" [:hello])
Expand Down Expand Up @@ -117,7 +149,7 @@
{:root (m/root {:foo [{:bar "wrong" :hello "goodbye"}
{:bar "baz" :hello "world"}]})}) => (list (m/create "world" [:foo 1 :hello]))
(walk [:path [[:root]]
[:selector [:filter [:some [:path [[:current]
[:selector [:filter [:bool [:path [[:current]
[:child]
[:key "bar"]]]]]]]
{:root (m/root {:hello "world" :foo {:bar "baz"}})}) => (list (m/create {:bar "baz"} [:foo])))
Expand Down

0 comments on commit 8eeb586

Please sign in to comment.