From 3e8cd1ea382c14a0a51aa4b67b00024f1c4acb68 Mon Sep 17 00:00:00 2001 From: Felix <188768+fb55@users.noreply.github.com> Date: Sun, 22 May 2022 23:36:33 +0100 Subject: [PATCH] feat: Add `generate` and `sequence` methods (#207) --- README.md | 58 ++++++++++++++++++++++++++++++++- src/__fixtures__/rules.ts | 1 + src/compile.spec.ts | 58 ++++++++++++++++++++++++++++++++- src/compile.ts | 68 +++++++++++++++++++++++++++++++++++++++ src/index.ts | 40 +++++++++++++++++++++-- 5 files changed, 220 insertions(+), 5 deletions(-) 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 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