Skip to content

Commit 8eeb586

Browse files
oakmaccburgmer
authored andcommitted
add boolean expressions
1 parent 894e7b4 commit 8eeb586

File tree

4 files changed

+75
-23
lines changed

4 files changed

+75
-23
lines changed

src/json_path/parser.clj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
(cond
3131
(some boolean-ops-strings expr) (parse-boolean-expr expr)
3232
(some (set (keys comparator-ops)) expr) (parse-comparator-expr expr)
33-
:else [:some (parse expr)]))
33+
:else [:bool (parse expr)]))
3434

3535
(defn parse-indexer [remaining]
3636
(let [next (first remaining)]

src/json_path/walker.clj

Lines changed: 31 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,34 @@
33

44
(declare walk eval-expr)
55

6-
(defn eval-eq-expr [op-form context operands]
7-
(apply op-form (map #(eval-expr % context) operands)))
6+
(defn map-with-value? [m]
7+
(and (map? m)
8+
(contains? m :value)))
9+
10+
(defn eval-bool-expr [op-form context operands]
11+
(boolean (apply op-form (map #(eval-expr % context) operands))))
12+
13+
(def boolean-ops
14+
"expression operands that result in a boolean result"
15+
{:eq =
16+
:neq not=
17+
:lt <
18+
:lt-eq <=
19+
:gt >
20+
:gt-eq >=
21+
;; NOTE: 'and' and 'or' are macros in Clojure, so we need to wrap them in functions here
22+
:and #(and %1 %2)
23+
:or #(or %1 %2)})
824

925
(defn eval-expr [[expr-type & operands :as expr] context]
10-
(let [ops {:eq =, :neq not=, :lt <, :lt-eq <=, :gt >, :gt-eq >=, :and every?}]
11-
(cond
12-
(contains? ops expr-type) (eval-eq-expr (expr-type ops) context operands)
13-
(= expr-type :some) (some? (:value (walk (first operands) context)))
14-
(= expr-type :val) (first operands)
15-
(= expr-type :path) (:value (walk expr context)))))
26+
(cond
27+
(contains? boolean-ops expr-type) (eval-bool-expr (get boolean-ops expr-type) context operands)
28+
(= expr-type :bool) (let [inner-val (walk (first operands) context)]
29+
(if (map-with-value? inner-val)
30+
(:value inner-val)
31+
inner-val))
32+
(= expr-type :val) (first operands)
33+
(= expr-type :path) (:value (walk expr context))))
1634

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

84-
(defn walk [[opcode operand continuation] context]
102+
(defn walk [[opcode operand continuation :as expr] context]
85103
(let [down-obj (cond
86-
(= opcode :path) (walk-path operand context)
87-
(= opcode :selector) (walk-selector operand context))]
104+
(= opcode :path) (walk-path operand context)
105+
(= opcode :selector) (walk-selector operand context)
106+
(= opcode :val) (eval-expr expr context)
107+
:else nil)]
88108
(if continuation
89109
(map# #(walk continuation (assoc context :current %)) down-obj)
90110
down-obj)))

test/json_path/test/parser_test.clj

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,11 @@
2222
(parse-expr '("\"" "bar" "\"" "=" "3.1415")) => [:eq [:val "bar"] [:val 3.1415]])
2323

2424
(facts "boolean expressions should be parseable"
25-
(parse-expr '("\"" "bar" "\"" "&&" "\"" "bar" "\"")) => [:and [:some [:val "bar"]] [:some [:val "bar"]]]
26-
(parse-expr '("\"" "bar" "\"" "&&" "true")) => [:and [:some [:val "bar"]] [:some [:val true]]]
27-
(parse-expr '("false" "&&" "\"" "bar" "\"")) => [:and [:some [:val false]] [:some [:val "bar"]]]
28-
(parse-expr '("\"" "bar" "\"" "||" "\"" "bar" "\"")) => [:or [:some [:val "bar"]] [:some [:val "bar"]]]
29-
(parse-expr '("\"" "bar" "\"" "=" "42" "&&" "\"" "bar" "\"")) => [:and [:eq [:val "bar"] [:val 42]] [:some [:val "bar"]]])
25+
(parse-expr '("\"" "bar" "\"" "&&" "\"" "bar" "\"")) => [:and [:bool [:val "bar"]] [:bool [:val "bar"]]]
26+
(parse-expr '("\"" "bar" "\"" "&&" "true")) => [:and [:bool [:val "bar"]] [:bool [:val true]]]
27+
(parse-expr '("false" "&&" "\"" "bar" "\"")) => [:and [:bool [:val false]] [:bool [:val "bar"]]]
28+
(parse-expr '("\"" "bar" "\"" "||" "\"" "bar" "\"")) => [:or [:bool [:val "bar"]] [:bool [:val "bar"]]]
29+
(parse-expr '("\"" "bar" "\"" "=" "42" "&&" "\"" "bar" "\"")) => [:and [:eq [:val "bar"] [:val 42]] [:bool [:val "bar"]]])
3030

3131
(fact
3232
(parse-indexer '("*")) => [:index "*"]
@@ -51,7 +51,7 @@
5151
(parse-path "$.foo[3]") => [:path [[:root] [:child] [:key "foo"]] [:selector [:index "3"]]]
5252
(parse-path "foo[*]") => [:path [[:key "foo"]] [:selector [:index "*"]]]
5353
(parse-path "$[?(@.baz)]") => [:path [[:root]]
54-
[:selector [:filter [:some [:path [[:current]
54+
[:selector [:filter [:bool [:path [[:current]
5555
[:child]
5656
[:key "baz"]]]]]]]
5757
(parse-path "$[?(@.bar<2)]") => [:path [[:root]]

test/json_path/test/walker_test.clj

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,50 @@
55

66
(unfinished)
77

8-
(facts
8+
(facts "evaluate expressions"
99
(eval-expr [:eq [:val "a"] [:val "b"]] {}) => falsey
1010
(eval-expr [:eq [:val "a"] [:val "a"]] {}) => truthy
1111
(eval-expr [:neq [:val "a"] [:val "b"]] {}) => truthy
1212
(eval-expr [:lt [:val 10] [:val 11]] {}) => truthy
1313
(eval-expr [:lt-eq [:val 10] [:val 10]] {}) => truthy
1414
(eval-expr [:gt [:val 10] [:val 9]] {}) => truthy
15+
(eval-expr [:gt-eq [:val 10] [:val 9]] {}) => truthy
1516
(eval-expr [:gt-eq [:val 10] [:val 10]] {}) => truthy
1617
(eval-expr [:path [[:key "foo"]]] {:current (m/root {:foo "bar"})}) => "bar"
17-
(eval-expr [:some [:path [[:current] [:child] [:key "foo"]]]] {:current (m/root {:foo "bar"})}) => truthy
18-
(eval-expr [:some [:path [[:current] [:child] [:key "foo"]]]] {:current (m/root {:foo nil})}) => falsey
19-
(eval-expr [:eq [:path [[:key "foo"]]] [:val "bar"]] {:current (m/root {:foo "bar"})}) => truthy)
18+
(eval-expr [:bool [:path [[:current] [:child] [:key "foo"]]]] {:current (m/root {:foo "bar"})}) => truthy
19+
(eval-expr [:bool [:path [[:current] [:child] [:key "foo"]]]] {:current (m/root {:foo nil})}) => falsey
20+
(eval-expr [:eq [:path [[:key "foo"]]] [:val "bar"]] {:current (m/root {:foo "bar"})}) => truthy
21+
22+
(eval-expr [:val true] {}) => truthy
23+
(eval-expr [:val false] {}) => falsey
24+
(eval-expr [:bool [:val true]] {}) => truthy
25+
(eval-expr [:bool [:val false]] {}) => falsey
26+
27+
;; 'and' expressions
28+
(eval-expr [:and [:bool [:val true]] [:bool [:val true]]] {}) => truthy
29+
(eval-expr [:and [:bool [:val true]] [:bool [:val false]]] {}) => falsey
30+
(eval-expr [:and [:bool [:val false]] [:bool [:val true]]] {}) => falsey
31+
(eval-expr [:and [:bool [:val false]] [:bool [:val false]]] {}) => falsey
32+
(eval-expr [:and [:bool [:val true]] [:bool [:val "bar"]]] {}) => truthy
33+
(eval-expr [:and [:bool [:val "bar"]] [:bool [:val false]]] {}) => falsey
34+
(eval-expr [:and [:bool [:val true]] [:lt [:val 10] [:val 11]]] {}) => truthy
35+
(eval-expr [:and [:bool [:val true]]
36+
[:path [[:key "foo"]]]] {:current (m/root {:foo "bar"}) => truthy})
37+
(eval-expr [:and [:bool [:val true]]
38+
[:path [[:key "foo"]]]] {:current (m/root {:foo nil}) => falsey})
39+
40+
;; 'or' expressions
41+
(eval-expr [:or [:bool [:val true]] [:bool [:val true]]] {}) => truthy
42+
(eval-expr [:or [:bool [:val true]] [:bool [:val false]]] {}) => truthy
43+
(eval-expr [:or [:bool [:val false]] [:bool [:val true]]] {}) => truthy
44+
(eval-expr [:or [:bool [:val false]] [:bool [:val false]]] {}) => falsey
45+
(eval-expr [:or [:bool [:val true]] [:bool [:val "bar"]]] {}) => truthy
46+
(eval-expr [:or [:bool [:val "bar"]] [:bool [:val false]]] {}) => truthy
47+
(eval-expr [:or [:bool [:val true]] [:lt [:val 10] [:val 11]]] {}) => truthy
48+
(eval-expr [:or [:bool [:val false]]
49+
[:path [[:key "foo"]]]] {:current (m/root {:foo "bar"}) => truthy})
50+
(eval-expr [:or [:bool [:val false]]
51+
[:path [[:key "foo"]]]] {:current (m/root {:foo nil}) => falsey}))
2052

2153
(facts
2254
(select-by [:key "hello"] (m/root {:hello "world"})) => (m/create "world" [:hello])
@@ -117,7 +149,7 @@
117149
{:root (m/root {:foo [{:bar "wrong" :hello "goodbye"}
118150
{:bar "baz" :hello "world"}]})}) => (list (m/create "world" [:foo 1 :hello]))
119151
(walk [:path [[:root]]
120-
[:selector [:filter [:some [:path [[:current]
152+
[:selector [:filter [:bool [:path [[:current]
121153
[:child]
122154
[:key "bar"]]]]]]]
123155
{:root (m/root {:hello "world" :foo {:bar "baz"}})}) => (list (m/create {:bar "baz"} [:foo])))

0 commit comments

Comments
 (0)