preserves/implementations/javascript/src/fold.ts

135 lines
4.0 KiB
TypeScript

import { Bytes, Value, Record, Set, Dictionary, Single, Double, Annotated, annotate, Float } from "./values";
export type Fold<T extends object, R = Value<T>> = (v: Value<T>) => R;
export interface FoldMethods<T extends object, R> {
boolean(b: boolean): R;
single(f: number): R;
double(f: number): R;
integer(i: number): R;
string(s: string): R;
bytes(b: Bytes): R;
symbol(s: symbol): R;
record(r: Record<any, any, T>, k: Fold<T, R>): R;
array(a: Array<Value<T>>, k: Fold<T, R>): R;
set(s: Set<T>, k: Fold<T, R>): R;
dictionary(d: Dictionary<Value<T>, T>, k: Fold<T, R>): R;
annotated(a: Annotated<T>, k: Fold<T, R>): R;
pointer(t: T, k: Fold<T, R>): R;
}
export abstract class ValueFold<T extends object, R extends object = T> implements FoldMethods<T, Value<R>> {
boolean(b: boolean): Value<R> {
return b;
}
single(f: number): Value<R> {
return Single(f);
}
double(f: number): Value<R> {
return Double(f);
}
integer(i: number): Value<R> {
return i;
}
string(s: string): Value<R> {
return s;
}
bytes(b: Bytes): Value<R> {
return b;
}
symbol(s: symbol): Value<R> {
return s;
}
record(r: Record<any, any, T>, k: Fold<T, Value<R>>): Value<R> {
return Record(k(r.label), r.map(k));
}
array(a: Value<T>[], k: Fold<T, Value<R>>): Value<R> {
return a.map(k);
}
set(s: Set<T>, k: Fold<T, Value<R>>): Value<R> {
return s.map(k);
}
dictionary(d: Dictionary<Value<T>, T>, k: Fold<T, Value<R>>): Value<R> {
return d.mapEntries(([key, value]) => [k(key), k(value)]);
}
annotated(a: Annotated<T>, k: Fold<T, Value<R>>): Value<R> {
return annotate(k(a.item), ...a.annotations.map(k));
}
abstract pointer(t: T, k: Fold<T, Value<R>>): Value<R>;
}
export class IdentityFold<T extends object> extends ValueFold<T, T> {
pointer(t: T, _k: Fold<T, Value<T>>): Value<T> {
return t;
}
}
export class MapFold<T extends object, R extends object> extends ValueFold<T, R> {
readonly f: (t: T) => R;
constructor(f: (t: T) => R) {
super();
this.f = f;
}
pointer(t: T, _k: Fold<T, Value<R>>): Value<R> {
return this.f(t);
}
}
export const IDENTITY_FOLD = new IdentityFold();
export function fold<T extends object, R>(v: Value<T>, o: FoldMethods<T, R>): R {
const walk = (v: Value<T>): R => {
switch (typeof v) {
case 'boolean':
return o.boolean(v);
case 'number':
if (!Number.isInteger(v)) {
// TODO: Is this convenience warranted?
return o.double(v);
} else {
return o.integer(v);
}
case 'string':
return o.string(v);
case 'symbol':
return o.symbol(v);
case 'object':
if (Record.isRecord<T>(v)) {
return o.record(v, walk);
} else if (Array.isArray(v)) {
return o.array(v, walk);
} else if (Set.isSet<T>(v)) {
return o.set(v, walk);
} else if (Dictionary.isDictionary<Value<T>, T>(v)) {
return o.dictionary(v, walk);
} else if (Annotated.isAnnotated<T>(v)) {
return o.annotated(v, walk);
} else if (Bytes.isBytes(v)) {
return o.bytes(v);
} else if (Float.isSingle(v)) {
return o.single(v.value);
} else if (Float.isDouble(v)) {
return o.double(v.value);
} else {
/* fall through */
}
default:
return o.pointer(v, walk);
}
};
return walk(v);
}
export function mapPointers<T extends object, R extends object>(
v: Value<T>,
f: (t: T) => R,
): Value<R>
{
return fold(v, new MapFold(f));
}