141 lines
4.2 KiB
TypeScript
141 lines
4.2 KiB
TypeScript
import { Record, Tuple } from "./record";
|
|
import { Bytes } from "./bytes";
|
|
import { Value } from "./values";
|
|
import { Set, Dictionary } from "./dictionary";
|
|
import { annotate, Annotated } from "./annotated";
|
|
import { Double, Float, Single } from "./float";
|
|
import { Pointer } from "./pointer";
|
|
|
|
export type Fold<T, R = Value<T>> = (v: Value<T>) => R;
|
|
|
|
export interface FoldMethods<T, 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<Value<T>, Tuple<Value<T>>, 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<T>, k: Fold<T, R>): R;
|
|
|
|
annotated(a: Annotated<T>, k: Fold<T, R>): R;
|
|
|
|
pointer(t: Pointer<T>, k: Fold<T, R>): R;
|
|
}
|
|
|
|
export abstract class ValueFold<T, R = 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<Value<T>, Tuple<Value<T>>, 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<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: Pointer<T>, k: Fold<T, Value<R>>): Value<R>;
|
|
}
|
|
|
|
export class IdentityFold<T> extends ValueFold<T, T> {
|
|
pointer(t: Pointer<T>, _k: Fold<T, Value<T>>): Value<T> {
|
|
return t;
|
|
}
|
|
}
|
|
|
|
export class MapFold<T, R> extends ValueFold<T, R> {
|
|
readonly f: (t: T) => Value<R>;
|
|
|
|
constructor(f: (t: T) => Value<R>) {
|
|
super();
|
|
this.f = f;
|
|
}
|
|
|
|
pointer(t: Pointer<T>, _k: Fold<T, Value<R>>): Value<R> {
|
|
return this.f(t.embeddedValue);
|
|
}
|
|
}
|
|
|
|
export const IDENTITY_FOLD = new IdentityFold<any>();
|
|
|
|
export function fold<T, 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<Value<T>, Tuple<Value<T>>, 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<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 {
|
|
return o.pointer(v, walk);
|
|
}
|
|
default:
|
|
((_v: never): never => { throw new Error("Internal error"); })(v);
|
|
}
|
|
};
|
|
return walk(v);
|
|
}
|
|
|
|
export function mapPointers<T, R>(
|
|
v: Value<T>,
|
|
f: (t: T) => Value<R>,
|
|
): Value<R>
|
|
{
|
|
return fold(v, new MapFold(f));
|
|
}
|