chore: documentation and optimization
What a rhyme.
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
import * as esbuild from "esbuild";
|
||||
|
||||
const result = esbuild.buildSync({
|
||||
entryPoints: ["src/index.ts"],
|
||||
bundle: true,
|
||||
minify: true,
|
||||
outdir: "dist",
|
||||
define: {
|
||||
"import.meta.vitest": "undefined",
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,165 @@
|
||||
import { writeFileSync } from "fs";
|
||||
import { relative } from "path";
|
||||
|
||||
function capitalize(/** @type {string} */ s) {
|
||||
return s.slice(0, 1).toUpperCase() + s.slice(1).toLowerCase();
|
||||
}
|
||||
|
||||
const script = import.meta.filename;
|
||||
|
||||
const path = "./src/mixin/math/swizzlePermutations.d.ts";
|
||||
const axis = ["x", "y", "z", "w", "v"];
|
||||
|
||||
const property = ["first", "second", "third", "fourth", "fifth"];
|
||||
|
||||
const mixin = "./src/mixin/base/mixin";
|
||||
|
||||
let conditional = "";
|
||||
|
||||
for (let n = 0; n < axis.length; n++) {
|
||||
if (n === axis.length - 1) {
|
||||
conditional += `Swizzle${n + 1}D<T, t>`;
|
||||
break;
|
||||
}
|
||||
const type = new Array(n + 1).fill("number").join(", ");
|
||||
const indent = "\t".repeat(n + 1);
|
||||
conditional += `T extends readonly [${type}]\n`;
|
||||
conditional += `${indent}? Swizzle${n + 1}D<T, t>\n`;
|
||||
conditional += `${indent}: `;
|
||||
}
|
||||
|
||||
const type = new Array(axis.length).fill("number").join(", ");
|
||||
|
||||
let src = `
|
||||
// .d.ts file auto-generated by ${relative(path, script)}
|
||||
// Do not edit
|
||||
|
||||
import type { Props, Return } from '${relative(path, mixin)}';
|
||||
|
||||
type IsValid<T extends readonly number[]> = number extends T['length']
|
||||
? false
|
||||
: T extends readonly []
|
||||
? false
|
||||
: true;
|
||||
|
||||
export type SwizzleProps<T extends readonly number[], t extends Props> =
|
||||
IsValid<T> extends false
|
||||
? {}
|
||||
: ${conditional
|
||||
.split("\n")
|
||||
.map((line) => "\t\t" + line)
|
||||
.join("\n")
|
||||
.trimStart()};
|
||||
|
||||
export interface ISwizzle<
|
||||
T extends readonly [${type}] =
|
||||
readonly [${type}],
|
||||
t extends Props = Props
|
||||
> extends Swizzle${axis.length}D<T, t> {};
|
||||
|
||||
`.trimStart();
|
||||
|
||||
for (let n = 0; n < axis.length; n++) {
|
||||
const state = new Array(axis.length).fill(-1);
|
||||
|
||||
const props = [];
|
||||
|
||||
outer: do {
|
||||
for (let i = state.length - 1; i >= 0; i--)
|
||||
if (++state[i] > n) {
|
||||
state[i] = -1;
|
||||
} else break;
|
||||
|
||||
let nulled = false;
|
||||
for (const item of state) {
|
||||
if (item === -1) {
|
||||
nulled = true;
|
||||
continue;
|
||||
}
|
||||
if (nulled) continue outer;
|
||||
}
|
||||
if (state.every((x) => x === -1)) break;
|
||||
|
||||
const key = state
|
||||
.filter((x) => x !== -1)
|
||||
.map((x) => axis[x])
|
||||
.join("");
|
||||
if (state.slice(1).every((x) => x === -1)) {
|
||||
const arr = [5, 3, 8, 2, 1].map((x) =>
|
||||
Math.abs(x - n - state[0]),
|
||||
);
|
||||
props.push({
|
||||
doc: `
|
||||
/**
|
||||
* Vector-style accessor, ${property[state[0]]} property of the value
|
||||
* @from {@link Math \`Math\`}
|
||||
* @example
|
||||
* \`\`\`ts
|
||||
* const vec = [${arr
|
||||
.slice(0, Math.max(n + 1, 2))
|
||||
.map((n) => n.toString())
|
||||
.join(", ")}] as const;
|
||||
*
|
||||
* const ${axis[state[0]]} = $(vec).${axis[state[0]]}.value;
|
||||
* expect(${axis[state[0]]}).toBe(${arr[state[0]]})
|
||||
* \`\`\`
|
||||
*/`.trimStart(),
|
||||
property: `readonly ${key}: Return<T[${state[0]}], t>`,
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
let count = 0;
|
||||
if (state.every((x) => x === count++)) continue;
|
||||
|
||||
const arr = [6, 2, 3, 5, 4, 7].map((x, idx) =>
|
||||
Math.abs(x - state[idx]),
|
||||
);
|
||||
|
||||
const type = state
|
||||
.filter((x) => x !== -1)
|
||||
.map((x) => `T[${x}]`)
|
||||
.join(", ");
|
||||
props.push({
|
||||
doc: `
|
||||
/**
|
||||
* Vector-style swizzle accessor, rearannges the elements of the value
|
||||
*
|
||||
* Equivalent to \`[${state
|
||||
.filter((x) => x !== -1)
|
||||
.map((x) => `vec[${x}]`)
|
||||
.join(", ")}]\`
|
||||
* @from {@link Math \`Math\`}
|
||||
* @example
|
||||
* \`\`\`ts
|
||||
* const vec = [${arr.slice(0, n + 1).join(", ")}] as const;
|
||||
*
|
||||
* const ${key} = $(vec).${key}().value;
|
||||
* expect(${key}).toMatchObject([${state
|
||||
.filter((x) => x !== -1)
|
||||
.map((x) => arr[x])
|
||||
.join(", ")}])
|
||||
* \`\`\`
|
||||
*/
|
||||
`.trimStart(),
|
||||
property: `readonly ${key}: () => Return<[${type}], t>`,
|
||||
});
|
||||
} while (state.some((x) => x !== -1));
|
||||
|
||||
props.sort((a, b) => a.property.length - b.property.length);
|
||||
|
||||
src += `interface Swizzle${n + 1}D<T extends readonly [${new Array(n + 1).fill("number").join(", ")}], t extends Props> {\n`;
|
||||
src += `${props
|
||||
.map((x) =>
|
||||
[x.doc, x.property]
|
||||
.join("\n")
|
||||
.split("\n")
|
||||
.map((line) => "\t" + line)
|
||||
.join("\n"),
|
||||
)
|
||||
.join("\n")}\n`;
|
||||
src += "}\n";
|
||||
src += "\n";
|
||||
}
|
||||
|
||||
writeFileSync(path, src);
|
||||
@@ -1,3 +1,5 @@
|
||||
src/**/*.d.ts
|
||||
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
|
||||
@@ -2,6 +2,7 @@ import { defineConfig } from "gen-doctests/config";
|
||||
|
||||
export default defineConfig({
|
||||
include: ["src/**/*.{js,ts}"],
|
||||
exclude: ["src/**/*.d.ts"],
|
||||
outDir: "tests/generated",
|
||||
templateHeader: [
|
||||
"import { test, expect, expectTypeOf, describe, vi } from 'vitest'",
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@ import tseslint from "typescript-eslint";
|
||||
import { defineConfig, globalIgnores } from "eslint/config";
|
||||
|
||||
export default defineConfig([
|
||||
globalIgnores(["dist", "tests/generated", "coverage"]),
|
||||
globalIgnores(["dist", "tests/generated", "coverage", "*.d.ts"]),
|
||||
{
|
||||
files: ["**/*.{ts,tsx}"],
|
||||
extends: [
|
||||
|
||||
+7
-4
@@ -4,11 +4,14 @@
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"test": "gen-doctests && vitest --run --reporter=tree --coverage --typecheck",
|
||||
"build": "tsc -b && esbuild --minify --bundle src/index.ts --outdir=dist --define:import.meta.vitest=undefined",
|
||||
"test": "gen-doctests && vitest --run --reporter=tree --typecheck --cache",
|
||||
"coverage": "gen-doctests && vitest --run --reporter=dot --coverage --cache",
|
||||
"build:generate": "node .config/generateSwizzle.js",
|
||||
"build": "tsc -b && node .config/build.js",
|
||||
"fmt": "prettier --write .",
|
||||
"lint": "eslint .",
|
||||
"preview": "vite preview"
|
||||
"prepublish": "pnpm build:generate",
|
||||
"prepack": "pnpm test && pnpm build"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^10.0.1",
|
||||
@@ -21,7 +24,7 @@
|
||||
"jiti": "^2.7.0",
|
||||
"madge": "^8.0.0",
|
||||
"prettier": "^3.8.4",
|
||||
"typescript": "6",
|
||||
"typescript": "^6.0.3",
|
||||
"typescript-eslint": "^8.59.2",
|
||||
"vitest": "^4.1.9"
|
||||
}
|
||||
|
||||
Generated
+1
-1
@@ -39,7 +39,7 @@ importers:
|
||||
specifier: ^3.8.4
|
||||
version: 3.8.4
|
||||
typescript:
|
||||
specifier: '6'
|
||||
specifier: ^6.0.3
|
||||
version: 6.0.3
|
||||
typescript-eslint:
|
||||
specifier: ^8.59.2
|
||||
|
||||
+3
-3
@@ -1,5 +1,5 @@
|
||||
import type { Fluent } from ".";
|
||||
import { never } from "../internal";
|
||||
import { phantom } from "../internal";
|
||||
import type { Registry } from "../registry";
|
||||
import type { RecPartial } from "../utility";
|
||||
import type { HKT } from "./hkt";
|
||||
@@ -29,8 +29,8 @@ export function Mixin<I, T extends MixinHKT<I>>(
|
||||
fn: MixinFn<I>,
|
||||
): Mixin<I, T> {
|
||||
return {
|
||||
interface: never,
|
||||
hkt: never,
|
||||
interface: phantom,
|
||||
hkt: phantom,
|
||||
fn,
|
||||
};
|
||||
}
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@ export interface Identity extends HKT {
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
||||
export const never = undefined as never;
|
||||
export const phantom = undefined as never;
|
||||
|
||||
export function isArray(v: unknown): v is unknown[] {
|
||||
return typeof v === "object" && v !== null && Array.isArray(v);
|
||||
|
||||
+21
-3
@@ -6,8 +6,9 @@ import {
|
||||
type Input,
|
||||
type Props,
|
||||
type Return,
|
||||
type Shim,
|
||||
} from "../base/mixin";
|
||||
import { assert, never } from "../internal";
|
||||
import { assert, phantom } from "../internal";
|
||||
import { Base } from "./base";
|
||||
|
||||
interface AwaitedIdentitity extends HKT {
|
||||
@@ -21,7 +22,7 @@ class Awaited<T extends Promise<unknown>> {
|
||||
}
|
||||
|
||||
public get [shim]() {
|
||||
return never as {
|
||||
return phantom as {
|
||||
input: T extends Promise<infer U> ? U : never;
|
||||
output: AwaitedIdentitity;
|
||||
value: T;
|
||||
@@ -114,11 +115,28 @@ export const AsyncMixin = Mixin<IAsync, AsyncMixin>(
|
||||
);
|
||||
|
||||
if (import.meta.vitest) {
|
||||
const { test, expect } = import.meta.vitest;
|
||||
const { test, expect, expectTypeOf, describe } = import.meta
|
||||
.vitest;
|
||||
|
||||
const registry = [Base, AsyncMixin] as const;
|
||||
const $ = makeFluent(registry);
|
||||
|
||||
describe("Awaited<T> class", () => {
|
||||
test("constructor", () => {
|
||||
const promise = new Promise(() => void {});
|
||||
const awaited = new Awaited(promise);
|
||||
|
||||
expect(awaited.value).toBe(promise);
|
||||
});
|
||||
|
||||
test("shim", () => {
|
||||
const awaited = new Awaited(new Promise(() => void {}));
|
||||
|
||||
expect(awaited[shim]).toBeUndefined();
|
||||
expectTypeOf(awaited[shim]).toExtend<Shim>();
|
||||
});
|
||||
});
|
||||
|
||||
test(".awaited", async () => {
|
||||
const value = 10 as const;
|
||||
const promise = new Promise<number>((r) => {
|
||||
|
||||
+57
-12
@@ -1,5 +1,14 @@
|
||||
import type { Inc, Vec } from "../../utility";
|
||||
|
||||
export type Truncate<T extends number> =
|
||||
`${T}` extends `${infer N extends number}.${number}` ? N : T;
|
||||
export type Decimal<T extends number> =
|
||||
`${T}` extends `${infer Sign extends "-" | ""}${number}.${infer N extends number}`
|
||||
? `${Sign}0.${N}` extends `${infer O extends number}`
|
||||
? O
|
||||
: never
|
||||
: 0;
|
||||
|
||||
export type Floor<T extends number> =
|
||||
`${T}` extends `${infer N extends number}.${number}` ? N : T;
|
||||
export type Ceil<T extends number> =
|
||||
@@ -36,110 +45,144 @@ type Fn<
|
||||
interface IBase {
|
||||
/**
|
||||
* `x + y`
|
||||
* @param other The second term of the addition (`y`)
|
||||
* @param other Addend term of the addition (`y`)
|
||||
* @from {@link Math `Math`}
|
||||
*/
|
||||
add: Fn<1>;
|
||||
/**
|
||||
* `x - y`
|
||||
* @param other The second term of the subtraction (`y`)
|
||||
* @param other Subtrahend term of the subtraction (`y`)
|
||||
* @from {@link Math `Math`}
|
||||
*/
|
||||
subtract: Fn<1>;
|
||||
/**
|
||||
* `x * y`
|
||||
* @param factor Factor term of the multiplication (`y`)
|
||||
* @from {@link Math `Math`}
|
||||
*/
|
||||
multiply: Fn<1>;
|
||||
/**
|
||||
* `x ** y`
|
||||
* @param exponent Exponent term of the power (`y`)
|
||||
* @from {@link Math `Math`}
|
||||
*/
|
||||
pow: Fn<1, true>;
|
||||
/**
|
||||
* `Math.sqrt(x)` (or `Math.log2(x)`)
|
||||
* @from {@link Math `Math`}
|
||||
*/
|
||||
sqrt: Fn<0, true>;
|
||||
/**
|
||||
* `x / y`
|
||||
* @param divisor Divisor term of the division (`y`)
|
||||
* @from {@link Math `Math`}
|
||||
*/
|
||||
divide: Fn<1, true>;
|
||||
/**
|
||||
* `x mod y`, not to be confused with `x % y` (remainder operation)
|
||||
* `x mod y` modulo operation (not to be confused with `x % y` (remainder
|
||||
* operation))
|
||||
* @param divisor Divisor term of the modulo operation (`y`)
|
||||
* @see `.rem()` for the remainder operation
|
||||
* @from {@link Math `Math`}
|
||||
*/
|
||||
mod: Fn<1, true>;
|
||||
/**
|
||||
* `x % y` remainder operation, not to be confused with `x mod y` (modulo)
|
||||
* `x % y` remainder operation (not to be confused with `x mod y` (modulo
|
||||
* operation))
|
||||
* @param divisor Divisor term of the remainder operation (`y`)
|
||||
* @see `.mod()` for the true modulo operation
|
||||
* @from {@link Math `Math`}
|
||||
*/
|
||||
rem: Fn<1, true>;
|
||||
/**
|
||||
* `ln(x)`, `log(x, base)`, or alternatively `Math.log(x) / Math.log(base)`
|
||||
* @param base Base of the logarithm (`base`). Defaults to `Math.E` for a natural logarithm function
|
||||
* @from {@link Math `Math`}
|
||||
*/
|
||||
log: Fn<0, true>;
|
||||
|
||||
/**
|
||||
* `Math.trunc(x)` or `x - (x % 1)`, remove the decimal part of the value
|
||||
* @from {@link Math `Math`}
|
||||
*/
|
||||
truncate: Fn<0, true>;
|
||||
/**
|
||||
* `x % 1`, remove the whole part of the value, leaving you with the decimals
|
||||
* @from {@link Math `Math`}
|
||||
*/
|
||||
decimal: Fn<0, true>;
|
||||
/**
|
||||
* `Math.floor(x)`
|
||||
* @from {@link Math `Math`}
|
||||
*/
|
||||
floor: Fn<0, true>;
|
||||
/**
|
||||
* `Math.ceil(x)`
|
||||
* @from {@link Math `Math`}
|
||||
*/
|
||||
ceil: Fn<0, true>;
|
||||
/**
|
||||
* `Math.round(x)` or `Math.round(x / multiple) * multiple` to round to the
|
||||
* `Math.round(x)` or `Math.round(x / multiple) * multiple`, round to the
|
||||
* specified multiple
|
||||
* @param multiple Multiple to round to, 1 if unspecified
|
||||
* @from {@link Math `Math`}
|
||||
*/
|
||||
round: Fn<1, true>;
|
||||
/**
|
||||
* `Math.fround(x)`, round to the nearest 32-bit float
|
||||
* approximation of value
|
||||
* @from {@link Math `Math`}
|
||||
*/
|
||||
fround: Fn<0, true>;
|
||||
/**
|
||||
* `Math.f16round(x)`, round to the nearest 16-bit float
|
||||
* approximation of value
|
||||
* @from {@link Math `Math`}
|
||||
*/
|
||||
ffround: Fn<0, true>;
|
||||
|
||||
/**
|
||||
* `Math.abs(x)`, calculate the absolute value (distance
|
||||
* from 0).
|
||||
* from 0)
|
||||
* @from {@link Math `Math`}
|
||||
*/
|
||||
abs: Fn<0, true>;
|
||||
/**
|
||||
* `Math.sign(x)`, returns the sign (1 or -1) of the value or zero
|
||||
* @from {@link Math `Math`}
|
||||
*/
|
||||
sign: Fn<0, true>;
|
||||
/**
|
||||
* `-x` or alternatively `x * -1`
|
||||
* @from {@link Math `Math`}
|
||||
*/
|
||||
negate: Fn<0, true>;
|
||||
|
||||
/**
|
||||
* `Math.min(x, ...values)`
|
||||
* `Math.min(x, ...values)`, return the smallest of the provided values
|
||||
* @param values Other canidates
|
||||
* @from {@link Math `Math`}
|
||||
*/
|
||||
min: Fn<0>;
|
||||
/**
|
||||
* `Math.min(x, ...values)`
|
||||
* `Math.min(x, ...values)`, return the largest of the provided values
|
||||
* @param values Other canidates
|
||||
* @from {@link Math `Math`}
|
||||
*/
|
||||
max: Fn<0>;
|
||||
|
||||
/**
|
||||
* `Math.min(Math.max(x, min), max)`
|
||||
* @param min Smallest allowed value to clamp to
|
||||
* @param max Largest allowed value to clamp to
|
||||
* `Math.min(Math.max(x, min), max)`, clamp value to the specified range
|
||||
* [min, max]
|
||||
* @param min Minimum value of the range
|
||||
* @param max Maximum value of the range
|
||||
* @from {@link Math `Math`}
|
||||
*/
|
||||
clamp: Fn<2, true>;
|
||||
/**
|
||||
* Clamps value into the range [0.0, 1.0]
|
||||
* Clamp value into the range [0.0, 1.0]
|
||||
* @see `.clamp()` for a customizable range
|
||||
* @from {@link Math `Math`}
|
||||
*/
|
||||
saturate: Fn<0, true>;
|
||||
|
||||
@@ -156,6 +199,7 @@ interface IBase {
|
||||
* @param b Target value to interpolate to
|
||||
* @param t Interpolation factor, the point on the line where 0.0 is `a` and 1.0 is `b`
|
||||
* @see `.inverseLerp()` for the inverse operation (to calculate `t`)
|
||||
* @from {@link Math `Math`}
|
||||
*/
|
||||
lerp: Fn<2, true>;
|
||||
/**
|
||||
@@ -170,6 +214,7 @@ interface IBase {
|
||||
* @param a The 0.0 point of the range
|
||||
* @param b The 1.0 point of the range
|
||||
* @see `.lerp()` for the inverse operation
|
||||
* @from {@link Math `Math`}
|
||||
*/
|
||||
inverseLerp: Fn<2, true>;
|
||||
}
|
||||
|
||||
+625
-65
File diff suppressed because it is too large
Load Diff
+57
-32
@@ -7,16 +7,17 @@ import {
|
||||
type Abs,
|
||||
type Base,
|
||||
type Ceil,
|
||||
type Decimal,
|
||||
type Floor,
|
||||
type Negate,
|
||||
type Round,
|
||||
type Sign,
|
||||
type Truncate,
|
||||
} from "./base";
|
||||
|
||||
namespace INumber {
|
||||
export type BaseImpl<T extends number, t extends Props> = Base<{
|
||||
/**
|
||||
* @from {@link Math `Math`}
|
||||
* @example
|
||||
* ```ts
|
||||
* const v = $(12).add(8).value;
|
||||
@@ -24,11 +25,10 @@ namespace INumber {
|
||||
* ```
|
||||
*/
|
||||
add: (
|
||||
/** Term to add */
|
||||
/** Addend term of the addition (`y`) */
|
||||
other: number,
|
||||
) => Return<number, t>;
|
||||
/**
|
||||
* @from {@link Math `Math`}
|
||||
* @example
|
||||
* ```ts
|
||||
* const v = $(12).subtract(8).value;
|
||||
@@ -36,11 +36,10 @@ namespace INumber {
|
||||
* ```
|
||||
*/
|
||||
subtract: (
|
||||
/** Term to subtract by */
|
||||
/** Subtrahend term of the subtraction (`y`) */
|
||||
other: number,
|
||||
) => Return<number, t>;
|
||||
/**
|
||||
* @from {@link Math `Math`}
|
||||
* @example
|
||||
* ```ts
|
||||
* const v = $(12).multiply(8).value;
|
||||
@@ -48,11 +47,10 @@ namespace INumber {
|
||||
* ```
|
||||
*/
|
||||
multiply: (
|
||||
/** Factor to multiply by */
|
||||
/** Factor term of the multiplication (`y`) */
|
||||
factor: number,
|
||||
) => Return<number, t>;
|
||||
/**
|
||||
* @from {@link Math `Math`}
|
||||
* @example
|
||||
* ```ts
|
||||
* const v = $(12).pow(2).value;
|
||||
@@ -60,11 +58,10 @@ namespace INumber {
|
||||
* ```
|
||||
*/
|
||||
pow: (
|
||||
/** Exponent to raise to */
|
||||
/** Exponent term of the power (`y`) */
|
||||
exponent: number,
|
||||
) => Return<number, t>;
|
||||
/**
|
||||
* @from {@link Math `Math`}
|
||||
* @example
|
||||
* ```ts
|
||||
* const v = $(144).sqrt().value;
|
||||
@@ -73,7 +70,6 @@ namespace INumber {
|
||||
*/
|
||||
sqrt: () => Return<number, t>;
|
||||
/**
|
||||
* @from {@link Math `Math`}
|
||||
* @example
|
||||
* ```ts
|
||||
* const v = $(12).divide(8).value;
|
||||
@@ -81,11 +77,10 @@ namespace INumber {
|
||||
* ```
|
||||
*/
|
||||
divide: (
|
||||
/** Divisor to divide by */
|
||||
/** Divisor term of the division (`y`) */
|
||||
divisor: number,
|
||||
) => Return<number, t>;
|
||||
/**
|
||||
* @from {@link Math `Math`}
|
||||
* @example
|
||||
* ```ts
|
||||
* const v = $(12).mod(8).value;
|
||||
@@ -93,11 +88,10 @@ namespace INumber {
|
||||
* ```
|
||||
*/
|
||||
mod: (
|
||||
/** Divisor to divide by */
|
||||
/** Divisor term of the modulo operation (`y`) */
|
||||
divisor: number,
|
||||
) => Return<number, t>;
|
||||
/**
|
||||
* @from {@link Math `Math`}
|
||||
* @example
|
||||
* ```ts
|
||||
* const v = $(12).rem(8).value;
|
||||
@@ -105,11 +99,10 @@ namespace INumber {
|
||||
* ```
|
||||
*/
|
||||
rem: (
|
||||
/** Divisor to divide by */
|
||||
/** Divisor term of the remainder operation (`y`) */
|
||||
divisor: number,
|
||||
) => Return<number, t>;
|
||||
/**
|
||||
* @from {@link Math `Math`}
|
||||
* @example
|
||||
* ```ts
|
||||
* const a = $(Math.E).log().value;
|
||||
@@ -120,12 +113,31 @@ namespace INumber {
|
||||
* ```
|
||||
*/
|
||||
log: (
|
||||
/** The base of the logarithm */
|
||||
/**
|
||||
* Base term of the logarithm (`base`)
|
||||
* @default Math.E
|
||||
*/
|
||||
base?: number,
|
||||
) => Return<number, t>;
|
||||
|
||||
/**
|
||||
* @from {@link Math `Math`}
|
||||
* @example
|
||||
* ```ts
|
||||
* const v = $(-1.8).truncate().value;
|
||||
* expect(v).toBe(-1);
|
||||
* ```
|
||||
*/
|
||||
truncate: () => Return<Truncate<T>, t>;
|
||||
/**
|
||||
* @example
|
||||
* ```ts
|
||||
* const v = $(4.25).decimal().value;
|
||||
* expect(v).toBeCloseTo(0.25);
|
||||
* ```
|
||||
*/
|
||||
decimal: () => Return<Decimal<T>, t>;
|
||||
|
||||
/**
|
||||
* @example
|
||||
* ```ts
|
||||
* const v = $(1.7).floor().value;
|
||||
@@ -134,7 +146,6 @@ namespace INumber {
|
||||
*/
|
||||
floor: () => Return<Floor<T>, t>;
|
||||
/**
|
||||
* @from {@link Math `Math`}
|
||||
* @example
|
||||
* ```ts
|
||||
* const v = $(1.3).ceil().value;
|
||||
@@ -143,7 +154,6 @@ namespace INumber {
|
||||
*/
|
||||
ceil: () => Return<Ceil<T>, t>;
|
||||
/**
|
||||
* @from {@link Math `Math`}
|
||||
* @example
|
||||
* ```ts
|
||||
* const a = $(1.3).round().value;
|
||||
@@ -167,7 +177,6 @@ namespace INumber {
|
||||
t
|
||||
>;
|
||||
/**
|
||||
* @from {@link Math `Math`}
|
||||
* @example
|
||||
* ```ts
|
||||
* const v = $(0.99999999).fround().value;
|
||||
@@ -176,7 +185,6 @@ namespace INumber {
|
||||
*/
|
||||
fround: () => Return<number, t>;
|
||||
/**
|
||||
* @from {@link Math `Math`}
|
||||
* @example
|
||||
* ```ts
|
||||
* const v = $(0.9999).ffround().value;
|
||||
@@ -186,7 +194,6 @@ namespace INumber {
|
||||
ffround: () => Return<number, t>;
|
||||
|
||||
/**
|
||||
* @from {@link Math `Math`}
|
||||
* @example
|
||||
* ```ts
|
||||
* const v = 4 as const;
|
||||
@@ -200,7 +207,6 @@ namespace INumber {
|
||||
*/
|
||||
abs: () => Return<Abs<T>, t>;
|
||||
/**
|
||||
* @from {@link Math `Math`}
|
||||
* @example
|
||||
* ```ts
|
||||
* const v = 10 as const;
|
||||
@@ -214,7 +220,6 @@ namespace INumber {
|
||||
*/
|
||||
negate: () => Return<Negate<T>, t>;
|
||||
/**
|
||||
* @from {@link Math `Math`}
|
||||
* @example
|
||||
* ```ts
|
||||
* const a = $(-42).sign().value;
|
||||
@@ -229,7 +234,6 @@ namespace INumber {
|
||||
sign: () => Return<Sign<T>, t>;
|
||||
|
||||
/**
|
||||
* @from {@link Math `Math`}
|
||||
* @example
|
||||
* ```ts
|
||||
* const maximum = 10;
|
||||
@@ -246,7 +250,6 @@ namespace INumber {
|
||||
...values: U
|
||||
) => Return<T | U[number], t>;
|
||||
/**
|
||||
* @from {@link Math `Math`}
|
||||
* @example
|
||||
* ```ts
|
||||
* const minimum = 0;
|
||||
@@ -263,7 +266,6 @@ namespace INumber {
|
||||
...values: U
|
||||
) => Return<T | U[number], t>;
|
||||
/**
|
||||
* @from {@link Math `Math`}
|
||||
* @example
|
||||
* ```ts
|
||||
* const min = 0;
|
||||
@@ -279,13 +281,12 @@ namespace INumber {
|
||||
* ```
|
||||
*/
|
||||
clamp: <Min extends number, Max extends number>(
|
||||
/** The minimum (smallest) value of the range */
|
||||
/** Minimum value of the range */
|
||||
min: Min,
|
||||
/** The maximum (largest) value of the range */
|
||||
/** Maximum value of the range */
|
||||
max: Max,
|
||||
) => Return<T | Min | Max, t>;
|
||||
/**
|
||||
* @from {@link Math `Math`}
|
||||
* @example
|
||||
* ```ts
|
||||
* const a = $(-2).saturate().value;
|
||||
@@ -300,7 +301,6 @@ namespace INumber {
|
||||
saturate: () => Return<number, t>;
|
||||
|
||||
/**
|
||||
* @from {@link Math `Math`}
|
||||
* @example
|
||||
* ```ts
|
||||
* const from = 12;
|
||||
@@ -327,6 +327,27 @@ namespace INumber {
|
||||
/** Interpolation factor, 0.0 corresponds to `a` and 1.0 corresponds to `b` */
|
||||
t: number,
|
||||
) => Return<number, t>;
|
||||
/**
|
||||
* @example
|
||||
* ```ts
|
||||
* const from = 5;
|
||||
* const to = 10;
|
||||
*
|
||||
* const a = $(5).inverseLerp(from, to).value;
|
||||
* const b = $(7.5).inverseLerp(from, to).value;
|
||||
* const c = $(10).inverseLerp(from, to).value;
|
||||
*
|
||||
* expect(a).toBe(0.0);
|
||||
* expect(b).toBe(0.5);
|
||||
* expect(c).toBe(1.0);
|
||||
*
|
||||
* const d = $(15).inverseLerp(from, to).value;
|
||||
* const e = $(0).inverseLerp(from, to).value;
|
||||
*
|
||||
* expect(d).toBe(2);
|
||||
* expect(e).toBe(-1);
|
||||
* ```
|
||||
*/
|
||||
inverseLerp: (
|
||||
/** 0.0 point of the range */
|
||||
a: number,
|
||||
@@ -483,6 +504,8 @@ export const applyNumber = Mixin.partial<INumber, number>(
|
||||
return fluent(Math.abs(value - other) < delta);
|
||||
};
|
||||
|
||||
$.truncate = () => fluent(Math.trunc(value));
|
||||
$.decimal = () => fluent(value % 1);
|
||||
$.floor = () => fluent(Math.floor(value));
|
||||
$.ceil = () => fluent(Math.ceil(value));
|
||||
$.round = (multiple?: number) =>
|
||||
@@ -575,6 +598,8 @@ if (import.meta.vitest) {
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
t("truncate", -6.4, -6);
|
||||
t("decimal", -6.4, -6.4 % 1);
|
||||
test("floor()", () => {
|
||||
expect($(1).floor().value).toBe(1);
|
||||
expect($(1.7).floor().value).toBe(1);
|
||||
|
||||
+57
-246
@@ -1,230 +1,65 @@
|
||||
import { makeFluent } from "../../base";
|
||||
import { Mixin, type Props, type Return } from "../../base/mixin";
|
||||
import { Mixin } from "../../base/mixin";
|
||||
import { Math } from "../math";
|
||||
import type { Vec, Dec, Inc as __Inc } from "../../utility";
|
||||
import type { ISwizzle, SwizzleProps } from "./swizzlePermutations";
|
||||
|
||||
type Axis = typeof axis;
|
||||
const axis = ["x", "y", "z", "w", "v"] as const;
|
||||
|
||||
type Inc<N extends number | undefined> = N extends number
|
||||
? __Inc<N>
|
||||
: 0;
|
||||
|
||||
type Next<
|
||||
N extends number,
|
||||
State extends readonly (number | undefined)[],
|
||||
TOut extends (number | undefined)[] = [],
|
||||
> = State extends readonly [
|
||||
infer Current extends number | undefined,
|
||||
...infer Rest extends (number | undefined)[],
|
||||
]
|
||||
? Inc<Current> extends infer TInc
|
||||
? TInc extends N
|
||||
? Next<N, Rest, [...TOut, undefined]>
|
||||
: [...TOut, TInc, ...Rest]
|
||||
: never
|
||||
: null;
|
||||
|
||||
type Key<
|
||||
State extends (number | undefined)[],
|
||||
TOut extends string = "",
|
||||
> = State extends readonly [infer Only extends number, ...undefined[]]
|
||||
? `${TOut}${Axis[Only]}`
|
||||
: State extends readonly [
|
||||
infer Current extends number,
|
||||
...infer Rest extends (number | undefined)[],
|
||||
]
|
||||
? Key<Rest, `${TOut}${Axis[Current]}`>
|
||||
: never;
|
||||
|
||||
type IsAscending<
|
||||
State extends (number | undefined)[],
|
||||
Prev extends number | undefined = undefined,
|
||||
> = State extends readonly [Inc<Prev>, ...infer Rest extends number[]]
|
||||
? IsAscending<Rest, Inc<Prev>>
|
||||
: State extends readonly []
|
||||
? true
|
||||
: false;
|
||||
|
||||
type Pretty<T> = { [K in keyof T]: T[K] };
|
||||
|
||||
type SwizzlePermutations<
|
||||
N extends number,
|
||||
State extends Vec<number | undefined, N> = Vec<undefined, N>,
|
||||
TOut extends Record<string, readonly number[]> = Record<
|
||||
never,
|
||||
never
|
||||
>,
|
||||
> =
|
||||
Next<N, State> extends infer TNext
|
||||
? TNext extends Vec<number | undefined, N>
|
||||
? SwizzlePermutations<
|
||||
N,
|
||||
TNext,
|
||||
TOut &
|
||||
(IsAscending<State> extends true
|
||||
? unknown
|
||||
: Record<Key<State>, State>)
|
||||
>
|
||||
: Pretty<TOut & Record<Key<State>, State>>
|
||||
: never;
|
||||
|
||||
type Sequence<
|
||||
T extends readonly unknown[],
|
||||
Indexes extends (number | undefined)[],
|
||||
TOut extends T[number][] = [],
|
||||
> = Indexes extends readonly [
|
||||
infer Current extends number,
|
||||
...infer Rest extends (number | undefined)[],
|
||||
]
|
||||
? Sequence<T, Rest, [...TOut, T[Current]]>
|
||||
: TOut;
|
||||
|
||||
type Primative<
|
||||
T extends readonly unknown[],
|
||||
t extends Props,
|
||||
Axes extends readonly string[] = Axis,
|
||||
Acc = unknown,
|
||||
TOut extends unknown[] = [],
|
||||
> = Axes extends readonly [
|
||||
infer Current extends string,
|
||||
...infer Rest extends string[],
|
||||
]
|
||||
? Acc &
|
||||
Record<
|
||||
Current,
|
||||
Return<T[TOut["length"]], t>
|
||||
> extends infer TAcc
|
||||
? Primative<T, t, Rest, TAcc, [...TOut, TAcc]>
|
||||
: never
|
||||
: TOut;
|
||||
|
||||
interface Swizzle4D extends SwizzlePermutations<4> {}
|
||||
|
||||
type SwizzleCache = [
|
||||
{ x: [0] },
|
||||
SwizzlePermutations<2>,
|
||||
SwizzlePermutations<3>,
|
||||
Swizzle4D,
|
||||
];
|
||||
|
||||
export type Swizzle<
|
||||
T extends readonly unknown[],
|
||||
t extends Props,
|
||||
> = number extends T["length"]
|
||||
? unknown
|
||||
: T extends readonly []
|
||||
? unknown
|
||||
: (
|
||||
Dec<T["length"]> extends infer K extends
|
||||
| 0
|
||||
| 1
|
||||
| 2
|
||||
| 3
|
||||
? { k: K; c: SwizzleCache[K] }
|
||||
: SwizzleCache extends [
|
||||
...unknown[],
|
||||
infer Last,
|
||||
]
|
||||
? {
|
||||
k: Dec<SwizzleCache["length"]>;
|
||||
c: Last;
|
||||
}
|
||||
: never
|
||||
) extends {
|
||||
k: infer K extends number;
|
||||
c: infer C extends Record<
|
||||
PropertyKey,
|
||||
(number | undefined)[]
|
||||
>;
|
||||
}
|
||||
? Omit<
|
||||
{
|
||||
readonly [K in keyof C]: Return<
|
||||
Sequence<T, C[K]>,
|
||||
t
|
||||
>;
|
||||
},
|
||||
Axis[number]
|
||||
> &
|
||||
Primative<T, t>[K]
|
||||
: never;
|
||||
|
||||
export type ISwizzle<
|
||||
T extends readonly unknown[] = readonly unknown[],
|
||||
t extends Props = Props,
|
||||
> = {
|
||||
readonly [K in keyof Swizzle4D]: Return<
|
||||
Sequence<T, Swizzle4D[K]>,
|
||||
t
|
||||
>;
|
||||
} & Primative<T, t>[3];
|
||||
|
||||
const axis = ["x", "y", "z", "w"] as const;
|
||||
export type { ISwizzle, SwizzleProps as Swizzle };
|
||||
|
||||
export const applySwizzle = Mixin.partial<
|
||||
ISwizzle,
|
||||
readonly unknown[]
|
||||
>((value, $, fluent) => {
|
||||
const length = globalThis.Math.min(value.length, axis.length);
|
||||
const state = new Array<number>(length).fill(-1);
|
||||
const state = new Array<number>(axis.length).fill(-1);
|
||||
|
||||
main: do {
|
||||
for (let i = 0; i < state.length; i++) {
|
||||
state[i] += 1;
|
||||
if (state[i] >= length) state[i] = -1;
|
||||
else break;
|
||||
}
|
||||
outer: do {
|
||||
for (let i = 0; i < state.length; i++)
|
||||
if (++state[i] >= length) {
|
||||
state[i] = -1;
|
||||
} else break;
|
||||
|
||||
let reachedEmpty = false;
|
||||
let nulled = false;
|
||||
for (const item of state) {
|
||||
if (item === -1) {
|
||||
reachedEmpty = true;
|
||||
} else if (reachedEmpty) {
|
||||
continue main;
|
||||
nulled = true;
|
||||
continue;
|
||||
}
|
||||
if (nulled) continue outer;
|
||||
}
|
||||
if (state.every((x) => x === -1)) break;
|
||||
|
||||
const key = state
|
||||
.filter((x) => x !== -1)
|
||||
.map((x) => axis[x])
|
||||
.join("");
|
||||
if (key.length <= 1 || key in $) continue;
|
||||
if (state.slice(1).every((x) => x === -1)) {
|
||||
const v = value[state[0]];
|
||||
Object.defineProperty($, key, {
|
||||
get: () => {
|
||||
return fluent(v);
|
||||
},
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
let count = 0;
|
||||
if (state.every((x) => x === count++)) continue;
|
||||
|
||||
const permutation = state
|
||||
.filter((x) => x !== -1)
|
||||
.map((x) => value[x]);
|
||||
Object.defineProperty($, key, {
|
||||
get: () => {
|
||||
// OPTIMIZE? Reduce amount of arrays allocated and held for the lambda..?
|
||||
value: () => {
|
||||
return fluent(permutation);
|
||||
},
|
||||
});
|
||||
} while (state.some((x) => x !== -1));
|
||||
|
||||
Object.defineProperty($, "x", {
|
||||
get: () => {
|
||||
return fluent(value[0]);
|
||||
},
|
||||
});
|
||||
Object.defineProperty($, "y", {
|
||||
get: () => {
|
||||
return fluent(value[1]);
|
||||
},
|
||||
});
|
||||
Object.defineProperty($, "z", {
|
||||
get: () => {
|
||||
return fluent(value[2]);
|
||||
},
|
||||
});
|
||||
Object.defineProperty($, "w", {
|
||||
get: () => {
|
||||
return fluent(value[3]);
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
if (import.meta.vitest) {
|
||||
const { test, describe, expect, expectTypeOf } = import.meta
|
||||
.vitest;
|
||||
const { test, expect, expectTypeOf } = import.meta.vitest;
|
||||
|
||||
const registry = [Math] as const;
|
||||
const $ = makeFluent(registry);
|
||||
@@ -235,20 +70,33 @@ if (import.meta.vitest) {
|
||||
|
||||
const vec = [x, y] as const;
|
||||
|
||||
expect($(vec).xx.value).toMatchObject([x, x]);
|
||||
expect($(vec).yy.value).toMatchObject([y, y]);
|
||||
expect($(vec).xx().value).toMatchObject([x, x]);
|
||||
expect($(vec).yy().value).toMatchObject([y, y]);
|
||||
|
||||
const yx = $(vec).yx.value;
|
||||
expect($(vec).xyxy().value).toMatchObject([x, y, x, y]);
|
||||
|
||||
const yx = $(vec).yx().value;
|
||||
expect(yx).toMatchObject([y, x]);
|
||||
expectTypeOf(yx).toEqualTypeOf<[typeof y, typeof x]>();
|
||||
|
||||
const large = [x, y, x, y] as const;
|
||||
|
||||
expect($(large).wzyx.value).toMatchObject([y, x, y, x]);
|
||||
expect($(large).xzyw.value).toMatchObject([x, x, y, y]);
|
||||
expect($(large).ywxz.value).toMatchObject([y, y, x, x]);
|
||||
expect($(large).xxxx.value).toMatchObject([x, x, x, x]);
|
||||
expect($(large).wwww.value).toMatchObject([y, y, y, y]);
|
||||
expect($(large).wzyx().value).toMatchObject([y, x, y, x]);
|
||||
expect($(large).xzyw().value).toMatchObject([x, x, y, y]);
|
||||
expect($(large).ywxz().value).toMatchObject([y, y, x, x]);
|
||||
expect($(large).xxxx().value).toMatchObject([x, x, x, x]);
|
||||
expect($(large).wwww().value).toMatchObject([y, y, y, y]);
|
||||
|
||||
const v = 3 as const;
|
||||
const gigantic = [x, y, v, y, x] as const;
|
||||
|
||||
expect($(gigantic).vwzyx().value).toMatchObject([
|
||||
x,
|
||||
y,
|
||||
v,
|
||||
y,
|
||||
x,
|
||||
]);
|
||||
});
|
||||
|
||||
test(".x", () => {
|
||||
@@ -287,49 +135,12 @@ if (import.meta.vitest) {
|
||||
expectTypeOf(v).toEqualTypeOf(w);
|
||||
});
|
||||
|
||||
describe("types", () => {
|
||||
test("Next", () => {
|
||||
type N = 3;
|
||||
test(".v", () => {
|
||||
const v = 0 as const;
|
||||
const arr = [v, v, v, v, v] as const;
|
||||
|
||||
expectTypeOf<
|
||||
Next<N, [undefined, undefined, undefined]>
|
||||
>().toEqualTypeOf<[0, undefined, undefined]>();
|
||||
|
||||
expectTypeOf<
|
||||
Next<N, [2, undefined, undefined]>
|
||||
>().toEqualTypeOf<[undefined, 0, undefined]>();
|
||||
|
||||
expectTypeOf<Next<N, [2, 2, 2]>>().toEqualTypeOf<null>();
|
||||
});
|
||||
|
||||
test("Key", () => {
|
||||
expectTypeOf<
|
||||
Key<[1, 2, undefined]>
|
||||
>().toEqualTypeOf<"yz">();
|
||||
expectTypeOf<Key<[1, 1, 1]>>().toEqualTypeOf<"yyy">();
|
||||
});
|
||||
|
||||
test("Sequence", () => {
|
||||
type In = [1, 2, 3, 4, 5];
|
||||
type Out = [5, 4, 3, 2, 1];
|
||||
type Reverse = [4, 3, 2, 1, 0];
|
||||
|
||||
expectTypeOf<
|
||||
Sequence<In, Reverse>
|
||||
>().toEqualTypeOf<Out>();
|
||||
});
|
||||
|
||||
test("Primative", () => {
|
||||
interface Props {
|
||||
value: [number, number];
|
||||
meta: { registry: [] };
|
||||
}
|
||||
|
||||
type V = Primative<[number, number], Props>[1];
|
||||
|
||||
expectTypeOf<V>().toHaveProperty("x");
|
||||
expectTypeOf<V>().toHaveProperty("y");
|
||||
expectTypeOf<V>().not.toHaveProperty("z");
|
||||
});
|
||||
const value = $(arr).v.value;
|
||||
expect(value).toBe(v);
|
||||
expectTypeOf(value).toEqualTypeOf(v);
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user