~subsetpark/fugue

089f4254955ed0de4cced025fae738c401cf031d — Zach Smith 5 months ago aedb09f
Update with-slots to allow nesting
3 files changed, 51 insertions(+), 27 deletions(-)

M README.md
M fugue.janet
M test/fugue.janet
M README.md => README.md +11 -8
@@ 84,7 84,7 @@ runtime, checks that `some-object` is a descendent of
`SomePrototype` and if so, translates to `(some-object
:some-field)`.

[1]: fugue.janet#L707
[1]: fugue.janet#L718

## Root



@@ 428,15 428,18 @@ Is `obj` the result of a `defproto ` call?

Anaphoric macro with transformed getter/setters.

Injects `this` into scope as a reference to `obj`.
Introduces two useful forms for referring to `obj`.

The reference symbol, `@` by default (see `with-slots-as` to specify
the symbol), in a 1-tuple: `(@)` will be translated into a
reference to `obj`.

The pattern `(@ <field name>)` is transformed into `(this (keyword
<field name>))`, if and only if `<field name>` is defined for
`proto`, so that `(@ name)` or its setter form `(set (@ name) foo)`
do the right thing.

Returns `obj`. Between this fact and the injection of `this`, `obj`
can be an anonymous object.
Returns `obj`.

---



@@ 447,13 450,13 @@ repl:2:> (defproto Foo nil name {:default "Jane Doe"})
repl:4:> (with-slots Foo (new-Foo)
           (set (@ name) "Cosmo Kramer")
           (print (@ name))
           (print (Foo? this)))
           (print (Foo? (@))))
Cosmo Kramer
true
@Foo{:_meta @{:object-type :instance} :name "Cosmo Kramer"}
```

[17]: fugue.janet#L664
[17]: fugue.janet#L672

## with-slots-as



@@ 465,9 468,9 @@ true

Anaphoric macro with transformed getter/setters.

Injects the arg `as` into scope as a reference to `obj`.
Specifies `as` as the reference symbol for `with-slots`.

See `with-slots` documentation for more details.

[18]: fugue.janet#L696
[18]: fugue.janet#L707


M fugue.janet => fugue.janet +24 -13
@@ 642,38 642,49 @@
        (emit-varfn multi (symbol fn-name) docstring args cond-form)))))

(defn- field-transformer
  [fields as proto-name]
  [fields obj-sym as proto-name]
  (fn [sym]
    (if (and (tuple? sym) (= (length sym) 2) (= (sym 0) '@))
    (cond
      (and (tuple? sym) (= (length sym) 1) (= (sym 0) as))
      obj-sym

      (and (tuple? sym) (= (length sym) 2) (= (sym 0) as))
      (let [field-name (-> sym (1) (keyword))]
        (unless (index-of field-name fields)
          (errorf "Encountered field reference %q for prototype %q; expected one of: %q"
                  sym
                  proto-name
                  fields))
        ~(,as ,field-name))
        ~(,obj-sym ,field-name))

      true
      sym)))

(defn- do-with-slots-as
  [proto obj as body]
  (let [f (-> proto (eval) (fields) (field-transformer as proto))]
    ~(let [,as ,obj]
       ,;(prewalk f body)
       ,as)))
  (with-syms [x]
    (let [f (-> proto (eval) (fields) (field-transformer x as proto))
          walked (prewalk f body)]
      ~(let [,x ,obj]
         ,;walked
         ,x))))

(defmacro with-slots
  ````
  Anaphoric macro with transformed getter/setters.

  Injects `this` into scope as a reference to `obj`.
  Introduces two useful forms for referring to `obj`.

  The reference symbol, `@` by default (see `with-slots-as` to specify
  the symbol), in a 1-tuple: `(@)` will be translated into a
  reference to `obj`.

  The pattern `(@ <field name>)` is transformed into `(this (keyword
  <field name>))`, if and only if `<field name>` is defined for
  `proto`, so that `(@ name)` or its setter form `(set (@ name) foo)`
  do the right thing.

  Returns `obj`. Between this fact and the injection of `this`, `obj`
  can be an anonymous object.
  Returns `obj`.
  
  ---



@@ 684,20 695,20 @@
  repl:4:> (with-slots Foo (new-Foo)
             (set (@ name) "Cosmo Kramer")
             (print (@ name))
             (print (Foo? this)))
             (print (Foo? (@))))
  Cosmo Kramer
  true
  @Foo{:_meta @{:object-type :instance} :name "Cosmo Kramer"}
  ```
  ````
  [proto obj & body]
  (do-with-slots-as proto obj 'this body))
  (do-with-slots-as proto obj '@ body))

(defmacro with-slots-as
  ```
  Anaphoric macro with transformed getter/setters.

  Injects the arg `as` into scope as a reference to `obj`.
  Specifies `as` as the reference symbol for `with-slots`.

  See `with-slots` documentation for more details.
  ```

M test/fugue.janet => test/fugue.janet +16 -6
@@ 354,24 354,34 @@
    (def res (fugue/with-slots SlotHaver a-slot-haver
                               (set (@ name) "will shortz")
                               (is (= "will shortz" (@ name)))
                               (is (= "will shortz" (this :name)))
                               (is (= "will shortz" (name this)))))
                               (is (= "will shortz" ((@) :name)))
                               (is (= "will shortz" (name (@))))))
    (is (= res a-slot-haver))
    (is (= "will shortz" (a-slot-haver :name)))))

(deftest slots-as-test
  (let [a-slot-haver (:new SlotHaver)]
    (def res (fugue/with-slots-as SlotHaver a-slot-haver s
                               (set (@ name) "will shortz")
                               (is (= "will shortz" (@ name)))
                               (is (= "will shortz" (s :name)))
                               (is (= "will shortz" (name s)))))
                               (set (s name) "will shortz")
                               (is (= "will shortz" (s name)))
                               (is (= "will shortz" ((s) :name)))
                               (is (= "will shortz" (name (s))))))
    (is (= res a-slot-haver))
    (is (= "will shortz" (a-slot-haver :name)))))

(deftest slots-validation
  (is (thrown? (apply fugue/with-slots '[SlotHaver {} (@ other)]))))

(deftest nested-with-slots
  (let [a-slot-haver (new-SlotHaver :name "A")
        b-slot-haver (new-SlotHaver :name "B")]
    (fugue/with-slots SlotHaver a-slot-haver
                      (fugue/with-slots-as SlotHaver b-slot-haver @@
                                           (set (@ name) "A2")
                                           (set (@@ name) "B2")))
    (is (= "A2" (a-slot-haver :name)))
    (is (= "B2" (b-slot-haver :name)))))

(deftest @-macro
  (is (= (fugue/@ SlotHaver :name) :name))
  (let [a-slot-haver (new-SlotHaver :name "Freddie")