patroon 0.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.nyc_output/494cd8ae-ab5a-494b-a750-1fef00a0ede2.json +1 -0
- package/.nyc_output/4e26d788-63bb-44c4-ab10-10cfdb5c4b73.json +1 -0
- package/.nyc_output/e0dd9c3e-df57-4924-bb75-c79df34c76c1.json +1 -0
- package/.nyc_output/processinfo/494cd8ae-ab5a-494b-a750-1fef00a0ede2.json +1 -0
- package/.nyc_output/processinfo/4e26d788-63bb-44c4-ab10-10cfdb5c4b73.json +1 -0
- package/.nyc_output/processinfo/e0dd9c3e-df57-4924-bb75-c79df34c76c1.json +1 -0
- package/.nyc_output/processinfo/index.json +1 -0
- package/LICENSE +674 -0
- package/README.md +299 -0
- package/README.mz +275 -0
- package/matching-with-regexes.md +0 -0
- package/matching-with-strings.md +9 -0
- package/package.json +19 -0
- package/src/helpers.js +52 -0
- package/src/index.js +73 -0
- package/src/index.test.js +62 -0
- package/src/walkable.js +86 -0
- package/tape-test +27 -0
package/README.md
ADDED
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
# Patroon
|
|
2
|
+
|
|
3
|
+
Patroon.js as an attempt to add pattern matching-ish functionality without
|
|
4
|
+
introducing new syntax.
|
|
5
|
+
|
|
6
|
+
## Implementation
|
|
7
|
+
|
|
8
|
+
1. [./src/walkable.js][2] - Patroon allows one to define deeply nested
|
|
9
|
+
patterns. To implement these features succinctly we use an abstraction
|
|
10
|
+
which allows the traversing of a tree. This is an area of computer science
|
|
11
|
+
named [tree traversal][1].
|
|
12
|
+
2. [./src/index.js][3] - We now take the walkable utility and implement
|
|
13
|
+
patroon's functionality.
|
|
14
|
+
3. [./src/helpers.js][4] - You might have noticed that both the patroon and
|
|
15
|
+
walkable modules have common helper functions.
|
|
16
|
+
|
|
17
|
+
## Specifications
|
|
18
|
+
|
|
19
|
+
Let's see what valid and less valid uses of patroon are.
|
|
20
|
+
|
|
21
|
+
### Arrays
|
|
22
|
+
|
|
23
|
+
A less intuitive case (atleast initially) is the matching with an empty array.
|
|
24
|
+
|
|
25
|
+
```js ./tape-test
|
|
26
|
+
patroon(
|
|
27
|
+
[], end,
|
|
28
|
+
[1], fail
|
|
29
|
+
)([1])
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Notice that the empty array watches with `[1]`. This is because the empty array
|
|
33
|
+
is a subset of `[1]`.
|
|
34
|
+
|
|
35
|
+
In this case you might as well write the following for readability sake:
|
|
36
|
+
|
|
37
|
+
```js ./tape-test
|
|
38
|
+
patroon(
|
|
39
|
+
typed(Array), end,
|
|
40
|
+
)([1])
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Patroon even tries to determine if something is a constructor. No need to use
|
|
44
|
+
typed in that case.
|
|
45
|
+
|
|
46
|
+
```js ./tape-test
|
|
47
|
+
patroon(
|
|
48
|
+
Number, fail,
|
|
49
|
+
Array, end,
|
|
50
|
+
)([1])
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
If you wish to match on the reference of a constructor you can use the `ref` helper.
|
|
54
|
+
|
|
55
|
+
```js ./tape-test
|
|
56
|
+
patroon(
|
|
57
|
+
1, fail,
|
|
58
|
+
Number, fail,
|
|
59
|
+
ref(Number), end,
|
|
60
|
+
)(Number)
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Some more array examples to wrap your brain around.
|
|
64
|
+
|
|
65
|
+
```js ./tape-test
|
|
66
|
+
const arrayMatch = patroon(
|
|
67
|
+
[1,2], () => 2,
|
|
68
|
+
[1,2,3], () => 3,
|
|
69
|
+
[2], () => 1,
|
|
70
|
+
[], () => null
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
t.equal(arrayMatch([1,2]), 2)
|
|
74
|
+
t.equal(arrayMatch([1,2,3]), 2)
|
|
75
|
+
t.equal(arrayMatch([]), null)
|
|
76
|
+
t.equal(arrayMatch([2, 3]), 1)
|
|
77
|
+
t.end()
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
The array pattern assumes that the array has rest elements. It's a design
|
|
81
|
+
choice which avoids adding additional helpers with little to no downsides.
|
|
82
|
+
|
|
83
|
+
In case the seamingly unexpected case seems truely unexpected; I suggest you
|
|
84
|
+
think of patterns as a subset of the value you are trying to match. In the case
|
|
85
|
+
of arrays. `[1,2]` is a subset of `[1,2,3]`. `[2,3]` is not a subset of
|
|
86
|
+
`[1,2,3]` because arrays also care about the order of elements.
|
|
87
|
+
|
|
88
|
+
Now is a good time to introduce the placeholder(`_`) concept.
|
|
89
|
+
|
|
90
|
+
### Placeholders
|
|
91
|
+
|
|
92
|
+
```js ./tape-test
|
|
93
|
+
const alwaysMatches = patroon(_, t.end())('any value really')
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
A function that looks for a certain pattern in an array.
|
|
97
|
+
|
|
98
|
+
```js ./tape-test
|
|
99
|
+
const containsPattern = patroon(
|
|
100
|
+
[0, 0], true,
|
|
101
|
+
[_, _], ([, ...rest]) => containsPattern(rest),
|
|
102
|
+
[], false
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
t.true(containsPattern([0,0]))
|
|
106
|
+
t.true(containsPattern([1,0,0]))
|
|
107
|
+
t.false(containsPattern([1,0,1]))
|
|
108
|
+
t.true(containsPattern([1,0,1,0,0]))
|
|
109
|
+
t.end()
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
A toPairs function:
|
|
113
|
+
|
|
114
|
+
```js ./tape-test
|
|
115
|
+
const toPairs = patroon(
|
|
116
|
+
[_, _], ([a, b, ...c], p = []) => toPairs(c, [...p, [a, b]]),
|
|
117
|
+
_, (_, p = []) => p
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
t.deepEquals(toPairs([1]), [])
|
|
121
|
+
t.deepEquals(toPairs([1, 2]), [[1, 2]])
|
|
122
|
+
t.deepEquals(toPairs([1, 2, 3]), [[1, 2]])
|
|
123
|
+
t.deepEquals(toPairs([1, 2, 3, 4]), [[1, 2], [3, 4]])
|
|
124
|
+
t.end()
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
> An exercise would be to change toPairs to throw when an uneven length array
|
|
128
|
+
> is passed. Multiple answers are possible and some are more optimized than
|
|
129
|
+
> others.
|
|
130
|
+
|
|
131
|
+
So that's arrays. What about objects.
|
|
132
|
+
|
|
133
|
+
### Objects
|
|
134
|
+
|
|
135
|
+
Just like an empty array; matching on an empty object can be written in two
|
|
136
|
+
ways.
|
|
137
|
+
|
|
138
|
+
```js ./tape-test
|
|
139
|
+
patroon(
|
|
140
|
+
{}, pass,
|
|
141
|
+
)({a: 1})
|
|
142
|
+
|
|
143
|
+
patroon(
|
|
144
|
+
typed(Object), pass,
|
|
145
|
+
)({a: 1})
|
|
146
|
+
|
|
147
|
+
patroon(
|
|
148
|
+
Object, end,
|
|
149
|
+
)({a: 1})
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Next we match on the existance of object keys. We use the `_` to
|
|
153
|
+
achieve this.
|
|
154
|
+
|
|
155
|
+
```js ./tape-test
|
|
156
|
+
patroon(
|
|
157
|
+
{a: _}, end
|
|
158
|
+
)({a: 2})
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Next we also match on the key's value.
|
|
162
|
+
|
|
163
|
+
```js ./tape-test
|
|
164
|
+
patroon(
|
|
165
|
+
{a: 1}, fail,
|
|
166
|
+
{a: 2}, end,
|
|
167
|
+
{a: 3}, fail
|
|
168
|
+
)({a: 2})
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
What about nested objects? No problem!
|
|
172
|
+
|
|
173
|
+
```js ./tape-test
|
|
174
|
+
patroon(
|
|
175
|
+
{a: {a: 1}}, fail,
|
|
176
|
+
{a: {a: 2}}, end,
|
|
177
|
+
{a: {a: 3}}, fail
|
|
178
|
+
)({a: {a: 2}})
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### Types
|
|
182
|
+
|
|
183
|
+
We'll match on type using `typed` which internally uses `instanceof`.
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
```js ./tape-test
|
|
187
|
+
patroon(
|
|
188
|
+
typed(TypeError), fail,
|
|
189
|
+
typed(Error), pass
|
|
190
|
+
)(new Error())
|
|
191
|
+
|
|
192
|
+
patroon(
|
|
193
|
+
TypeError, end,
|
|
194
|
+
Error, fail
|
|
195
|
+
)(new TypeError())
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
An object of a certain type might also have values we would want to match on.
|
|
199
|
+
|
|
200
|
+
```js ./tape-test
|
|
201
|
+
patroon(
|
|
202
|
+
typed(TypeError, { value: 20 }), fail,
|
|
203
|
+
typed(Error, { value: 30 }), fail,
|
|
204
|
+
typed(Error, { value: 20 }), end
|
|
205
|
+
)(Object.assign(new Error(), { value: 20 }))
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
Simply pass a pattern as the second argument of typed.
|
|
209
|
+
|
|
210
|
+
Now we'll try predicates.
|
|
211
|
+
|
|
212
|
+
### Predicates
|
|
213
|
+
|
|
214
|
+
By default a function is assumed to be a predicate.
|
|
215
|
+
|
|
216
|
+
```js ./tape-test
|
|
217
|
+
const isTrue = v => v === true
|
|
218
|
+
|
|
219
|
+
patroon(
|
|
220
|
+
isTrue, end
|
|
221
|
+
)(true)
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
You might have a case where you want to match on the reference of a function.
|
|
225
|
+
Some people are weird like that. In that case one can use the ref helper.
|
|
226
|
+
|
|
227
|
+
```js ./tape-test
|
|
228
|
+
const fun = () => false
|
|
229
|
+
|
|
230
|
+
patroon(
|
|
231
|
+
fun, fail,
|
|
232
|
+
ref(fun), end
|
|
233
|
+
)(fun)
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
Could one combine predicates with arrays and objects? Sure one can!
|
|
237
|
+
|
|
238
|
+
```js ./tape-test
|
|
239
|
+
const is20 = v => v === 20
|
|
240
|
+
|
|
241
|
+
patroon(
|
|
242
|
+
[[is20]], end,
|
|
243
|
+
)([[20]])
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
```js ./tape-test
|
|
247
|
+
const is42 = v => v === 42
|
|
248
|
+
|
|
249
|
+
patroon(
|
|
250
|
+
[{a: is42}], end,
|
|
251
|
+
)([{a: 42}])
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
## Tests
|
|
255
|
+
|
|
256
|
+
Now for some additional edge cases and some generative testing.
|
|
257
|
+
[./src/index.test.js][5]
|
|
258
|
+
|
|
259
|
+
```bash bash
|
|
260
|
+
npm test
|
|
261
|
+
```
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
> patroon@0.0.0 test
|
|
265
|
+
> tape ./src/index.test.js
|
|
266
|
+
|
|
267
|
+
TAP version 13
|
|
268
|
+
# Matches always when pattern equals value
|
|
269
|
+
# Matches none of the patterns and throws
|
|
270
|
+
ok 1 should be truthy
|
|
271
|
+
# Does not match when a value does not exist
|
|
272
|
+
# Throws when a typed does not receice a constructor
|
|
273
|
+
ok 2 should be truthy
|
|
274
|
+
# Throws in a predicate function
|
|
275
|
+
ok 3 should be truthy
|
|
276
|
+
# Throws when an uneven amount of arguments are passed
|
|
277
|
+
ok 4 should be truthy
|
|
278
|
+
|
|
279
|
+
1..4
|
|
280
|
+
# tests 4
|
|
281
|
+
# pass 4
|
|
282
|
+
|
|
283
|
+
# ok
|
|
284
|
+
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
## Formatting
|
|
288
|
+
|
|
289
|
+
Standard is good enough.
|
|
290
|
+
|
|
291
|
+
```bash bash 2>&1
|
|
292
|
+
npx standard || npx standard --fix
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
[1]:https://en.wikipedia.org/wiki/Tree_traversal
|
|
296
|
+
[2]:./src/walkable.js
|
|
297
|
+
[3]:./src/index.js
|
|
298
|
+
[4]:./src/helpers.js
|
|
299
|
+
[5]:./src/index.test.js
|
package/README.mz
ADDED
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
# Patroon
|
|
2
|
+
|
|
3
|
+
Patroon.js as an attempt to add pattern matching-ish functionality without
|
|
4
|
+
introducing new syntax.
|
|
5
|
+
|
|
6
|
+
## Implementation
|
|
7
|
+
|
|
8
|
+
1. [./src/walkable.js][2] - Patroon allows one to define deeply nested
|
|
9
|
+
patterns. To implement these features succinctly we use an abstraction
|
|
10
|
+
which allows the traversing of a tree. This is an area of computer science
|
|
11
|
+
named [tree traversal][1].
|
|
12
|
+
2. [./src/index.js][3] - We now take the walkable utility and implement
|
|
13
|
+
patroon's functionality.
|
|
14
|
+
3. [./src/helpers.js][4] - You might have noticed that both the patroon and
|
|
15
|
+
walkable modules have common helper functions.
|
|
16
|
+
|
|
17
|
+
## Specifications
|
|
18
|
+
|
|
19
|
+
Let's see what valid and less valid uses of patroon are.
|
|
20
|
+
|
|
21
|
+
### Arrays
|
|
22
|
+
|
|
23
|
+
A less intuitive case (atleast initially) is the matching with an empty array.
|
|
24
|
+
|
|
25
|
+
```js ./tape-test
|
|
26
|
+
patroon(
|
|
27
|
+
[], end,
|
|
28
|
+
[1], fail
|
|
29
|
+
)([1])
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Notice that the empty array watches with `[1]`. This is because the empty array
|
|
33
|
+
is a subset of `[1]`.
|
|
34
|
+
|
|
35
|
+
In this case you might as well write the following for readability sake:
|
|
36
|
+
|
|
37
|
+
```js ./tape-test
|
|
38
|
+
patroon(
|
|
39
|
+
typed(Array), end,
|
|
40
|
+
)([1])
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Patroon even tries to determine if something is a constructor. No need to use
|
|
44
|
+
typed in that case.
|
|
45
|
+
|
|
46
|
+
```js ./tape-test
|
|
47
|
+
patroon(
|
|
48
|
+
Number, fail,
|
|
49
|
+
Array, end,
|
|
50
|
+
)([1])
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
If you wish to match on the reference of a constructor you can use the `ref` helper.
|
|
54
|
+
|
|
55
|
+
```js ./tape-test
|
|
56
|
+
patroon(
|
|
57
|
+
1, fail,
|
|
58
|
+
Number, fail,
|
|
59
|
+
ref(Number), end,
|
|
60
|
+
)(Number)
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Some more array examples to wrap your brain around.
|
|
64
|
+
|
|
65
|
+
```js ./tape-test
|
|
66
|
+
const arrayMatch = patroon(
|
|
67
|
+
[1,2], () => 2,
|
|
68
|
+
[1,2,3], () => 3,
|
|
69
|
+
[2], () => 1,
|
|
70
|
+
[], () => null
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
t.equal(arrayMatch([1,2]), 2)
|
|
74
|
+
t.equal(arrayMatch([1,2,3]), 2)
|
|
75
|
+
t.equal(arrayMatch([]), null)
|
|
76
|
+
t.equal(arrayMatch([2, 3]), 1)
|
|
77
|
+
t.end()
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
The array pattern assumes that the array has rest elements. It's a design
|
|
81
|
+
choice which avoids adding additional helpers with little to no downsides.
|
|
82
|
+
|
|
83
|
+
In case the seamingly unexpected case seems truely unexpected; I suggest you
|
|
84
|
+
think of patterns as a subset of the value you are trying to match. In the case
|
|
85
|
+
of arrays. `[1,2]` is a subset of `[1,2,3]`. `[2,3]` is not a subset of
|
|
86
|
+
`[1,2,3]` because arrays also care about the order of elements.
|
|
87
|
+
|
|
88
|
+
Now is a good time to introduce the placeholder(`_`) concept.
|
|
89
|
+
|
|
90
|
+
### Placeholders
|
|
91
|
+
|
|
92
|
+
```js ./tape-test
|
|
93
|
+
const alwaysMatches = patroon(_, t.end())('any value really')
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
A function that looks for a certain pattern in an array.
|
|
97
|
+
|
|
98
|
+
```js ./tape-test
|
|
99
|
+
const containsPattern = patroon(
|
|
100
|
+
[0, 0], true,
|
|
101
|
+
[_, _], ([, ...rest]) => containsPattern(rest),
|
|
102
|
+
[], false
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
t.true(containsPattern([0,0]))
|
|
106
|
+
t.true(containsPattern([1,0,0]))
|
|
107
|
+
t.false(containsPattern([1,0,1]))
|
|
108
|
+
t.true(containsPattern([1,0,1,0,0]))
|
|
109
|
+
t.end()
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
A toPairs function:
|
|
113
|
+
|
|
114
|
+
```js ./tape-test
|
|
115
|
+
const toPairs = patroon(
|
|
116
|
+
[_, _], ([a, b, ...c], p = []) => toPairs(c, [...p, [a, b]]),
|
|
117
|
+
_, (_, p = []) => p
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
t.deepEquals(toPairs([1]), [])
|
|
121
|
+
t.deepEquals(toPairs([1, 2]), [[1, 2]])
|
|
122
|
+
t.deepEquals(toPairs([1, 2, 3]), [[1, 2]])
|
|
123
|
+
t.deepEquals(toPairs([1, 2, 3, 4]), [[1, 2], [3, 4]])
|
|
124
|
+
t.end()
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
> An exercise would be to change toPairs to throw when an uneven length array
|
|
128
|
+
> is passed. Multiple answers are possible and some are more optimized than
|
|
129
|
+
> others.
|
|
130
|
+
|
|
131
|
+
So that's arrays. What about objects.
|
|
132
|
+
|
|
133
|
+
### Objects
|
|
134
|
+
|
|
135
|
+
Just like an empty array; matching on an empty object can be written in two
|
|
136
|
+
ways.
|
|
137
|
+
|
|
138
|
+
```js ./tape-test
|
|
139
|
+
patroon(
|
|
140
|
+
{}, pass,
|
|
141
|
+
)({a: 1})
|
|
142
|
+
|
|
143
|
+
patroon(
|
|
144
|
+
typed(Object), pass,
|
|
145
|
+
)({a: 1})
|
|
146
|
+
|
|
147
|
+
patroon(
|
|
148
|
+
Object, end,
|
|
149
|
+
)({a: 1})
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Next we match on the existance of object keys. We use the `_` to
|
|
153
|
+
achieve this.
|
|
154
|
+
|
|
155
|
+
```js ./tape-test
|
|
156
|
+
patroon(
|
|
157
|
+
{a: _}, end
|
|
158
|
+
)({a: 2})
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Next we also match on the key's value.
|
|
162
|
+
|
|
163
|
+
```js ./tape-test
|
|
164
|
+
patroon(
|
|
165
|
+
{a: 1}, fail,
|
|
166
|
+
{a: 2}, end,
|
|
167
|
+
{a: 3}, fail
|
|
168
|
+
)({a: 2})
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
What about nested objects? No problem!
|
|
172
|
+
|
|
173
|
+
```js ./tape-test
|
|
174
|
+
patroon(
|
|
175
|
+
{a: {a: 1}}, fail,
|
|
176
|
+
{a: {a: 2}}, end,
|
|
177
|
+
{a: {a: 3}}, fail
|
|
178
|
+
)({a: {a: 2}})
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### Types
|
|
182
|
+
|
|
183
|
+
We'll match on type using `typed` which internally uses `instanceof`.
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
```js ./tape-test
|
|
187
|
+
patroon(
|
|
188
|
+
typed(TypeError), fail,
|
|
189
|
+
typed(Error), pass
|
|
190
|
+
)(new Error())
|
|
191
|
+
|
|
192
|
+
patroon(
|
|
193
|
+
TypeError, end,
|
|
194
|
+
Error, fail
|
|
195
|
+
)(new TypeError())
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
An object of a certain type might also have values we would want to match on.
|
|
199
|
+
|
|
200
|
+
```js ./tape-test
|
|
201
|
+
patroon(
|
|
202
|
+
typed(TypeError, { value: 20 }), fail,
|
|
203
|
+
typed(Error, { value: 30 }), fail,
|
|
204
|
+
typed(Error, { value: 20 }), end
|
|
205
|
+
)(Object.assign(new Error(), { value: 20 }))
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
Simply pass a pattern as the second argument of typed.
|
|
209
|
+
|
|
210
|
+
Now we'll try predicates.
|
|
211
|
+
|
|
212
|
+
### Predicates
|
|
213
|
+
|
|
214
|
+
By default a function is assumed to be a predicate.
|
|
215
|
+
|
|
216
|
+
```js ./tape-test
|
|
217
|
+
const isTrue = v => v === true
|
|
218
|
+
|
|
219
|
+
patroon(
|
|
220
|
+
isTrue, end
|
|
221
|
+
)(true)
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
You might have a case where you want to match on the reference of a function.
|
|
225
|
+
Some people are weird like that. In that case one can use the ref helper.
|
|
226
|
+
|
|
227
|
+
```js ./tape-test
|
|
228
|
+
const fun = () => false
|
|
229
|
+
|
|
230
|
+
patroon(
|
|
231
|
+
fun, fail,
|
|
232
|
+
ref(fun), end
|
|
233
|
+
)(fun)
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
Could one combine predicates with arrays and objects? Sure one can!
|
|
237
|
+
|
|
238
|
+
```js ./tape-test
|
|
239
|
+
const is20 = v => v === 20
|
|
240
|
+
|
|
241
|
+
patroon(
|
|
242
|
+
[[is20]], end,
|
|
243
|
+
)([[20]])
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
```js ./tape-test
|
|
247
|
+
const is42 = v => v === 42
|
|
248
|
+
|
|
249
|
+
patroon(
|
|
250
|
+
[{a: is42}], end,
|
|
251
|
+
)([{a: 42}])
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
## Tests
|
|
255
|
+
|
|
256
|
+
Now for some additional edge cases and some generative testing.
|
|
257
|
+
[./src/index.test.js][5]
|
|
258
|
+
|
|
259
|
+
```bash bash
|
|
260
|
+
npm test
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
## Formatting
|
|
264
|
+
|
|
265
|
+
Standard is good enough.
|
|
266
|
+
|
|
267
|
+
```bash bash 2>&1
|
|
268
|
+
npx standard || npx standard --fix
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
[1]:https://en.wikipedia.org/wiki/Tree_traversal
|
|
272
|
+
[2]:./src/walkable.js
|
|
273
|
+
[3]:./src/index.js
|
|
274
|
+
[4]:./src/helpers.js
|
|
275
|
+
[5]:./src/index.test.js
|
|
File without changes
|
package/package.json
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "patroon",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"description": "Pattern matching library",
|
|
5
|
+
"main": "./src/index.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"test": "tape ./src/index.test.js"
|
|
8
|
+
},
|
|
9
|
+
"keywords": [
|
|
10
|
+
"pattern",
|
|
11
|
+
"matching"
|
|
12
|
+
],
|
|
13
|
+
"author": "Bassim Huis",
|
|
14
|
+
"devDependencies": {
|
|
15
|
+
"tape": "^5.1.1",
|
|
16
|
+
"tape-check": "^1.0.0-rc.0",
|
|
17
|
+
"testcheck": "^1.0.0-rc.2"
|
|
18
|
+
}
|
|
19
|
+
}
|
package/src/helpers.js
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
const toPairs = items => {
|
|
2
|
+
if (items.length < 2) { return [] }
|
|
3
|
+
|
|
4
|
+
const [a, b, ...rest] = items
|
|
5
|
+
|
|
6
|
+
return [[a, b], ...toPairs(rest)]
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const isNil = x => !(x != null)
|
|
10
|
+
|
|
11
|
+
function isConstructor (func) {
|
|
12
|
+
return Boolean(func && typeof func === 'function' && func.prototype && func.prototype.constructor)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
module.exports = {
|
|
16
|
+
always: x => () => x,
|
|
17
|
+
isConstructor,
|
|
18
|
+
toPairs,
|
|
19
|
+
isNil,
|
|
20
|
+
isEven (x) {
|
|
21
|
+
return x % 2 === 0
|
|
22
|
+
},
|
|
23
|
+
is: Ctor => {
|
|
24
|
+
if (!isConstructor(Ctor)) {
|
|
25
|
+
throw new Error('Ctor is not a constructor')
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return instance =>
|
|
29
|
+
instance.constructor === Ctor || instance instanceof Ctor
|
|
30
|
+
},
|
|
31
|
+
tryCatch (tryFn, catchFn) {
|
|
32
|
+
return (...args) => {
|
|
33
|
+
try {
|
|
34
|
+
return tryFn(...args)
|
|
35
|
+
} catch (e) {
|
|
36
|
+
return catchFn(e)
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
T () {
|
|
41
|
+
return true
|
|
42
|
+
},
|
|
43
|
+
equals (a, b) {
|
|
44
|
+
return a === b || Object.is(a, b)
|
|
45
|
+
},
|
|
46
|
+
isFunction (x) {
|
|
47
|
+
return typeof x === 'function'
|
|
48
|
+
},
|
|
49
|
+
isDefined (x) {
|
|
50
|
+
return x != null
|
|
51
|
+
}
|
|
52
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
const { mapLeaves, path, PathError } = require('./walkable')()
|
|
2
|
+
const { isConstructor, isFunction, equals, T, is, tryCatch, isEven, isNil, toPairs, always } = require('./helpers')
|
|
3
|
+
|
|
4
|
+
const match = pattern => {
|
|
5
|
+
// TODO: also check if something is a constructor
|
|
6
|
+
|
|
7
|
+
const patternPredicates = mapLeaves(
|
|
8
|
+
(value, pth) => tryCatch(
|
|
9
|
+
isConstructor(value)
|
|
10
|
+
? typed(value)
|
|
11
|
+
: (isFunction(value)
|
|
12
|
+
? arg => value(path(pth, arg))
|
|
13
|
+
: arg => equals(path(pth, arg), value)),
|
|
14
|
+
e => {
|
|
15
|
+
if (e instanceof PathError) { return false }
|
|
16
|
+
|
|
17
|
+
throw e // How to test this case?
|
|
18
|
+
}
|
|
19
|
+
),
|
|
20
|
+
pattern
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
return (...args) => patternPredicates.every(pred => pred(...args))
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
class NoMatchError extends Error {
|
|
27
|
+
constructor (...args) {
|
|
28
|
+
super(...args)
|
|
29
|
+
this.name = 'NoMatchError'
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
const toFunction = x => isFunction(x) ? x : always(x)
|
|
33
|
+
|
|
34
|
+
const patroon = (...list) => {
|
|
35
|
+
if (!isEven(list.length)) { throw new TypeError('Patroon should have even amount of arguments.') }
|
|
36
|
+
|
|
37
|
+
const patterns = toPairs(list).map(([pattern, doFn]) => [match(pattern), toFunction(doFn)])
|
|
38
|
+
|
|
39
|
+
return (...args) => {
|
|
40
|
+
const found = patterns.find(([matches]) => matches(...args))
|
|
41
|
+
|
|
42
|
+
if (isNil(found)) { throw new NoMatchError(`Not able to match any pattern for value ${JSON.stringify(args)}`) }
|
|
43
|
+
|
|
44
|
+
const [, doFn] = found
|
|
45
|
+
|
|
46
|
+
return doFn(...args)
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function typed (Ctor, ...args) {
|
|
51
|
+
if (args.length === 0) {
|
|
52
|
+
return is(Ctor)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const [pattern] = args
|
|
56
|
+
|
|
57
|
+
return (instance) =>
|
|
58
|
+
typed(Ctor)(instance) &&
|
|
59
|
+
match(pattern)(instance)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function ref (fn) {
|
|
63
|
+
return (arg) => fn === arg
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
module.exports = Object.assign(patroon, {
|
|
67
|
+
NoMatchError,
|
|
68
|
+
patroon,
|
|
69
|
+
ref,
|
|
70
|
+
_: T,
|
|
71
|
+
typed,
|
|
72
|
+
t: typed
|
|
73
|
+
})
|