import { Converter } from "../types.js"; import { Nullable } from "../utils.js"; import { fromSync } from "./async.js"; import { Comparer, ComparisonOrComparer, Comparison, AsyncComparer } from "./types.js"; export function isComparer(obj: any): obj is Comparer { return obj instanceof BaseComparer; } export function asComparer(comparer: ComparisonOrComparer) { return typeof comparer === "function" ? createComparer(comparer) : comparer; } export function asComparison(comparer: ComparisonOrComparer) { return typeof comparer === "function" ? comparer : comparer.comparison(); } export function createComparer(comparison: Comparison): Comparer { return new SimpleComparer(comparison); } export function createComparerUsing(projection: Converter, comparison?: ComparisonOrComparer): Comparer { return new MappedComparer(projection, comparison); } export function reverseComparison(comparison: Comparison): Comparison { return (a, b) => comparison(b, a); } export function combineNullableComparers(comparers: Nullable>[]) { let result = defaultComparer; for (const comparer of comparers) { if (!comparer) { continue; } result = result.then(asComparer(comparer)); } return result === defaultComparer ? undefined : result; } export abstract class BaseComparer implements Comparer { #cachedBoundComparison: Comparison | undefined; public abstract compare(a: T, b: T): number; public comparison(): Comparison { return this.#cachedBoundComparison ??= this.compare.bind(this); } public reverse(): Comparer { return new ReversedComparer(this); } public then(comparer: Comparer): Comparer { return new ThenComparer(this, comparer); } public thenCompare(comparison: Comparison): Comparer { return this.then(createComparer(comparison)); } public thenCompareUsing(projection: Converter, comparison?: ComparisonOrComparer): Comparer { return this.then(createComparerUsing(projection, comparison)); } public toAsync(): AsyncComparer { return fromSync(this); } public nullAwareComparer(): Comparer> { return new NullAwareComparer(this); } } class SimpleComparer extends BaseComparer { readonly #comparison: Comparison; public constructor(comparison: Comparison) { super(); this.#comparison = comparison; } public override compare(a: T, b: T) { return this.#comparison(a, b); } public override comparison() { return this.#comparison; } } class MappedComparer extends BaseComparer { readonly #projection: Converter; readonly #comparison: Comparer; public constructor(projection: Converter, comparison?: ComparisonOrComparer) { super(); this.#projection = projection; this.#comparison = comparison ? asComparer(comparison) : defaultComparer; } public override compare(a: T, b: T) { return this.#comparison.compare(this.#projection(a), this.#projection(b)); } } class ReversedComparer extends BaseComparer { readonly #base: Comparer; public constructor(base: Comparer) { super(); this.#base = base; } public override compare(a: T, b: T): number { return this.#base.compare(b, a); } public override reverse(): Comparer { return this.#base; } } class ThenComparer extends BaseComparer { readonly #base: Comparer; readonly #comparer: Comparer; public constructor(base: Comparer, comparer: Comparer) { super(); this.#base = base; this.#comparer = comparer; } public override compare(a: T, b: T) { return this.#base.compare(a, b) || this.#comparer.compare(a, b); } } export const defaultComparer: Comparer = new class DefaultComparer extends BaseComparer { public override compare(a: any, b: any): number { if (a === undefined) { if (b === undefined) { return 0; } return 1; } if (b === undefined) { return -1; } const aStr = `${a}`, bStr = `${b}`; return aStr > bStr ? 1 : aStr < bStr ? -1 : 0; } }; export function getDefaultComparer(): Comparer { return defaultComparer; } class NullAwareComparer extends BaseComparer> { readonly #base: Comparer; constructor(baseComparer: Comparer) { super(); this.#base = baseComparer; } public override compare(a: Nullable, b: Nullable): number { if (a === undefined) { if (b === undefined) { return 0; } return 1; } if (b === undefined) { return -1; } if (a === null) { if (b === null) { return 0; } return 1; } if (b === null) { return -1; } return this.#base.compare(a, b); } public override nullAwareComparer(): Comparer> { return this; } } export function nullAwareComparer(baseComparer: Comparer): Comparer> { return new NullAwareComparer(baseComparer); } export const stringComparer: Comparer = new class StringComparer extends BaseComparer { public override compare(a: string, b: string): number { return a.localeCompare(b); } }; export const numberComparer: Comparer = new class NumberComparer extends BaseComparer { public override compare(a: number, b: number): number { return a - b; } }; export const bigintComparer: Comparer = new class BigIntComparer extends BaseComparer { public override compare(a: bigint, b: bigint): number { return Number(a - b); } }; export const booleanComparer: Comparer = new class BooleanComparer extends BaseComparer { public override compare(a: boolean, b: boolean): number { return Number(a) - Number(b); } }; export const dateComparer: Comparer = new class DateComparer extends BaseComparer { public override compare(a: Date, b: Date): number { return a.getTime() - b.getTime(); } };