mirror of https://github.com/status-im/pluto.git
[Fixes #28] Added destructuring support
Signed-off-by: Julien Eluard <julien.eluard@gmail.com>
This commit is contained in:
parent
043465b051
commit
c9c9eb7730
|
@ -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]}}
|
||||
```
|
|
@ -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
|
||||
(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
|
||||
(let [m (bindings->env bindings)
|
||||
child (last body)]
|
||||
[let-block {:env m} child])}))
|
||||
{: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
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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])))))
|
||||
|
|
|
@ -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 {} ""]}
|
||||
|
|
Loading…
Reference in New Issue