Skip to content

Commit

Permalink
Update pull grammar (#27)
Browse files Browse the repository at this point in the history
  • Loading branch information
jsmassa authored Nov 23, 2022
1 parent 1895855 commit abb3d3f
Show file tree
Hide file tree
Showing 2 changed files with 105 additions and 63 deletions.
76 changes: 42 additions & 34 deletions src/datalog/parser/pull.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -81,12 +81,9 @@
(unlimited-recursion? spec) (PullRecursionLimit. nil)
(pos? spec) (PullRecursionLimit. spec)))

(defn- maybe-attr-expr? [spec]
(and (sequential? spec) (= 3 (count spec))))

(def ^:private limit? #{'limit :limit "limit"})

(defn- parse-limit-expr [spec]
(defn- parse-legacy-limit-expr [spec]
(let [[limit-sym attr-name-spec pos-num] spec]
(when (limit? limit-sym)
(if-let [attr-name (and (or (nil? pos-num) (pos? pos-num))
Expand All @@ -97,50 +94,60 @@

(def ^:private default? #{'default :default "default"})

(defn- parse-default-expr [spec]
(defn- parse-legacy-default-expr [spec]
(let [[default-sym attr-name-spec default-val] spec]
(when (default? default-sym)
(if-let [attr-name (parse-attr-name attr-name-spec)]
(PullDefaultExpr. attr-name default-val)
(raise "Expected [\"default\" attr-name any-value]"
{:error :parser/pull, :fragment spec})))))

(def ^:private opt? #{:as :limit :default})

(defn- parse-attr-with-opts [spec]
(when (sequential? spec)
(let [[attr-name-spec & opts-spec] spec]
(if-some [attr-name (parse-attr-name attr-name-spec)]
(if (even? (count opts-spec))
(if-let [invalid-opt (first (drop-while opt? (take-nth 2 opts-spec)))]
(raise (str "Invalid attribute option: " invalid-opt)
{:error :parser/pull, :fragment spec})
(PullAttrWithOpts. attr-name (apply array-map opts-spec)))
(raise "Option list must contain even number of elements"
{:error :parser/pull, :fragment spec}))
(raise "Expected [attr-name attr-option+]"
{:error :parser/pull, :fragment spec})))))

(defn- parse-legacy-attr-expr [spec]
(when (and (sequential? spec)
(= 3 (count spec)))
(or (parse-legacy-limit-expr spec)
(parse-legacy-default-expr spec))))

(defn- parse-attr-expr [spec]
(or (parse-legacy-attr-expr spec)
(parse-attr-with-opts spec)))

(defn- parse-map-spec-entry [[k v]]
(if-let [attr-name (or (parse-attr-name k)
(when (maybe-attr-expr? k)
(parse-limit-expr k)))]
(parse-attr-expr k))]
(if-let [pattern-or-rec (or (parse-recursion-limit v)
(parse-pattern v))]
(PullMapSpecEntry. attr-name pattern-or-rec)
(raise "Expected (pattern | recursion-limit)"
{:error :parser/pull, :fragment [k v]}))
(raise "Expected (attr-name | limit-expr)"
(raise "Expected (attr-name | attr-expr)"
{:error :parser/pull, :fragment [k v]})))

(defn- parse-map-spec [spec]
(when (map? spec)
(assert (= 1 (count spec)) "Maps should contain exactly 1 entry")
(parse-map-spec-entry (first spec))))

(let [opt? #{:as :limit :default}]
(defn- parse-attr-with-opts [spec]
(when (sequential? spec)
(let [[attr-name-spec & opts-spec] spec]
(when-some [attr-name (parse-attr-name attr-name-spec)]
(when (and (even? (count opts-spec))
(every? opt? (take-nth 2 opts-spec)))
(PullAttrWithOpts. attr-name (apply array-map opts-spec))))))))

(defn- parse-attr-expr [spec]
(when (maybe-attr-expr? spec)
(or (parse-limit-expr spec)
(parse-default-expr spec))))

(defn- parse-attr-spec [spec]
(or (parse-attr-name spec)
(parse-wildcard spec)
(parse-map-spec spec)
(parse-attr-with-opts spec)
(parse-attr-expr spec)
(raise "Cannot parse attr-spec, expected: (attr-name | wildcard | map-spec | attr-expr)"
{:error :parser/pull, :fragment spec})))
Expand Down Expand Up @@ -169,17 +176,18 @@
grammar:
```
pattern = [attr-spec+]
attr-spec = attr-name | wildcard | map-spec | attr-expr
attr-name = an edn keyword that names an attr
wildcard = \"*\" or '*'
map-spec = { ((attr-name | limit-expr) (pattern | recursion-limit))+ }
attr-with-opts = [attr-name attr-options+]
attr-options = :as any-value | :limit (positive-number | nil) | :default any-value
attr-expr = limit-expr | default-expr
limit-expr = [\"limit\" attr-name (positive-number | nil)]
default-expr = [\"default\" attr-name any-value]
recursion-limit = positive-number | '...'
pattern = [attr-spec+]
attr-spec = attr-name | wildcard | map-spec | attr-expr
attr-name = an edn keyword that names an attr
wildcard = \"*\" or '*'
map-spec = { ((attr-name | attr-expr) (pattern | recursion-limit))+ }
attr-expr = attr-with-opts | legacy-attr-expr
attr-with-opts = [attr-name attr-option+]
attr-option = :as any-value | :limit (positive-number | nil) | :default any-value
recursion-limit = positive-number | '...'
legacy-attr-expr = legacy-limit-expr | legacy-default-expr
legacy-limit-expr = [(\"limit\" | 'limit') attr-name (positive-number | nil)]
legacy-default-expr = [(\"default\" | 'default') attr-name any-value]
```"
[pattern]
(when (sequential? pattern)
Expand Down
92 changes: 63 additions & 29 deletions test/datalog/parser/pull_test.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -6,37 +6,71 @@
#?(:cljs
(def Throwable js/Error))

(deftest test-parse-pattern
(are [pattern expected] (= expected (dpp/parse-pull pattern))
'[:db/id :foo/bar]
(dpp/->PullSpec false {:db/id {:attr :db/id}
:foo/bar {:attr :foo/bar}})
(def wildcard (dpp/->PullSpec true {}))

'[(limit :foo 1)]
(dpp/->PullSpec false {:foo {:attr :foo :limit 1}})
(deftest parse-pattern-test
(testing "simple attribute"
(is (= (dpp/->PullSpec false {:foo {:attr :foo}})
(dpp/parse-pull '[:foo]))))
(testing "namespaced attribute"
(is (= (dpp/->PullSpec false {:db/id {:attr :db/id}
:foo/bar {:attr :foo/bar}})
(dpp/parse-pull '[:db/id :foo/bar]))))
(testing "limit"
(is (= (dpp/->PullSpec false {:foo {:attr :foo :limit 1}})
(dpp/parse-pull '[(:foo :limit 1)]))))
(testing "default"
(is (= (dpp/->PullSpec false {:foo {:attr :foo :default "bar"}})
(dpp/parse-pull '[(:foo :default "bar")]))))
(testing "as"
(is (= (dpp/->PullSpec false {:foo {:attr :foo :as "bar"}})
(dpp/parse-pull '[(:foo :as "bar")])))))

'[* (default :foo "bar")]
(dpp/->PullSpec true {:foo {:attr :foo :default "bar"}})
(deftest map-specs-test
(testing "wildcard"
(is (= (dpp/->PullSpec false {:foo {:attr :foo :subpattern wildcard}})
(dpp/parse-pull '[{:foo [*]}]))))
(testing "subpattern"
(is (= (dpp/->PullSpec
false
{:foo {:attr :foo
:subpattern (dpp/->PullSpec
false
{:bar {:attr :bar}
:me {:attr :me}})}})
(dpp/parse-pull '[{:foo [:bar :me]}]))))
(testing "recursion"
(is (= (dpp/->PullSpec false {:foo {:attr :foo :recursion nil}})
(dpp/parse-pull '[{:foo ...}]))))
(testing "limit"
(is (= (dpp/->PullSpec false {:foo {:attr :foo :limit 1 :subpattern wildcard}})
(dpp/parse-pull '[{(:foo :limit 1) [*]}]))))
(testing "default"
(is (= (dpp/->PullSpec false {:foo {:attr :foo :default "bar" :subpattern wildcard}})
(dpp/parse-pull '[{(:foo :default "bar") [*]}]))))
(testing "as"
(is (= (dpp/->PullSpec false {:foo {:attr :foo :as "bar" :subpattern wildcard}})
(dpp/parse-pull '[{(:foo :as "bar") [*]}])))))

'[{:foo ...}]
(dpp/->PullSpec false {:foo {:attr :foo :recursion nil}})
(deftest legacy-parse-pattern-test
(testing "limit"
(is (= (dpp/->PullSpec false {:foo {:attr :foo :limit 1}})
(dpp/parse-pull '[(limit :foo 1)])))
(is (thrown? Throwable (dpp/parse-pull '[(limit :foo "bar")]))))
(testing "default"
(is (= (dpp/->PullSpec false {:foo {:attr :foo :default "bar"}})
(dpp/parse-pull '[(default :foo "bar")])))
(is (thrown? Throwable (dpp/parse-pull '[(default 1 :bar)])))))

'[{(limit :foo 2) [:bar :me]}]
(dpp/->PullSpec
false
{:foo {:attr :foo
:limit 2
:subpattern (dpp/->PullSpec
false
{:bar {:attr :bar}
:me {:attr :me}})}})))
(deftest legacy-map-specs-test
(testing "limit"
(is (= (dpp/->PullSpec false {:foo {:attr :foo :limit 1 :subpattern wildcard}})
(dpp/parse-pull '[{(limit :foo 1) [*]}])))
(is (thrown? Throwable
(dpp/parse-pull '[{(limit :foo "bar") [*]}]))))
(testing "default"
(is (= (dpp/->PullSpec false {:foo {:attr :foo :default "bar" :subpattern wildcard}})
(dpp/parse-pull '[{(default :foo "bar") [*]}])))
(is (thrown? Throwable
(dpp/parse-pull '[{(default 1 :bar) [*]}])))))

(deftest test-parse-bad-limit
(is
(thrown? Throwable (dpp/parse-pull '[(limit :foo :bar)]))))

(deftest test-parse-bad-default
(is
(thrown? Throwable (dpp/parse-pull '[(default 1 :bar)]))))

#_(t/test-ns 'datahike.test.pull-parser)

0 comments on commit abb3d3f

Please sign in to comment.