import { int } from "./primitives.js"; import { assertNeverT } from "./test.js"; import { freeze } from "./utils.js"; export enum Relation { UNRELATED, LESS, EQUAL, GREATER } export type Total = Relation.LESS | Relation.EQUAL | Relation.GREATER export function relationAsNumber(r : Relation) : number | undefined { switch(r) { case Relation.LESS: return -1; case Relation.EQUAL: return 0; case Relation.GREATER: return 1; default: return undefined; } } export function invertRelation(relation : Relation) : Relation { switch (relation) { case Relation.LESS: return Relation.GREATER; case Relation.GREATER: return Relation.LESS; case Relation.EQUAL: return Relation.EQUAL; case Relation.UNRELATED: return Relation.UNRELATED; default: assertNeverT(relation); } } freeze(invertRelation); export interface Thing { name : string is(value : any) : value is T assert(value : any) : asserts value is T display(value : T) : string } export interface Equality extends Thing { /** Here we can assume that `is(x)` and `is(y)` both hold. */ equal(x : T, y : T) : boolean } export interface Compare { compare(x : T, y : T) : Relation } export interface Order extends Equality, Compare { /** * Here we can assume that `is(x)` and `is(y)` both hold. * Must be compatible with {@link Equality.equals}: * `equal(x, y) === (compare(x, y) === Relation.EQUAL)` **/ compare(x : T, y : T) : Relation } export interface Hash extends Equality { /** * Here we can assume that `is(x)` holds. * Must be compatible with {@link Equality.equals}: * `equal(x, y)` implies `hash(x) === hash(y)` */ hash(x : T) : int } export interface Data extends Order, Hash {} const defaultThingName = "thing"; export function mkThing( name : string | undefined, check : (value : any) => boolean, display : (value : T) => string = (v) => "" + v) : Thing { name = name ?? defaultThingName; const thing : Thing = { name: name, is: function (value: any): value is T { return check(value); }, assert: function (value: any): asserts value is T { if (!check(value)) throw new Error("not a " + name + ": " + value); }, display: display }; return freeze(thing); } freeze(mkThing); export function assertThings(thing : Thing, ...values : T[]) { for (const v of values) thing.assert(v); } export function mkEquality( name : string | undefined, check : (x : T) => boolean, equal : (x : T, y : T) => boolean, display : (value : T) => string = (v) => "" + v) : Equality { name = name ?? defaultThingName; const eq : Equality = { name: name, is: function (value: any): value is T { return check(value); }, assert: function (value: any): asserts value is T { if (!check(value)) throw new Error("not a " + name + ": " + value); }, equal: function (x: T, y: T): boolean { return equal(x, y); }, display: display }; return freeze(eq); } freeze(mkEquality); export function mkOrder( name : string | undefined, check : (x : T) => boolean, compare : (x : T, y : T) => Relation, display : (value : T) => string = (v) => "" + v) : Order { name = name ?? defaultThingName; const order : Order = { name: name, is: function (value: any): value is T { return check(value); }, assert: function (value: any): asserts value is T { if (!check(value)) throw new Error("not a " + name + ": " + value); }, equal: function (x: T, y: T): boolean { return compare(x, y) === Relation.EQUAL; }, compare: function (x: T, y: T): Relation { return compare(x, y); }, display: display }; return order; } freeze(mkOrder); export function mkHash( name : string | undefined, check : (x : T) => boolean, equal : (x : T, y : T) => boolean, hashing : (x : T) => int, display : (value : T) => string = (v) => "" + v) : Hash { name = name ?? defaultThingName; const hash : Hash = { name: name, is: function (value: any): value is T { return check(value); }, assert: function (value: any): asserts value is T { if (!check(value)) throw new Error("not a " + name + ": " + value); }, equal: function (x: T, y: T): boolean { return equal(x, y); }, hash: function(x: T): int { return hashing(x); }, display: display }; return hash; } freeze(mkHash); export function mkOrderAndHash( name : string | undefined, check : (x : T) => boolean, compare : (x : T, y : T) => Relation, hash : (x : T) => int, display : (value : T) => string = (v) => "" + v) : Order & Hash { name = name ?? defaultThingName; const order : Order & Hash = { name: name, is: function (value: any): value is T { return check(value); }, assert: function (value: any): asserts value is T { if (!check(value)) throw new Error("not a " + name + ": " + value); }, equal: function (x: T, y: T): boolean { return compare(x, y) === Relation.EQUAL; }, compare: function (x: T, y: T): Relation { return compare(x, y); }, hash: function(x: T): int { return hash(x); }, display: display }; return order; } freeze(mkOrderAndHash); export function mkData( name : string | undefined, check : (x : T) => boolean, compare : (x : T, y : T) => Relation, hash : (x : T) => int, display : (value : T) => string = (v) => "" + v) : Data { return mkOrderAndHash(name, check, compare, hash, display); } freeze(mkData);