This repository has been archived on 2026-03-28. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files

85 lines
2.2 KiB
TypeScript

import { Type } from './type'
class Case<In, Out> {
type: Type<In>
fn: (x: In) => Out
constructor(type: Type<In>, fn: (x: In) => Out) {
this.type = type
this.fn = fn
}
}
type InOfCase<T extends Case<any, any>> = T extends Case<infer In, any> ?
In : never
type OutOfCase<T extends Case<any, any>> = T extends Case<any, infer Out> ?
Out : never
export class CaseSwitch<Cases extends Case<any, any>> {
cases: Cases[]
private memoAccept: Type<InOfCase<Cases>> | undefined
constructor(cases: Cases[]) {
if (cases.length === 0) {
throw new Error("must have at least one case")
}
this.cases = cases
}
// implemented lazily because not every CaseSwitch will be used
get accept(): Type<InOfCase<Cases>> {
if (this.memoAccept) {
return this.memoAccept
}
const cases = this.cases
let type = cases[0].type
for (let i = 1; i < cases.length; i++) {
type = type.or(cases[i].type)
}
this.memoAccept = type
return this.memoAccept
}
run(val: InOfCase<Cases>): OutOfCase<Cases> {
for (let c of this.cases) {
if (c.type.guard(val)) {
return c.fn(val);
}
}
// raise a type error if none of the cases match the value.
this.accept.assert(val);
/* istanbul ignore next */ throw "unreachable";
}
/**
* Create a new CaseSwitch that also handles the specified case.
*/
when<In, Out>(type: Type<In>, fn: (v: In) => Out): CaseSwitch<Cases | Case<In, Out>> {
const c = new Case(type, fn)
return new CaseSwitch([...this.cases, c])
}
}
/**
* Create a type switch that can be used with `match()`.
*/
export function when<In, Out>(type: Type<In>, fn: (v: In) => Out) {
const c = new Case(type, fn)
return new CaseSwitch([c])
}
/**
* Match a value against a type switch created with `when()`.
*
* @example
* const asString = t.match(foo,
* t.when(t.array(t.str), xs => xs.join(' and '))
* .when(t.str, s => s)
* .when(t.any, x => '' + x))
*/
// export function match<I, O, Cases extends Case<I, O>>(val: I, cases: CaseSwitch<Cases>): O {
export function match<Cases extends Case<any, any>>(val: InOfCase<Cases>, sw: CaseSwitch<Cases>): OutOfCase<Cases> {
return sw.run(val)
}