Skip to content

Commit 3e8cd1e

Browse files
authored
feat: Add generate and sequence methods (#207)
1 parent 57a5c62 commit 3e8cd1e

File tree

5 files changed

+220
-5
lines changed

5 files changed

+220
-5
lines changed

README.md

+57-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ Parses and compiles CSS nth-checks to highly optimized functions.
44

55
### About
66

7-
This module can be used to parse & compile nth-checks, as they are found in CSS 3's `nth-child()` and `nth-last-of-type()`.
7+
This module can be used to parse & compile nth-checks, as they are found in CSS 3's `nth-child()` and `nth-last-of-type()`. It can be used to check if a given index matches a given nth-rule, or to generate a sequence of indices matching a given nth-rule.
88

99
`nth-check` focusses on speed, providing optimized functions for different kinds of nth-child formulas, while still following the [spec](http://www.w3.org/TR/css3-selectors/#nth-child-pseudo).
1010

@@ -64,6 +64,62 @@ check(5); // `false`
6464
check(6); // `true`
6565
```
6666

67+
##### `generate([a, b])`
68+
69+
Returns a function that produces a monotonously increasing sequence of indices.
70+
71+
If the sequence has an end, the returned function will return `null` after the last index in the sequence.
72+
73+
**Example:** An always increasing sequence
74+
75+
```js
76+
const gen = nthCheck.generate([2, 3]);
77+
78+
gen(); // `1`
79+
gen(); // `3`
80+
gen(); // `5`
81+
gen(); // `8`
82+
gen(); // `11`
83+
```
84+
85+
**Example:** With an end value
86+
87+
```js
88+
const gen = nthCheck.generate([-2, 5]);
89+
90+
gen(); // 0
91+
gen(); // 2
92+
gen(); // 4
93+
gen(); // null
94+
```
95+
96+
##### `sequence(formula)`
97+
98+
Parses and compiles a formula to a generator that produces a sequence of indices. Combination of `parse` and `generate`.
99+
100+
**Example:** An always increasing sequence
101+
102+
```js
103+
const gen = nthCheck.sequence("2n+3");
104+
105+
gen(); // `1`
106+
gen(); // `3`
107+
gen(); // `5`
108+
gen(); // `8`
109+
gen(); // `11`
110+
```
111+
112+
**Example:** With an end value
113+
114+
```js
115+
const gen = nthCheck.sequence("-2n+5");
116+
117+
gen(); // 0
118+
gen(); // 2
119+
gen(); // 4
120+
gen(); // null
121+
```
122+
67123
---
68124

69125
License: BSD-2-Clause

src/__fixtures__/rules.ts

+1
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ export const valid: [string, [number, number]][] = [
3333
// Surprisingly, neither sizzle, qwery or nwmatcher cover these cases
3434
["-4n+13", [-4, 13]],
3535
["-2n + 12", [-2, 12]],
36+
["-n", [-1, 0]],
3637
];
3738

3839
export const invalid = [

src/compile.spec.ts

+57-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import nthCheck, { compile } from ".";
1+
import nthCheck, { compile, generate, sequence } from ".";
22
import { valid } from "./__fixtures__/rules";
33

44
const valArray = new Array(...Array(2e3)).map((_, i) => i);
@@ -37,3 +37,59 @@ describe("parse", () => {
3737
}
3838
});
3939
});
40+
41+
describe("generate", () => {
42+
it("should return a function", () => {
43+
expect(generate([1, 2])).toBeInstanceOf(Function);
44+
});
45+
46+
it("should only return valid values", () => {
47+
for (const [_, parsed] of valid) {
48+
const gen = generate(parsed);
49+
const check = compile(parsed);
50+
let val = gen();
51+
52+
for (let i = 0; i < 1e3; i++) {
53+
// Should pass the check iff `i` is the next value.
54+
expect(val === i).toBe(check(i));
55+
56+
if (val === i) {
57+
val = gen();
58+
}
59+
}
60+
}
61+
});
62+
63+
it("should produce an increasing sequence", () => {
64+
const gen = generate([2, 2]);
65+
66+
expect(gen()).toBe(1);
67+
expect(gen()).toBe(3);
68+
expect(gen()).toBe(5);
69+
expect(gen()).toBe(7);
70+
expect(gen()).toBe(9);
71+
});
72+
73+
it("should produce an increasing sequence for a negative `n`", () => {
74+
const gen = generate([-1, 2]);
75+
76+
expect(gen()).toBe(0);
77+
expect(gen()).toBe(1);
78+
expect(gen()).toBe(null);
79+
});
80+
81+
it("should not produce any values for `-n`", () => {
82+
const gen = generate([-1, 0]);
83+
84+
expect(gen()).toBe(null);
85+
});
86+
87+
it("should parse selectors with `sequence`", () => {
88+
const gen = sequence("-2n+5");
89+
90+
expect(gen()).toBe(0);
91+
expect(gen()).toBe(2);
92+
expect(gen()).toBe(4);
93+
expect(gen()).toBe(null);
94+
});
95+
});

src/compile.ts

+68
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import { trueFunc, falseFunc } from "boolbase";
77
* @param parsed A tuple [a, b], as returned by `parse`.
88
* @returns A highly optimized function that returns whether an index matches the nth-check.
99
* @example
10+
*
11+
* ```js
1012
* const check = nthCheck.compile([2, 3]);
1113
*
1214
* check(0); // `false`
@@ -16,6 +18,7 @@ import { trueFunc, falseFunc } from "boolbase";
1618
* check(4); // `true`
1719
* check(5); // `false`
1820
* check(6); // `true`
21+
* ```
1922
*/
2023
export function compile(
2124
parsed: [a: number, b: number]
@@ -52,3 +55,68 @@ export function compile(
5255
? (index) => index >= b && index % absA === bMod
5356
: (index) => index <= b && index % absA === bMod;
5457
}
58+
59+
/**
60+
* Returns a function that produces a monotonously increasing sequence of indices.
61+
*
62+
* If the sequence has an end, the returned function will return `null` after
63+
* the last index in the sequence.
64+
*
65+
* @param parsed A tuple [a, b], as returned by `parse`.
66+
* @returns A function that produces a sequence of indices.
67+
* @example <caption>Always increasing (2n+3)</caption>
68+
*
69+
* ```js
70+
* const gen = nthCheck.generate([2, 3])
71+
*
72+
* gen() // `1`
73+
* gen() // `3`
74+
* gen() // `5`
75+
* gen() // `8`
76+
* gen() // `11`
77+
* ```
78+
*
79+
* @example <caption>With end value (-2n+10)</caption>
80+
*
81+
* ```js
82+
*
83+
* const gen = nthCheck.generate([-2, 5]);
84+
*
85+
* gen() // 0
86+
* gen() // 2
87+
* gen() // 4
88+
* gen() // null
89+
* ```
90+
*/
91+
export function generate(parsed: [a: number, b: number]): () => number | null {
92+
const a = parsed[0];
93+
// Subtract 1 from `b`, to convert from one- to zero-indexed.
94+
let b = parsed[1] - 1;
95+
96+
let n = 0;
97+
98+
// Make sure to always return an increasing sequence
99+
if (a < 0) {
100+
const aPos = -a;
101+
// Get `b mod a`
102+
const minValue = ((b % aPos) + aPos) % aPos;
103+
return () => {
104+
const val = minValue + aPos * n++;
105+
106+
return val > b ? null : val;
107+
};
108+
}
109+
110+
if (a === 0)
111+
return b < 0
112+
? // There are no result — always return `null`
113+
() => null
114+
: // Return `b` exactly once
115+
() => (n++ === 0 ? b : null);
116+
117+
if (b < 0) {
118+
b += a * Math.ceil(-b / a);
119+
}
120+
121+
return () => a * n++ + b;
122+
}

src/index.ts

+37-3
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import { parse } from "./parse";
2-
import { compile } from "./compile";
2+
import { compile, generate } from "./compile";
33

4-
export { parse, compile };
4+
export { parse, compile, generate };
55

66
/**
77
* Parses and compiles a formula to a highly optimized function.
8-
* Combination of `parse` and `compile`.
8+
* Combination of {@link parse} and {@link compile}.
99
*
1010
* If the formula doesn't match any elements,
1111
* it returns [`boolbase`](https://github.com/fb55/boolbase)'s `falseFunc`.
@@ -29,3 +29,37 @@ export { parse, compile };
2929
export default function nthCheck(formula: string): (index: number) => boolean {
3030
return compile(parse(formula));
3131
}
32+
33+
/**
34+
* Parses and compiles a formula to a generator that produces a sequence of indices.
35+
* Combination of {@link parse} and {@link generate}.
36+
*
37+
* @param formula The formula to compile.
38+
* @returns A function that produces a sequence of indices.
39+
* @example <caption>Always increasing</caption>
40+
*
41+
* ```js
42+
* const gen = nthCheck.sequence('2n+3')
43+
*
44+
* gen() // `1`
45+
* gen() // `3`
46+
* gen() // `5`
47+
* gen() // `8`
48+
* gen() // `11`
49+
* ```
50+
*
51+
* @example <caption>With end value</caption>
52+
*
53+
* ```js
54+
*
55+
* const gen = nthCheck.sequence('-2n+5');
56+
*
57+
* gen() // 0
58+
* gen() // 2
59+
* gen() // 4
60+
* gen() // null
61+
* ```
62+
*/
63+
export function sequence(formula: string): () => number | null {
64+
return generate(parse(formula));
65+
}

0 commit comments

Comments
 (0)