From 4d0ee4881c73ea416be98372147add53d8921f25 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Felix=20B=C3=B6hm?= <188768+fb55@users.noreply.github.com>
Date: Sun, 22 May 2022 23:30:00 +0100
Subject: [PATCH 1/2] feat: Add `generate` and `sequence` methods
---
src/__fixtures__/rules.ts | 1 +
src/compile.spec.ts | 58 ++++++++++++++++++++++++++++++++-
src/compile.ts | 68 +++++++++++++++++++++++++++++++++++++++
src/index.ts | 40 +++++++++++++++++++++--
4 files changed, 163 insertions(+), 4 deletions(-)
diff --git a/src/__fixtures__/rules.ts b/src/__fixtures__/rules.ts
index d96c731f..f7ff3088 100644
--- a/src/__fixtures__/rules.ts
+++ b/src/__fixtures__/rules.ts
@@ -33,6 +33,7 @@ export const valid: [string, [number, number]][] = [
// Surprisingly, neither sizzle, qwery or nwmatcher cover these cases
["-4n+13", [-4, 13]],
["-2n + 12", [-2, 12]],
+ ["-n", [-1, 0]],
];
export const invalid = [
diff --git a/src/compile.spec.ts b/src/compile.spec.ts
index c689a6ff..722e153b 100644
--- a/src/compile.spec.ts
+++ b/src/compile.spec.ts
@@ -1,4 +1,4 @@
-import nthCheck, { compile } from ".";
+import nthCheck, { compile, generate, sequence } from ".";
import { valid } from "./__fixtures__/rules";
const valArray = new Array(...Array(2e3)).map((_, i) => i);
@@ -37,3 +37,59 @@ describe("parse", () => {
}
});
});
+
+describe("generate", () => {
+ it("should return a function", () => {
+ expect(generate([1, 2])).toBeInstanceOf(Function);
+ });
+
+ it("should only return valid values", () => {
+ for (const [_, parsed] of valid) {
+ const gen = generate(parsed);
+ const check = compile(parsed);
+ let val = gen();
+
+ for (let i = 0; i < 1e3; i++) {
+ // Should pass the check iff `i` is the next value.
+ expect(val === i).toBe(check(i));
+
+ if (val === i) {
+ val = gen();
+ }
+ }
+ }
+ });
+
+ it("should produce an increasing sequence", () => {
+ const gen = generate([2, 2]);
+
+ expect(gen()).toBe(1);
+ expect(gen()).toBe(3);
+ expect(gen()).toBe(5);
+ expect(gen()).toBe(7);
+ expect(gen()).toBe(9);
+ });
+
+ it("should produce an increasing sequence for a negative `n`", () => {
+ const gen = generate([-1, 2]);
+
+ expect(gen()).toBe(0);
+ expect(gen()).toBe(1);
+ expect(gen()).toBe(null);
+ });
+
+ it("should not produce any values for `-n`", () => {
+ const gen = generate([-1, 0]);
+
+ expect(gen()).toBe(null);
+ });
+
+ it("should parse selectors with `sequence`", () => {
+ const gen = sequence("-2n+5");
+
+ expect(gen()).toBe(0);
+ expect(gen()).toBe(2);
+ expect(gen()).toBe(4);
+ expect(gen()).toBe(null);
+ });
+});
diff --git a/src/compile.ts b/src/compile.ts
index 2b6ee506..d7d37e31 100644
--- a/src/compile.ts
+++ b/src/compile.ts
@@ -7,6 +7,8 @@ import { trueFunc, falseFunc } from "boolbase";
* @param parsed A tuple [a, b], as returned by `parse`.
* @returns A highly optimized function that returns whether an index matches the nth-check.
* @example
+ *
+ * ```js
* const check = nthCheck.compile([2, 3]);
*
* check(0); // `false`
@@ -16,6 +18,7 @@ import { trueFunc, falseFunc } from "boolbase";
* check(4); // `true`
* check(5); // `false`
* check(6); // `true`
+ * ```
*/
export function compile(
parsed: [a: number, b: number]
@@ -52,3 +55,68 @@ export function compile(
? (index) => index >= b && index % absA === bMod
: (index) => index <= b && index % absA === bMod;
}
+
+/**
+ * Returns a function that produces a monotonously increasing sequence of indices.
+ *
+ * If the sequence has an end, the returned function will return `null` after
+ * the last index in the sequence.
+ *
+ * @param parsed A tuple [a, b], as returned by `parse`.
+ * @returns A function that produces a sequence of indices.
+ * @example
Always increasing (2n+3)
+ *
+ * ```js
+ * const gen = nthCheck.generate([2, 3])
+ *
+ * gen() // `1`
+ * gen() // `3`
+ * gen() // `5`
+ * gen() // `8`
+ * gen() // `11`
+ * ```
+ *
+ * @example With end value (-2n+10)
+ *
+ * ```js
+ *
+ * const gen = nthCheck.generate([-2, 5]);
+ *
+ * gen() // 0
+ * gen() // 2
+ * gen() // 4
+ * gen() // null
+ * ```
+ */
+export function generate(parsed: [a: number, b: number]): () => number | null {
+ const a = parsed[0];
+ // Subtract 1 from `b`, to convert from one- to zero-indexed.
+ let b = parsed[1] - 1;
+
+ let n = 0;
+
+ // Make sure to always return an increasing sequence
+ if (a < 0) {
+ const aPos = -a;
+ // Get `b mod a`
+ const minValue = ((b % aPos) + aPos) % aPos;
+ return () => {
+ const val = minValue + aPos * n++;
+
+ return val > b ? null : val;
+ };
+ }
+
+ if (a === 0)
+ return b < 0
+ ? // There are no result — always return `null`
+ () => null
+ : // Return `b` exactly once
+ () => (n++ === 0 ? b : null);
+
+ if (b < 0) {
+ b += a * Math.ceil(-b / a);
+ }
+
+ return () => a * n++ + b;
+}
diff --git a/src/index.ts b/src/index.ts
index a47a5479..2494e25e 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -1,11 +1,11 @@
import { parse } from "./parse";
-import { compile } from "./compile";
+import { compile, generate } from "./compile";
-export { parse, compile };
+export { parse, compile, generate };
/**
* Parses and compiles a formula to a highly optimized function.
- * Combination of `parse` and `compile`.
+ * Combination of {@link parse} and {@link compile}.
*
* If the formula doesn't match any elements,
* it returns [`boolbase`](https://github.com/fb55/boolbase)'s `falseFunc`.
@@ -29,3 +29,37 @@ export { parse, compile };
export default function nthCheck(formula: string): (index: number) => boolean {
return compile(parse(formula));
}
+
+/**
+ * Parses and compiles a formula to a generator that produces a sequence of indices.
+ * Combination of {@link parse} and {@link generate}.
+ *
+ * @param formula The formula to compile.
+ * @returns A function that produces a sequence of indices.
+ * @example Always increasing
+ *
+ * ```js
+ * const gen = nthCheck.sequence('2n+3')
+ *
+ * gen() // `1`
+ * gen() // `3`
+ * gen() // `5`
+ * gen() // `8`
+ * gen() // `11`
+ * ```
+ *
+ * @example With end value
+ *
+ * ```js
+ *
+ * const gen = nthCheck.sequence('-2n+5');
+ *
+ * gen() // 0
+ * gen() // 2
+ * gen() // 4
+ * gen() // null
+ * ```
+ */
+export function sequence(formula: string): () => number | null {
+ return generate(parse(formula));
+}
From 65f1cecf03c94aee7a30651862005891b4f3fdf4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Felix=20B=C3=B6hm?= <188768+fb55@users.noreply.github.com>
Date: Sun, 22 May 2022 23:34:26 +0100
Subject: [PATCH 2/2] Update README.md
---
README.md | 58 ++++++++++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 57 insertions(+), 1 deletion(-)
diff --git a/README.md b/README.md
index d9d1acb4..7a19d64f 100644
--- a/README.md
+++ b/README.md
@@ -4,7 +4,7 @@ Parses and compiles CSS nth-checks to highly optimized functions.
### About
-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()`.
+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.
`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).
@@ -64,6 +64,62 @@ check(5); // `false`
check(6); // `true`
```
+##### `generate([a, b])`
+
+Returns a function that produces a monotonously increasing sequence of indices.
+
+If the sequence has an end, the returned function will return `null` after the last index in the sequence.
+
+**Example:** An always increasing sequence
+
+```js
+const gen = nthCheck.generate([2, 3]);
+
+gen(); // `1`
+gen(); // `3`
+gen(); // `5`
+gen(); // `8`
+gen(); // `11`
+```
+
+**Example:** With an end value
+
+```js
+const gen = nthCheck.generate([-2, 5]);
+
+gen(); // 0
+gen(); // 2
+gen(); // 4
+gen(); // null
+```
+
+##### `sequence(formula)`
+
+Parses and compiles a formula to a generator that produces a sequence of indices. Combination of `parse` and `generate`.
+
+**Example:** An always increasing sequence
+
+```js
+const gen = nthCheck.sequence("2n+3");
+
+gen(); // `1`
+gen(); // `3`
+gen(); // `5`
+gen(); // `8`
+gen(); // `11`
+```
+
+**Example:** With an end value
+
+```js
+const gen = nthCheck.sequence("-2n+5");
+
+gen(); // 0
+gen(); // 2
+gen(); // 4
+gen(); // null
+```
+
---
License: BSD-2-Clause