Compare commits
1 commit
master
...
inline-nex
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8d7c786fe9 |
30 changed files with 2443 additions and 3369 deletions
37
.github/workflows/ci.yml
vendored
37
.github/workflows/ci.yml
vendored
|
|
@ -1,37 +0,0 @@
|
|||
name: CI
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
fetch_depth: 0
|
||||
|
||||
- name: Setup Babashka
|
||||
uses: turtlequeue/setup-babashka@v1.3.0
|
||||
with:
|
||||
babashka-version: 0.7.8
|
||||
|
||||
- name: Prepare java
|
||||
uses: actions/setup-java@v2
|
||||
with:
|
||||
distribution: 'zulu'
|
||||
java-version: '8'
|
||||
|
||||
- name: Install clojure tools
|
||||
uses: DeLaGuardo/setup-clojure@4.0
|
||||
with:
|
||||
lein: 2.9.1
|
||||
|
||||
- name: Run clj tests
|
||||
run: bb test:clj
|
||||
|
||||
- name: Run cljs tests
|
||||
run: bb test:cljs
|
||||
|
||||
- name: Run babashka tests
|
||||
run: bb test:bb
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -9,7 +9,4 @@ pom.xml.asc
|
|||
.lein-repl-history
|
||||
.lein-plugins/
|
||||
.lein-failures
|
||||
.cljs_node_repl
|
||||
out/
|
||||
.cpcache
|
||||
.cache
|
||||
|
|
|
|||
3
.travis.yml
Normal file
3
.travis.yml
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
language: clojure
|
||||
script:
|
||||
- lein test
|
||||
207
CHANGES.md
207
CHANGES.md
|
|
@ -1,148 +1,18 @@
|
|||
## 1.1.4
|
||||
## 0.13.0 (unreleased)
|
||||
|
||||
* Add arglist metadata to navs (thanks @phronmophobic)
|
||||
* Improve before-index performance by 150x on lists and 5x on vectors (thanks @jeff303)
|
||||
* Bug fix: BEFORE-ELEM, AFTER-ELEM, FIRST, LAST, BEGINNING, and END on subvecs now produce vector type in cljs
|
||||
* Add compatibility with [babashka](https://babashka.org/)
|
||||
|
||||
## 1.1.3 - 2019-10-13
|
||||
|
||||
* Better AOT behavior: path functions for inline caching and protpath extensions no longer write eval'd class files. You can force path functions to not override `*compile-files*` by binding `com.rpl.specter.impl/*path-compile-files*` to `true`.
|
||||
* Bug fix: fix throw-illegal in cljs
|
||||
|
||||
## 1.1.2 - 2018-11-01
|
||||
|
||||
* Eliminate reflection warning
|
||||
* Bug fix: Fix inline compiler symbol handling so class references can be used as constants within paths
|
||||
* Bug fix: Fix handling of subvector paths in cljs
|
||||
|
||||
## 1.1.1 - 2018-04-23
|
||||
|
||||
* ClojureScript 1.10 introduced a change causing the `walker` navigator to fail to walk records. `ALL` has been updated to operate over `MapEntry` in ClojureScript, fixing the issue.
|
||||
* Change ns form to comply with cljs.core.specs.alpha (thanks @gnl)
|
||||
* Remove usage of pprint in cljs so advanced optimization doesn't include it
|
||||
|
||||
## 1.1.0 - 2018-01-02
|
||||
|
||||
* Add `vtransform` variant of `transform` that takes in collected values as a vector in the first argument rather than spliced into argument list.
|
||||
* Add `vterminal` that takes in collected vals as vector in first argument rather than spliced into argument list.
|
||||
* Add `compact` navigator. After each step of navigation of its subpath, `compact` removes the collection if it's empty.
|
||||
* Change `terminal` to be a no-op on select codepath
|
||||
* Bug fix: `subselect`/`filterer` removes first matched element instead of setting to nil when transformed to empty sequence
|
||||
|
||||
## 1.0.5 - 2017-11-16
|
||||
|
||||
* Add `regex-nav` navigator for regexes, which navigates to every match in a string and supports replacement with a new substring (thanks @mwfogleman)
|
||||
* Regexes implicitly convert to `regex-nav` in paths
|
||||
* Strings, numbers, booleans, characters, and symbols implicitly convert to `keypath` in paths
|
||||
|
||||
## 1.0.4 - 2017-10-17
|
||||
|
||||
* Add `indexed-vals` navigator, a variant of `INDEXED-VALS` that allows for a customized start index.
|
||||
* Bug fix: Fix `INDEXED-VALS` invalidly overwriting elements in some transforms involving multiple index changes
|
||||
|
||||
## 1.0.3 - 2017-08-14
|
||||
|
||||
* Added `before-index` navigator for inserting a single element into a sequence.
|
||||
* Added `index-nav` navigator for moving an element in a sequence to a new index, shifting other elements in the process.
|
||||
* Added `INDEXED-VALS` navigator for navigating to every element of a sequence as [index elem] pair. Transform on index portion works the same as `index-nav`.
|
||||
* Workaround for ClojureScript regression that causes warnings for record fields named "var" or other reserved names
|
||||
|
||||
## 1.0.2 - 2017-06-12
|
||||
|
||||
* Added `pred=`, `pred<`, `pred>`, `pred<=`, `pred>=` for filtering using common comparisons
|
||||
* Add `map-key` navigator
|
||||
* Add `set-elem` navigator
|
||||
* Add `ALL-WITH-META` navigator
|
||||
* `walker` and `codewalker` can now be used with `NONE` to remove elements
|
||||
* Improve `walker` performance by 70% by replacing clojure.walk implementation with custom recursive path
|
||||
* Extend `ALL` to work on records (navigate to key/value pairs)
|
||||
* Add ability to declare a function for end index of `srange-dynamic` that takes in the result of the start index fn. Use `end-fn` macro to declare this function (takes in 2 args of [collection, start-index]). Functions defined with normal mechanisms (e.g. `fn`) will still only take in the collection as an argument.
|
||||
* Workaround for ClojureScript bug that emits warnings for vars named the same as a private var in cljs.core (in this case `NONE`, added as private var to cljs.core with 1.9.562)
|
||||
* For ALL transforms on maps, interpret transformed key/value pair of size < 2 as removal
|
||||
* Bug fix: Fix incorrect inline compilation when a dynamic function invocation is nested in a data structure within a parameter to a navigator builder
|
||||
|
||||
## 1.0.1 - 2017-04-17
|
||||
|
||||
* `subselect`/`filterer` can remove entries in source by transforming to a smaller sequence
|
||||
* Add `satisfies-protpath?`
|
||||
* Inline cache vars are marked private so as not to interfere with tooling
|
||||
* Improve performance of `ALL` transform on lists by 20%
|
||||
* Bug fix: Using `pred` no longer inserts unnecessary `coerce-nav` call at callsite
|
||||
* Bug fix: Dynamic navs in argument position to another nav now properly expanded and compiled
|
||||
* Bug fix: Dynamic parameters nested inside data structures as arguments are now compiled correctly by inline compiler
|
||||
|
||||
## 1.0.0 - 2017-03-01
|
||||
|
||||
* Transform to `com.rpl.specter/NONE` to remove elements from data structures. Works with `keypath` (for both sequences and maps), `must`, `nthpath`, `ALL`, `MAP-VALS`, `FIRST`, and `LAST`
|
||||
* Add `nthpath` navigator
|
||||
* Add `with-fresh-collected` higher order navigator
|
||||
* Added `traverse-all` which returns a transducer that traverses over all elements matching the given path.
|
||||
* `select-first` and `select-any` now avoid traversal beyond the first value matched by the path (like when using `ALL`), so they are faster now for those use cases.
|
||||
* Add `MAP-KEYS` navigator that's more efficient than `[ALL FIRST]`
|
||||
* Add `NAME` and `NAMESPACE` navigators
|
||||
* Extend `srange`, `BEGINNING`, `END` to work on strings. Navigates to a substring.
|
||||
* Extend `FIRST` and `LAST` to work on strings. Navigates to a character.
|
||||
* Add `BEFORE-ELEM` and `AFTER-ELEM` for prepending or appending a single element to a sequence
|
||||
* Add `NONE-ELEM` to efficiently add a single element to a set
|
||||
* Improved `ALL` performance for PersistentHashSet
|
||||
* Dynamic navs automatically compile sequence returns if completely static
|
||||
* Eliminate reflection warnings for clj (thanks @mpenet)
|
||||
* Bug fix: Collected vals now properly passed to subpaths for `if-path`, `selected?`, and `not-selected?`
|
||||
* Bug fix: `LAST`, `FIRST`, `BEGINNING`, and `END` properly transform subvector types to a vector type
|
||||
|
||||
## 0.13.2 - 2016-12-21
|
||||
|
||||
* Bug fix: Fix race condition relating to retrieving path from cache and AOT compilation
|
||||
* Bug fix: LAST no longer converts lists to vectors
|
||||
* Bug fix: Workaround issue with aot + uberjar
|
||||
|
||||
## 0.13.1 - 2016-11-07
|
||||
|
||||
* Remove any? in com.rpl.specter.impl to avoid conflict with Clojure 1.9
|
||||
* Enhanced dynamic navigators to continue expanding if any other dynamic navs are returned
|
||||
* Added `eachnav` to turn any 1-argument navigator into a navigator that accepts any number of arguments, navigating by each argument in order
|
||||
* `keypath` and `must` enhanced to take in multiple arguments for concisely specifying multiple steps
|
||||
* Added `traversed`
|
||||
* Bug fix: Fix regression from 0.13.0 where [ALL FIRST] on a PersistentArrayMap that created duplicate keys would create an invalid PersistentArrayMap
|
||||
* Bug fix: Fix problems with multi-path and if-path in latest versions of ClojureScript
|
||||
* Bug fix: Inline compiler no longer flattens and changes the type of sequential params
|
||||
|
||||
## 0.13.0 - 2016-09-06
|
||||
|
||||
* BREAKING CHANGE: `com.rpl.specter.macros` namespace removed and all macros moved into core `com.rpl.specter` namespace
|
||||
* BREAKING CHANGE: Core protocol `Navigator` changed to `RichNavigator` and functions now have an extra argument.
|
||||
* BREAKING CHANGE: All navigators must be defined with `defnav` and its variations from `com.rpl.specter`. The core protocols may no longer be extended. Existing types can be turned into navigators with the new `IndirectNav` protocol.
|
||||
* BREAKING CHANGE: Removed `fixed-pathed-nav` and `variable-pathed-nav` and replaced with much more generic `late-bound-nav`. `late-bound-nav` can have normal values be late-bound parameterized (not just paths). Use `late-path` function to indicate which parameters are paths. If all bindings given to `late-bound-nav` are static, the navigator will be resolved and cached immediately. See `transformed` and `selected?` for examples.
|
||||
* BREAKING CHANGE: Paths can no longer be compiled without their parameters. Instead, use the `path` macro to handle the parameterization.
|
||||
* BREAKING CHANGE: Parameterized protocol paths now work differently since paths cannot be specified without their parameters. Instead, use the parameter names from the declaration in the extension to specify where the parameters should go. For example:
|
||||
```clojure
|
||||
(defprotocolpath MyProtPath [a])
|
||||
(extend-protocolpath MyProtPath
|
||||
clojure.lang.PersistentArrayMap
|
||||
(must a))
|
||||
```
|
||||
* BREAKING CHANGE: Removed `defpathedfn` and replaced with much more generic `defdynamicnav`. `defdynamicnav` works similar to a macro and takes as input the parameters seen during inline caching. Use `dynamic-param?` to distinguish which parameters are statically specified and which are dynamic. `defdynamicnav` is typically used in conjunction with `late-bound-nav` – see implementation of `selected?` for an example.
|
||||
* Inline caching now works with locals, dynamic vars, and special forms used in the nav position. When resolved at runtime, those values will be coerced to a navigator if a vector or implicit nav (e.g. keyword). Can hint with ^:direct-nav metadata to remove this coercion if know for sure those values will be implementations of `RichNavigator` interface.
|
||||
* Redesigned internals so navigators use interface dispatch rather than storing transform/selection functions as separate fields.
|
||||
* Added `local-declarepath` to assist in making local recursive or mutually recursive paths. Use with `providepath`.
|
||||
* Added `recursive-path` to assist in making recursive paths, both parameterized and unparameterized. Example:
|
||||
```clojure
|
||||
(let [tree-walker (recursive-path [] p (if-path vector? [ALL p] STAY))]
|
||||
(select tree-walker [1 [2 [3 4] 5] [[6]]]))
|
||||
```
|
||||
* Significantly improved performance of paths that use dynamic parameters.
|
||||
* BREAKING CHANGE: May no longer extend the `Navigator` or `Collector` protocols. All navigators must be defined with `defnav`. Existing types can be turned into navigators with the new `IndirectNav` protocol.
|
||||
* Redesigned internals so navigators use interface dispatch rather than storing transform/selection functions as separate fields
|
||||
* `defnav` with parameters now produces a function that returns the parameterized nav when invoked (with parameters in the closure rather than being late-bound parameterized). These functions can still be composed without their parameters, falling back to the late-bound parameterized codepath in that case. The end result of this is more optimized execution when parameters are provided immediately.
|
||||
* Inline factoring now parameterizes navigators immediately when all parameters are constants (rather than factoring it to use late-bound parameterization). This creates leaner, faster code.
|
||||
* Added `IndirectNav` protocol for turning a value type into a navigator.
|
||||
* Removed `must-cache-paths!`. No longer relevant since all paths can now be compiled and cached.
|
||||
* Added `with-inline-debug` macro that prints information about the code being analyzed and produced by the inline compiler / cacher.
|
||||
* Higher order navigators (like `selected?`, `subselect`) more intelligently parameterize the nested paths and use the leaner execution mode if none of the nested paths require late-bound parameterization or value collection.
|
||||
* Removed `variable-pathed-nav`
|
||||
* Switched codebase from cljx to cljc
|
||||
* Improved performance of ALL and MAP-VALS on PersistentArrayMap by about 2x
|
||||
* `defnav` now generates helper functions for every method. For example, `keypath` now has helpers `keypath-select*` and `keypath-transform*`. These functions take parameters `[key structure next-fn]`
|
||||
* Bug fix: ALL and MAP-VALS transforms on PersistentArrayMap above the threshold now output PersistentArrayMap instead of PersistentHashMap
|
||||
|
||||
|
||||
## 0.12.0 - 2016-08-05
|
||||
## 0.12.0
|
||||
|
||||
* BREAKING CHANGE: Changed semantics of `Navigator` protocol `select*` in order to enable very large performance improvements to `select`, `select-one`, `select-first`, and `select-one!`. Custom navigators will need to be updated to conform to the new required semantics. Codebases that do not use custom navigators do not require any changes. See the docstring on the protocol for the details.
|
||||
* Added `select-any` operation which selects a single element navigated to by the path. Which element returned is undefined. If no elements are navigated to, returns `com.rpl.specter/NONE`. This is the fastest selection operation.
|
||||
|
|
@ -164,13 +34,11 @@ transformations with `transform` one after another when the transformations shar
|
|||
* Bug fix: Using value collection along with `setval` no longer throws exception
|
||||
* Bug fix: Fix error when trying to use Specter along with AOT compilation
|
||||
|
||||
## 0.11.2 - 2016-06-09
|
||||
|
||||
## 0.11.2
|
||||
* Renamed com.rpl.specter.transient namespace to com.rpl.specter.transients to eliminate ClojureScript compiler warning about reserved keyword
|
||||
* Eliminated compiler warnings for ClojureScript version
|
||||
|
||||
## 0.11.1 - 2016-06-08
|
||||
|
||||
## 0.11.1
|
||||
* More efficient inline caching for Clojure version, benchmarks show inline caching within 5% of manually precompiled code for all cases
|
||||
* Added navigators for transients in com.rpl.specter.transient namespace (thanks @aengelberg)
|
||||
* Huge performance improvement for ALL transform on maps and vectors
|
||||
|
|
@ -184,8 +52,7 @@ transformations with `transform` one after another when the transformations shar
|
|||
* Bug fix: Fixed nil->val to not replace the val on `false`
|
||||
* Bug fix: Eliminate reflection when using primitive parameters in an inline cached path
|
||||
|
||||
## 0.11.0 - 2016-05-31
|
||||
|
||||
## 0.11.0
|
||||
* New `path` macro does intelligent inline caching of the provided path. The path is factored into a static portion and into params which may change on each usage of the path (e.g. local parameters). The static part is factored and compiled on the first run-through, and then re-used for all subsequent invocations. As an example, `[ALL (keypath k)]` is factored into `[ALL keypath]`, which is compiled and cached, and `[k]`, which is provided on each execution. If it is not possible to precompile the path (e.g. [ALL some-local-variable]), nothing is cached and the path will be compiled on each run-through.
|
||||
* BREAKING CHANGE: all `select/transform/setval/replace-in` functions changed to macros and moved to com.rpl.specter.macros namespace. The new macros now automatically wrap the provided path in `path` to enable inline caching. Expect up to a 100x performance improvement without using explicit precompilation, and to be within 2% to 15% of the performance of explicitly precompiled usage.
|
||||
* Added `select*/transform*/setval*/replace-in*/etc.` functions that have the same functionality as the old `select/transform/setval/replace-in` functions.
|
||||
|
|
@ -199,8 +66,7 @@ transformations with `transform` one after another when the transformations shar
|
|||
* Added "navigator constructors" that can be defined via `defnavconstructor`. These allow defining a flexible function to parameterize a defnav, and the function integrates with inline caching for high performance.
|
||||
|
||||
|
||||
## 0.10.0 - 2016-04-26
|
||||
|
||||
## 0.10.0
|
||||
* Make codebase bootstrap cljs compatible
|
||||
* Remove usage of reducers in cljs version in favor of transducers (thanks @StephenRudolph)
|
||||
* ALL now maintains type of queues (thanks @StephenRudolph)
|
||||
|
|
@ -210,8 +76,7 @@ transformations with `transform` one after another when the transformations shar
|
|||
* Fix filterer to maintain the type of the input sequence in transforms
|
||||
* Integrated zipper navigation into com.rpl.specter.zipper namespace
|
||||
|
||||
## 0.9.3 - 2016-04-15
|
||||
|
||||
## 0.9.3
|
||||
* Change clojure/clojurescript to provided dependencies
|
||||
* ALL on maps auto-coerces MapEntry to vector, enabling smoother transformation of map keys
|
||||
* declarepath can now be parameterized
|
||||
|
|
@ -219,8 +84,7 @@ transformations with `transform` one after another when the transformations shar
|
|||
* Added convenience syntax for defprotocolpath with no params, e.g. (defprotocolpath foo)
|
||||
* Rename VOID to STOP
|
||||
|
||||
## 0.9.2 - 2016-01-26
|
||||
|
||||
## 0.9.2
|
||||
* Added VOID selector which navigates nowhere
|
||||
* Better syntax checking for defpath
|
||||
* Fixed bug in protocol paths (#48)
|
||||
|
|
@ -231,39 +95,33 @@ transformations with `transform` one after another when the transformations shar
|
|||
* Added declarepath and providepath, which enable arbitrary recursive or mutually recursive paths
|
||||
* Renamed paramspath to path
|
||||
|
||||
## 0.9.1 - 2016-01-05
|
||||
|
||||
## 0.9.1
|
||||
* Fixed reflection in protocol path code
|
||||
* Optimized late-bound parameterization for JVM implementation by directly creating the object array rather than use object-array
|
||||
* Incorrectly specified function names in defpath will now throw error
|
||||
|
||||
## 0.9.0 - 2015-12-12
|
||||
|
||||
## 0.9.0
|
||||
* Fixed bug where comp-paths wouldn't work on lazy seqs in cljs
|
||||
* Renamed defparamspath and defparamscollector to defpath and defcollector
|
||||
* For Clojure version only, implemented protocol paths (see #38)
|
||||
|
||||
## 0.8.0 - 2015-10-10
|
||||
|
||||
## 0.8.0
|
||||
* Now compatible with Clojure 1.6.0 and 1.5.1 by switching build to cljx (thanks @MerelyAPseudonym)
|
||||
* Added subset selector (like srange but for sets)
|
||||
* Added nil->val, NIL->SET, NIL->LIST, and NIL->VECTOR selectors to make it easier to manipulate maps (e.g. (setval [:akey NIL->VECTOR END] [:a :b] amap) to append that vector into that value for the map, even if nothing was at that value at the start)
|
||||
|
||||
## 0.7.1 - 2015-09-24
|
||||
|
||||
## 0.7.1
|
||||
* view can now be late-bound parameterized
|
||||
* Added a late-bound parameterized version of using a function as a selector called "pred"
|
||||
* Added paramsfn helper macro for defining filter functions that take late-bound parameters
|
||||
* walker and codewalker can now be late-bound parameterized
|
||||
|
||||
## 0.7.0 - 2015-09-11
|
||||
|
||||
## 0.7.0
|
||||
* Added late-bound parameterization feature: allows selectors that require params to be precompiled without the parameters, and the parameters are supplied later in bulk. This effectively enables Specter to be used in any situation with very high performance.
|
||||
* Converted Specter built-in selectors to use late-bound parameterization when appropriate
|
||||
* ALL, FIRST, and LAST are now precompiled
|
||||
|
||||
## 0.6.2 - 2015-07-03
|
||||
|
||||
## 0.6.2
|
||||
* Added not-selected? selector
|
||||
* Added transformed selector
|
||||
* Sped up CLJS implementation for comp-paths by replacing obj-extends? call with satisfies?
|
||||
|
|
@ -271,42 +129,33 @@ transformations with `transform` one after another when the transformations shar
|
|||
* Used not-native hint to enable direct method invocation to speed up CLJS implementation
|
||||
|
||||
|
||||
## 0.6.1 - 2015-07-01
|
||||
|
||||
## 0.6.1
|
||||
* Huge speedup to ClojureScript implementation by optimizing field access
|
||||
|
||||
## 0.6.0 - 2015-07-01
|
||||
|
||||
## 0.6.0
|
||||
* Added ClojureScript compatibility
|
||||
|
||||
## 0.5.7 - 2015-06-30
|
||||
|
||||
## 0.5.7
|
||||
* Fix bug in select-one! which wouldn't allow nil result
|
||||
|
||||
## 0.5.6 - 2015-06-29
|
||||
|
||||
## 0.5.6
|
||||
* Add multi-path implementation
|
||||
* change FIRST/LAST to select nothing on an empty sequence
|
||||
* Allow sets to be used directly as selectors (acts as filter)
|
||||
|
||||
## 0.5.5 - 2015-06-22
|
||||
|
||||
## 0.5.5
|
||||
* Change filterer to accept a selector (that acts like selected? to determine whether or not to select value)
|
||||
|
||||
## 0.5.4 - 2015-06-19
|
||||
|
||||
## 0.5.4
|
||||
* Change cond-path and if-path to take in a selector for conditionals (same idea as selected?)
|
||||
|
||||
## 0.5.3 - 2015-06-18
|
||||
|
||||
## 0.5.3
|
||||
* Added cond-path and if-path selectors for choosing paths depending on value of structure at that location
|
||||
|
||||
## 0.5.2 - 2015-06-01
|
||||
|
||||
## 0.5.2
|
||||
* Fix error for selectors with one element defined using comp-paths, e.g. [:a (comp-paths :b)]
|
||||
|
||||
## 0.5.1 - 2015-05-31
|
||||
|
||||
## 0.5.1
|
||||
* Added putval for adding external values to collected values list
|
||||
* nil is now interpreted as identity selector
|
||||
* empty selector is now interpreted as identity selector instead of producing error
|
||||
|
|
|
|||
|
|
@ -1,9 +0,0 @@
|
|||
# Contributing
|
||||
|
||||
We welcome external contributions to this project. For ideas for new functionality, we recommend first opening an issue so we can discuss whether it makes sense for the project.
|
||||
|
||||
## Contribution process
|
||||
|
||||
1. Open a pull request. If possible, please include thorough tests of the new code.
|
||||
2. If you have not already signed a contributor agreement, we will request that you sign one. We use Adobe Sign for this so it's very quick and easy. Note that we cannot look at your pull request until a contributor agreement is signed.
|
||||
3. We will then review your pull request and possibly ask for changes.
|
||||
19
DEVELOPER.md
19
DEVELOPER.md
|
|
@ -1,25 +1,14 @@
|
|||
# Running Clojure tests
|
||||
|
||||
```
|
||||
lein do clean, test
|
||||
lein cleantest
|
||||
```
|
||||
|
||||
# Running ClojureScript tests
|
||||
|
||||
```
|
||||
rm -rf out/
|
||||
lein javac
|
||||
lein doo node test-build once
|
||||
rlwrap java -cp `lein classpath` clojure.main repl.clj
|
||||
(require 'com.rpl.specter.cljs-test-runner)
|
||||
```
|
||||
|
||||
# Running benchmarks
|
||||
## All benchmarks
|
||||
```
|
||||
scripts/run-benchmarks
|
||||
```
|
||||
## Individual benchmark(s)
|
||||
Specify the benchmark names as command line args. They will likely each need quoted because they contain spaces.
|
||||
Order is ignored.
|
||||
```
|
||||
scripts/run-benchmarks "prepend to a vector" "filter a sequence"
|
||||
```
|
||||
|
||||
|
|
|
|||
183
README.md
183
README.md
|
|
@ -1,16 +1,24 @@
|
|||
# Specter
|
||||
# Specter [](http://travis-ci.org/nathanmarz/specter)
|
||||
|
||||
Specter rejects Clojure's restrictive approach to immutable data structure manipulation, instead exposing an elegant API to allow any sort of manipulation imaginable. Specter especially excels at querying and transforming nested and recursive data, important use cases that are very complex to handle with vanilla Clojure.
|
||||
Specter is a Clojure and ClojureScript library that, because of its far-ranging applicability, is hard to describe in just a few sentences. At its core, Specter is a library for "composable navigation". Most commonly it is used for querying and transforming nested data structures, but the concept generalizes far beyond that. Its effect is to enable you to write programs much more rapidly in a much more maintainable way.
|
||||
|
||||
Specter has an extremely simple core, just a single abstraction called "navigator". Queries and transforms are done by composing navigators into a "path" precisely targeting what you want to retrieve or change. Navigators can be composed with any other navigators, allowing sophisticated manipulations to be expressed very concisely.
|
||||
Here are three areas where Specter greatly improves Clojure programming:
|
||||
|
||||
In addition, Specter has performance rivaling hand-optimized code (see [this benchmark](https://gist.github.com/nathanmarz/b7c612b417647db80b9eaab618ff8d83)). Clojure's only comparable built-in operations are `get-in` and `update-in`, and the Specter equivalents are 30% and 85% faster respectively (while being just as concise). Under the hood, Specter uses [advanced dynamic techniques](https://github.com/redplanetlabs/specter/wiki/Specter's-inline-caching-implementation) to strip away the overhead of composition.
|
||||
**Specter makes common tasks concise instead of cumbersome and simple instead of complex**
|
||||
|
||||
There are some key differences between the Clojure approach to data manipulation and the Specter approach. Unlike Clojure, Specter always uses the most efficient method possible to implement an operation for a datatype (e.g. `last` vs. `LAST`). Clojure intentionally leaves out many operations, such as prepending to a vector or inserting into the middle of a sequence. Specter has navigators that cover these use cases (`BEFORE-ELEM` and `before-index`) and many more. Finally, Specter transforms always target precise parts of a data structure, leaving everything else the same. For instance, `ALL` targets every value within a sequence, and the resulting transform is always the same type as the input (e.g. a vector stays a vector, a sorted map stays a sorted map).
|
||||
Example 1: Append a sequence of elements to a nested vector
|
||||
|
||||
Consider these examples:
|
||||
```clojure
|
||||
(def data {:a [1 2 3]})
|
||||
|
||||
**Example 1: Increment every even number nested within map of vector of maps**
|
||||
;; Manual Clojure
|
||||
(update data :a (fn [v] (into (if v v []) [4 5])))
|
||||
|
||||
;; Specter
|
||||
(setval [:a END] [4 5] data)
|
||||
```
|
||||
|
||||
Example 2: Increment every even number nested within map of vector of maps
|
||||
|
||||
```clojure
|
||||
(def data {:a [{:aa 1 :bb 2}
|
||||
|
|
@ -19,7 +27,7 @@ Consider these examples:
|
|||
|
||||
;; Manual Clojure
|
||||
(defn map-vals [m afn]
|
||||
(->> m (map (fn [[k v]] [k (afn v)])) (into (empty m))))
|
||||
(->> m (map (fn [[k v]] [k (afn v)])) (into {})))
|
||||
|
||||
(map-vals data
|
||||
(fn [v]
|
||||
|
|
@ -34,43 +42,62 @@ Consider these examples:
|
|||
(transform [MAP-VALS ALL MAP-VALS even?] inc data)
|
||||
```
|
||||
|
||||
**Example 2: Append a sequence of elements to a nested vector**
|
||||
**Specter is much faster than Clojure's limited built-in alternatives**
|
||||
|
||||
Example 1: Specter's `select` is 27% faster than `get-in`:
|
||||
|
||||
```clojure
|
||||
(def data {:a [1 2 3]})
|
||||
(time
|
||||
(dotimes [_ 10000000]
|
||||
(get-in {:a {:b {:c 1}}} [:a :b :c])))
|
||||
"Elapsed time: 640.666 msecs"
|
||||
|
||||
;; Manual Clojure
|
||||
(update data :a (fn [v] (into (if v v []) [4 5])))
|
||||
|
||||
;; Specter
|
||||
(setval [:a END] [4 5] data)
|
||||
(time
|
||||
(dotimes [_ 10000000]
|
||||
(select [:a :b :c] {:a {:b {:c 1}}})))
|
||||
"Elapsed time: 470.167 msecs"
|
||||
```
|
||||
|
||||
**Example 3: Increment the last odd number in a sequence**
|
||||
Example 2: Specter's `transform` is 6x faster than `update-in`:
|
||||
|
||||
```clojure
|
||||
(def data [1 2 3 4 5 6 7 8])
|
||||
(time
|
||||
(dotimes [_ 10000000]
|
||||
(update-in {:a {:b {:c 1}}} [:a :b :c] inc)))
|
||||
"Elapsed time: 10662.014 msecs"
|
||||
|
||||
;; Manual Clojure
|
||||
(let [idx (reduce-kv (fn [res i v] (if (odd? v) i res)) nil data)]
|
||||
(if idx (update data idx inc) data))
|
||||
|
||||
;; Specter
|
||||
(transform [(filterer odd?) LAST] inc data)
|
||||
(time
|
||||
(dotimes [_ 10000000]
|
||||
(transform [:a :b :c] inc {:a {:b {:c 1}}})))
|
||||
"Elapsed time: 1699.016 msecs"
|
||||
```
|
||||
|
||||
**Example 4: Map a function over a sequence without changing the type or order of the sequence**
|
||||
**Specter makes sophisticated tasks – that are difficult to program manually – easy**
|
||||
|
||||
Example 1: Reverse the order of even numbers in a tree (with order based on depth first search):
|
||||
|
||||
```clojure
|
||||
;; Manual Clojure
|
||||
(map inc data) ;; doesn't work, becomes a lazy sequence
|
||||
(into (empty data) (map inc data)) ;; doesn't work, reverses the order of lists
|
||||
|
||||
;; Specter
|
||||
(transform ALL inc data) ;; works for all Clojure datatypes with near-optimal efficiency
|
||||
(transform (subselect (walker number?) even?)
|
||||
reverse
|
||||
[1 [[[2]] 3] 5 [6 [7 8]] 10])
|
||||
;; => [1 [[[10]] 3] 5 [8 [7 6]] 2]
|
||||
```
|
||||
|
||||
|
||||
Example 2: Replace every continuous sequence of odd numbers with its sum:
|
||||
|
||||
```clojure
|
||||
(transform (continuous-subseqs odd?)
|
||||
(fn [aseq] [(reduce + aseq)])
|
||||
[1 3 6 8 9 11 15 16]
|
||||
)
|
||||
;; => [4 6 8 35 16]
|
||||
```
|
||||
|
||||
This is just the tip of the iceberg. Because Specter is completely extensible, it can be used to navigate any data structure or object you have. All the navigators that come with Specter are built upon [very simple abstractions](https://github.com/nathanmarz/specter/blob/master/src/clj/com/rpl/specter/protocols.cljc).
|
||||
|
||||
Even though Specter is so generic and flexible, its performance rivals hand-optimized code. Under the hood, Specter uses [advanced dynamic techniques](https://github.com/nathanmarz/specter/wiki/Specter-0.11.0:-Performance-without-the-tradeoffs) to strip away the overhead of composition. Additionally, the built-in navigators use the most efficient means possible of accessing data structures. For example, `ALL` uses `mapv` on vectors, `reduce-kv` on small maps, and `reduce-kv` in conjunction with transients on larger maps. You get the best of both worlds of elegance and performance.
|
||||
|
||||
|
||||
# Latest Version
|
||||
|
||||
|
|
@ -80,27 +107,23 @@ The latest release version of Specter is hosted on [Clojars](https://clojars.org
|
|||
|
||||
# Learn Specter
|
||||
|
||||
- Introductory blog post: [Clojure's missing piece](http://nathanmarz.com/blog/clojures-missing-piece.html)
|
||||
- Introductory blog post: [Functional-navigational programming in Clojure(Script) with Specter](http://nathanmarz.com/blog/functional-navigational-programming-in-clojurescript-with-sp.html)
|
||||
- Presentation about Specter: [Specter: Powerful and Simple Data Structure Manipulation](https://www.youtube.com/watch?v=VTCy_DkAJGk)
|
||||
- Note that this presentation was given before Specter's inline compilation/caching system was developed. You no longer need to do anything special to get near-optimal performance.
|
||||
- Screencast on Specter: [Understanding Specter](https://www.youtube.com/watch?v=rh5J4vacG98)
|
||||
- List of navigators with examples: [This wiki page](https://github.com/redplanetlabs/specter/wiki/List-of-Navigators) provides a more comprehensive overview than the API docs about the behavior of specific navigators and includes many examples.
|
||||
- Core operations and defining new navigators: [This wiki page](https://github.com/redplanetlabs/specter/wiki/List-of-Macros) provides a more comprehensive overview than the API docs of the core select/transform/etc. operations and the operations for defining new navigators.
|
||||
- [This wiki page](https://github.com/redplanetlabs/specter/wiki/Using-Specter-Recursively) explains how to do precise and efficient recursive navigation with Specter.
|
||||
- [This wiki page](https://github.com/redplanetlabs/specter/wiki/Using-Specter-With-Zippers) provides a comprehensive overview of how to use Specter's zipper navigators. Zippers are a much slower navigation method but can perform certain tasks that are not possible with Specter's regular navigators. Note that zippers are rarely needed.
|
||||
- [Cheat Sheet](https://github.com/redplanetlabs/specter/wiki/Cheat-Sheet)
|
||||
- [API docs](http://redplanetlabs.github.io/specter/)
|
||||
- Performance guide: [This post](https://github.com/redplanetlabs/specter/wiki/Specter's-inline-caching-implementation) provides an overview of how Specter achieves its performance.
|
||||
- List of navigators with examples: [This wiki page](https://github.com/nathanmarz/specter/wiki/List-of-Navigators) provides a more comprehensive overview than the API docs about the behavior of specific navigators and includes many examples.
|
||||
- Core operations and defining new navigators: [This wiki page](https://github.com/nathanmarz/specter/wiki/List-of-Macros) provides a more comprehensive overview than the API docs of the core select/transform/etc. operations and the operations for defining new navigators.
|
||||
- [API docs](http://nathanmarz.github.io/specter/)
|
||||
- Performance guide: The [Specter 0.11.0 announcement post](https://github.com/nathanmarz/specter/wiki/Specter-0.11.0:-Performance-without-the-tradeoffs) provides a comprehensive overview of how Specter achieves its performance and what you need to know as a user to enable Specter to perform its optimizations.
|
||||
|
||||
Specter's API is contained in these files:
|
||||
|
||||
- [specter.cljc](https://github.com/redplanetlabs/specter/blob/master/src/clj/com/rpl/specter.cljc): This contains the built-in navigators and the definition of the core operations.
|
||||
- [transients.cljc](https://github.com/redplanetlabs/specter/blob/master/src/clj/com/rpl/specter/transients.cljc): This contains navigators for transient collections.
|
||||
- [zipper.cljc](https://github.com/redplanetlabs/specter/blob/master/src/clj/com/rpl/specter/zipper.cljc): This integrates zipper-based navigation into Specter.
|
||||
- [macros.clj](https://github.com/nathanmarz/specter/blob/master/src/clj/com/rpl/specter/macros.clj): This contains the core `select/transform/etc.` operations as well as macros for defining new navigators.
|
||||
- [specter.cljc](https://github.com/nathanmarz/specter/blob/master/src/clj/com/rpl/specter.cljc): This contains the built-in navigators and functional versions of `select/transform/etc.`
|
||||
- [transients.cljc](https://github.com/nathanmarz/specter/blob/master/src/clj/com/rpl/specter/transients.cljc): This contains navigators for transient collections.
|
||||
- [zipper.cljc](https://github.com/nathanmarz/specter/blob/master/src/clj/com/rpl/specter/zipper.cljc): This integrates zipper-based navigation into Specter.
|
||||
|
||||
# Questions?
|
||||
|
||||
You can ask questions about Specter by [opening an issue](https://github.com/redplanetlabs/specter/issues?utf8=%E2%9C%93&q=is%3Aissue+label%3Aquestion+) on Github.
|
||||
You can ask questions about Specter by [opening an issue](https://github.com/nathanmarz/specter/issues?utf8=%E2%9C%93&q=is%3Aissue+label%3Aquestion+) on Github.
|
||||
|
||||
You can also find help in the #specter channel on [Clojurians](http://clojurians.net/).
|
||||
|
||||
|
|
@ -109,6 +132,7 @@ You can also find help in the #specter channel on [Clojurians](http://clojurians
|
|||
Increment all the values in maps of maps:
|
||||
```clojure
|
||||
user> (use 'com.rpl.specter)
|
||||
user> (use 'com.rpl.specter.macros)
|
||||
user> (transform [MAP-VALS MAP-VALS]
|
||||
inc
|
||||
{:a {:aa 1} :b {:ba -1 :bb 2}})
|
||||
|
|
@ -140,27 +164,6 @@ user> (transform [(filterer odd?) LAST]
|
|||
[2 1 3 6 10 4 8]
|
||||
```
|
||||
|
||||
Remove nils from a nested sequence:
|
||||
|
||||
```clojure
|
||||
user> (setval [:a ALL nil?] NONE {:a [1 2 nil 3 nil]})
|
||||
{:a [1 2 3]}
|
||||
```
|
||||
|
||||
Remove key/value pair from nested map:
|
||||
|
||||
```clojure
|
||||
user> (setval [:a :b :c] NONE {:a {:b {:c 1}}})
|
||||
{:a {:b {}}}
|
||||
```
|
||||
|
||||
Remove key/value pair from nested map, removing maps that become empty along the way:
|
||||
|
||||
```clojure
|
||||
user> (setval [:a (compact :b :c)] NONE {:a {:b {:c 1}}})
|
||||
{}
|
||||
```
|
||||
|
||||
Increment all the odd numbers between indices 1 (inclusive) and 4 (exclusive):
|
||||
|
||||
```clojure
|
||||
|
|
@ -190,10 +193,10 @@ user> (select (walker number?)
|
|||
[2 1 2 1 2 6 7 4]
|
||||
```
|
||||
|
||||
Navigate with string keys:
|
||||
Navigate via non-keyword keys:
|
||||
|
||||
```clojure
|
||||
user> (select ["a" "b"]
|
||||
user> (select [(keypath "a") (keypath "b")]
|
||||
{"a" {"b" 10}})
|
||||
[10]
|
||||
```
|
||||
|
|
@ -210,7 +213,7 @@ user> (transform [(srange 4 11) (filterer even?)]
|
|||
Append [:c :d] to every subsequence that has at least two even numbers:
|
||||
```clojure
|
||||
user> (setval [ALL
|
||||
(selected? (filterer even?) (view count) (pred>= 2))
|
||||
(selected? (filterer even?) (view count) #(>= % 2))
|
||||
END]
|
||||
[:c :d]
|
||||
[[1 2 3 4 5 6] [7 0 -1] [8 8] []])
|
||||
|
|
@ -261,6 +264,7 @@ You can make an "AccountPath" that dynamically chooses its path based on the typ
|
|||
|
||||
|
||||
```clojure
|
||||
(use 'com.rpl.specter.macros)
|
||||
(defprotocolpath AccountPath [])
|
||||
(extend-protocolpath AccountPath
|
||||
User :account
|
||||
|
|
@ -279,7 +283,7 @@ user> (select [ALL AccountPath :funds]
|
|||
[50 51 1 2]
|
||||
```
|
||||
|
||||
The next examples demonstrate recursive navigation. Here's one way to double all the even numbers in a tree:
|
||||
The next examples demonstrate recursive navigation. Here's how to double all the even numbers in a tree:
|
||||
|
||||
```clojure
|
||||
(defprotocolpath TreeWalker [])
|
||||
|
|
@ -292,52 +296,31 @@ The next examples demonstrate recursive navigation. Here's one way to double all
|
|||
;; => [:a 1 [4 [[[3]]] :e] [8 5 [12 7]]]
|
||||
```
|
||||
|
||||
Here's how to reverse the positions of all even numbers in a tree (with order based on a depth first search). This example uses conditional navigation instead of protocol paths to do the walk:
|
||||
Here's how to reverse the positions of all even numbers in a tree (with order based on a depth first search). This example uses conditional navigation instead of protocol paths to do the walk and is much more efficient than using `walker`:
|
||||
|
||||
```clojure
|
||||
(def TreeValues
|
||||
(recursive-path [] p
|
||||
(declarepath TreeValues)
|
||||
|
||||
(providepath TreeValues
|
||||
(if-path vector?
|
||||
[ALL p]
|
||||
[ALL TreeValues]
|
||||
STAY
|
||||
)))
|
||||
))
|
||||
|
||||
|
||||
(transform (subselect TreeValues even?)
|
||||
reverse
|
||||
[1 2 [3 [[4]] 5] [6 [7 8] 9 [[10]]]]
|
||||
)
|
||||
|
||||
;; => [1 10 [3 [[8]] 5] [6 [7 4] 9 [[2]]]]
|
||||
```
|
||||
|
||||
# ClojureScript
|
||||
|
||||
Specter supports ClojureScript! However, some of the differences between Clojure and ClojureScript affect how you use Specter in ClojureScript, in particular with the namespace declarations. In Clojure, you might `(use 'com.rpl.specter)` or say `(:require [com.rpl.specter :refer :all])` in your namespace declaration. But in ClojureScript, these options [aren't allowed](https://groups.google.com/d/msg/clojurescript/SzYK08Oduxo/MxLUjg50gQwJ). Instead, consider using one of these options:
|
||||
|
||||
```clojure
|
||||
(:require [com.rpl.specter :as s])
|
||||
(:require [com.rpl.specter :as s :refer-macros [select transform]]) ;; add in the Specter macros that you need
|
||||
```
|
||||
|
||||
# Future work
|
||||
- Integrate Specter with other kinds of data structures, such as graphs. Desired navigations include: reduction in topological order, navigate to outgoing/incoming nodes, to a subgraph (with metadata indicating how to attach external edges on transformation), to node attributes, to node values, to specific nodes.
|
||||
|
||||
# clj-kondo
|
||||
|
||||
When using Specter in a project with [clj-kondo](https://github.com/clj-kondo/clj-kondo), a lot of the vars will be considered unresolved because internally Specter defines them with macros. The following configuration snippet will resolve these issues if you include it in your `.clj-kondo/config.edn` file.
|
||||
|
||||
```clojure
|
||||
{:lint-as {com.rpl.specter/defcollector clojure.core/defn
|
||||
com.rpl.specter/defdynamicnav clojure.core/defn
|
||||
com.rpl.specter/defmacroalias clojure.core/def
|
||||
com.rpl.specter/defnav clojure.core/defn
|
||||
com.rpl.specter/defrichnav clojure.core/defn}}
|
||||
```
|
||||
|
||||
# Babashka
|
||||
|
||||
This library is compatible with [babashka](https://babashka.org/) as of specter 1.1.4 and babashka 0.7.8.
|
||||
- Make it possible to parallelize selects/transforms
|
||||
- Any connection to transducers?
|
||||
|
||||
# License
|
||||
|
||||
Copyright 2015-2020 Red Planet Labs, Inc. Specter is licensed under Apache License v2.0.
|
||||
Copyright 2015-2016 Red Planet Labs, Inc. Specter is licensed under Apache License v2.0.
|
||||
|
|
|
|||
2
VERSION
2
VERSION
|
|
@ -1 +1 @@
|
|||
1.1.5-SNAPSHOT
|
||||
0.13.0-SNAPSHOT
|
||||
|
|
|
|||
19
bb.edn
19
bb.edn
|
|
@ -1,19 +0,0 @@
|
|||
{:paths ["src/clj"]
|
||||
:tasks
|
||||
{test:clj {:doc "Run clj tests with leiningen"
|
||||
:task (shell "lein test")}
|
||||
|
||||
test:cljs {:doc "Run cljs tests with leiningen"
|
||||
:task (shell "lein doo node test-build once")}
|
||||
|
||||
test:bb {:doc "Run bb tests"
|
||||
:extra-paths ["test"]
|
||||
:extra-deps {org.clojure/test.check {:mvn/version "0.9.0"}
|
||||
io.github.cognitect-labs/test-runner
|
||||
{:git/tag "v0.5.0" :git/sha "b3fd0d2"}
|
||||
org.clojure/tools.namespace {:git/url "https://github.com/babashka/tools.namespace"
|
||||
:git/sha "3625153ee66dfcec2ba600851b5b2cbdab8fae6c"}}
|
||||
:requires ([cognitect.test-runner :as tr])
|
||||
:task (apply tr/-main
|
||||
"-d" "test"
|
||||
*command-line-args*)}}}
|
||||
24
project.clj
24
project.clj
|
|
@ -9,10 +9,10 @@
|
|||
:test-paths ["test", "target/test-classes"]
|
||||
:auto-clean false
|
||||
:dependencies [[riddley "0.1.12"]]
|
||||
:plugins [[lein-codox "0.10.7"]
|
||||
[lein-doo "0.1.7"]]
|
||||
:plugins [[lein-codox "0.9.5"]]
|
||||
:codox {:source-paths ["target/classes" "src/clj"]
|
||||
:namespaces [com.rpl.specter
|
||||
com.rpl.specter.macros
|
||||
com.rpl.specter.zipper
|
||||
com.rpl.specter.protocols
|
||||
com.rpl.specter.transients]
|
||||
|
|
@ -21,23 +21,11 @@
|
|||
#".*" "https://github.com/nathanmarz/specter/tree/{version}/src/clj/{classpath}#L{line}"}}
|
||||
|
||||
|
||||
:cljsbuild {:builds [{:id "test-build"
|
||||
:source-paths ["src/clj" "target/classes" "test"]
|
||||
:compiler {:output-to "out/testable.js"
|
||||
:main 'com.rpl.specter.cljs-test-runner
|
||||
:target :nodejs
|
||||
:optimizations :none}}]}
|
||||
|
||||
:profiles {:dev {:dependencies
|
||||
[[org.clojure/test.check "0.9.0"]
|
||||
[org.clojure/clojure "1.9.0"]
|
||||
[org.clojure/clojurescript "1.10.439"]]}
|
||||
:bench {:dependencies [[org.clojure/clojure "1.9.0"]
|
||||
[criterium "0.4.4"]]}
|
||||
[[org.clojure/test.check "0.7.0"]
|
||||
[org.clojure/clojure "1.7.0"]
|
||||
[org.clojure/clojurescript "1.7.10"]]}
|
||||
|
||||
:test {:dependencies [[org.clojure/clojure "1.7.0"]]}}
|
||||
|
||||
:deploy-repositories
|
||||
[["clojars" {:url "https://repo.clojars.org"
|
||||
:sign-releases false}]]
|
||||
|
||||
:aliases {"deploy" ["do" "clean," "deploy" "clojars"]})
|
||||
|
|
|
|||
20
repl.clj
Normal file
20
repl.clj
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
(require 'cljs.repl)
|
||||
(require 'cljs.build.api)
|
||||
(require 'cljs.repl.node)
|
||||
|
||||
(cljs.build.api/build "target/classes/com/rpl"
|
||||
{:output-to "out/main.js"
|
||||
:verbose true
|
||||
:warning-handlers [(fn [warning-type env extra]
|
||||
(when (warning-type cljs.analyzer/*cljs-warnings*)
|
||||
(when-let [s (cljs.analyzer/error-message warning-type extra)]
|
||||
(binding [*out* *err*]
|
||||
(println "WARNING:" (cljs.analyzer/message env s))
|
||||
(println "Failed to build because of warning!"))
|
||||
|
||||
(System/exit 1))))]})
|
||||
|
||||
(cljs.repl/repl (cljs.repl.node/repl-env)
|
||||
:watch "target/classes/com/rpl"
|
||||
:output-dir "out"
|
||||
:static-fns true)
|
||||
|
|
@ -1,69 +1,74 @@
|
|||
(ns com.rpl.specter.benchmarks
|
||||
(:use [com.rpl.specter]
|
||||
[com.rpl.specter.transients])
|
||||
(:require [clojure.walk :as walk]
|
||||
[com.rpl.specter.impl :as i]
|
||||
[criterium.core :as bench]))
|
||||
[com.rpl.specter macros]
|
||||
[com.rpl.specter.transients]
|
||||
[com.rpl.specter.impl :only [benchmark]])
|
||||
(:require [clojure.walk :as walk]))
|
||||
|
||||
|
||||
;; run via `lein repl` with `(load-file "scripts/benchmarks.clj")`
|
||||
|
||||
|
||||
(defn pretty-float5 [anum]
|
||||
(format "%.5g" anum))
|
||||
|
||||
(defn pretty-float3 [anum]
|
||||
(format "%.3g" anum))
|
||||
|
||||
(defn mean [a-fn]
|
||||
(-> a-fn (bench/benchmark* {}) :mean first (* 1000000)))
|
||||
(defn time-ms [amt afn]
|
||||
(let [start (System/nanoTime)
|
||||
_ (dotimes [_ amt] (afn))
|
||||
end (System/nanoTime)]
|
||||
(/ (- end start) 1000000.0)))
|
||||
|
||||
(defn compare-benchmark [afn-map]
|
||||
(let [results (transform MAP-VALS mean afn-map)
|
||||
|
||||
(defn avg [numbers]
|
||||
(/ (reduce + numbers)
|
||||
(count numbers)
|
||||
1.0))
|
||||
|
||||
(defn average-time-ms [iters amt-per-iter afn]
|
||||
(avg
|
||||
;; treat 1st run as warmup
|
||||
(next
|
||||
(for [i (range (inc iters))]
|
||||
(time-ms amt-per-iter afn)))))
|
||||
|
||||
(defn compare-benchmark [amt-per-iter afn-map]
|
||||
(System/runFinalization)
|
||||
(System/gc)
|
||||
(let [results (transform MAP-VALS
|
||||
(fn [afn]
|
||||
(average-time-ms 8 amt-per-iter afn))
|
||||
afn-map)
|
||||
[[_ best-time] & _ :as sorted] (sort-by last results)]
|
||||
(println "\nMean(us)\tvs best\t\tCode")
|
||||
(doseq [[k t] sorted]
|
||||
(println (pretty-float3 t) "\t\t" (pretty-float3 (/ t best-time 1.0)) "\t\t" k))))
|
||||
|
||||
(defmacro run-benchmark [name & exprs]
|
||||
(let [only-benchmarks (set (filter some? *command-line-args*))
|
||||
all-benchmarks? (empty? only-benchmarks)]
|
||||
(if (or all-benchmarks? (contains? only-benchmarks name))
|
||||
(println "\nAvg(ms)\t\tvs best\t\tCode")
|
||||
(doseq [[k t] sorted]
|
||||
(println (pretty-float5 t) "\t\t" (pretty-float3 (/ t best-time 1.0)) "\t\t" k))))
|
||||
|
||||
|
||||
(defmacro run-benchmark [name amt-per-iter & exprs]
|
||||
(let [afn-map (->> exprs shuffle (map (fn [e] [`(quote ~e) `(fn [] ~e)])) (into {}))]
|
||||
`(do
|
||||
(println "Benchmark:" ~name)
|
||||
(compare-benchmark ~afn-map)
|
||||
(println "\n********************************\n"))))))
|
||||
(println "Benchmark:" ~name (str "(" ~amt-per-iter " iterations)"))
|
||||
(compare-benchmark ~amt-per-iter ~afn-map)
|
||||
(println "\n********************************\n"))))
|
||||
|
||||
(defn specter-dynamic-nested-get [data a b c]
|
||||
(select-any (keypath a b c) data))
|
||||
|
||||
|
||||
(defn get-k [k] (fn [m next] (next (get m k))))
|
||||
|
||||
(def get-a-b-c
|
||||
(reduce
|
||||
(fn [curr afn]
|
||||
(fn [structure]
|
||||
(afn structure curr)))
|
||||
[identity (get-k :c) (get-k :b) (get-k :a)]))
|
||||
|
||||
(let [data {:a {:b {:c 1}}}
|
||||
p (comp-paths :a :b :c)]
|
||||
(run-benchmark "get value in nested map"
|
||||
(run-benchmark "get value in nested map" 2500000
|
||||
(select-any [:a :b :c] data)
|
||||
(select-any (keypath :a :b :c) data)
|
||||
(select-one [:a :b :c] data)
|
||||
(select-first [:a :b :c] data)
|
||||
(select-one! [:a :b :c] data)
|
||||
(compiled-select-any p data)
|
||||
(specter-dynamic-nested-get data :a :b :c)
|
||||
(get-in data [:a :b :c])
|
||||
(get-a-b-c data)
|
||||
(-> data :a :b :c identity)
|
||||
(-> data (get :a) (get :b) (get :c))
|
||||
(-> data :a :b :c)
|
||||
(select-any [(keypath :a) (keypath :b) (keypath :c)] data)))
|
||||
|
||||
|
||||
(let [data {:a {:b {:c 1}}}]
|
||||
(run-benchmark "set value in nested map"
|
||||
(assoc-in data [:a :b :c] 1)
|
||||
(setval [:a :b :c] 1 data)))
|
||||
|
||||
|
||||
;; because below 1.7 there is no update function
|
||||
(defn- my-update [m k afn]
|
||||
|
|
@ -77,7 +82,7 @@
|
|||
(my-update m3 :c afn))))))
|
||||
|
||||
(let [data {:a {:b {:c 1}}}]
|
||||
(run-benchmark "update value in nested map"
|
||||
(run-benchmark "update value in nested map" 500000
|
||||
(update-in data [:a :b :c] inc)
|
||||
(transform [:a :b :c] inc data)
|
||||
(manual-transform data inc)))
|
||||
|
|
@ -108,15 +113,8 @@
|
|||
ret)))))
|
||||
|
||||
|
||||
(let [data '(1 2 3 4 5)]
|
||||
(run-benchmark "transform values of a list"
|
||||
(transform ALL inc data)
|
||||
(doall (sequence (map inc) data))
|
||||
(reverse (into '() (map inc) data))
|
||||
))
|
||||
|
||||
(let [data {:a 1 :b 2 :c 3 :d 4}]
|
||||
(run-benchmark "transform values of a small map"
|
||||
(run-benchmark "transform values of a small map" 500000
|
||||
(into {} (for [[k v] data] [k (inc v)]))
|
||||
(reduce-kv (fn [m k v] (assoc m k (inc v))) {} data)
|
||||
(persistent! (reduce-kv (fn [m k v] (assoc! m k (inc v))) (transient {}) data))
|
||||
|
|
@ -125,14 +123,12 @@
|
|||
(transform MAP-VALS inc data)
|
||||
(zipmap (keys data) (map inc (vals data)))
|
||||
(into {} (map (fn [e] [(key e) (inc (val e))]) data))
|
||||
(into {} (map (fn [e] [(key e) (inc (val e))])) data)
|
||||
(map-vals-map-iterable data inc)
|
||||
(map-vals-map-iterable-transient data inc)
|
||||
))
|
||||
(map-vals-map-iterable-transient data inc)))
|
||||
|
||||
|
||||
(let [data (->> (for [i (range 1000)] [i i]) (into {}))]
|
||||
(run-benchmark "transform values of large map"
|
||||
(run-benchmark "transform values of large map" 600
|
||||
(into {} (for [[k v] data] [k (inc v)]))
|
||||
(reduce-kv (fn [m k v] (assoc m k (inc v))) {} data)
|
||||
(persistent! (reduce-kv (fn [m k v] (assoc! m k (inc v))) (transient {}) data))
|
||||
|
|
@ -142,39 +138,28 @@
|
|||
(transform MAP-VALS inc data)
|
||||
(zipmap (keys data) (map inc (vals data)))
|
||||
(into {} (map (fn [e] [(key e) (inc (val e))]) data))
|
||||
(into {} (map (fn [e] [(key e) (inc (val e))])) data)
|
||||
(map-vals-map-iterable data inc)
|
||||
(map-vals-map-iterable-transient data inc)))
|
||||
|
||||
|
||||
(let [data [1 2 3 4 5 6 7 8 9 10]]
|
||||
(run-benchmark "first value of a size 10 vector"
|
||||
(first data)
|
||||
(select-any ALL data)
|
||||
(select-any FIRST data)
|
||||
(select-first ALL data)
|
||||
))
|
||||
|
||||
(let [data [1 2 3 4 5]]
|
||||
(run-benchmark "map a function over a vector"
|
||||
(run-benchmark "map a function over a vector" 1000000
|
||||
(vec (map inc data))
|
||||
(mapv inc data)
|
||||
(transform ALL inc data)
|
||||
(into [] (map inc) data)))
|
||||
(transform ALL inc data)))
|
||||
|
||||
|
||||
(let [data [1 2 3 4 5 6 7 8 9 10]]
|
||||
(run-benchmark "filter a sequence"
|
||||
(run-benchmark "filter a sequence" 500000
|
||||
(doall (filter even? data))
|
||||
(filterv even? data)
|
||||
(select [ALL even?] data)
|
||||
(select-any (filterer even?) data)
|
||||
(into [] (filter even?) data)))
|
||||
(select-any (filterer even?) data)))
|
||||
|
||||
|
||||
(let [data [{:a 2 :b 2} {:a 1} {:a 4} {:a 6}]
|
||||
xf (comp (map :a) (filter even?))]
|
||||
(run-benchmark "even :a values from sequence of maps"
|
||||
(run-benchmark "even :a values from sequence of maps" 500000
|
||||
(select [ALL :a even?] data)
|
||||
(->> data (mapv :a) (filter even?) doall)
|
||||
(into [] (comp (map :a) (filter even?)) data)
|
||||
|
|
@ -182,18 +167,12 @@
|
|||
|
||||
|
||||
(let [v (vec (range 1000))]
|
||||
(run-benchmark "Append to a large vector"
|
||||
(run-benchmark "END on large vector"
|
||||
2000000
|
||||
(setval END [1] v)
|
||||
(setval AFTER-ELEM 1 v)
|
||||
(reduce conj v [1])
|
||||
(conj v 1)))
|
||||
|
||||
(let [data [1 2 3 4 5 6 7 8 9 10]]
|
||||
(run-benchmark "prepend to a vector"
|
||||
(vec (cons 0 data))
|
||||
(setval BEFORE-ELEM 0 data)
|
||||
(into [0] data)
|
||||
))
|
||||
|
||||
(declarepath TreeValues)
|
||||
|
||||
|
|
@ -217,6 +196,7 @@
|
|||
|
||||
(let [data [1 2 [[3]] [4 6 [7 [8]] 10]]]
|
||||
(run-benchmark "update every value in a tree (represented with vectors)"
|
||||
50000
|
||||
(walk/postwalk (fn [e] (if (and (number? e) (even? e)) (inc e) e)) data)
|
||||
(transform [(walker number?) even?] inc data)
|
||||
(transform [TreeValues even?] inc data)
|
||||
|
|
@ -226,6 +206,7 @@
|
|||
|
||||
(let [toappend (range 1000)]
|
||||
(run-benchmark "transient comparison: building up vectors"
|
||||
8000
|
||||
(reduce (fn [v i] (conj v i)) [] toappend)
|
||||
(reduce (fn [v i] (conj! v i)) (transient []) toappend)
|
||||
(setval END toappend [])
|
||||
|
|
@ -233,6 +214,7 @@
|
|||
|
||||
(let [toappend (range 1000)]
|
||||
(run-benchmark "transient comparison: building up vectors one at a time"
|
||||
7000
|
||||
(reduce (fn [v i] (conj v i)) [] toappend)
|
||||
(reduce (fn [v i] (conj! v i)) (transient []) toappend)
|
||||
(reduce (fn [v i] (setval END [i] v)) [] toappend)
|
||||
|
|
@ -243,6 +225,7 @@
|
|||
tdata (transient data)
|
||||
tdata2 (transient data)]
|
||||
(run-benchmark "transient comparison: assoc'ing in vectors"
|
||||
2500000
|
||||
(assoc data 600 0)
|
||||
(assoc! tdata 600 0)
|
||||
(setval (keypath 600) 0 data)
|
||||
|
|
@ -253,6 +236,7 @@
|
|||
tdata (transient data)
|
||||
tdata2 (transient data)]
|
||||
(run-benchmark "transient comparison: assoc'ing in maps"
|
||||
1500000
|
||||
(assoc data 600 0)
|
||||
(assoc! tdata 600 0)
|
||||
(setval (keypath 600) 0 data)
|
||||
|
|
@ -266,27 +250,31 @@
|
|||
[k (rand)]))
|
||||
tdata (transient data)]
|
||||
(run-benchmark "transient comparison: submap"
|
||||
150000
|
||||
(transform (submap [600 700]) modify-submap data)
|
||||
(transform (submap! [600 700]) modify-submap tdata)))
|
||||
|
||||
(let [data {:x 1}
|
||||
meta-map {:my :metadata}]
|
||||
(run-benchmark "set metadata"
|
||||
1500000
|
||||
(with-meta data meta-map)
|
||||
(setval META meta-map data)))
|
||||
|
||||
(let [data (with-meta {:x 1} {:my :metadata})]
|
||||
(run-benchmark "get metadata"
|
||||
15000000
|
||||
(meta data)
|
||||
(select-any META data)))
|
||||
|
||||
(let [data (with-meta {:x 1} {:my :metadata})]
|
||||
(run-benchmark "vary metadata"
|
||||
800000
|
||||
(vary-meta data assoc :y 2)
|
||||
(setval [META :y] 2 data)))
|
||||
|
||||
(let [data (range 1000)]
|
||||
(run-benchmark "Traverse into a set"
|
||||
(run-benchmark "Traverse into a set" 5000
|
||||
(set data)
|
||||
(set (select ALL data))
|
||||
(into #{} (traverse ALL data))
|
||||
|
|
@ -298,60 +286,12 @@
|
|||
(defn mult-10 [v] (* 10 v))
|
||||
|
||||
(let [data [1 2 3 4 5 6 7 8 9]]
|
||||
(run-benchmark "multi-transform vs. consecutive transforms, one shared nav"
|
||||
(run-benchmark "multi-transform vs. consecutive transforms, one shared nav" 300000
|
||||
(->> data (transform [ALL even?] mult-10) (transform [ALL odd?] dec))
|
||||
(multi-transform [ALL (multi-path [even? (terminal mult-10)] [odd? (terminal dec)])] data)))
|
||||
|
||||
|
||||
(let [data [[1 2 3 4 :a] [5] [6 7 :b 8 9] [10 11 12 13]]]
|
||||
(run-benchmark "multi-transform vs. consecutive transforms, three shared navs"
|
||||
(run-benchmark "multi-transform vs. consecutive transforms, three shared navs" 150000
|
||||
(->> data (transform [ALL ALL number? even?] mult-10) (transform [ALL ALL number? odd?] dec))
|
||||
(multi-transform [ALL ALL number? (multi-path [even? (terminal mult-10)] [odd? (terminal dec)])] data)))
|
||||
|
||||
(let [data {:a 1 :b 2 :c 3 :d 4}]
|
||||
(run-benchmark "namespace qualify keys of a small map"
|
||||
(into {}
|
||||
(map (fn [[k v]] [(keyword (str *ns*) (name k)) v]))
|
||||
data)
|
||||
(reduce-kv (fn [m k v] (assoc m (keyword (str *ns*) (name k)) v)) {} data)
|
||||
(setval [MAP-KEYS NAMESPACE] (str *ns*) data)
|
||||
))
|
||||
|
||||
|
||||
(let [data (->> (for [i (range 1000)] [(keyword (str i)) i]) (into {}))]
|
||||
(run-benchmark "namespace qualify keys of a large map"
|
||||
(into {}
|
||||
(map (fn [[k v]] [(keyword (str *ns*) (name k)) v]))
|
||||
data)
|
||||
(reduce-kv (fn [m k v] (assoc m (keyword (str *ns*) (name k)) v)) {} data)
|
||||
(setval [MAP-KEYS NAMESPACE] (str *ns*) data)
|
||||
))
|
||||
|
||||
(defnav walker-old [afn]
|
||||
(select* [this structure next-fn]
|
||||
(i/walk-select afn next-fn structure))
|
||||
(transform* [this structure next-fn]
|
||||
(i/walk-until afn next-fn structure)))
|
||||
|
||||
(let [data {:a [1 2 {:c '(3 4) :d {:e [1 2 3] 7 8 9 10}}]}]
|
||||
(run-benchmark "walker vs. clojure.walk version"
|
||||
(transform (walker number?) inc data)
|
||||
(transform (walker-old number?) inc data)
|
||||
))
|
||||
|
||||
(let [size 1000
|
||||
middle-idx (/ size 2)
|
||||
v -1
|
||||
rng (range size)
|
||||
data-vec (vec rng)
|
||||
data-lst (apply list rng)]
|
||||
(run-benchmark "before-index vs. srange in middle (vector)"
|
||||
(setval (before-index middle-idx) v data-vec)
|
||||
(setval (srange middle-idx middle-idx) [v] data-vec))
|
||||
(run-benchmark "before-index vs. srange in middle (list)"
|
||||
(setval (before-index middle-idx) v data-lst)
|
||||
(setval (srange middle-idx middle-idx) [v] data-lst))
|
||||
(run-benchmark "before-index at 0 vs. srange vs. cons (list)"
|
||||
(setval (before-index 0) v data-lst)
|
||||
(setval (srange 0 0) [v] data-lst)
|
||||
(cons v data-lst)))
|
||||
|
|
|
|||
|
|
@ -1,4 +0,0 @@
|
|||
#!/bin/bash
|
||||
|
||||
rlwrap java -cp `lein classpath` clojure.main scripts/repl.clj
|
||||
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
(require
|
||||
'[cljs.repl :as repl]
|
||||
'[cljs.repl.node :as node])
|
||||
|
||||
(repl/repl (node/repl-env))
|
||||
|
|
@ -1,8 +1,4 @@
|
|||
#!/bin/bash
|
||||
|
||||
lein javac
|
||||
lein version
|
||||
echo
|
||||
lein show-profiles bench
|
||||
echo
|
||||
java -server -XX:MaxInlineSize=100 -cp "$(lein with-profile bench classpath)" clojure.main scripts/benchmarks.clj "$@"
|
||||
|
||||
java -server -XX:MaxPermSize=128m -XX:MaxInlineSize=100 -cp `lein classpath` clojure.main scripts/benchmarks.clj
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
19
src/clj/com/rpl/specter/defhelpers.clj
Normal file
19
src/clj/com/rpl/specter/defhelpers.clj
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
(ns com.rpl.specter.defhelpers)
|
||||
|
||||
(defn gensyms [amt]
|
||||
(vec (repeatedly amt gensym)))
|
||||
|
||||
(defmacro define-ParamsNeededPath [clj? fn-type invoke-name var-arity-impl]
|
||||
(let [a (with-meta (gensym "array") {:tag 'objects})
|
||||
impls (for [i (range 21)
|
||||
:let [args (vec (gensyms i))
|
||||
setters (for [j (range i)] `(aset ~a ~j ~(get args j)))]]
|
||||
`(~invoke-name [this# ~@args]
|
||||
(let [~a (~(if clj? 'com.rpl.specter.impl/fast-object-array 'object-array) ~i)]
|
||||
~@setters
|
||||
(com.rpl.specter.impl/bind-params* this# ~a 0))))]
|
||||
|
||||
`(defrecord ~'ParamsNeededPath [~'rich-nav ~'num-needed-params]
|
||||
~fn-type
|
||||
~@impls
|
||||
~var-arity-impl)))
|
||||
13
src/clj/com/rpl/specter/defnavhelpers.cljc
Normal file
13
src/clj/com/rpl/specter/defnavhelpers.cljc
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
(ns com.rpl.specter.defnavhelpers
|
||||
(:require [com.rpl.specter.impl :as i]))
|
||||
|
||||
(defn param-delta [i]
|
||||
(fn [^objects params params-idx]
|
||||
(aget params (+ params-idx i))))
|
||||
|
||||
|
||||
(defn bound-params [path start-delta]
|
||||
(fn [^objects params params-idx]
|
||||
(if (i/params-needed-path? path)
|
||||
(i/bind-params* path params (+ params-idx start-delta))
|
||||
path)))
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,54 +1,750 @@
|
|||
(ns com.rpl.specter.macros
|
||||
(:use [com.rpl.specter.protocols :only [RichNavigator]])
|
||||
(:require [com.rpl.specter.impl :as i]))
|
||||
(:use [com.rpl.specter.protocols :only [Navigator]]
|
||||
[com.rpl.specter.impl :only [RichNavigator]])
|
||||
(:require [com.rpl.specter.impl :as i]
|
||||
[clojure.walk :as cljwalk]
|
||||
[com.rpl.specter.defnavhelpers :as dnh]))
|
||||
|
||||
|
||||
(defn- determine-params-impls [impls]
|
||||
(defn ^:no-doc gensyms [amt]
|
||||
(vec (repeatedly amt gensym)))
|
||||
|
||||
(defn ^:no-doc determine-params-impls [impls]
|
||||
(let [grouped (->> impls (map (fn [[n & body]] [n body])) (into {}))]
|
||||
(if-not (= #{'select* 'transform*} (-> grouped keys set))
|
||||
(throw (ex-info "defnav must implement select* and transform*"
|
||||
{:methods (keys grouped)})))
|
||||
(i/throw-illegal "defnav must implement select* and transform*, instead got "
|
||||
(keys grouped)))
|
||||
grouped))
|
||||
|
||||
|
||||
(defmacro richnav [params & impls]
|
||||
(if (empty? params)
|
||||
`(reify RichNavigator ~@impls)
|
||||
`(i/direct-nav-obj
|
||||
(fn ~params
|
||||
(reify RichNavigator
|
||||
~@impls)))))
|
||||
(defmacro richnav
|
||||
"Defines a navigator with full access to collected vals, the parameters array,
|
||||
and the parameters array index. `next-fn` expects to receive the params array,
|
||||
a params index, the collected vals, and finally the next structure.
|
||||
`next-fn` will automatically skip ahead in params array by `num-params`, so the
|
||||
index passed to it is ignored.
|
||||
This is the lowest level way of making navigators."
|
||||
[num-params & impls]
|
||||
(let [{[s-params & s-body] 'select*
|
||||
[t-params & t-body] 'transform*} (determine-params-impls impls)
|
||||
s-next-fn-sym (last s-params)
|
||||
s-pidx-sym (nth s-params 2)
|
||||
t-next-fn-sym (last t-params)
|
||||
t-pidx-sym (nth t-params 2)]
|
||||
|
||||
|
||||
(defmacro nav [params & impls]
|
||||
(let [{[[_ s-structure-sym s-next-fn-sym] & s-body] 'select*
|
||||
[[_ t-structure-sym t-next-fn-sym] & t-body] 'transform*} (determine-params-impls impls)]
|
||||
`(richnav ~params
|
||||
(~'select* [this# vals# ~s-structure-sym next-fn#]
|
||||
(let [~s-next-fn-sym (fn [s#] (next-fn# vals# s#))]
|
||||
`(let [num-params# ~num-params
|
||||
nav# (reify RichNavigator
|
||||
(~'rich-select* ~s-params
|
||||
(let [~s-next-fn-sym (i/mk-jump-next-fn ~s-next-fn-sym ~s-pidx-sym num-params#)]
|
||||
~@s-body))
|
||||
(~'transform* [this# vals# ~t-structure-sym next-fn#]
|
||||
(let [~t-next-fn-sym (fn [s#] (next-fn# vals# s#))]
|
||||
~@t-body)))))
|
||||
(~'rich-transform* ~t-params
|
||||
(let [~t-next-fn-sym (i/mk-jump-next-fn ~t-next-fn-sym ~t-pidx-sym num-params#)]
|
||||
~@t-body)))]
|
||||
|
||||
(defn- helper-name [name method-name]
|
||||
(with-meta (symbol (str name "-" method-name)) {:no-doc true}))
|
||||
(if (zero? num-params#)
|
||||
(i/no-params-rich-compiled-path nav#)
|
||||
(i/->ParamsNeededPath nav# num-params#)))))
|
||||
|
||||
|
||||
(defmacro ^:no-doc lean-nav* [& impls]
|
||||
`(reify Navigator ~@impls))
|
||||
|
||||
(defn ^:no-doc operation-with-bindings [bindings params-sym params-idx-sym op-maker]
|
||||
(let [bindings (partition 2 bindings)
|
||||
binding-fn-syms (gensyms (count bindings))
|
||||
binding-syms (map first bindings)
|
||||
fn-exprs (map second bindings)
|
||||
binding-fn-declarations (vec (mapcat vector binding-fn-syms fn-exprs))
|
||||
binding-declarations (vec (mapcat (fn [s f] [s `(~f ~params-sym ~params-idx-sym)])
|
||||
binding-syms
|
||||
binding-fn-syms))
|
||||
body (op-maker binding-declarations)]
|
||||
`(let [~@binding-fn-declarations]
|
||||
~body)))
|
||||
|
||||
|
||||
(defn- rich-nav-with-bindings-not-inlined [num-params-code bindings impls]
|
||||
(let [{[[_ s-structure-sym s-next-fn-sym] & s-body] 'select*
|
||||
[[_ t-structure-sym t-next-fn-sym] & t-body] 'transform*}
|
||||
(determine-params-impls impls)
|
||||
params-sym (gensym "params")
|
||||
params-idx-sym (gensym "params-idx")]
|
||||
(operation-with-bindings
|
||||
bindings
|
||||
params-sym
|
||||
params-idx-sym
|
||||
(fn [binding-declarations]
|
||||
`(reify RichNavigator
|
||||
(~'rich-select* [this# ~params-sym ~params-idx-sym vals# ~s-structure-sym next-fn#]
|
||||
(let [~@binding-declarations
|
||||
next-params-idx# (+ ~params-idx-sym ~num-params-code)
|
||||
~s-next-fn-sym (fn [structure#]
|
||||
(next-fn# ~params-sym
|
||||
next-params-idx#
|
||||
vals#
|
||||
structure#))]
|
||||
~@s-body))
|
||||
|
||||
(~'rich-transform* [this# ~params-sym ~params-idx-sym vals# ~t-structure-sym next-fn#]
|
||||
(let [~@binding-declarations
|
||||
next-params-idx# (+ ~params-idx-sym ~num-params-code)
|
||||
~t-next-fn-sym (fn [structure#]
|
||||
(next-fn# ~params-sym
|
||||
next-params-idx#
|
||||
vals#
|
||||
structure#))]
|
||||
~@t-body)))))))
|
||||
|
||||
(defn inline-next-fn [body next-fn-sym extra-params]
|
||||
(i/codewalk-until
|
||||
#(and (i/fn-invocation? %) (= next-fn-sym (first %)))
|
||||
(fn [code]
|
||||
(let [code (map #(inline-next-fn % next-fn-sym extra-params) code)]
|
||||
(concat [next-fn-sym] extra-params (rest code))))
|
||||
body))
|
||||
|
||||
(defn- rich-nav-with-bindings-inlined [num-params-code bindings impls]
|
||||
(let [{[[_ s-structure-sym s-next-fn-sym] & s-body] 'select*
|
||||
[[_ t-structure-sym t-next-fn-sym] & t-body] 'transform*}
|
||||
(determine-params-impls impls)
|
||||
params-sym (gensym "params")
|
||||
params-idx-sym (gensym "params-idx")
|
||||
vals-sym (gensym "vals")
|
||||
next-params-idx-sym (gensym "next-params-idx")
|
||||
s-body (inline-next-fn s-body s-next-fn-sym [params-sym next-params-idx-sym vals-sym])
|
||||
t-body (inline-next-fn t-body t-next-fn-sym [params-sym next-params-idx-sym vals-sym])]
|
||||
(operation-with-bindings
|
||||
bindings
|
||||
params-sym
|
||||
params-idx-sym
|
||||
(fn [binding-declarations]
|
||||
`(reify RichNavigator
|
||||
(~'rich-select* [this# ~params-sym ~params-idx-sym ~vals-sym ~s-structure-sym ~s-next-fn-sym]
|
||||
(let [~@binding-declarations
|
||||
~next-params-idx-sym (+ ~params-idx-sym ~num-params-code)]
|
||||
~@s-body))
|
||||
|
||||
(~'rich-transform* [this# ~params-sym ~params-idx-sym ~vals-sym ~t-structure-sym ~t-next-fn-sym]
|
||||
(let [~@binding-declarations
|
||||
~next-params-idx-sym (+ ~params-idx-sym ~num-params-code)]
|
||||
~@t-body)))))))
|
||||
|
||||
(defmacro ^:no-doc rich-nav-with-bindings [opts num-params-code bindings & impls]
|
||||
(if (:inline-next-fn opts)
|
||||
(rich-nav-with-bindings-inlined num-params-code bindings impls)
|
||||
(rich-nav-with-bindings-not-inlined num-params-code bindings impls)))
|
||||
|
||||
|
||||
(defmacro ^:no-doc collector-with-bindings [num-params-code bindings impl]
|
||||
(let [[_ [_ structure-sym] & body] impl
|
||||
params-sym (gensym "params")
|
||||
params-idx-sym (gensym "params")]
|
||||
(operation-with-bindings
|
||||
bindings
|
||||
params-sym
|
||||
params-idx-sym
|
||||
(fn [binding-declarations]
|
||||
`(let [num-params# ~num-params-code
|
||||
cfn# (fn [~params-sym ~params-idx-sym vals# ~structure-sym next-fn#]
|
||||
(let [~@binding-declarations]
|
||||
(next-fn# ~params-sym (+ ~params-idx-sym num-params#) (conj vals# (do ~@body)) ~structure-sym)))]
|
||||
|
||||
(reify RichNavigator
|
||||
(~'rich-select* [this# params# params-idx# vals# structure# next-fn#]
|
||||
(cfn# params# params-idx# vals# structure# next-fn#))
|
||||
(~'rich-transform* [this# params# params-idx# vals# structure# next-fn#]
|
||||
(cfn# params# params-idx# vals# structure# next-fn#))))))))
|
||||
|
||||
|
||||
(defn- delta-param-bindings [params]
|
||||
(->> params
|
||||
(map-indexed (fn [i p] [p `(dnh/param-delta ~i)]))
|
||||
(apply concat)
|
||||
vec))
|
||||
|
||||
|
||||
(defmacro nav
|
||||
"Defines a navigator with late bound parameters. This navigator can be precompiled
|
||||
with other navigators without knowing the parameters. When precompiled with other
|
||||
navigators, the resulting path takes in parameters for all navigators in the path
|
||||
that needed parameters (in the order in which they were declared)."
|
||||
[& impl]
|
||||
(let [[opts params & impls] (if (map? (first impl))
|
||||
impl
|
||||
(cons {} impl))]
|
||||
(if (empty? params)
|
||||
`(i/lean-compiled-path (lean-nav* ~@impls))
|
||||
`(vary-meta
|
||||
(fn ~params (i/lean-compiled-path (lean-nav* ~@impls)))
|
||||
assoc
|
||||
:highernav
|
||||
{:type :lean
|
||||
:params-needed-path
|
||||
(i/->ParamsNeededPath
|
||||
(rich-nav-with-bindings ~opts
|
||||
~(count params)
|
||||
~(delta-param-bindings params)
|
||||
~@impls)
|
||||
|
||||
~(count params))}))))
|
||||
|
||||
|
||||
(defmacro collector
|
||||
"Defines a Collector with late bound parameters. This collector can be precompiled
|
||||
with other selectors without knowing the parameters. When precompiled with other
|
||||
selectors, the resulting selector takes in parameters for all selectors in the path
|
||||
that needed parameters (in the order in which they were declared).
|
||||
"
|
||||
[params body]
|
||||
`(let [rich-nav# (collector-with-bindings ~(count params)
|
||||
~(delta-param-bindings params)
|
||||
~body)]
|
||||
|
||||
(if ~(empty? params)
|
||||
(i/no-params-rich-compiled-path rich-nav#)
|
||||
(vary-meta
|
||||
(fn ~params
|
||||
(i/no-params-rich-compiled-path
|
||||
(collector-with-bindings 0 []
|
||||
~body)))
|
||||
assoc
|
||||
:highernav
|
||||
{:type :rich
|
||||
:params-needed-path
|
||||
(i/->ParamsNeededPath
|
||||
rich-nav#
|
||||
~(count params))}))))
|
||||
|
||||
|
||||
|
||||
(defn ^:no-doc fixed-pathed-operation [bindings op-maker]
|
||||
(let [bindings (partition 2 bindings)
|
||||
late-path-syms (map first bindings)
|
||||
paths-code (vec (map second bindings))
|
||||
delta-syms (vec (gensyms (count bindings)))
|
||||
compiled-syms (vec (gensyms (count bindings)))
|
||||
runtime-bindings (vec (mapcat
|
||||
(fn [l c d]
|
||||
`[~l (dnh/bound-params ~c ~d)])
|
||||
|
||||
late-path-syms
|
||||
compiled-syms
|
||||
delta-syms))
|
||||
total-params-sym (gensym "total-params")
|
||||
body (op-maker runtime-bindings compiled-syms total-params-sym)]
|
||||
`(let [compiled# (doall (map i/comp-paths* ~paths-code))
|
||||
~compiled-syms compiled#
|
||||
deltas# (cons 0 (reductions + (map i/num-needed-params compiled#)))
|
||||
~delta-syms deltas#
|
||||
~total-params-sym (last deltas#)]
|
||||
|
||||
~body)))
|
||||
|
||||
|
||||
(defmacro fixed-pathed-nav
|
||||
"This helper is used to define navigators that take in a fixed number of other
|
||||
paths as input. Those paths may require late-bound params, so this helper
|
||||
will create a parameterized navigator if that is the case. If no late-bound params
|
||||
are required, then the result is executable."
|
||||
[bindings & impls]
|
||||
(fixed-pathed-operation bindings
|
||||
(fn [runtime-bindings compiled-syms total-params-sym]
|
||||
(let [late-syms (map first (partition 2 bindings))
|
||||
lean-bindings (mapcat vector late-syms compiled-syms)]
|
||||
`(if (zero? ~total-params-sym)
|
||||
(let [~@lean-bindings]
|
||||
(i/lean-compiled-path (lean-nav* ~@impls)))
|
||||
|
||||
(i/->ParamsNeededPath
|
||||
(rich-nav-with-bindings {}
|
||||
~total-params-sym
|
||||
~runtime-bindings
|
||||
~@impls)
|
||||
|
||||
~total-params-sym))))))
|
||||
|
||||
|
||||
|
||||
|
||||
(defmacro fixed-pathed-collector
|
||||
"This helper is used to define collectors that take in a fixed number of
|
||||
paths as input. That path may require late-bound params, so this helper
|
||||
will create a parameterized navigator if that is the case. If no late-bound params
|
||||
are required, then the result is executable."
|
||||
[bindings & body]
|
||||
(fixed-pathed-operation bindings
|
||||
(fn [runtime-bindings compiled-syms total-params-sym]
|
||||
(let [late-syms (map first (partition 2 bindings))
|
||||
lean-bindings (mapcat vector late-syms compiled-syms)]
|
||||
`(if (zero? ~total-params-sym)
|
||||
(let [~@lean-bindings]
|
||||
(i/no-params-rich-compiled-path
|
||||
(collector-with-bindings 0 [] ~@body)))
|
||||
(i/->ParamsNeededPath
|
||||
(collector-with-bindings ~total-params-sym
|
||||
~runtime-bindings
|
||||
~@body)
|
||||
|
||||
~total-params-sym))))))
|
||||
|
||||
|
||||
(defmacro paramsfn [params [structure-sym] & impl]
|
||||
`(nav ~params
|
||||
(~'select* [this# structure# next-fn#]
|
||||
(let [afn# (fn [~structure-sym] ~@impl)]
|
||||
(i/filter-select afn# structure# next-fn#)))
|
||||
|
||||
(~'transform* [this# structure# next-fn#]
|
||||
(let [afn# (fn [~structure-sym] ~@impl)]
|
||||
(i/filter-transform afn# structure# next-fn#)))))
|
||||
|
||||
|
||||
(defmacro defnav [name & body]
|
||||
`(def ~name (nav ~@body)))
|
||||
|
||||
(defmacro defcollector [name & body]
|
||||
`(def ~name (collector ~@body)))
|
||||
|
||||
|
||||
(defn- protpath-sym [name]
|
||||
(-> name (str "-prot") symbol))
|
||||
|
||||
|
||||
(defmacro defprotocolpath
|
||||
"Defines a navigator that chooses the path to take based on the type
|
||||
of the value at the current point. May be specified with parameters to
|
||||
specify that all extensions must require that number of parameters.
|
||||
|
||||
Currently not available for ClojureScript.
|
||||
|
||||
Example of usage:
|
||||
(defrecord SingleAccount [funds])
|
||||
(defrecord FamilyAccount [single-accounts])
|
||||
|
||||
(defprotocolpath FundsPath)
|
||||
(extend-protocolpath FundsPath
|
||||
SingleAccount :funds
|
||||
FamilyAccount [ALL FundsPath]
|
||||
)
|
||||
"
|
||||
([name]
|
||||
`(defprotocolpath ~name []))
|
||||
([name params]
|
||||
(let [prot-name (protpath-sym name)
|
||||
m (-> name (str "-retrieve") symbol)
|
||||
num-params (count params)
|
||||
ssym (gensym "structure")
|
||||
rargs [(gensym "params") (gensym "pidx") (gensym "vals") ssym (gensym "next-fn")]
|
||||
retrieve `(~m ~ssym)]
|
||||
|
||||
(defmacro defnav [name params & impls]
|
||||
;; remove the "this" param for the helper
|
||||
(let [helpers (for [[mname [_ & mparams] & mbody] impls]
|
||||
`(defn ~(helper-name name mname) [~@params ~@mparams] ~@mbody))
|
||||
decls (for [[mname & _] impls]
|
||||
`(declare ~(helper-name name mname)))
|
||||
name-with-meta (vary-meta name
|
||||
assoc :arglists (list 'quote (list params)))]
|
||||
`(do
|
||||
~@decls
|
||||
~@helpers
|
||||
(def ~name-with-meta (nav ~params ~@impls)))))
|
||||
(defprotocol ~prot-name (~m [structure#]))
|
||||
(let [nav# (reify RichNavigator
|
||||
(~'rich-select* [this# ~@rargs]
|
||||
(let [inav# ~retrieve]
|
||||
(i/exec-rich-select* inav# ~@rargs)))
|
||||
|
||||
(defmacro defrichnav [name params & impls]
|
||||
(let [name-with-meta (vary-meta name
|
||||
assoc :arglists (list 'quote (list params)))]
|
||||
`(def ~name-with-meta
|
||||
(richnav ~params ~@impls))))
|
||||
(~'rich-transform* [this# ~@rargs]
|
||||
(let [inav# ~retrieve]
|
||||
(i/exec-rich-transform* inav# ~@rargs))))]
|
||||
|
||||
(def ~name
|
||||
(if (= ~num-params 0)
|
||||
(i/no-params-rich-compiled-path nav#)
|
||||
(i/->ParamsNeededPath nav# ~num-params))))))))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
(defn ^:no-doc declared-name [name]
|
||||
(vary-meta (symbol (str name "-declared"))
|
||||
assoc :no-doc true))
|
||||
|
||||
|
||||
(defmacro declarepath
|
||||
([name]
|
||||
`(declarepath ~name []))
|
||||
([name params]
|
||||
(let [platform (if (contains? &env :locals) :cljs :clj)
|
||||
select-exec (if (= platform :clj)
|
||||
`i/exec-rich-select*
|
||||
`i/rich-select*)
|
||||
transform-exec (if (= platform :clj)
|
||||
`i/exec-rich-transform*
|
||||
`i/rich-transform*)
|
||||
num-params (count params)
|
||||
declared (declared-name name)
|
||||
rargs [(gensym "params") (gensym "pidx") (gensym "vals")
|
||||
(gensym "structure") (gensym "next-fn")]]
|
||||
`(do
|
||||
(declare ~declared)
|
||||
(def ~name
|
||||
(let [nav# (reify RichNavigator
|
||||
(~'rich-select* [this# ~@rargs]
|
||||
(~select-exec ~declared ~@rargs))
|
||||
(~'rich-transform* [this# ~@rargs]
|
||||
(~transform-exec ~declared ~@rargs)))]
|
||||
|
||||
(if (= ~num-params 0)
|
||||
(i/no-params-rich-compiled-path nav#)
|
||||
(i/->ParamsNeededPath nav# ~num-params))))))))
|
||||
|
||||
|
||||
(defmacro providepath [name apath]
|
||||
`(let [comped# (i/comp-paths-internalized ~apath)
|
||||
expected-params# (i/num-needed-params ~name)
|
||||
needed-params# (i/num-needed-params comped#)]
|
||||
(if-not (= needed-params# expected-params#)
|
||||
(i/throw-illegal "Invalid number of params in provided path, expected "
|
||||
expected-params# " but got " needed-params#))
|
||||
(def ~(declared-name name)
|
||||
(i/extract-rich-nav (i/coerce-compiled->rich-nav comped#)))))
|
||||
|
||||
|
||||
(defmacro extend-protocolpath
|
||||
"Used in conjunction with `defprotocolpath`. See [[defprotocolpath]]."
|
||||
[protpath & extensions]
|
||||
`(i/extend-protocolpath* ~protpath ~(protpath-sym protpath) ~(vec extensions)))
|
||||
|
||||
;; copied from tools.macro to avoid the dependency
|
||||
(defn ^:no-doc name-with-attributes
|
||||
"To be used in macro definitions.
|
||||
Handles optional docstrings and attribute maps for a name to be defined
|
||||
in a list of macro arguments. If the first macro argument is a string,
|
||||
it is added as a docstring to name and removed from the macro argument
|
||||
list. If afterwards the first macro argument is a map, its entries are
|
||||
added to the name's metadata map and the map is removed from the
|
||||
macro argument list. The return value is a vector containing the name
|
||||
with its extended metadata map and the list of unprocessed macro
|
||||
arguments."
|
||||
[name macro-args]
|
||||
(let [[docstring macro-args] (if (string? (first macro-args))
|
||||
[(first macro-args) (next macro-args)]
|
||||
[nil macro-args])
|
||||
[attr macro-args] (if (map? (first macro-args))
|
||||
[(first macro-args) (next macro-args)]
|
||||
[{} macro-args])
|
||||
attr (if docstring
|
||||
(assoc attr :doc docstring)
|
||||
attr)
|
||||
attr (if (meta name)
|
||||
(conj (meta name) attr)
|
||||
attr)]
|
||||
[(with-meta name attr) macro-args]))
|
||||
|
||||
(defmacro defpathedfn
|
||||
"Defines a higher order navigator that itself takes in one or more paths
|
||||
as input. This macro is generally used in conjunction with [[fixed-pathed-nav]]
|
||||
or [[variable-pathed-nav]]. When inline factoring is applied to a path containing
|
||||
one of these higher order navigators, it will automatically interepret all
|
||||
arguments as paths, factor them accordingly, and set up the callsite to
|
||||
provide the parameters dynamically. Use ^:notpath metadata on arguments
|
||||
to indicate non-path arguments that should not be factored – note that in order
|
||||
to be inline factorable, these arguments must be statically resolvable (e.g. a
|
||||
top level var). See `transformed` for an example."
|
||||
[name & args]
|
||||
(let [[name args] (name-with-attributes name args)
|
||||
name (vary-meta name assoc :pathedfn true)]
|
||||
`(defn ~name ~@args)))
|
||||
|
||||
(defmacro defnavconstructor [name & args]
|
||||
(let [[name [[csym anav] & body-or-bodies]] (name-with-attributes name args)
|
||||
bodies (if (-> body-or-bodies first vector?) [body-or-bodies] body-or-bodies)
|
||||
|
||||
checked-code
|
||||
(doall
|
||||
(for [[args & body] bodies]
|
||||
`(~args
|
||||
(let [ret# (do ~@body)]
|
||||
(if (i/layered-nav? ret#)
|
||||
(i/layered-nav-underlying ret#)
|
||||
(i/throw-illegal "Expected result navigator '" (quote ~anav)
|
||||
"' from nav constructor '" (quote ~name) "'"
|
||||
" constructed with the provided constructor '" (quote ~csym)
|
||||
"'"))))))]
|
||||
|
||||
`(def ~name
|
||||
(vary-meta
|
||||
(let [~csym (i/layered-wrapper ~anav)]
|
||||
(fn ~@checked-code))
|
||||
assoc :layerednav (or (-> ~anav meta :highernav :type) :rich)))))
|
||||
|
||||
|
||||
|
||||
|
||||
(defn ^:no-doc ic-prepare-path [locals-set path]
|
||||
(cond
|
||||
(vector? path)
|
||||
(mapv #(ic-prepare-path locals-set %) path)
|
||||
|
||||
(symbol? path)
|
||||
(if (contains? locals-set path)
|
||||
`(com.rpl.specter.impl/->LocalSym ~path (quote ~path))
|
||||
;; var-get doesn't work in cljs, so capture the val in the macro instead
|
||||
`(com.rpl.specter.impl/->VarUse ~path (var ~path) (quote ~path)))
|
||||
|
||||
|
||||
(i/fn-invocation? path)
|
||||
(let [[op & params] path]
|
||||
;; need special case for 'fn since macroexpand does NOT
|
||||
;; expand fn when run on cljs code, but it's also not considered a special symbol
|
||||
(if (or (= 'fn op) (special-symbol? op))
|
||||
`(com.rpl.specter.impl/->SpecialFormUse ~path (quote ~path))
|
||||
`(com.rpl.specter.impl/->FnInvocation
|
||||
~(ic-prepare-path locals-set op)
|
||||
~(mapv #(ic-prepare-path locals-set %) params)
|
||||
(quote ~path))))
|
||||
|
||||
|
||||
:else
|
||||
`(quote ~path)))
|
||||
|
||||
|
||||
(defn ^:no-doc ic-possible-params [path]
|
||||
(do
|
||||
(mapcat
|
||||
(fn [e]
|
||||
(cond (or (set? e)
|
||||
(map? e) ; in case inline maps are ever extended
|
||||
(and (i/fn-invocation? e) (contains? #{'fn* 'fn} (first e))))
|
||||
[e]
|
||||
|
||||
(i/fn-invocation? e)
|
||||
;; the [e] here handles nav constructors
|
||||
(concat [e] (rest e) (ic-possible-params e))
|
||||
|
||||
(vector? e)
|
||||
(ic-possible-params e)))
|
||||
|
||||
path)))
|
||||
|
||||
|
||||
(defn cljs-macroexpand [env form]
|
||||
(let [expand-fn (i/cljs-analyzer-macroexpand-1)
|
||||
mform (expand-fn env form)]
|
||||
(cond (identical? form mform) mform
|
||||
(and (seq? mform) (#{'js*} (first mform))) form
|
||||
:else (cljs-macroexpand env mform))))
|
||||
|
||||
(defn cljs-macroexpand-all* [env form]
|
||||
(if (and (seq? form)
|
||||
(#{'fn 'fn* 'cljs.core/fn} (first form)))
|
||||
form
|
||||
(let [expanded (if (seq? form) (cljs-macroexpand env form) form)]
|
||||
(cljwalk/walk #(cljs-macroexpand-all* env %) identity expanded))))
|
||||
|
||||
|
||||
(defn cljs-macroexpand-all [env form]
|
||||
(let [ret (cljs-macroexpand-all* env form)]
|
||||
ret))
|
||||
|
||||
|
||||
;; still possible to mess this up with alter-var-root
|
||||
(defmacro path
|
||||
"Same as calling comp-paths, except it caches the composition of the static part
|
||||
of the path for later re-use (when possible). For almost all idiomatic uses
|
||||
of Specter provides huge speedup. This macro is automatically used by the
|
||||
select/transform/setval/replace-in/etc. macros."
|
||||
[& path]
|
||||
(let [;;this is a hack, but the composition of &env is considered stable for cljs
|
||||
platform (if (contains? &env :locals) :cljs :clj)
|
||||
local-syms (if (= platform :cljs)
|
||||
(-> &env :locals keys set) ;cljs
|
||||
(-> &env keys set)) ;clj
|
||||
|
||||
used-locals-cell (i/mutable-cell [])
|
||||
_ (cljwalk/postwalk
|
||||
(fn [e]
|
||||
(if (local-syms e)
|
||||
(i/update-cell! used-locals-cell #(conj % e))
|
||||
e))
|
||||
|
||||
path)
|
||||
used-locals (i/get-cell used-locals-cell)
|
||||
|
||||
;; note: very important to use riddley's macroexpand-all here, so that
|
||||
;; &env is preserved in any potential nested calls to select (like via
|
||||
;; a view function)
|
||||
expanded (if (= platform :clj)
|
||||
(i/clj-macroexpand-all (vec path))
|
||||
(cljs-macroexpand-all &env (vec path)))
|
||||
|
||||
prepared-path (ic-prepare-path local-syms expanded)
|
||||
possible-params (vec (ic-possible-params expanded))
|
||||
|
||||
;; - with invokedynamic here, could go directly to the code
|
||||
;; to invoke and/or parameterize the precompiled path without
|
||||
;; a bunch of checks beforehand
|
||||
cache-sym (vary-meta
|
||||
(gensym "pathcache")
|
||||
assoc :cljs.analyzer/no-resolve true)
|
||||
|
||||
info-sym (gensym "info")
|
||||
|
||||
get-cache-code (if (= platform :clj)
|
||||
`(try (i/get-cell ~cache-sym)
|
||||
(catch ClassCastException e#
|
||||
(if (bound? (var ~cache-sym))
|
||||
(throw e#)
|
||||
(do
|
||||
(alter-var-root
|
||||
(var ~cache-sym)
|
||||
(fn [_#] (i/mutable-cell)))
|
||||
nil))))
|
||||
|
||||
cache-sym)
|
||||
|
||||
add-cache-code (if (= platform :clj)
|
||||
`(i/set-cell! ~cache-sym ~info-sym)
|
||||
`(def ~cache-sym ~info-sym))
|
||||
|
||||
|
||||
precompiled-sym (gensym "precompiled")
|
||||
params-maker-sym (gensym "params-maker")
|
||||
|
||||
handle-params-code
|
||||
(if (= platform :clj)
|
||||
`(i/bind-params* ~precompiled-sym (~params-maker-sym ~@used-locals) 0)
|
||||
`(i/handle-params
|
||||
~precompiled-sym
|
||||
~params-maker-sym
|
||||
~(mapv (fn [p] `(fn [] ~p)) possible-params)))]
|
||||
|
||||
|
||||
(if (= platform :clj)
|
||||
(i/intern* *ns* cache-sym (i/mutable-cell)))
|
||||
`(let [info# ~get-cache-code
|
||||
|
||||
^com.rpl.specter.impl.CachedPathInfo info#
|
||||
(if (nil? info#)
|
||||
(let [~info-sym (i/magic-precompilation
|
||||
~prepared-path
|
||||
~(str *ns*)
|
||||
(quote ~used-locals)
|
||||
(quote ~possible-params))]
|
||||
|
||||
~add-cache-code
|
||||
~info-sym)
|
||||
|
||||
info#)
|
||||
|
||||
|
||||
~precompiled-sym (.-precompiled info#)
|
||||
~params-maker-sym (.-params-maker info#)]
|
||||
(if (nil? ~precompiled-sym)
|
||||
(i/comp-paths* ~(if (= (count path) 1) (first path) (vec path)))
|
||||
(if (nil? ~params-maker-sym)
|
||||
~precompiled-sym
|
||||
~handle-params-code)))))
|
||||
|
||||
|
||||
|
||||
|
||||
(defmacro select
|
||||
"Navigates to and returns a sequence of all the elements specified by the path.
|
||||
This macro will attempt to do inline factoring and caching of the path, falling
|
||||
back to compiling the path on every invocation if it's not possible to
|
||||
factor/cache the path."
|
||||
[apath structure]
|
||||
`(i/compiled-select* (path ~apath) ~structure))
|
||||
|
||||
(defmacro select-one!
|
||||
"Returns exactly one element, throws exception if zero or multiple elements found.
|
||||
This macro will attempt to do inline factoring and caching of the path, falling
|
||||
back to compiling the path on every invocation if it's not possible to
|
||||
factor/cache the path."
|
||||
[apath structure]
|
||||
`(i/compiled-select-one!* (path ~apath) ~structure))
|
||||
|
||||
(defmacro select-one
|
||||
"Like select, but returns either one element or nil. Throws exception if multiple elements found.
|
||||
This macro will attempt to do inline factoring and caching of the path, falling
|
||||
back to compiling the path on every invocation if it's not possible to
|
||||
factor/cache the path."
|
||||
[apath structure]
|
||||
`(i/compiled-select-one* (path ~apath) ~structure))
|
||||
|
||||
(defmacro select-first
|
||||
"Returns first element found.
|
||||
This macro will attempt to do inline factoring and caching of the path, falling
|
||||
back to compiling the path on every invocation if it's not possible to
|
||||
factor/cache the path."
|
||||
[apath structure]
|
||||
`(i/compiled-select-first* (path ~apath) ~structure))
|
||||
|
||||
(defmacro select-any
|
||||
"Returns any element found or [[NONE]] if nothing selected. This is the most
|
||||
efficient of the various selection operations.
|
||||
This macro will attempt to do inline factoring and caching of the path, falling
|
||||
back to compiling the path on every invocation if it's not possible to
|
||||
factor/cache the path."
|
||||
[apath structure]
|
||||
`(i/compiled-select-any* (path ~apath) ~structure))
|
||||
|
||||
(defmacro selected-any?
|
||||
"Returns true if any element was selected, false otherwise.
|
||||
This macro will attempt to do inline factoring and caching of the path, falling
|
||||
back to compiling the path on every invocation if it's not possible to
|
||||
factor/cache the path."
|
||||
[apath structure]
|
||||
`(i/compiled-selected-any?* (path ~apath) ~structure))
|
||||
|
||||
(defmacro transform
|
||||
"Navigates to each value specified by the path and replaces it by the result of running
|
||||
the transform-fn on it.
|
||||
This macro will attempt to do inline factoring and caching of the path, falling
|
||||
back to compiling the path on every invocation if it's not possible to
|
||||
factor/cache the path."
|
||||
[apath transform-fn structure]
|
||||
`(i/compiled-transform* (path ~apath) ~transform-fn ~structure))
|
||||
|
||||
(defmacro multi-transform
|
||||
"Just like `transform` but expects transform functions to be specified
|
||||
inline in the path using `terminal`. Error is thrown if navigation finishes
|
||||
at a non-`terminal` navigator. `terminal-val` is a wrapper around `terminal` and is
|
||||
the `multi-transform` equivalent of `setval`.
|
||||
This macro will attempt to do inline factoring and caching of the path, falling
|
||||
back to compiling the path on every invocation if it's not possible to
|
||||
factor/cache the path."
|
||||
[apath structure]
|
||||
`(i/compiled-multi-transform* (path ~apath) ~structure))
|
||||
|
||||
|
||||
(defmacro setval
|
||||
"Navigates to each value specified by the path and replaces it by `aval`.
|
||||
This macro will attempt to do inline factoring and caching of the path, falling
|
||||
back to compiling the path on every invocation if it's not possible to
|
||||
factor/cache the path."
|
||||
[apath aval structure]
|
||||
`(i/compiled-setval* (path ~apath) ~aval ~structure))
|
||||
|
||||
(defmacro traverse
|
||||
"Return a reducible object that traverses over `structure` to every element
|
||||
specified by the path.
|
||||
This macro will attempt to do inline factoring and caching of the path, falling
|
||||
back to compiling the path on every invocation if it's not possible to
|
||||
factor/cache the path."
|
||||
[apath structure]
|
||||
`(i/do-compiled-traverse (path ~apath) ~structure))
|
||||
|
||||
(defmacro replace-in
|
||||
"Similar to transform, except returns a pair of [transformed-structure sequence-of-user-ret].
|
||||
The transform-fn in this case is expected to return [ret user-ret]. ret is
|
||||
what's used to transform the data structure, while user-ret will be added to the user-ret sequence
|
||||
in the final return. replace-in is useful for situations where you need to know the specific values
|
||||
of what was transformed in the data structure.
|
||||
This macro will attempt to do inline factoring and caching of the path, falling
|
||||
back to compiling the path on every invocation if it's not possible to
|
||||
factor/cache the path."
|
||||
[apath transform-fn structure & args]
|
||||
`(i/compiled-replace-in* (path ~apath) ~transform-fn ~structure ~@args))
|
||||
|
||||
(defmacro collected?
|
||||
"Creates a filter function navigator that takes in all the collected values
|
||||
as input. For arguments, can use `(collected? [a b] ...)` syntax to look
|
||||
at each collected value as individual arguments, or `(collected? v ...)` syntax
|
||||
to capture all the collected values as a single vector."
|
||||
[params & body]
|
||||
(let [platform (if (contains? &env :locals) :cljs :clj)]
|
||||
`(i/collected?* (~'fn [~params] ~@body))))
|
||||
|
|
|
|||
|
|
@ -1,26 +1,60 @@
|
|||
(ns com.rpl.specter.navs
|
||||
#?(:cljs (:require-macros
|
||||
[com.rpl.specter
|
||||
[com.rpl.specter.macros
|
||||
:refer
|
||||
[defnav defrichnav]]
|
||||
[fixed-pathed-collector
|
||||
fixed-pathed-nav
|
||||
defcollector
|
||||
defnav
|
||||
defpathedfn
|
||||
richnav
|
||||
defnavconstructor]]
|
||||
|
||||
[com.rpl.specter.util-macros :refer
|
||||
[doseqres]]))
|
||||
#?(:clj (:use [com.rpl.specter.macros :only [defnav defrichnav]]
|
||||
[com.rpl.specter.util-macros :only [doseqres]]))
|
||||
(:require [com.rpl.specter.impl :as i]
|
||||
#?@(:bb []
|
||||
:clj [[clojure.core.reducers :as r]])))
|
||||
|
||||
(:use #?(:clj [com.rpl.specter macros])
|
||||
#?(:clj [com.rpl.specter.util-macros :only [doseqres]]))
|
||||
(:require [com.rpl.specter.impl :as i]
|
||||
[clojure.walk :as walk]
|
||||
#?(:clj [clojure.core.reducers :as r])
|
||||
[com.rpl.specter.defnavhelpers])) ; so that for cljs it's loaded as macros expand to this
|
||||
|
||||
|
||||
|
||||
(defn- append [coll elem]
|
||||
(-> coll vec (conj elem)))
|
||||
|
||||
(defn not-selected?*
|
||||
[compiled-path vals structure]
|
||||
[compiled-path structure]
|
||||
(->> structure
|
||||
(i/compiled-select-any* compiled-path vals)
|
||||
(i/compiled-select-any* compiled-path)
|
||||
(identical? i/NONE)))
|
||||
|
||||
(defn selected?*
|
||||
[compiled-path vals structure]
|
||||
(not (not-selected?* compiled-path vals structure)))
|
||||
[compiled-path structure]
|
||||
(not (not-selected?* compiled-path structure)))
|
||||
|
||||
(defn walk-select [pred continue-fn structure]
|
||||
(let [ret (i/mutable-cell i/NONE)
|
||||
walker (fn this [structure]
|
||||
(if (pred structure)
|
||||
(let [r (continue-fn structure)]
|
||||
(if-not (identical? r i/NONE)
|
||||
(i/set-cell! ret r))
|
||||
r)
|
||||
|
||||
(walk/walk this identity structure)))]
|
||||
|
||||
(walker structure)
|
||||
(i/get-cell ret)))
|
||||
|
||||
|
||||
(defn key-select [akey structure next-fn]
|
||||
(next-fn (get structure akey)))
|
||||
|
||||
(defn key-transform [akey structure next-fn]
|
||||
(assoc structure akey (next-fn (get structure akey))))
|
||||
|
||||
|
||||
(defn all-select [structure next-fn]
|
||||
|
|
@ -40,33 +74,15 @@
|
|||
(defprotocol AllTransformProtocol
|
||||
(all-transform [structure next-fn]))
|
||||
|
||||
(defn void-transformed-kv-pair? [newkv]
|
||||
(or (identical? newkv i/NONE) (< (count newkv) 2)))
|
||||
|
||||
(defn- non-transient-map-all-transform [structure next-fn empty-map]
|
||||
(reduce-kv
|
||||
(fn [m k v]
|
||||
(let [newkv (next-fn [k v])]
|
||||
(if (void-transformed-kv-pair? newkv)
|
||||
m
|
||||
(assoc m (nth newkv 0) (nth newkv 1)))))
|
||||
(let [[newk newv] (next-fn [k v])]
|
||||
(assoc m newk newv)))
|
||||
|
||||
empty-map
|
||||
structure))
|
||||
|
||||
(defn not-NONE? [v]
|
||||
(-> v (identical? i/NONE) not))
|
||||
|
||||
|
||||
(defn- all-transform-list [structure next-fn]
|
||||
(doall (sequence (comp (map next-fn) (filter not-NONE?)) structure)))
|
||||
|
||||
(defn- all-transform-record [structure next-fn]
|
||||
(reduce
|
||||
(fn [res kv] (conj res (next-fn kv)))
|
||||
structure
|
||||
structure
|
||||
))
|
||||
|
||||
(extend-protocol AllTransformProtocol
|
||||
nil
|
||||
|
|
@ -74,6 +90,7 @@
|
|||
nil)
|
||||
|
||||
|
||||
;; in cljs they're PersistentVector so don't need a special case
|
||||
#?(:clj clojure.lang.MapEntry)
|
||||
#?(:clj
|
||||
(all-transform [structure next-fn]
|
||||
|
|
@ -82,65 +99,25 @@
|
|||
(clojure.lang.MapEntry. newk newv))))
|
||||
|
||||
|
||||
#?(:cljs cljs.core/MapEntry)
|
||||
#?(:cljs
|
||||
#?(:clj clojure.lang.PersistentVector :cljs cljs.core/PersistentVector)
|
||||
(all-transform [structure next-fn]
|
||||
(let [newk (next-fn (key structure))
|
||||
newv (next-fn (val structure))]
|
||||
(cljs.core/->MapEntry newk newv nil))))
|
||||
|
||||
#?(:clj clojure.lang.IPersistentVector :cljs cljs.core/PersistentVector)
|
||||
(all-transform [structure next-fn]
|
||||
(into []
|
||||
(comp (map next-fn)
|
||||
(filter not-NONE?))
|
||||
structure))
|
||||
|
||||
#?(:clj String :cljs string)
|
||||
(all-transform [structure next-fn]
|
||||
(apply str (into []
|
||||
(comp (map next-fn)
|
||||
(filter not-NONE?))
|
||||
structure)))
|
||||
|
||||
#?(:clj clojure.lang.PersistentHashSet :cljs cljs.core/PersistentHashSet)
|
||||
(all-transform [structure next-fn]
|
||||
(into #{}
|
||||
(comp (map next-fn)
|
||||
(filter not-NONE?))
|
||||
structure))
|
||||
(mapv next-fn structure))
|
||||
|
||||
#?(:clj clojure.lang.PersistentArrayMap)
|
||||
#?(:bb
|
||||
(all-transform [structure next-fn]
|
||||
(non-transient-map-all-transform structure next-fn {}))
|
||||
:clj
|
||||
#?(:clj
|
||||
(all-transform [structure next-fn]
|
||||
(let [k-it (.keyIterator structure)
|
||||
v-it (.valIterator structure)
|
||||
none-cell (i/mutable-cell 0)
|
||||
len (.count structure)
|
||||
array (i/fast-object-array (* 2 len))]
|
||||
(loop [i 0
|
||||
j 0]
|
||||
array (i/fast-object-array (* 2 (.count structure)))]
|
||||
(loop [i 0]
|
||||
(if (.hasNext k-it)
|
||||
(let [k (.next k-it)
|
||||
v (.next v-it)
|
||||
newkv (next-fn [k v])]
|
||||
(if (void-transformed-kv-pair? newkv)
|
||||
(do
|
||||
(i/update-cell! none-cell inc)
|
||||
(recur (+ i 2) j))
|
||||
(do
|
||||
(aset array j (nth newkv 0))
|
||||
(aset array (inc j) (nth newkv 1))
|
||||
(recur (+ i 2) (+ j 2)))))))
|
||||
(let [none-count (i/get-cell none-cell)
|
||||
array (if (not= 0 none-count)
|
||||
(java.util.Arrays/copyOf array (int (* 2 (- len none-count))))
|
||||
array
|
||||
)]
|
||||
(clojure.lang.PersistentArrayMap/createAsIfByAssoc array)))))
|
||||
[newk newv] (next-fn [k v])]
|
||||
(aset array i newk)
|
||||
(aset array (inc i) newv)
|
||||
(recur (+ i 2)))))
|
||||
(clojure.lang.PersistentArrayMap. array))))
|
||||
|
||||
|
||||
#?(:cljs cljs.core/PersistentArrayMap)
|
||||
|
|
@ -153,20 +130,14 @@
|
|||
(all-transform [structure next-fn]
|
||||
(non-transient-map-all-transform structure next-fn (empty structure)))
|
||||
|
||||
#?(:clj clojure.lang.IRecord)
|
||||
#?(:clj
|
||||
(all-transform [structure next-fn]
|
||||
(all-transform-record structure next-fn)))
|
||||
|
||||
#?(:clj clojure.lang.PersistentHashMap :cljs cljs.core/PersistentHashMap)
|
||||
(all-transform [structure next-fn]
|
||||
(persistent!
|
||||
(reduce-kv
|
||||
(fn [m k v]
|
||||
(let [newkv (next-fn [k v])]
|
||||
(if (void-transformed-kv-pair? newkv)
|
||||
m
|
||||
(assoc! m (nth newkv 0) (nth newkv 1)))))
|
||||
(let [[newk newv] (next-fn [k v])]
|
||||
(assoc! m newk newv)))
|
||||
|
||||
(transient
|
||||
#?(:clj clojure.lang.PersistentHashMap/EMPTY :cljs cljs.core.PersistentHashMap.EMPTY))
|
||||
|
|
@ -180,170 +151,77 @@
|
|||
(all-transform [structure next-fn]
|
||||
(let [empty-structure (empty structure)]
|
||||
(cond (and (list? empty-structure) (not (queue? empty-structure)))
|
||||
(all-transform-list structure next-fn)
|
||||
;; this is done to maintain order, otherwise lists get reversed
|
||||
(doall (map next-fn structure))
|
||||
|
||||
(map? structure)
|
||||
;; reduce-kv is much faster than doing r/map through call to (into ...)
|
||||
(reduce-kv
|
||||
(fn [m k v]
|
||||
(let [newkv (next-fn [k v])]
|
||||
(if (void-transformed-kv-pair? newkv)
|
||||
m
|
||||
(assoc m (nth newkv 0) (nth newkv 1)))))
|
||||
(let [[newk newv] (next-fn [k v])]
|
||||
(assoc m newk newv)))
|
||||
|
||||
empty-structure
|
||||
structure)
|
||||
|
||||
|
||||
:else
|
||||
#?(:bb (into empty-structure
|
||||
(comp (map next-fn) (filter not-NONE?))
|
||||
structure)
|
||||
:clj (->> structure
|
||||
(r/map next-fn)
|
||||
(r/filter not-NONE?)
|
||||
(into empty-structure)))))))
|
||||
(->> structure (r/map next-fn) (into empty-structure))))))
|
||||
|
||||
|
||||
#?(:cljs default)
|
||||
#?(:cljs
|
||||
(all-transform [structure next-fn]
|
||||
(if (record? structure)
|
||||
;; this case is solely for cljs since extending to IRecord doesn't work for cljs
|
||||
(all-transform-record structure next-fn)
|
||||
(let [empty-structure (empty structure)]
|
||||
(cond
|
||||
(and (list? empty-structure) (not (queue? empty-structure)))
|
||||
(all-transform-list structure next-fn)
|
||||
|
||||
(map? structure)
|
||||
(reduce-kv
|
||||
(fn [m k v]
|
||||
(let [newkv (next-fn [k v])]
|
||||
(if (void-transformed-kv-pair? newkv)
|
||||
m
|
||||
(assoc m (nth newkv 0) (nth newkv 1)))))
|
||||
empty-structure
|
||||
structure)
|
||||
|
||||
:else
|
||||
(into empty-structure
|
||||
(comp (map next-fn) (filter not-NONE?))
|
||||
structure)))))))
|
||||
(if (and (list? empty-structure) (not (queue? empty-structure)))
|
||||
;; this is done to maintain order, otherwise lists get reversed
|
||||
(doall (map next-fn structure))
|
||||
(into empty-structure (map #(next-fn %)) structure))))))
|
||||
|
||||
|
||||
|
||||
(defprotocol MapTransformProtocol
|
||||
(map-vals-transform [structure next-fn])
|
||||
(map-keys-transform [structure next-fn])
|
||||
)
|
||||
|
||||
|
||||
(defprotocol MapValsTransformProtocol
|
||||
(map-vals-transform [structure next-fn]))
|
||||
|
||||
(defn map-vals-non-transient-transform [structure empty-map next-fn]
|
||||
(reduce-kv
|
||||
(fn [m k v]
|
||||
(let [newv (next-fn v)]
|
||||
(if (identical? newv i/NONE)
|
||||
m
|
||||
(assoc m k newv))))
|
||||
(assoc m k (next-fn v)))
|
||||
empty-map
|
||||
structure))
|
||||
|
||||
(defn map-keys-non-transient-transform [structure empty-map next-fn]
|
||||
(reduce-kv
|
||||
(fn [m k v]
|
||||
(let [newk (next-fn k)]
|
||||
(if (identical? newk i/NONE)
|
||||
m
|
||||
(assoc m newk v))))
|
||||
empty-map
|
||||
structure))
|
||||
|
||||
(extend-protocol MapTransformProtocol
|
||||
(extend-protocol MapValsTransformProtocol
|
||||
nil
|
||||
(map-vals-transform [structure next-fn]
|
||||
nil)
|
||||
(map-keys-transform [structure next-fn]
|
||||
nil)
|
||||
|
||||
|
||||
#?(:clj clojure.lang.PersistentArrayMap)
|
||||
#?(:bb
|
||||
(map-vals-transform [structure next-fn]
|
||||
(map-vals-non-transient-transform structure {} next-fn))
|
||||
:clj
|
||||
#?(:clj
|
||||
(map-vals-transform [structure next-fn]
|
||||
(let [k-it (.keyIterator structure)
|
||||
v-it (.valIterator structure)
|
||||
none-cell (i/mutable-cell 0)
|
||||
len (.count structure)
|
||||
array (i/fast-object-array (* 2 len))]
|
||||
(loop [i 0
|
||||
j 0]
|
||||
array (i/fast-object-array (* 2 (.count structure)))]
|
||||
(loop [i 0]
|
||||
(if (.hasNext k-it)
|
||||
(let [k (.next k-it)
|
||||
v (.next v-it)
|
||||
newv (next-fn v)]
|
||||
(if (identical? newv i/NONE)
|
||||
(do
|
||||
(i/update-cell! none-cell inc)
|
||||
(recur (+ i 2) j))
|
||||
(do
|
||||
(aset array j k)
|
||||
(aset array (inc j) newv)
|
||||
(recur (+ i 2) (+ j 2)))))))
|
||||
(let [none-count (i/get-cell none-cell)
|
||||
array (if (not= 0 none-count)
|
||||
(java.util.Arrays/copyOf array (int (* 2 (- len none-count))))
|
||||
array
|
||||
)]
|
||||
(clojure.lang.PersistentArrayMap. array)))))
|
||||
#?(:bb
|
||||
(map-keys-transform [structure next-fn]
|
||||
(map-keys-non-transient-transform structure {} next-fn))
|
||||
:clj
|
||||
(map-keys-transform [structure next-fn]
|
||||
(let [k-it (.keyIterator structure)
|
||||
v-it (.valIterator structure)
|
||||
none-cell (i/mutable-cell 0)
|
||||
len (.count structure)
|
||||
array (i/fast-object-array (* 2 len))]
|
||||
(loop [i 0
|
||||
j 0]
|
||||
(if (.hasNext k-it)
|
||||
(let [k (.next k-it)
|
||||
v (.next v-it)
|
||||
newk (next-fn k)]
|
||||
(if (identical? newk i/NONE)
|
||||
(do
|
||||
(i/update-cell! none-cell inc)
|
||||
(recur (+ i 2) j))
|
||||
(do
|
||||
(aset array j newk)
|
||||
(aset array (inc j) v)
|
||||
(recur (+ i 2) (+ j 2)))))))
|
||||
(let [none-count (i/get-cell none-cell)
|
||||
array (if (not= 0 none-count)
|
||||
(java.util.Arrays/copyOf array (int (* 2 (- len none-count))))
|
||||
array
|
||||
)]
|
||||
(clojure.lang.PersistentArrayMap/createAsIfByAssoc array)))))
|
||||
(aset array i k)
|
||||
(aset array (inc i) newv)
|
||||
(recur (+ i 2)))))
|
||||
(clojure.lang.PersistentArrayMap. array))))
|
||||
|
||||
|
||||
#?(:cljs cljs.core/PersistentArrayMap)
|
||||
#?(:cljs
|
||||
(map-vals-transform [structure next-fn]
|
||||
(map-vals-non-transient-transform structure {} next-fn)))
|
||||
#?(:cljs
|
||||
(map-keys-transform [structure next-fn]
|
||||
(map-keys-non-transient-transform structure {} next-fn)))
|
||||
|
||||
|
||||
#?(:clj clojure.lang.PersistentTreeMap :cljs cljs.core/PersistentTreeMap)
|
||||
(map-vals-transform [structure next-fn]
|
||||
(map-vals-non-transient-transform structure (empty structure) next-fn))
|
||||
(map-keys-transform [structure next-fn]
|
||||
(map-keys-non-transient-transform structure (empty structure) next-fn))
|
||||
|
||||
|
||||
#?(:clj clojure.lang.PersistentHashMap :cljs cljs.core/PersistentHashMap)
|
||||
|
|
@ -351,56 +229,47 @@
|
|||
(persistent!
|
||||
(reduce-kv
|
||||
(fn [m k v]
|
||||
(let [newv (next-fn v)]
|
||||
(if (identical? newv i/NONE)
|
||||
m
|
||||
(assoc! m k newv))))
|
||||
(assoc! m k (next-fn v)))
|
||||
(transient
|
||||
#?(:clj clojure.lang.PersistentHashMap/EMPTY :cljs cljs.core.PersistentHashMap.EMPTY))
|
||||
|
||||
structure)))
|
||||
(map-keys-transform [structure next-fn]
|
||||
(persistent!
|
||||
(reduce-kv
|
||||
(fn [m k v]
|
||||
(let [newk (next-fn k)]
|
||||
(if (identical? newk i/NONE)
|
||||
m
|
||||
(assoc! m newk v))))
|
||||
(transient
|
||||
#?(:clj clojure.lang.PersistentHashMap/EMPTY :cljs cljs.core.PersistentHashMap.EMPTY))
|
||||
|
||||
structure)))
|
||||
|
||||
#?(:clj Object :cljs default)
|
||||
(map-vals-transform [structure next-fn]
|
||||
(reduce-kv
|
||||
(fn [m k v]
|
||||
(let [newv (next-fn v)]
|
||||
(if (identical? newv i/NONE)
|
||||
m
|
||||
(assoc m k newv))))
|
||||
(empty structure)
|
||||
structure))
|
||||
(map-keys-transform [structure next-fn]
|
||||
(reduce-kv
|
||||
(fn [m k v]
|
||||
(let [newk (next-fn k)]
|
||||
(if (identical? newk i/NONE)
|
||||
m
|
||||
(assoc m newk v))))
|
||||
(assoc m k (next-fn v)))
|
||||
(empty structure)
|
||||
structure)))
|
||||
|
||||
|
||||
(defn srange-select [structure start end next-fn]
|
||||
(next-fn
|
||||
(if (string? structure)
|
||||
(subs structure start end)
|
||||
(-> structure vec (subvec start end))
|
||||
)))
|
||||
(next-fn (-> structure vec (subvec start end))))
|
||||
|
||||
(def srange-transform i/srange-transform*)
|
||||
|
||||
(defn- matching-indices [aseq p]
|
||||
(keep-indexed (fn [i e] (if (p e) i)) aseq))
|
||||
|
||||
(defn matching-ranges [aseq p]
|
||||
(first
|
||||
(reduce
|
||||
(fn [[ranges curr-start curr-last :as curr] i]
|
||||
(cond
|
||||
(nil? curr-start)
|
||||
[ranges i i]
|
||||
|
||||
(= i (inc curr-last))
|
||||
[ranges curr-start i]
|
||||
|
||||
:else
|
||||
[(conj ranges [curr-start (inc curr-last)]) i i]))
|
||||
|
||||
[[] nil nil]
|
||||
(concat (matching-indices aseq p) [-1]))))
|
||||
|
||||
|
||||
(defn extract-basic-filter-fn [path]
|
||||
(cond (fn? path)
|
||||
|
|
@ -418,31 +287,48 @@
|
|||
|
||||
|
||||
|
||||
(defn if-select [vals structure next-fn then-tester then-nav else-nav]
|
||||
(i/exec-select*
|
||||
(if (then-tester structure) then-nav else-nav)
|
||||
(defn if-select [params params-idx vals structure next-fn then-tester then-nav then-params else-nav]
|
||||
(let [test? (then-tester structure)
|
||||
sel (if test?
|
||||
then-nav
|
||||
else-nav)
|
||||
idx (if test? params-idx (+ params-idx then-params))]
|
||||
(i/exec-rich-select*
|
||||
sel
|
||||
params
|
||||
idx
|
||||
vals
|
||||
structure
|
||||
next-fn))
|
||||
next-fn)))
|
||||
|
||||
|
||||
|
||||
(defn if-transform [vals structure next-fn then-tester then-nav else-nav]
|
||||
(i/exec-transform*
|
||||
(if (then-tester structure) then-nav else-nav)
|
||||
(defn if-transform [params params-idx vals structure next-fn then-tester then-nav then-params else-nav]
|
||||
(let [test? (then-tester structure)
|
||||
tran (if test?
|
||||
then-nav
|
||||
else-nav)
|
||||
idx (if test? params-idx (+ params-idx then-params))]
|
||||
(i/exec-rich-transform*
|
||||
tran
|
||||
params
|
||||
idx
|
||||
vals
|
||||
structure
|
||||
next-fn))
|
||||
next-fn)))
|
||||
|
||||
|
||||
(defn terminal* [params params-idx vals structure]
|
||||
(let [afn (aget ^objects params params-idx)]
|
||||
(if (identical? vals [])
|
||||
(afn structure)
|
||||
(apply afn (conj vals structure)))))
|
||||
|
||||
|
||||
|
||||
(defprotocol AddExtremes
|
||||
(append-all [structure elements])
|
||||
(prepend-all [structure elements])
|
||||
(append-one [structure elem])
|
||||
(prepend-one [structure elem])
|
||||
)
|
||||
(prepend-all [structure elements]))
|
||||
|
||||
(extend-protocol AddExtremes
|
||||
nil
|
||||
|
|
@ -450,12 +336,8 @@
|
|||
elements)
|
||||
(prepend-all [_ elements]
|
||||
elements)
|
||||
(append-one [_ elem]
|
||||
(list elem))
|
||||
(prepend-one [_ elem]
|
||||
(list elem))
|
||||
|
||||
#?(:clj clojure.lang.IPersistentVector :cljs cljs.core/PersistentVector)
|
||||
#?(:clj clojure.lang.PersistentVector :cljs cljs.core/PersistentVector)
|
||||
(append-all [structure elements]
|
||||
(reduce conj structure elements))
|
||||
(prepend-all [structure elements]
|
||||
|
|
@ -464,40 +346,13 @@
|
|||
(reduce conj! <> elements)
|
||||
(reduce conj! <> structure)
|
||||
(persistent! <>))))
|
||||
(append-one [structure elem]
|
||||
(conj structure elem))
|
||||
(prepend-one [structure elem]
|
||||
(into [elem] structure))
|
||||
|
||||
#?(:cljs cljs.core/Subvec)
|
||||
#?(:cljs
|
||||
(append-all [structure elements]
|
||||
(reduce conj structure elements)))
|
||||
#?(:cljs
|
||||
(prepend-all [structure elements]
|
||||
(let [ret (transient [])]
|
||||
(as-> ret <>
|
||||
(reduce conj! <> elements)
|
||||
(reduce conj! <> structure)
|
||||
(persistent! <>)))))
|
||||
#?(:cljs
|
||||
(append-one [structure elem]
|
||||
(conj structure elem)))
|
||||
#?(:cljs
|
||||
(prepend-one [structure elem]
|
||||
(into [elem] structure)))
|
||||
|
||||
|
||||
#?(:clj Object :cljs default)
|
||||
(append-all [structure elements]
|
||||
(concat structure elements))
|
||||
(prepend-all [structure elements]
|
||||
(concat elements structure))
|
||||
(append-one [structure elem]
|
||||
(concat structure [elem]))
|
||||
(prepend-one [structure elem]
|
||||
(cons elem structure))
|
||||
)
|
||||
(concat elements structure)))
|
||||
|
||||
|
||||
|
||||
|
|
@ -512,9 +367,6 @@
|
|||
(defprotocol FastEmpty
|
||||
(fast-empty? [s]))
|
||||
|
||||
(defprotocol InsertBeforeIndex
|
||||
(insert-before-idx [aseq idx val]))
|
||||
|
||||
(defnav PosNavigator [getter updater]
|
||||
(select* [this structure next-fn]
|
||||
(if-not (fast-empty? structure)
|
||||
|
|
@ -525,10 +377,13 @@
|
|||
structure
|
||||
(updater structure next-fn))))
|
||||
|
||||
#?(:bb
|
||||
(defn vec-count [v]
|
||||
(count v))
|
||||
(defn- update-first-list [l afn]
|
||||
(cons (afn (first l)) (rest l)))
|
||||
|
||||
(defn- update-last-list [l afn]
|
||||
(append (butlast l) (afn (last l))))
|
||||
|
||||
#?(
|
||||
:clj
|
||||
(defn vec-count [^clojure.lang.IPersistentVector v]
|
||||
(.length v))
|
||||
|
|
@ -537,53 +392,8 @@
|
|||
(defn vec-count [v]
|
||||
(count v)))
|
||||
|
||||
(defn- update-first-list [l afn]
|
||||
(let [newf (afn (first l))
|
||||
restl (rest l)]
|
||||
(if (identical? i/NONE newf)
|
||||
restl
|
||||
(cons newf restl))))
|
||||
|
||||
(defn- update-last-list [l afn]
|
||||
(let [lastl (afn (last l))
|
||||
bl (butlast l)]
|
||||
(if (identical? i/NONE lastl)
|
||||
(if (nil? bl) '() bl)
|
||||
(concat bl [lastl]))))
|
||||
|
||||
(defn- update-first-vector [v afn]
|
||||
(let [val (nth v 0)
|
||||
newv (afn val)]
|
||||
(if (identical? i/NONE newv)
|
||||
(subvec v 1)
|
||||
(assoc v 0 newv)
|
||||
)))
|
||||
|
||||
(defn- update-last-vector [v afn]
|
||||
;; type-hinting vec-count to ^int caused weird errors with case
|
||||
(let [c (int (vec-count v))]
|
||||
(case c
|
||||
1 (let [[e] v
|
||||
newe (afn e)]
|
||||
(if (identical? i/NONE newe)
|
||||
[]
|
||||
[newe]))
|
||||
2 (let [[e1 e2] v
|
||||
newe (afn e2)]
|
||||
(if (identical? i/NONE newe)
|
||||
[e1]
|
||||
[e1 newe]))
|
||||
(let [i (dec c)
|
||||
newe (afn (nth v i))]
|
||||
(if (identical? i/NONE newe)
|
||||
(pop v)
|
||||
(assoc v i newe))))))
|
||||
|
||||
|
||||
#?(:bb
|
||||
(defn transient-vec-count [v]
|
||||
(count v))
|
||||
|
||||
#?(
|
||||
:clj
|
||||
(defn transient-vec-count [^clojure.lang.ITransientVector v]
|
||||
(.count v))
|
||||
|
|
@ -594,45 +404,19 @@
|
|||
|
||||
|
||||
(extend-protocol UpdateExtremes
|
||||
#?(:clj clojure.lang.IPersistentVector :cljs cljs.core/PersistentVector)
|
||||
#?(:clj clojure.lang.PersistentVector :cljs cljs.core/PersistentVector)
|
||||
(update-first [v afn]
|
||||
(update-first-vector v afn))
|
||||
(let [val (nth v 0)]
|
||||
(assoc v 0 (afn val))))
|
||||
|
||||
(update-last [v afn]
|
||||
(update-last-vector v afn))
|
||||
|
||||
#?(:cljs cljs.core/Subvec)
|
||||
#?(:cljs
|
||||
(update-first [v afn]
|
||||
(update-first-vector v afn)))
|
||||
#?(:cljs
|
||||
(update-last [v afn]
|
||||
(update-last-vector v afn)))
|
||||
|
||||
#?(:clj String :cljs string)
|
||||
(update-first [s afn]
|
||||
(let [rests (subs s 1 (count s))
|
||||
newb (afn (nth s 0))]
|
||||
(if (identical? i/NONE newb)
|
||||
rests
|
||||
(str newb rests))))
|
||||
|
||||
(update-last [s afn]
|
||||
(let [last-idx (-> s count dec)
|
||||
newl (afn (nth s last-idx))
|
||||
begins (subs s 0 last-idx)]
|
||||
(if (identical? i/NONE newl)
|
||||
begins
|
||||
(str begins newl)
|
||||
)))
|
||||
|
||||
#?(:cljs cljs.core/MapEntry)
|
||||
#?(:cljs
|
||||
(update-first [e afn]
|
||||
(cljs.core/->MapEntry (-> e key afn) (val e) nil)))
|
||||
#?(:cljs
|
||||
(update-last [e afn]
|
||||
(cljs.core/->MapEntry (key e) (-> e val afn) nil)))
|
||||
;; type-hinting vec-count to ^int caused weird errors with case
|
||||
(let [c (int (vec-count v))]
|
||||
(case c
|
||||
1 (let [[e] v] [(afn e)])
|
||||
2 (let [[e1 e2] v] [e1 (afn e2)])
|
||||
(let [i (dec c)]
|
||||
(assoc v i (afn (nth v i)))))))
|
||||
|
||||
#?(:clj Object :cljs default)
|
||||
(update-first [l val]
|
||||
|
|
@ -647,27 +431,11 @@
|
|||
(nth v 0))
|
||||
(get-last [v]
|
||||
(peek v))
|
||||
|
||||
#?(:clj Object :cljs default)
|
||||
(get-first [s]
|
||||
(first s))
|
||||
(get-last [s]
|
||||
(last s))
|
||||
|
||||
#?(:cljs cljs.core/MapEntry)
|
||||
#?(:cljs
|
||||
(get-first [e]
|
||||
(key e)))
|
||||
#?(:cljs
|
||||
(get-last [e]
|
||||
(val e)))
|
||||
|
||||
#?(:clj String :cljs string)
|
||||
(get-first [s]
|
||||
(nth s 0))
|
||||
(get-last [s]
|
||||
(nth s (-> s count dec))
|
||||
))
|
||||
(last s)))
|
||||
|
||||
|
||||
|
||||
|
|
@ -686,101 +454,17 @@
|
|||
(empty? s)))
|
||||
|
||||
|
||||
(defn- do-keypath-transform [vals structure key next-fn]
|
||||
(let [newv (next-fn vals (get structure key))]
|
||||
(if (identical? newv i/NONE)
|
||||
(if (sequential? structure)
|
||||
(i/srange-transform* structure key (inc key) (fn [_] []))
|
||||
(dissoc structure key))
|
||||
(assoc structure key newv))))
|
||||
|
||||
(defrichnav
|
||||
^{:doc "Navigates to the specified key, navigating to nil if it does not exist.
|
||||
Setting the value to NONE will remove it from the collection."}
|
||||
keypath*
|
||||
[key]
|
||||
(select* [this vals structure next-fn]
|
||||
(next-fn vals (get structure key)))
|
||||
(transform* [this vals structure next-fn]
|
||||
(do-keypath-transform vals structure key next-fn)
|
||||
))
|
||||
(defn walk-until [pred on-match-fn structure]
|
||||
(if (pred structure)
|
||||
(on-match-fn structure)
|
||||
(walk/walk (partial walk-until pred on-match-fn) identity structure)))
|
||||
|
||||
|
||||
(defrichnav
|
||||
^{:doc "Navigates to the key only if it exists in the map. Setting the value to NONE
|
||||
will remove it from the collection."}
|
||||
must*
|
||||
[k]
|
||||
(select* [this vals structure next-fn]
|
||||
(if (contains? structure k)
|
||||
(next-fn vals (get structure k))
|
||||
i/NONE))
|
||||
(transform* [this vals structure next-fn]
|
||||
(if (contains? structure k)
|
||||
(do-keypath-transform vals structure k next-fn)
|
||||
structure)))
|
||||
|
||||
(defrichnav nthpath*
|
||||
^{:doc "Navigates to the given position in the sequence. Setting the value to NONE
|
||||
will remove it from the sequence. Works for all sequence types."}
|
||||
[i]
|
||||
(select* [this vals structure next-fn]
|
||||
(next-fn vals (nth structure i)))
|
||||
(transform* [this vals structure next-fn]
|
||||
(if (vector? structure)
|
||||
(let [newv (next-fn vals (nth structure i))]
|
||||
(if (identical? newv i/NONE)
|
||||
(i/srange-transform* structure i (inc i) (fn [_] []))
|
||||
(assoc structure i newv)))
|
||||
(i/srange-transform* ; can make this much more efficient with alternate impl
|
||||
structure
|
||||
i
|
||||
(inc i)
|
||||
(fn [[e]]
|
||||
(let [v (next-fn vals e)]
|
||||
(if (identical? v i/NONE)
|
||||
[]
|
||||
[v])
|
||||
))))))
|
||||
|
||||
(defrecord SrangeEndFunction [end-fn])
|
||||
|
||||
;; done this way to maintain backwards compatibility
|
||||
(defn invoke-end-fn [end-fn structure start]
|
||||
(if (instance? SrangeEndFunction end-fn)
|
||||
((:end-fn end-fn) structure start)
|
||||
(end-fn structure)
|
||||
))
|
||||
|
||||
(defn- insert-before-index-list [lst idx val]
|
||||
;; an implementation that is most efficient for list style structures
|
||||
(let [[front back] (split-at idx lst)]
|
||||
(concat front (cons val back))))
|
||||
|
||||
(extend-protocol InsertBeforeIndex
|
||||
nil
|
||||
(insert-before-idx [_ idx val]
|
||||
(if (= 0 idx)
|
||||
(list val)
|
||||
(throw (ex-info "For a nil structure, can only insert before index 0"
|
||||
{:insertion-index idx}))))
|
||||
|
||||
#?(:clj java.lang.String :cljs string)
|
||||
(insert-before-idx [aseq idx val]
|
||||
(apply str (insert-before-index-list aseq idx val)))
|
||||
|
||||
#?(:clj clojure.lang.LazySeq :cljs cljs.core/LazySeq)
|
||||
(insert-before-idx [aseq idx val]
|
||||
(insert-before-index-list aseq idx val))
|
||||
|
||||
#?(:clj clojure.lang.IPersistentVector :cljs cljs.core/PersistentVector)
|
||||
(insert-before-idx [aseq idx val]
|
||||
(let [front (subvec aseq 0 idx)
|
||||
back (subvec aseq idx)]
|
||||
(into (conj front val) back)))
|
||||
|
||||
#?(:clj clojure.lang.IPersistentList :cljs cljs.core/List)
|
||||
(insert-before-idx [aseq idx val]
|
||||
(cond (= idx 0)
|
||||
(cons val aseq)
|
||||
:else (insert-before-index-list aseq idx val))))
|
||||
(def DISPENSE*
|
||||
(i/no-params-rich-compiled-path
|
||||
(reify i/RichNavigator
|
||||
(rich-select* [this params params-idx vals structure next-fn]
|
||||
(next-fn params params-idx [] structure))
|
||||
(rich-transform* [this params params-idx vals structure next-fn]
|
||||
(next-fn params params-idx [] structure)))))
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
(ns com.rpl.specter.protocols)
|
||||
|
||||
(defprotocol RichNavigator
|
||||
"Do not use this protocol directly. All navigators must be created using macros
|
||||
in com.rpl.specter namespace."
|
||||
(select* [this vals structure next-fn]
|
||||
(defprotocol Navigator
|
||||
"Do not use this protocol directly. All navigators must be created using
|
||||
com.rpl.specter.macros namespace."
|
||||
(select* [this structure next-fn]
|
||||
"An implementation of `select*` must call `next-fn` on each
|
||||
subvalue of `structure`. The result of `select*` is specified
|
||||
as follows:
|
||||
|
|
@ -12,7 +12,7 @@
|
|||
2. `NONE` if all calls to `next-fn` return `NONE`
|
||||
3. Otherwise, any non-`NONE` return value from calling `next-fn`
|
||||
")
|
||||
(transform* [this vals structure next-fn]
|
||||
(transform* [this structure next-fn]
|
||||
"An implementation of `transform*` must use `next-fn` to transform
|
||||
any subvalues of `structure` and then merge those transformed values
|
||||
back into `structure`. Everything else in `structure` must be unchanged."))
|
||||
|
|
@ -20,7 +20,7 @@
|
|||
|
||||
(defprotocol Collector
|
||||
"Do not use this protocol directly. All navigators must be created using
|
||||
macros in com.rpl.specter namespace."
|
||||
com.rpl.specter.macros namespace."
|
||||
(collect-val [this structure]))
|
||||
|
||||
(defprotocol ImplicitNav
|
||||
|
|
|
|||
|
|
@ -1,11 +1,13 @@
|
|||
(ns com.rpl.specter.transients
|
||||
#?(:cljs
|
||||
(:require-macros [com.rpl.specter
|
||||
(:require-macros [com.rpl.specter.macros
|
||||
:refer
|
||||
[defnav]]))
|
||||
[defnav
|
||||
defpathedfn]]))
|
||||
(:use #?(:clj
|
||||
[com.rpl.specter :only
|
||||
[defnav]]))
|
||||
[com.rpl.specter.macros :only
|
||||
[defnav
|
||||
defpathedfn]]))
|
||||
(:require [com.rpl.specter.navs :as n]
|
||||
[com.rpl.specter :refer [subselect selected?]]))
|
||||
|
||||
|
|
|
|||
|
|
@ -6,63 +6,7 @@
|
|||
(let [ret# (do ~@body)]
|
||||
(if (identical? ret# ~backup-res)
|
||||
curr#
|
||||
(if (reduced? ret#) (reduced ret#) ret#))))
|
||||
ret#)))
|
||||
|
||||
~backup-res
|
||||
~aseq))
|
||||
|
||||
(defn- gensyms [amt]
|
||||
(vec (repeatedly amt gensym)))
|
||||
|
||||
(defmacro mk-comp-navs []
|
||||
(let [impls (for [i (range 3 20)]
|
||||
(let [[fsym & rsyms :as syms] (gensyms i)]
|
||||
`([~@syms] (~'comp-navs ~fsym (~'comp-navs ~@rsyms)))))
|
||||
last-syms (gensyms 19)]
|
||||
`(defn ~'comp-navs
|
||||
([] ~'com.rpl.specter.impl/STAY*)
|
||||
([nav1#] nav1#)
|
||||
([nav1# nav2#] (~'com.rpl.specter.impl/combine-two-navs nav1# nav2#))
|
||||
~@impls
|
||||
([~@last-syms ~'& rest#]
|
||||
(~'comp-navs
|
||||
(~'comp-navs ~@last-syms)
|
||||
(reduce ~'comp-navs rest#))))))
|
||||
|
||||
|
||||
|
||||
;;TODO: move these definitions somewhere else
|
||||
(defn late-fn-record-name [i]
|
||||
(symbol (str "LateFn" i)))
|
||||
|
||||
(defn late-fn-record-constructor-name [i]
|
||||
(symbol (str "->LateFn" i)))
|
||||
|
||||
(defn- mk-late-fn-record [i]
|
||||
(let [fields (concat ['fn] (for [j (range i)] (symbol (str "arg" j))))
|
||||
dparams (gensym "dynamic-params")
|
||||
resolvers (for [f fields]
|
||||
`(~'late-resolve ~f ~dparams))]
|
||||
`(defrecord ~(late-fn-record-name i) [~@fields]
|
||||
~'LateResolve
|
||||
(~'late-resolve [this# ~dparams]
|
||||
(~@resolvers)))))
|
||||
|
||||
|
||||
(defmacro mk-late-fn-records []
|
||||
(let [impls (for [i (range 20)] (mk-late-fn-record i))]
|
||||
`(do ~@impls)))
|
||||
|
||||
(defmacro mk-late-fn []
|
||||
(let [f (gensym "afn")
|
||||
args (gensym "args")
|
||||
cases (for [i (range 19)]
|
||||
[i
|
||||
(let [gets (for [j (range i)] `(nth ~args ~j))]
|
||||
`(~(late-fn-record-constructor-name i)
|
||||
~f
|
||||
~@gets))])]
|
||||
`(defn ~'late-fn [~f ~args]
|
||||
(case (count ~args)
|
||||
~@(apply concat cases)
|
||||
(throw (ex-info "Cannot have late function with more than 18 args" {}))))))
|
||||
|
|
|
|||
|
|
@ -1,11 +1,10 @@
|
|||
(ns com.rpl.specter.zipper
|
||||
#?(:cljs (:require-macros
|
||||
[com.rpl.specter
|
||||
:refer [defnav nav declarepath providepath recursive-path]]))
|
||||
[com.rpl.specter.macros
|
||||
:refer [defnav nav declarepath providepath]]))
|
||||
#?(:clj
|
||||
(:use
|
||||
[com.rpl.specter :only [defnav nav declarepath providepath
|
||||
recursive-path]]))
|
||||
[com.rpl.specter.macros :only [defnav nav declarepath providepath]]))
|
||||
(:require [com.rpl.specter :as s]
|
||||
[clojure.zip :as zip]))
|
||||
|
||||
|
|
@ -117,15 +116,15 @@
|
|||
(zip/remove inserted))))
|
||||
|
||||
|
||||
(def ^{:doc "Navigate the zipper to the first element
|
||||
(declarepath ^{:doc "Navigate the zipper to the first element
|
||||
in the structure matching predfn. A linear scan
|
||||
is done using NEXT to find the element."}
|
||||
find-first
|
||||
(recursive-path [predfn] p
|
||||
(s/if-path [NODE (s/pred predfn)]
|
||||
s/STAY
|
||||
[NEXT p])))
|
||||
find-first [predfn])
|
||||
|
||||
(providepath find-first
|
||||
(s/if-path [NODE s/pred]
|
||||
s/STAY
|
||||
[NEXT (s/params-reset find-first)]))
|
||||
|
||||
|
||||
(declarepath ^{:doc "Navigate to every element reachable using calls
|
||||
|
|
|
|||
|
|
@ -6,8 +6,8 @@
|
|||
vars (vec (map first parts))
|
||||
genned (reduce
|
||||
(fn [curr [v code]]
|
||||
`(clojure.test.check.generators/bind ~code (fn [~v] ~curr)))
|
||||
`(clojure.test.check.generators/return ~vars)
|
||||
`(cljs.test.check.generators/bind ~code (fn [~v] ~curr)))
|
||||
`(cljs.test.check.generators/return ~vars)
|
||||
(reverse parts))]
|
||||
`(clojure.test.check.properties/for-all [~vars ~genned]
|
||||
`(cljs.test.check.properties/for-all [~vars ~genned]
|
||||
~@body)))
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
(ns com.rpl.specter.cljs-test-runner
|
||||
(:require [doo.runner :refer-macros [doo-tests]]
|
||||
(:require [cljs.test :as test :refer-macros [run-tests]]
|
||||
[com.rpl.specter.core-test]
|
||||
[com.rpl.specter.zipper-test]))
|
||||
|
||||
(doo-tests 'com.rpl.specter.core-test
|
||||
'com.rpl.specter.zipper-test)
|
||||
|
||||
(run-tests 'com.rpl.specter.core-test)
|
||||
(run-tests 'com.rpl.specter.zipper-test)
|
||||
|
|
|
|||
|
|
@ -1,37 +1,33 @@
|
|||
(ns com.rpl.specter.core-test
|
||||
#?(:cljs (:require-macros
|
||||
[cljs.test :refer [is deftest]]
|
||||
[clojure.test.check.clojure-test :refer [defspec]]
|
||||
[cljs.test.check.cljs-test :refer [defspec]]
|
||||
[com.rpl.specter.cljs-test-helpers :refer [for-all+]]
|
||||
[com.rpl.specter.test-helpers :refer [ic-test]]
|
||||
[com.rpl.specter
|
||||
:refer [defprotocolpath defnav extend-protocolpath
|
||||
[com.rpl.specter.macros
|
||||
:refer [paramsfn defprotocolpath defnav extend-protocolpath
|
||||
nav declarepath providepath select select-one select-one!
|
||||
select-first transform setval replace-in
|
||||
select-first transform setval replace-in defnavconstructor
|
||||
select-any selected-any? collected? traverse
|
||||
multi-transform path dynamicnav recursive-path
|
||||
defdynamicnav traverse-all satisfies-protpath? end-fn
|
||||
vtransform]]))
|
||||
multi-transform path]]))
|
||||
(:use
|
||||
#?(:clj [clojure.test :only [deftest is]])
|
||||
#?(:clj [clojure.test.check.clojure-test :only [defspec]])
|
||||
#?(:clj [com.rpl.specter.test-helpers :only [for-all+ ic-test]])
|
||||
#?(:clj [com.rpl.specter
|
||||
:only [defprotocolpath defnav extend-protocolpath
|
||||
#?(:clj [com.rpl.specter.macros
|
||||
:only [paramsfn defprotocolpath defnav extend-protocolpath
|
||||
nav declarepath providepath select select-one select-one!
|
||||
select-first transform setval replace-in
|
||||
select-first transform setval replace-in defnavconstructor
|
||||
select-any selected-any? collected? traverse
|
||||
multi-transform path dynamicnav recursive-path
|
||||
defdynamicnav traverse-all satisfies-protpath? end-fn
|
||||
vtransform]]))
|
||||
multi-transform path]]))
|
||||
|
||||
|
||||
|
||||
(:require #?(:clj [clojure.test.check.generators :as gen])
|
||||
#?(:clj [clojure.test.check.properties :as prop])
|
||||
#?(:cljs [clojure.test.check :as tc])
|
||||
#?(:cljs [clojure.test.check.generators :as gen])
|
||||
#?(:cljs [clojure.test.check.properties :as prop :include-macros true])
|
||||
#?(:cljs [cljs.test.check :as tc])
|
||||
#?(:cljs [cljs.test.check.generators :as gen])
|
||||
#?(:cljs [cljs.test.check.properties :as prop :include-macros true])
|
||||
[com.rpl.specter :as s]
|
||||
[com.rpl.specter.transients :as t]
|
||||
[clojure.set :as set]))
|
||||
|
|
@ -480,12 +476,44 @@
|
|||
(gen-map-with-keys gen/keyword gen/int k2)
|
||||
k1))
|
||||
pred (gen/elements [inc dec])]
|
||||
(let [p (dynamicnav [a b] (path (s/keypath a) (s/keypath b)))]
|
||||
(let [p (s/comp-paths s/keypath s/keypath)]
|
||||
(and
|
||||
(= (s/compiled-select (p k1 k2) m1) (select [k1 k2] m1))
|
||||
(= (s/compiled-transform (p k1 k2) pred m1) (transform [k1 k2] pred m1))))))
|
||||
|
||||
|
||||
(defspec various-orders-comp-test
|
||||
(for-all+
|
||||
[k1 (limit-size 3 gen/keyword)
|
||||
k2 (limit-size 3 gen/keyword)
|
||||
k3 (limit-size 3 gen/keyword)
|
||||
m1 (limit-size 5
|
||||
(gen-map-with-keys
|
||||
gen/keyword
|
||||
(gen-map-with-keys
|
||||
gen/keyword
|
||||
(gen-map-with-keys
|
||||
gen/keyword
|
||||
gen/int
|
||||
k3)
|
||||
|
||||
k2)
|
||||
k1))
|
||||
pred (gen/elements [inc dec])]
|
||||
(let [paths [((s/comp-paths s/keypath s/keypath k3) k1 k2)
|
||||
(s/comp-paths k1 k2 k3)
|
||||
((s/comp-paths s/keypath k2 s/keypath) k1 k3)
|
||||
((s/comp-paths k1 s/keypath k3) k2)
|
||||
(s/comp-paths k1 (s/keypath k2) k3)
|
||||
((s/comp-paths (s/keypath k1) s/keypath (s/keypath k3)) k2)
|
||||
((s/comp-paths s/keypath (s/keypath k2) s/keypath) k1 k3)]]
|
||||
|
||||
|
||||
(and
|
||||
(apply = (for [p paths] (s/compiled-select p m1)))
|
||||
(apply = (for [p paths] (s/compiled-transform p pred m1)))))))
|
||||
|
||||
|
||||
(defspec filterer-param-test
|
||||
(for-all+
|
||||
[k gen/keyword
|
||||
|
|
@ -501,24 +529,18 @@
|
|||
pred (gen/elements [odd? even?])
|
||||
updater (gen/elements [inc dec])]
|
||||
(and
|
||||
(= (select (s/filterer (s/keypath k) pred) v)
|
||||
(select (s/filterer k pred) v))
|
||||
(= (transform [(s/filterer (s/keypath k) pred) s/ALL k2]
|
||||
(= (s/compiled-select ((s/filterer s/keypath pred) k) v)
|
||||
(s/compiled-select (s/filterer k pred) v))
|
||||
(= (s/compiled-transform ((s/comp-paths (s/filterer s/keypath pred) s/ALL k2) k)
|
||||
updater
|
||||
v)
|
||||
(transform [(s/filterer k pred) s/ALL k2]
|
||||
(s/compiled-transform (s/comp-paths (s/filterer k pred) s/ALL k2)
|
||||
updater
|
||||
v)))))
|
||||
|
||||
|
||||
(deftest nested-param-paths
|
||||
(let [p (fn [a b c]
|
||||
(path
|
||||
(s/filterer (s/keypath a)
|
||||
(s/selected? s/ALL
|
||||
(s/keypath b)
|
||||
(s/filterer (s/keypath c) even?)
|
||||
s/ALL))))
|
||||
(let [p (s/filterer s/keypath (s/selected? s/ALL s/keypath (s/filterer s/keypath even?) s/ALL))
|
||||
p2 (p :a :b :c)
|
||||
p3 (s/filterer :a (s/selected? s/ALL :b (s/filterer :c even?) s/ALL))
|
||||
data [{:a [{:b [{:c 4 :d 5}]}]}
|
||||
|
|
@ -552,9 +574,9 @@
|
|||
gen/int
|
||||
k)))]
|
||||
(and
|
||||
(= (s/compiled-select (s/subselect s/ALL (s/keypath k)) v)
|
||||
(= (s/compiled-select ((s/subselect s/ALL s/keypath) k) v)
|
||||
[(map k v)])
|
||||
(let [v2 (s/compiled-transform (s/comp-paths (s/subselect s/ALL (s/keypath k)))
|
||||
(let [v2 (s/compiled-transform ((s/comp-paths (s/subselect s/ALL s/keypath)) k)
|
||||
reverse
|
||||
v)]
|
||||
(and (= (map k v) (reverse (map k v2)))
|
||||
|
|
@ -579,11 +601,11 @@
|
|||
pred2 (gen/elements [odd? even?])
|
||||
updater (gen/elements [inc dec])]
|
||||
|
||||
(let [paths [(path (s/multi-path [(s/keypath k1) pred1] [(s/keypath k2) pred2] k3))
|
||||
(path (s/multi-path [k1 pred1] [(s/keypath k2) pred2] (s/keypath k3)))
|
||||
(path (s/multi-path [(s/keypath k1) pred1] [(s/keypath k2) pred2] (s/keypath k3)))
|
||||
(let [paths [((s/multi-path [s/keypath pred1] [s/keypath pred2] k3) k1 k2)
|
||||
((s/multi-path [k1 pred1] [s/keypath pred2] s/keypath) k2 k3)
|
||||
((s/multi-path [s/keypath pred1] [s/keypath pred2] s/keypath) k1 k2 k3)
|
||||
(s/multi-path [k1 pred1] [k2 pred2] k3)
|
||||
(path (s/multi-path [k1 pred1] [(s/keypath k2) pred2] k3))]]
|
||||
((s/multi-path [k1 pred1] [s/keypath pred2] k3) k2)]]
|
||||
|
||||
(and
|
||||
(apply =
|
||||
|
|
@ -595,6 +617,18 @@
|
|||
(transform p updater m)))))))
|
||||
|
||||
|
||||
|
||||
(defspec paramsfn-test
|
||||
(for-all+
|
||||
[v (gen/vector (gen/elements (range 10)))
|
||||
val (gen/elements (range 10))
|
||||
op (gen/elements [inc dec])
|
||||
comparator (gen/elements [= > <])]
|
||||
(let [cpath (s/comp-paths s/ALL (paramsfn [p] [v] (comparator v p)))]
|
||||
(= (transform (cpath val) op v)
|
||||
(transform [s/ALL #(comparator % val)] op v)))))
|
||||
|
||||
|
||||
(defspec subset-test
|
||||
(for-all+
|
||||
[s1 (gen/vector (limit-size 5 gen/keyword))
|
||||
|
|
@ -677,12 +711,14 @@
|
|||
[:bb [:aa 34 [:abc 10 [:ccc 9 8 [:abc 9 1]]]] [:abc 1 [:abc 3]]]))))
|
||||
|
||||
|
||||
(def map-key-walker
|
||||
(recursive-path [akey] p
|
||||
|
||||
(declarepath map-key-walker [akey])
|
||||
|
||||
(providepath map-key-walker
|
||||
[s/ALL
|
||||
(s/if-path [s/FIRST #(= % akey)]
|
||||
(s/if-path [s/FIRST (paramsfn [akey] [curr] (= curr akey))]
|
||||
s/LAST
|
||||
[s/LAST p])]))
|
||||
[s/LAST (s/params-reset map-key-walker)])])
|
||||
|
||||
(deftest recursive-params-path-test
|
||||
(is (= #{1 2 3} (set (select (map-key-walker :aaa)
|
||||
|
|
@ -695,7 +731,7 @@
|
|||
|
||||
|
||||
(deftest recursive-params-composable-path-test
|
||||
(let [p (fn [k k2] (path (s/keypath k) (map-key-walker k2)))]
|
||||
(let [p (s/comp-paths s/keypath map-key-walker)]
|
||||
(is (= [1] (select (p 1 :a) [{:a 3} {:a 1} {:a 2}])))))
|
||||
|
||||
|
||||
|
|
@ -704,13 +740,13 @@
|
|||
(is (= {3 21 4 31} (transform [s/ALL s/ALL] inc {2 20 3 30}))))
|
||||
|
||||
|
||||
(declarepath NestedHigherOrderWalker [k])
|
||||
|
||||
(def NestedHigherOrderWalker
|
||||
(recursive-path [k] p
|
||||
(providepath NestedHigherOrderWalker
|
||||
(s/if-path vector?
|
||||
(s/if-path [s/FIRST #(= % k)]
|
||||
(s/continue-then-stay s/ALL p)
|
||||
[s/ALL p]))))
|
||||
(s/if-path [s/FIRST (paramsfn [k] [e] (= k e))]
|
||||
(s/continue-then-stay s/ALL (s/params-reset NestedHigherOrderWalker))
|
||||
[s/ALL (s/params-reset NestedHigherOrderWalker)])))
|
||||
|
||||
|
||||
(deftest nested-higher-order-walker-test
|
||||
|
|
@ -722,13 +758,13 @@
|
|||
|
||||
#?(:clj
|
||||
(deftest large-params-test
|
||||
(let [path (apply com.rpl.specter.impl/comp-navs (for [i (range 25)] (s/keypath i)))
|
||||
(let [path (apply s/comp-paths (repeat 25 s/keypath))
|
||||
m (reduce
|
||||
(fn [m k]
|
||||
{k m})
|
||||
:a
|
||||
(reverse (range 25)))]
|
||||
(is (= :a (select-one path m))))))
|
||||
(is (= :a (select-one (apply path (range 25)) m))))))
|
||||
|
||||
;;TODO: there's a bug in clojurescript that won't allow
|
||||
;; non function implementations of IFn to have more than 20 arguments
|
||||
|
|
@ -763,8 +799,8 @@
|
|||
(defrecord LabeledUser [account])
|
||||
(defrecord LabeledFamily [accounts])
|
||||
(extend-protocolpath LabeledAccountPath
|
||||
LabeledUser [:account (s/keypath label)]
|
||||
LabeledFamily [:accounts (s/keypath label) s/ALL])))
|
||||
LabeledUser [:account s/keypath]
|
||||
LabeledFamily [:accounts s/keypath s/ALL])))
|
||||
|
||||
|
||||
#?(:clj
|
||||
|
|
@ -842,13 +878,16 @@
|
|||
(= q1 q2)
|
||||
(= (type q1) (type q2))))))
|
||||
|
||||
(defn ^:direct-nav double-str-keypath [s1 s2]
|
||||
(path (s/keypath (str s1 s2))))
|
||||
(defnavconstructor double-str-keypath
|
||||
[p s/keypath]
|
||||
[s1 s2]
|
||||
(p (str s1 s2)))
|
||||
|
||||
(defn ^:direct-nav some-keypath
|
||||
([] (s/keypath "a"))
|
||||
([k1] (s/keypath (str k1 "!")))
|
||||
([k & args] (s/keypath "bbb")))
|
||||
(defnavconstructor some-keypath
|
||||
[p s/keypath]
|
||||
([] (p "a"))
|
||||
([k1] (p (str k1 "!")))
|
||||
([k & args] (p "bbb")))
|
||||
|
||||
(deftest nav-constructor-test
|
||||
;; this also tests that the eval done by clj platform during inline
|
||||
|
|
@ -864,30 +903,35 @@
|
|||
|
||||
(deftest inline-caching-test
|
||||
(ic-test
|
||||
true
|
||||
[k]
|
||||
[s/ALL (s/must k)]
|
||||
inc
|
||||
[{:a 1} {:b 2 :c 3} {:a 7 :d -1}]
|
||||
[[:a] [:b] [:c] [:d] [:e]])
|
||||
(ic-test
|
||||
true
|
||||
[]
|
||||
[s/ALL #{4 5 11} #(> % 2) (fn [e] (< e 7))]
|
||||
inc
|
||||
(range 20)
|
||||
[])
|
||||
(ic-test
|
||||
false
|
||||
[v]
|
||||
(if v :a :b)
|
||||
inc
|
||||
{:a 1 :b 2}
|
||||
[[true] [false]])
|
||||
(ic-test
|
||||
true
|
||||
[v]
|
||||
[s/ALL (double-str-keypath v (inc v))]
|
||||
str
|
||||
[{"12" :a "1011" :b} {"1011" :c}]
|
||||
[[1] [10]])
|
||||
(ic-test
|
||||
false
|
||||
[k]
|
||||
(*APATH* k)
|
||||
str
|
||||
|
|
@ -896,6 +940,7 @@
|
|||
|
||||
(binding [*APATH* s/must]
|
||||
(ic-test
|
||||
false
|
||||
[k]
|
||||
(*APATH* k)
|
||||
inc
|
||||
|
|
@ -903,6 +948,7 @@
|
|||
[[:a] [:b] [:c]]))
|
||||
|
||||
(ic-test
|
||||
true
|
||||
[k k2]
|
||||
[s/ALL (s/selected? (s/must k) #(> % 2)) (s/must k2)]
|
||||
dec
|
||||
|
|
@ -910,6 +956,7 @@
|
|||
[[:a :b] [:b :a] [:c :d] [:b :c]])
|
||||
|
||||
(ic-test
|
||||
true
|
||||
[]
|
||||
[(s/transformed s/STAY inc)]
|
||||
inc
|
||||
|
|
@ -917,17 +964,30 @@
|
|||
[])
|
||||
|
||||
|
||||
;; verifying that these don't throw errors
|
||||
(is (= 1 (select-any (if true :a :b) {:a 1})))
|
||||
(is (= 3 (select-any (*APATH* :a) {:a 3})))
|
||||
(is (= 2 (select-any [:a (identity even?)] {:a 2})))
|
||||
(s/must-cache-paths!)
|
||||
(is (thrown? #?(:clj Exception :cljs js/Error)
|
||||
(select (if true :a :b) nil)))
|
||||
|
||||
(is (= [10 11] (select-one! [(s/putval 10) (s/transformed s/STAY #(inc %))] 10)))
|
||||
(is (thrown? #?(:clj Exception :cljs js/Error)
|
||||
(select (*APATH* :a) nil)))
|
||||
|
||||
(is (= 2 (let [p :a] (select-one! [p even?] {:a 2}))))
|
||||
(is (thrown? #?(:clj Exception :cljs js/Error)
|
||||
(select [:a (identity even?)] {:a 2})))
|
||||
|
||||
(is (= [{:a 2}] (let [p :a] (select [s/ALL (s/selected? p even?)] [{:a 2}])))))
|
||||
;; this tests a bug that existed before ^:staticparam annotation
|
||||
;; for pathedfns
|
||||
(is (thrown? #?(:clj Exception :cljs js/Error)
|
||||
(select [(s/putval 10) (s/transformed s/STAY #(inc %))] 10)))
|
||||
|
||||
(let [p :a]
|
||||
(is (thrown? #?(:clj Exception :cljs js/Error)
|
||||
(select [p even?] {:a 2}))))
|
||||
|
||||
(let [p :a]
|
||||
(is (thrown? #?(:clj Exception :cljs js/Error)
|
||||
(select [s/ALL (s/selected? p even?)] [{:a 2}]))))
|
||||
|
||||
(s/must-cache-paths! false))
|
||||
|
||||
|
||||
(deftest nested-inline-caching-test
|
||||
|
|
@ -964,7 +1024,8 @@
|
|||
|
||||
|
||||
|
||||
;; verifies that late binding of dynamic parameters works correctly
|
||||
;; there was a bug where the transform-fn was being factored by inline caching
|
||||
;; this verifies that it doesn't do inline caching
|
||||
(deftest transformed-inline-caching
|
||||
(dotimes [i 10]
|
||||
(is (= [(inc i)] (select (s/transformed s/STAY #(+ % i)) 1)))))
|
||||
|
|
@ -1210,11 +1271,12 @@
|
|||
(= (reduce + i (traverse [s/ALL p] v))
|
||||
(reduce + i (filter p v))))))
|
||||
|
||||
(def KeyAccumWalker
|
||||
(recursive-path [k] p
|
||||
(s/if-path (s/must k)
|
||||
s/STAY
|
||||
[s/ALL (s/collect-one s/FIRST) s/LAST p])))
|
||||
|
||||
(declarepath KeyAccumWalker [k])
|
||||
(providepath KeyAccumWalker
|
||||
(s/if-path
|
||||
s/must s/STAY
|
||||
[s/ALL (s/collect-one s/FIRST) s/LAST (s/params-reset KeyAccumWalker)]))
|
||||
|
||||
|
||||
(deftest recursive-if-path-select-vals-test
|
||||
|
|
@ -1276,438 +1338,13 @@
|
|||
[even? (s/terminal-val 100)]
|
||||
[#(= 100 %) (s/terminal inc)]
|
||||
[#(= 101 %) (s/terminal inc)])
|
||||
|
||||
|
||||
1))))
|
||||
|
||||
|
||||
(defdynamicnav ignorer [a]
|
||||
s/STAY)
|
||||
|
||||
(deftest dynamic-nav-ignores-dynamic-arg
|
||||
(let [a 1]
|
||||
(is (= 1 (select-any (ignorer a) 1)))
|
||||
(is (= 1 (select-any (ignorer :a) 1)))))
|
||||
|
||||
|
||||
(deftest nested-dynamic-nav
|
||||
(let [data {:a {:a 1 :b 2} :b {:a 3 :b 4}}
|
||||
afn (fn [a b] (select-any (s/selected? (s/must a)
|
||||
(s/selected? (s/must b)))
|
||||
data))]
|
||||
(is (= data (afn :a :a)))
|
||||
(is (= s/NONE (afn :a :c)))
|
||||
(is (= data (afn :a :b)))
|
||||
(is (= s/NONE (afn :c :a)))
|
||||
(is (= data (afn :b :a)))
|
||||
(is (= data (afn :b :b)))))
|
||||
|
||||
(deftest duplicate-map-keys-test
|
||||
(let [res (setval [s/ALL s/FIRST] "a" {:a 1 :b 2})]
|
||||
(is (= {"a" 2} res))
|
||||
(is (= 1 (count res)))))
|
||||
|
||||
(deftest inline-caching-vector-params-test
|
||||
(is (= [10 [11]] (multi-transform (s/terminal-val [10 [11]]) :a))))
|
||||
|
||||
(defn eachnav-fn-test [akey data]
|
||||
(select-any (s/keypath "a" akey) data))
|
||||
|
||||
(deftest eachnav-test
|
||||
(let [data {"a" {"b" 1 "c" 2}}]
|
||||
(is (= 1 (eachnav-fn-test "b" data)))
|
||||
(is (= 2 (eachnav-fn-test "c" data)))
|
||||
))
|
||||
|
||||
(deftest traversed-test
|
||||
(is (= 10 (select-any (s/traversed s/ALL +) [1 2 3 4]))))
|
||||
|
||||
(defn- predand= [pred v1 v2]
|
||||
(and (pred v1)
|
||||
(pred v2)
|
||||
(= v1 v2)))
|
||||
|
||||
(defn listlike? [v]
|
||||
(or (list? v) (seq? v)))
|
||||
|
||||
(deftest nthpath-test
|
||||
(is (predand= vector? [1 2 -3 4] (transform (s/nthpath 2) - [1 2 3 4])))
|
||||
(is (predand= vector? [1 2 4] (setval (s/nthpath 2) s/NONE [1 2 3 4])))
|
||||
(is (predand= (complement vector?) '(1 -2 3 4) (transform (s/nthpath 1) - '(1 2 3 4))))
|
||||
(is (predand= (complement vector?) '(1 2 4) (setval (s/nthpath 2) s/NONE '(1 2 3 4))))
|
||||
(is (= [0 1 [2 4 4]] (transform (s/nthpath 2 1) inc [0 1 [2 3 4]])))
|
||||
)
|
||||
|
||||
(deftest remove-with-NONE-test
|
||||
(is (predand= vector? [1 2 3] (setval [s/ALL nil?] s/NONE [1 2 nil 3 nil])))
|
||||
(is (predand= listlike? '(1 2 3) (setval [s/ALL nil?] s/NONE '(1 2 nil 3 nil))))
|
||||
(is (= {:b 2} (setval :a s/NONE {:a 1 :b 2})))
|
||||
(is (= {:b 2} (setval (s/must :a) s/NONE {:a 1 :b 2})))
|
||||
(is (predand= vector? [1 3] (setval (s/keypath 1) s/NONE [1 2 3])))
|
||||
;; test with PersistentArrayMap
|
||||
(is (= {:a 1 :c 3} (setval [s/MAP-VALS even?] s/NONE {:a 1 :b 2 :c 3 :d 4})))
|
||||
(is (= {:a 1 :c 3} (setval [s/ALL (s/selected? s/LAST even?)] s/NONE {:a 1 :b 2 :c 3 :d 4})))
|
||||
;; test with PersistentHashMap
|
||||
(let [m (into {} (for [i (range 500)] [i i]))]
|
||||
(is (= (dissoc m 31) (setval [s/MAP-VALS #(= 31 %)] s/NONE m)))
|
||||
(is (= (dissoc m 31) (setval [s/ALL (s/selected? s/LAST #(= 31 %))] s/NONE m)))
|
||||
))
|
||||
|
||||
(deftest fresh-collected-test
|
||||
(let [data [{:a 1 :b 2} {:a 3 :b 3}]]
|
||||
(is (= [[{:a 1 :b 2} 2]]
|
||||
(select [s/ALL
|
||||
s/VAL
|
||||
(s/with-fresh-collected
|
||||
(s/collect-one :a)
|
||||
(s/collected? [a] (= a 1)))
|
||||
:b]
|
||||
data)))
|
||||
(is (= [{:a 1 :b 3} {:a 3 :b 3}]
|
||||
(transform [s/ALL
|
||||
s/VAL
|
||||
(s/with-fresh-collected
|
||||
(s/collect-one :a)
|
||||
(s/collected? [a] (= a 1)))
|
||||
:b]
|
||||
(fn [m v] (+ (:a m) v))
|
||||
data
|
||||
)))
|
||||
))
|
||||
|
||||
(deftest traverse-all-test
|
||||
(is (= 3
|
||||
(transduce (comp (mapcat identity)
|
||||
(traverse-all :a))
|
||||
(completing (fn [r i] (if (= i 4) (reduced r) (+ r i))))
|
||||
0
|
||||
[[{:a 1}] [{:a 2}] [{:a 4}] [{:a 5}]])))
|
||||
(is (= 6
|
||||
(transduce (traverse-all [s/ALL :a])
|
||||
+
|
||||
0
|
||||
[[{:a 1} {:a 2}] [{:a 3}]]
|
||||
)))
|
||||
(is (= [1 2]
|
||||
(into [] (traverse-all :a) [{:a 1} {:a 2}])))
|
||||
)
|
||||
|
||||
(deftest early-terminate-traverse-test
|
||||
(is (= 6
|
||||
(reduce
|
||||
(completing (fn [r i] (if (> r 5) (reduced r) (+ r i))))
|
||||
0
|
||||
(traverse [s/ALL s/ALL]
|
||||
[[1 2] [3 4] [5]])))))
|
||||
|
||||
(deftest select-any-vals-test
|
||||
(is (= [1 1] (select-any s/VAL 1))))
|
||||
|
||||
(deftest conditional-vals-test
|
||||
(is (= 2 (select-any (s/with-fresh-collected
|
||||
(s/collect-one (s/keypath 0))
|
||||
(s/if-path (collected? [n] (even? n))
|
||||
(s/keypath 1)
|
||||
(s/keypath 2)))
|
||||
[4 2 3])))
|
||||
(is (= [4 2 3]
|
||||
(select-any (s/with-fresh-collected
|
||||
(s/collect-one (s/keypath 0))
|
||||
(s/selected? (collected? [n] (even? n))))
|
||||
[4 2 3])))
|
||||
)
|
||||
|
||||
(deftest name-namespace-test
|
||||
(is (= :a (setval s/NAME "a" :e)))
|
||||
(is (= :a/b (setval s/NAME "b" :a/e)))
|
||||
(is (= 'a (setval s/NAME "a" 'e)))
|
||||
(is (= 'a/b (setval s/NAME "b" 'a/e)))
|
||||
(is (= :a/e (setval s/NAMESPACE "a" :e)))
|
||||
(is (= :a/e (setval s/NAMESPACE "a" :f/e)))
|
||||
(is (= 'a/e (setval s/NAMESPACE "a" 'e)))
|
||||
(is (= 'a/e (setval s/NAMESPACE "a" 'f/e)))
|
||||
)
|
||||
|
||||
(deftest string-navigation-test
|
||||
(is (= "ad" (setval (s/srange 1 3) "" "abcd")))
|
||||
(is (= "abcxd" (setval [(s/srange 1 3) s/END] "x" "abcd")))
|
||||
(is (= "bc" (select-any (s/srange 1 3) "abcd")))
|
||||
(is (= "ab" (setval s/END "b" "a")))
|
||||
(is (= "ba" (setval s/BEGINNING "b" "a")))
|
||||
(is (= "" (select-any s/BEGINNING "abc")))
|
||||
(is (= "" (select-any s/END "abc")))
|
||||
(is (= \a (select-any s/FIRST "abc")))
|
||||
(is (= \c (select-any s/LAST "abc")))
|
||||
(is (= "qbc" (setval s/FIRST \q "abc")))
|
||||
(is (= "abq" (setval s/LAST "q" "abc")))
|
||||
)
|
||||
|
||||
(defn whitespace? [char]
|
||||
(re-matches #"\s" (str char)))
|
||||
|
||||
(deftest string-transform-test
|
||||
(is (= "123" (transform s/ALL identity "123")))
|
||||
(is (= "123" (transform [s/ALL whitespace?] s/NONE "1 2 3")))
|
||||
#?(:clj (is (= "123" (setval [s/ALL #(Character/isWhitespace %)] s/NONE "1 2 3"))))
|
||||
#?(:clj (is (= "123" (transform [(s/filterer #(Character/isWhitespace %))] s/NONE "1 2 3")))))
|
||||
|
||||
(deftest regex-navigation-test
|
||||
;; also test regexes as implicit navs
|
||||
(is (= (select #"t" "test") ["t" "t"]))
|
||||
(is (= (select [:a (s/regex-nav #"t")] {:a "test"}) ["t" "t"]))
|
||||
(is (= (transform (s/regex-nav #"t") clojure.string/capitalize "test") "TesT"))
|
||||
;; also test regexes as implicit navs
|
||||
(is (= (transform [:a #"t"] clojure.string/capitalize {:a "test"}) {:a "TesT"}))
|
||||
(is (= (transform (s/regex-nav #"\s+\w") clojure.string/triml "Hello World!") "HelloWorld!"))
|
||||
(is (= (setval (s/regex-nav #"t") "z" "test") "zesz"))
|
||||
(is (= (setval [:a (s/regex-nav #"t")] "z" {:a "test"}) {:a "zesz"}))
|
||||
(is (= (transform (s/regex-nav #"aa*") (fn [s] (-> s count str)) "aadt") "2dt"))
|
||||
(is (= (transform (s/regex-nav #"[Aa]+") (fn [s] (apply str (take (count s) (repeat "@")))) "Amsterdam Aardvarks") "@msterd@m @@rdv@rks"))
|
||||
(is (= (select [(s/regex-nav #"(\S+):\ (\d+)") (s/nthpath 2)] "Mary: 1st George: 2nd Arthur: 3rd") ["1" "2" "3"]))
|
||||
(is (= (transform (s/subselect (s/regex-nav #"\d\w+")) reverse "Mary: 1st George: 2nd Arthur: 3rd") "Mary: 3rd George: 2nd Arthur: 1st"))
|
||||
)
|
||||
|
||||
(deftest single-value-none-navigators-test
|
||||
(is (predand= vector? [1 2 3] (setval s/AFTER-ELEM 3 [1 2])))
|
||||
(is (predand= listlike? '(1 2 3) (setval s/AFTER-ELEM 3 '(1 2))))
|
||||
(is (predand= listlike? '(1) (setval s/AFTER-ELEM 1 nil)))
|
||||
(is (predand= vector? [3 1 2] (setval s/BEFORE-ELEM 3 [1 2])))
|
||||
(is (predand= listlike? '(3 1 2) (setval s/BEFORE-ELEM 3 '(1 2))))
|
||||
(is (predand= listlike? '(1) (setval s/BEFORE-ELEM 1 nil)))
|
||||
(is (= #{1 2 3} (setval s/NONE-ELEM 3 #{1 2})))
|
||||
(is (= #{1} (setval s/NONE-ELEM 1 nil)))
|
||||
)
|
||||
|
||||
(deftest subvec-test
|
||||
(let [v (subvec [1] 0)]
|
||||
(is (predand= vector? [2] (transform s/FIRST inc v)))
|
||||
(is (predand= vector? [2] (transform s/LAST inc v)))
|
||||
(is (predand= vector? [2] (transform s/ALL inc v)))
|
||||
(is (predand= vector? [0 1] (setval s/BEGINNING [0] v)))
|
||||
(is (predand= vector? [1 0] (setval s/END [0] v)))
|
||||
(is (predand= vector? [0 1] (setval s/BEFORE-ELEM 0 v)))
|
||||
(is (predand= vector? [1 0] (setval s/AFTER-ELEM 0 v)))
|
||||
(is (predand= vector? [1 0] (setval (s/srange 1 1) [0] v)))
|
||||
))
|
||||
|
||||
(defspec map-keys-all-first-equivalence-transform
|
||||
(for-all+
|
||||
[m (limit-size 10 (gen/map gen/int gen/keyword))]
|
||||
(= (transform s/MAP-KEYS inc m)
|
||||
(transform [s/ALL s/FIRST] inc m )
|
||||
)))
|
||||
|
||||
(defspec map-keys-all-first-equivalence-select
|
||||
(for-all+
|
||||
[m (limit-size 10 (gen/map gen/int gen/keyword))]
|
||||
(= (select s/MAP-KEYS m)
|
||||
(select [s/ALL s/FIRST] m)
|
||||
)))
|
||||
|
||||
(defspec remove-first-vector
|
||||
(for-all+
|
||||
[v (limit-size 10 (gen/not-empty (gen/vector gen/int)))]
|
||||
(let [newv (setval s/FIRST s/NONE v)]
|
||||
(and (= newv (vec (rest v)))
|
||||
(vector? newv)
|
||||
))))
|
||||
|
||||
(defspec remove-first-list
|
||||
(for-all+
|
||||
[l (limit-size 10 (gen/not-empty (gen/list gen/int)))]
|
||||
(let [newl (setval s/FIRST s/NONE l)]
|
||||
(and (= newl (rest l))
|
||||
(listlike? newl)
|
||||
))))
|
||||
|
||||
(defspec remove-last-vector
|
||||
(for-all+
|
||||
[v (limit-size 10 (gen/not-empty (gen/vector gen/int)))]
|
||||
(let [newv (setval s/LAST s/NONE v)]
|
||||
(and (= newv (vec (butlast v)))
|
||||
(vector? newv)
|
||||
))))
|
||||
|
||||
(defspec remove-last-list
|
||||
(for-all+
|
||||
[l (limit-size 10 (gen/not-empty (gen/list gen/int)))]
|
||||
(let [newl (setval s/LAST s/NONE l)
|
||||
bl (butlast l)]
|
||||
(and (or (= newl bl) (and (nil? bl) (= '() newl)))
|
||||
(seq? newl)
|
||||
))))
|
||||
|
||||
(deftest remove-extreme-string
|
||||
(is (= "b" (setval s/FIRST s/NONE "ab")))
|
||||
(is (= "a" (setval s/LAST s/NONE "ab")))
|
||||
)
|
||||
|
||||
(deftest nested-dynamic-arg-test
|
||||
(let [foo (fn [v] (multi-transform (s/terminal-val [v]) nil))]
|
||||
(is (= [1] (foo 1)))
|
||||
(is (= [10] (foo 10)))
|
||||
))
|
||||
|
||||
(deftest filterer-remove-test
|
||||
(is (= [1 :a 3 5] (setval (s/filterer even?) [:a] [1 2 3 4 5])))
|
||||
(is (= [1 3 5] (setval (s/filterer even?) [] [1 2 3 4 5])))
|
||||
(is (= [1 3 5] (setval (s/filterer even?) nil [1 2 3 4 5])))
|
||||
)
|
||||
|
||||
(deftest helper-preds-test
|
||||
(let [data [1 2 2 3 4 0]]
|
||||
(is (= [2 2] (select [s/ALL (s/pred= 2)] data)))
|
||||
(is (= [1 2 2 0] (select [s/ALL (s/pred< 3)] data)))
|
||||
(is (= [1 2 2 3 0] (select [s/ALL (s/pred<= 3)] data)))
|
||||
(is (= [4] (select [s/ALL (s/pred> 3)] data)))
|
||||
(is (= [3 4] (select [s/ALL (s/pred>= 3)] data)))
|
||||
))
|
||||
|
||||
(deftest map-key-test
|
||||
(is (= {:c 3} (setval (s/map-key :a) :b {:c 3})))
|
||||
(is (= {:b 2} (setval (s/map-key :a) :b {:a 2})))
|
||||
(is (= {:b 2} (setval (s/map-key :a) :b {:a 2 :b 1})))
|
||||
(is (= {:b 2} (setval (s/map-key :a) s/NONE {:a 1 :b 2})))
|
||||
)
|
||||
|
||||
(deftest set-elem-test
|
||||
(is (= #{:b :d} (setval (s/set-elem :a) :x #{:b :d})))
|
||||
(is (= #{:x :a} (setval (s/set-elem :b) :x #{:b :a})))
|
||||
(is (= #{:a} (setval (s/set-elem :b) :a #{:b :a})))
|
||||
(is (= #{:b} (setval (s/set-elem :a) s/NONE #{:a :b})))
|
||||
)
|
||||
|
||||
;; this function necessary to trigger the bug from happening
|
||||
(defn inc2 [v] (inc v))
|
||||
(deftest dynamic-function-arg-test
|
||||
(is (= {[2] 4} (let [a 1] (transform (s/keypath [(inc2 a)]) inc {[2] 3}))))
|
||||
)
|
||||
|
||||
(defrecord FooW [a b])
|
||||
|
||||
(deftest walker-test
|
||||
(is (= [1 2 3 4 5 6] (select (s/walker number?) [{1 2 :b '(3 :c 4)} 5 #{6 :d}])))
|
||||
(is (= [{:b '(:c)} #{:d}] (setval (s/walker number?) s/NONE [{:q 3 10 :l 1 2 :b '(3 :c 4)} 5 #{6 :d}])))
|
||||
(is (= [{:q 4 11 :l 2 3 :b '(4 :c 5)} 6 #{7 :d}]
|
||||
(transform (s/walker number?) inc [{:q 3 10 :l 1 2 :b '(3 :c 4)} 5 #{6 :d}])))
|
||||
(let [f (->FooW 1 2)]
|
||||
(is (= [[:a 1] [:b 2]] (select (s/walker (complement record?)) f)))
|
||||
(is (= (assoc f :a! 1 :b! 2) (setval [(s/walker (complement record?)) s/FIRST s/NAME s/END] "!" f)))
|
||||
(is (= (assoc f :b 1 :c 2) (transform [(s/walker (complement record?)) s/FIRST] (fn [k] (if (= :a k) :b :c)) f)))
|
||||
))
|
||||
|
||||
(def MIDDLE
|
||||
(s/comp-paths
|
||||
(s/srange-dynamic
|
||||
(fn [aseq] (long (/ (count aseq) 2)))
|
||||
(end-fn [aseq s] (if (empty? aseq) 0 (inc s))))
|
||||
s/FIRST
|
||||
))
|
||||
|
||||
(deftest srange-dynamic-test
|
||||
(is (= 2 (select-any MIDDLE [1 2 3])))
|
||||
(is (identical? s/NONE (select-any MIDDLE [])))
|
||||
(is (= 1 (select-any MIDDLE [1])))
|
||||
(is (= 2 (select-any MIDDLE [1 2])))
|
||||
(is (= [1 3 3] (transform MIDDLE inc [1 2 3])))
|
||||
)
|
||||
|
||||
(def ^:dynamic *dvar* :a)
|
||||
|
||||
(defn dvar-tester []
|
||||
(select-any *dvar* {:a 1 :b 2}))
|
||||
|
||||
(deftest dynamic-var-ic-test
|
||||
(is (= 1 (dvar-tester)))
|
||||
(is (= 2 (binding [*dvar* :b] (dvar-tester))))
|
||||
)
|
||||
|
||||
(deftest before-index-test
|
||||
(let [data [1 2 3]
|
||||
datal '(1 2 3)
|
||||
data-str "abcdef"]
|
||||
(is (predand= vector? [:a 1 2 3] (setval (s/before-index 0) :a data)))
|
||||
(is (predand= vector? [1 2 3] (setval (s/before-index 1) s/NONE data)))
|
||||
(is (predand= vector? [1 :a 2 3] (setval (s/before-index 1) :a data)))
|
||||
(is (predand= vector? [1 2 3 :a] (setval (s/before-index 3) :a data)))
|
||||
; ensure inserting at index 0 in nil structure works, as in previous impl
|
||||
(is (predand= listlike? '(:a) (setval (s/before-index 0) :a nil)))
|
||||
(is (predand= listlike? '(:a 1 2 3) (setval (s/before-index 0) :a datal)))
|
||||
(is (predand= listlike? '(1 :a 2 3) (setval (s/before-index 1) :a datal)))
|
||||
(is (predand= listlike? '(1 2 3 :a) (setval (s/before-index 3) :a datal)))
|
||||
(is (predand= string? "abcxdef" (setval (s/before-index 3) (char \x) data-str)))
|
||||
))
|
||||
|
||||
(deftest index-nav-test
|
||||
(let [data [1 2 3 4 5 6]
|
||||
datal '(1 2 3 4 5 6)]
|
||||
(is (predand= vector? [3 1 2 4 5 6] (setval (s/index-nav 2) 0 data)))
|
||||
(is (predand= vector? [1 3 2 4 5 6] (setval (s/index-nav 2) 1 data)))
|
||||
(is (predand= vector? [1 2 3 4 5 6] (setval (s/index-nav 2) 2 data)))
|
||||
(is (predand= vector? [1 2 4 5 3 6] (setval (s/index-nav 2) 4 data)))
|
||||
(is (predand= vector? [1 2 4 5 6 3] (setval (s/index-nav 2) 5 data)))
|
||||
(is (predand= vector? [6 1 2 3 4 5] (setval (s/index-nav 5) 0 data)))
|
||||
|
||||
(is (predand= listlike? '(3 1 2 4 5 6) (setval (s/index-nav 2) 0 datal)))
|
||||
(is (predand= listlike? '(1 3 2 4 5 6) (setval (s/index-nav 2) 1 datal)))
|
||||
(is (predand= listlike? '(1 2 3 4 5 6) (setval (s/index-nav 2) 2 datal)))
|
||||
(is (predand= listlike? '(1 2 4 5 3 6) (setval (s/index-nav 2) 4 datal)))
|
||||
(is (predand= listlike? '(1 2 4 5 6 3) (setval (s/index-nav 2) 5 datal)))
|
||||
(is (predand= listlike? '(6 1 2 3 4 5) (setval (s/index-nav 5) 0 datal)))
|
||||
))
|
||||
|
||||
(deftest indexed-vals-test
|
||||
(let [data [:a :b :c :d :e]]
|
||||
(is (= [[0 :a] [1 :b] [2 :c] [3 :d] [4 :e]] (select s/INDEXED-VALS data)))
|
||||
(is (= [:e :d :c :b :a] (setval [s/INDEXED-VALS s/FIRST] 0 data)))
|
||||
(is (= [:a :b :e :d :c] (setval [s/INDEXED-VALS s/FIRST] 2 data)))
|
||||
(is (= [:b :a :d :c :e] (transform [s/INDEXED-VALS s/FIRST odd?] dec data)))
|
||||
(is (= [:a :b :c :d :e] (transform [s/INDEXED-VALS s/FIRST odd?] inc data)))
|
||||
(is (= [0 2 2 4] (transform [s/INDEXED-VALS s/LAST odd?] inc [0 1 2 3])))
|
||||
(is (= [0 1 2 3] (transform [s/INDEXED-VALS (s/collect-one s/LAST) s/FIRST] (fn [i _] i) [2 1 3 0])))
|
||||
(is (= [-1 0 1 2 3] (transform [(s/indexed-vals -1) (s/collect-one s/LAST) s/FIRST] (fn [i _] i) [3 -1 0 2 1])))
|
||||
(is (= [[1 :a] [2 :b] [3 :c]] (select (s/indexed-vals 1) [:a :b :c])))
|
||||
))
|
||||
|
||||
(deftest other-implicit-navs-test
|
||||
(is (= 1 (select-any ["a" true \c 10 'd] {"a" {true {\c {10 {'d 1}}}}})))
|
||||
)
|
||||
|
||||
(deftest vterminal-test
|
||||
(is (= {:a {:b [[1 2] 3]}}
|
||||
(multi-transform [(s/putval 1) :a (s/putval 2) :b (s/vterminal (fn [vs v] [vs v]))]
|
||||
{:a {:b 3}})))
|
||||
)
|
||||
|
||||
(deftest vtransform-test
|
||||
(is (= {:a 6} (vtransform [:a (s/putval 2) (s/putval 3)] (fn [vs v] (+ v (reduce + vs))) {:a 1})))
|
||||
)
|
||||
|
||||
(deftest compact-test
|
||||
(is (= {} (setval [:a (s/compact :b :c)] s/NONE {:a {:b {:c 1}}})))
|
||||
(is (= {:a {:d 2}} (setval [:a (s/compact :b :c)] s/NONE {:a {:b {:c 1} :d 2}})))
|
||||
(let [TREE-VALUES (recursive-path [] p (s/if-path vector? [(s/compact s/ALL) p] s/STAY))
|
||||
tree [1 [2 3] [] [4 [[5] [[6]]]]]]
|
||||
(is (= [2 4 6] (select [TREE-VALUES even?] tree)))
|
||||
(is (= [1 [3] [[[5]]]] (setval [TREE-VALUES even?] s/NONE tree)))
|
||||
)
|
||||
(is (= [{:a [{:c 1}]}]
|
||||
(setval [s/ALL (s/compact :a s/ALL :b)]
|
||||
s/NONE
|
||||
[{:a [{:b 3}]}
|
||||
{:a [{:b 2 :c 1}]}])))
|
||||
)
|
||||
|
||||
(deftest class-constant-test
|
||||
(let [f (fn [p] (fn [v] (str p (inc v))))]
|
||||
(is (= (str #?(:clj String :cljs js/String) 2)
|
||||
(multi-transform (s/terminal (f #?(:clj String :cljs js/String))) 1)))
|
||||
))
|
||||
|
||||
#?(:clj
|
||||
(do
|
||||
(defprotocolpath FooPP)
|
||||
(extend-protocolpath FooPP String s/STAY)
|
||||
|
||||
(deftest satisfies-protpath-test
|
||||
(is (satisfies-protpath? FooPP "a"))
|
||||
(is (not (satisfies-protpath? FooPP 1)))
|
||||
)))
|
||||
(deftest inline-lean-path
|
||||
;; use executors from ALL because it's a lean navigator
|
||||
(let [e (.-executors s/ALL)]
|
||||
(is (identical? e (.-executors (path :a (s/view inc)))))
|
||||
(is (identical? e (.-executors (path (s/keypath :a) (s/srange 2 7)))))
|
||||
(is (identical? e (.-executors (path :a (s/selected? (s/view inc) (s/selected? (s/srange 2 7)))))))))
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@
|
|||
[properties :as prop]]
|
||||
[clojure.test])
|
||||
|
||||
(:use [com.rpl.specter :only [select transform]]
|
||||
[com.rpl.specter :only [select* transform*]]))
|
||||
(:use [com.rpl.specter.macros :only [select transform]]
|
||||
[com.rpl.specter :only [select* transform* must-cache-paths!]]))
|
||||
|
||||
|
||||
;; it seems like gen/bind and gen/return are a monad (hence the names)
|
||||
|
|
@ -22,7 +22,7 @@
|
|||
~@body)))
|
||||
|
||||
|
||||
(defmacro ic-test [params-decl apath transform-fn data params]
|
||||
(defmacro ic-test [must-cache? params-decl apath transform-fn data params]
|
||||
(let [platform (if (contains? &env :locals) :cljs :clj)
|
||||
is-sym (if (= platform :clj) 'clojure.test/is 'cljs.test/is)]
|
||||
`(let [icfnsel# (fn [~@params-decl] (select ~apath ~data))
|
||||
|
|
@ -30,7 +30,11 @@
|
|||
regfnsel# (fn [~@params-decl] (select* ~apath ~data))
|
||||
regfntran# (fn [~@params-decl] (transform* ~apath ~transform-fn ~data))
|
||||
params# (if (empty? ~params) [[]] ~params)]
|
||||
|
||||
(must-cache-paths! ~must-cache?)
|
||||
(dotimes [_# 3]
|
||||
(doseq [ps# params#]
|
||||
(~is-sym (= (apply icfnsel# ps#) (apply regfnsel# ps#)))
|
||||
(~is-sym (= (apply icfntran# ps#) (apply regfntran# ps#))))))))
|
||||
(~is-sym (= (apply icfntran# ps#) (apply regfntran# ps#)))))
|
||||
|
||||
(must-cache-paths! false))))
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
(ns com.rpl.specter.zipper-test
|
||||
#?(:cljs (:require-macros
|
||||
[cljs.test :refer [is deftest]]
|
||||
[clojure.test.check.clojure-test :refer [defspec]]
|
||||
[cljs.test.check.cljs-test :refer [defspec]]
|
||||
[com.rpl.specter.cljs-test-helpers :refer [for-all+]]
|
||||
[com.rpl.specter
|
||||
[com.rpl.specter.macros
|
||||
:refer [declarepath providepath select select-one select-one!
|
||||
select-first transform setval replace-in]]))
|
||||
|
||||
|
|
@ -11,15 +11,15 @@
|
|||
#?(:clj [clojure.test :only [deftest is]])
|
||||
#?(:clj [clojure.test.check.clojure-test :only [defspec]])
|
||||
#?(:clj [com.rpl.specter.test-helpers :only [for-all+]])
|
||||
#?(:clj [com.rpl.specter
|
||||
#?(:clj [com.rpl.specter.macros
|
||||
:only [declarepath providepath select select-one select-one!
|
||||
select-first transform setval replace-in]]))
|
||||
|
||||
(:require #?(:clj [clojure.test.check.generators :as gen])
|
||||
#?(:clj [clojure.test.check.properties :as prop])
|
||||
#?(:cljs [clojure.test.check :as tc])
|
||||
#?(:cljs [clojure.test.check.generators :as gen])
|
||||
#?(:cljs [clojure.test.check.properties :as prop :include-macros true])
|
||||
#?(:cljs [cljs.test.check :as tc])
|
||||
#?(:cljs [cljs.test.check.generators :as gen])
|
||||
#?(:cljs [cljs.test.check.properties :as prop :include-macros true])
|
||||
[com.rpl.specter :as s]
|
||||
[com.rpl.specter.zipper :as z]))
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue