Skip to content
This repository has been archived by the owner on Sep 14, 2023. It is now read-only.

Commit

Permalink
feat: add match (#1078)
Browse files Browse the repository at this point in the history
  • Loading branch information
tjjfvi authored Jun 15, 2023
1 parent 3da5d3b commit 0bf66dc
Show file tree
Hide file tree
Showing 3 changed files with 129 additions and 1 deletion.
38 changes: 38 additions & 0 deletions rune/Rune.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -251,3 +251,41 @@ async function* iter<T>(values: T[]) {
yield value
}
}

Deno.test("match abc", async () => {
for (
const value of [
{ type: "a", a: 0 },
{ type: "b", b: "0" },
{ type: "c", c: false },
] as const
) {
const result = await Rune.constant(value).match((_) =>
_
.when(is("a"), (x) => x.access("a"))
.when(is("b"), (x) => x.access("b"))
.else((x) => x.access("c"))
).run()
assertEquals(+result, 0)
}
})

Deno.test("match u", async () => {
assertEquals(
await Rune
.constant("hello")
.unhandle(is(String))
.match((_) =>
_.else((x) =>
x
.rehandle((_: never): _ is never => true)
.map(() => {
throw new Error("unreachable")
})
)
)
.rehandle(is(String))
.run(),
"hello",
)
})
59 changes: 59 additions & 0 deletions rune/ValueRune.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,65 @@ export class ValueRune<out T, out U = never> extends Rune<T, U> {
chain<T2, U2>(fn: (result: ValueRune<T, never>) => Rune<T2, U2>): ValueRune<T2, U | U2> {
return ValueRune.new(RunChain, this, fn(this as never))
}

match<T, U, T2, U2>(
this: ValueRune<T, U>,
fn: (match: Match<T, never, never>) => ExhaustiveMatch<T2, U2>,
): ValueRune<T2, U | U2> {
const match = new Match<T, never, never>(this as any as ValueRune<T, never>)
if (fn(match) !== match as any) {
throw new Error("The callback supplied to match must return the match passed in")
}
return ValueRune.new(RunMatch, this as any as ValueRune<T, never>, match.conditions)
}
}

type ExhaustiveMatch<T, U> = Match<never, T, U>
class Match<M, T, U> {
conditions: [(x: M) => boolean, ValueRune<T, U>][] = []

constructor(readonly value: ValueRune<M, never>) {}

when<M2 extends M, T2, U2>(
guard: Guard<M, M2>,
fn: (value: ValueRune<M2, never>) => ValueRune<T2, U2>,
): Match<Exclude<M, M2>, T | T2, U | U2> {
this.conditions.push([guard, fn(this.value as any) as any])
return this as any
}

else<T2, U2>(
fn: (value: ValueRune<M, never>) => ValueRune<T2, U2>,
): ExhaustiveMatch<T | T2, U | U2> {
this.conditions.push([() => true, fn(this.value as any) as any])
return this as any
}
}

class RunMatch<M, T, U> extends Run<T, U> {
value
conditions
constructor(
batch: Batch,
child: Rune<M, never>,
conditions: [(x: M) => boolean, ValueRune<T, U>][],
) {
super(batch)
this.value = batch.prime(child, this.signal)
this.conditions = conditions.map(([cond, val]) =>
[cond, batch.prime(val, this.signal)] as const
)
}

async _evaluate(time: number, receipt: Receipt) {
const value = await this.value.evaluate(time, receipt) as M
for (const [cond, val] of this.conditions) {
if (cond(value)) {
return await val.evaluate(time, receipt)
}
}
throw new Error("Match was not exhaustive")
}
}

class RunMap<T1, U, T2> extends Run<T2, U> {
Expand Down
33 changes: 32 additions & 1 deletion rune/is.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,15 @@ export function is(guard: typeof String): Guard<unknown, string>
export function is(guard: typeof Boolean): Guard<unknown, boolean>
export function is(guard: typeof BigInt): Guard<unknown, bigint>
export function is(guard: typeof Symbol): Guard<unknown, symbol>
export function is(guard: true): Guard<unknown, true>
export function is(guard: false): Guard<unknown, false>
export function is<T>(guard: abstract new(...args: any) => T): Guard<unknown, T>
export function is<
T extends string | { type: string },
U extends T extends { type: string } ? T["type"] : T,
>(
guard: U,
): Guard<T, Extract<T, U | { type: U }>>
export function is(guard: any): Guard<unknown, any> {
switch (guard) {
case undefined:
Expand All @@ -29,7 +37,14 @@ export function is(guard: any): Guard<unknown, any> {
case Symbol:
return isSymbol
default:
return isInstance(guard)
if (typeof guard === "string") {
return isType(guard)
} else if (typeof guard === "boolean") {
return guard ? isTrue : isFalse
}
{
return isInstance(guard)
}
}
}

Expand Down Expand Up @@ -61,6 +76,22 @@ function isSymbol(x: unknown): x is symbol {
return typeof x === "symbol"
}

function isTrue(x: unknown): x is true {
return x === true
}

function isFalse(x: unknown): x is false {
return x === false
}

function isType<
T extends string | { type: string },
U extends T extends { type: string } ? T["type"] : T,
>(type: U) {
return (value: T): value is Extract<T, U | { type: U }> =>
(value as any) === type || (value as any).type === type
}

const isInstanceMemo = new WeakMap<abstract new(...args: any) => any, Guard<unknown, any>>()
function isInstance<T>(ctor: abstract new(...args: any) => T): Guard<unknown, T> {
return getOrInit(
Expand Down

0 comments on commit 0bf66dc

Please sign in to comment.