[Fixes #28] Added destructuring support

Signed-off-by: Julien Eluard <julien.eluard@gmail.com>
This commit is contained in:
Julien Eluard 2018-07-24 11:31:46 +02:00
parent 043465b051
commit c9c9eb7730
No known key found for this signature in database
GPG Key ID: 6FD7DB5437FCBEF6
6 changed files with 206 additions and 15 deletions

55
docs/concepts/Block.md Normal file
View File

@ -0,0 +1,55 @@
---
title: Block
sidebar_label: Block
---
# Let block
## Destructuring
### Sequential data structure
Extract seq elements by index
Elements can be ignored with `_`
`:as symbol` denotes the whole sequence
#### Examples
```clojure
[[a _ c :as all] [1 2 3 4 5]]
;; a = 1
;; c = 3
;; all = [1 2 3 4 5]
```
### Associative data structure
Extract map values by keys `{a :a}`
Optional values via `[a 5]`
No namespaced keyword support, no short syntax
#### Examples
```clojure
[{a :a a :b [c 4] :c :as all} {:a 1 :b 2 :d 3}]
;; a = 1
;; b = 2
;; c = 4
;; all = {:a 1 :b 2 :d 3}
```
#### Nesting
Both structure type destrucuring can be combined
```clojure
[{[a1 _ a3] :a {d :d #{f 7} :f [[_ g2] 8}] :b [_ e2] :e :all all}
{:a [1 2 3] :b {:d 4 :e [5 6] :g [9 10]}}]
;; a1 = 1
;; a3 = 3
;; d = 4
;; e2 = 5
;; f = 7
;; g2 = 10
;; all = {:a [1 2 3] :b {:d 4 :e [5 6] :g [9 10]}}
```

View File

@ -1,11 +1,12 @@
(ns pluto.reader.blocks
(:refer-clojure :exclude [destructure])
(:require [clojure.walk :as walk]
[pluto.reader.errors :as errors]
[pluto.reader.reference :as reference]
[re-frame.core :as re-frame]))
(defmulti parse
""
"Parse a block element. Return hiccup data."
(fn [_ [type]] type))
(defn resolve-query [o]
@ -22,20 +23,86 @@
(cond
(coll? child) (walk/prewalk-replace (resolve-queries env) child)))
(defn bindings->env [v]
(apply hash-map v))
(defn symbol-afer-as? [bindings idx]
(and (pos? idx) (= :as (nth bindings (dec idx)))))
(declare destructure-assoc destructure-seq)
(defmethod parse 'let [_ [_ bindings & body]]
(defn indexed-bindings [bindings]
(into {} (map-indexed vector bindings)))
(defn merge-seq-bindings [bindings s m idx value]
(cond
(or (= :as value) (= '_ value)) m
(symbol-afer-as? bindings idx) (assoc-in m [:data value] s)
(symbol? value) (assoc-in m [:data value] (nth s idx))
;; Recursive destructuring
(map? value) (errors/merge-results m (destructure-assoc value (nth s idx)))
(sequential? value) (errors/merge-results m (destructure-seq value (nth s idx)))))
(defn bindings-size [bindings]
(let [size (count bindings)]
(if (some #{:as} bindings)
(- size 2)
size)))
(defn destructure-seq [bindings s]
(cond
(or
(not (sequential? bindings))
(not (every? #(or (symbol? %) (vector? %) (map? %) (= :as %)) bindings))
(> (bindings-size bindings) (count s)))
{:errors [(errors/error ::errors/invalid-destructuring-format bindings)]}
:else
(reduce-kv #(merge-seq-bindings bindings s %1 %2 %3) {} (indexed-bindings bindings))))
(defn merge-assoc-bindings [s m k v]
(cond
(vector? v) (assoc-in m [:data k] (or ((first v) s) (second v)))
(symbol? k) (assoc-in m [:data k] (v s))
(= :as k) (assoc-in m [:data v] s)
;; Recursive destructuring
(map? k) (errors/merge-results m (destructure-assoc k (v s)))
(sequential? k) (errors/merge-results m (destructure-seq k (v s)))))
(defn destructure-assoc [bindings s]
(cond
(or
(not (map? bindings))
(not (every? #(or (symbol? %) (vector? %) (map? %) (= :as %)) (keys bindings))))
{:errors [(errors/error ::errors/invalid-destructuring-format bindings)]}
:else
(reduce-kv #(merge-assoc-bindings s %1 %2 %3) {} bindings)))
(defn destructure [bindings s]
(cond
(sequential? s) (destructure-seq bindings s)
(map? s) (destructure-assoc bindings s)))
(defn merge-bindings [m k v]
(cond
(symbol? k) (assoc-in m [:data k] v)
(sequential? k) (errors/merge-results m (destructure-seq k v))
(map? k) (errors/merge-results m (destructure-assoc k v))
:else
{:errors [(errors/error ::errors/invalid-destructuring-format [k v])]}))
(defn bindings->env [bindings]
(cond
(odd? (count bindings))
{:errors [(errors/error ::errors/invalid-block bindings)]}
{:errors [(errors/error ::errors/invalid-destructuring-format bindings)]}
:else
{:data
;; TODO resolve query references only once, error if unknown
(let [m (bindings->env bindings)
child (last body)]
[let-block {:env m} child])}))
(reduce-kv merge-bindings
{} (apply hash-map bindings))))
(defmethod parse 'let [_ [_ bindings & body]]
(let [{:keys [data errors]} (bindings->env bindings)]
(errors/merge-errors
;; TODO resolve query references only once, error if unknown
{:data
(let [child (last body)]
[let-block {:env data} child])}
errors)))
(defn when-block [{:keys [test]} body]
;; TODO warning if test is not of boolean type

View File

@ -9,6 +9,8 @@
::invalid-property-name
::invalid-property-type
::invalid-property-value
::missing-property-value
::invalid-destructuring-format
::missing-keys
::unknown-reference
::unknown-component
@ -44,9 +46,14 @@
(update m :errors concat errors)
m))
(defn update-data [m data]
(if data
(update m :data merge data)
m))
(defn merge-result [m {:keys [data errors]}]
(-> m
(update :data merge data)
(update-data data)
(update-errors errors)))
(defn merge-results [& ms]

View File

@ -15,13 +15,17 @@
(= 'some.ref (reference->symbol '@views/some.ref))
```"
[ref]
(symbol (second ref)))
(when-let [s (second ref)]
(symbol s)))
(defn property-value [{:keys [name]} hook]
(get hook name))
(defmethod resolve-property :view [opts m def hook]
(views/parse opts (get m (reference->symbol (property-value def hook)))))
(let [o (property-value def hook)]
(if-let [s (reference->symbol o)]
(views/parse opts (get m s))
{:errors [(errors/error ::errors/missing-property-value def)]})))
(defn resolve-property-value [f {:keys [name] :as def} hook]
(if-let [o (property-value def hook)]

View File

@ -1,12 +1,64 @@
(ns pluto.reader.block-test
(:refer-clojure :exclude [destructure])
(:require [clojure.test :refer [is deftest]]
[pluto.reader.errors :as errors]
[pluto.reader.blocks :as blocks]))
(deftest destructure-seq
(is (= {:errors [{::errors/type ::errors/invalid-destructuring-format ::errors/value [1]}]}
(blocks/destructure-seq '[1] [1])))
(is (= {:errors [{::errors/type ::errors/invalid-destructuring-format ::errors/value '[a b]}]}
(blocks/destructure-seq '[a b] [1])))
(is (= {:data '{a 1}} (blocks/destructure-seq '[a] [1])))
(is (= {:data '{a 1 c 3}} (blocks/destructure-seq '[a _ c] [1 2 3])))
(is (= {} (blocks/destructure-seq '[_ _ _] [1 2 3])))
(is (= {:data '{all [1 2 3]}} (blocks/destructure-seq '[_ _ _ :as all] [1 2 3]))))
(deftest destructure-assoc
(is (= {:data '{a 1 b 2}}
(blocks/destructure-assoc '{a :a b :b} {:a 1 :b 2})))
(is (= {:errors [{:pluto.reader.errors/type :pluto.reader.errors/invalid-destructuring-format,
:pluto.reader.errors/value {1 :a}}]}
(blocks/destructure-assoc '{1 :a} {:a 1})))
(is (= {:errors [{:pluto.reader.errors/type :pluto.reader.errors/invalid-destructuring-format,
:pluto.reader.errors/value []}]}
(blocks/destructure-assoc [] {:a 1})))
(is (= {:errors [{:pluto.reader.errors/type :pluto.reader.errors/invalid-destructuring-format,
:pluto.reader.errors/value '[a1 a2]}]}
(blocks/destructure-assoc '{[a1 a2] :a} {:a [1]})))
(is (= {:data '{a 1 b 2 c 4 all {:a 1 :b 2 :d 3}}}
(blocks/destructure-assoc '{a :a b :b c [:c 4] :as all} {:a 1 :b 2 :d 3}))))
(deftest destructure
(is (= {:data '{a 1}} (blocks/destructure '[a] [1])))
(is (= {:data '{a 1 b 2}} (blocks/destructure '[a {b :b}] [1 {:b 2}])))
(is (= {:data '{a 1 b 2 c 3}} (blocks/destructure '[a [b [c]]] [1 [2 [3]]])))
(is (= {:data '{a 1 b 2}} (blocks/destructure '{a :a b :b} {:a 1 :b 2})))
(is (= {:data '{a 1 b2 3}} (blocks/destructure '{a :a [_ b2] :b} {:a 1 :b [2 3]})))
(is (= {:data '{a 1 c 3}} (blocks/destructure '{a :a {c :c} :b} {:a 1 :b {:c 3}})))
(is (= {:data '{a1 1 a3 3 d 4 e2 6 f 7 g2 10
all {:a [1 2 3] :b {:d 4 :e [5 6] :g [9 10]}}}}
(blocks/destructure '{[a1 _ a3] :a {d :d f [:f 7] [_ e2] :e [_ g2] :g} :b :as all}
{:a [1 2 3] :b {:d 4 :e [5 6] :g [9 10]}}))))
(deftest bindings->env
(is (= {:data '{a 1}} (blocks/bindings->env '[a 1])))
(is (= {:errors [{:pluto.reader.errors/type :pluto.reader.errors/invalid-destructuring-format
:pluto.reader.errors/value '[a 1 2]}]}
(blocks/bindings->env '[a 1 2])))
(is (= {:errors [{:pluto.reader.errors/type :pluto.reader.errors/invalid-destructuring-format
:pluto.reader.errors/value '[1 2]}]}
(blocks/bindings->env '[1 2])))
(is (= {:data '{a 1}} (blocks/bindings->env '[[a] [1]]))))
(deftest let-block
(is (= {:data [blocks/let-block {:env {'s "Hello"}} 's]}
(blocks/parse {} '(let [s "Hello"] s))))
(is (= {:data [blocks/let-block {:env {'s "Hello"}} ['test {} 's]]}
(blocks/parse {} (list 'let ['s "Hello"] ['test {} 's]))))
(is (= {:errors [{::errors/type ::errors/invalid-block ::errors/value ['s "Hello" 1]}]}
(blocks/parse {} (list 'let ['s "Hello" 1] ['test {} 's])))))
(is (= {:data [blocks/let-block {:env nil}
['test {} 's]]
:errors [{::errors/type ::errors/invalid-destructuring-format ::errors/value ['s "Hello" 1]}]}
(blocks/parse {} (list 'let ['s "Hello" 1] ['test {} 's]))))
(is (= {:data [blocks/let-block {:env {'a 1}} ['test {} 's]]}
(blocks/parse {} (list 'let ['{a :a} {:a 1}] ['test {} 's])))))

View File

@ -18,6 +18,12 @@
(is (= {:errors [{::errors/type ::errors/invalid-property-value
::errors/value 1}]}
(hooks/resolve-property {} {} {:name :keyword :type :keyword} {:keyword 1})))
(is (= {:errors [{:pluto.reader.errors/type ::errors/missing-property-value
:pluto.reader.errors/value {:name :view :type :view}}]}
(hooks/resolve-property {:capacities {:components {'text :text}}}
{'views/id ['text {} ""]}
{:type :view :name :view}
{})))
(is (= {:data [:text {} ""]}
(hooks/resolve-property {:capacities {:components {'text :text}}}
{'views/id ['text {} ""]}