2021-02-17 19:55:22 +00:00
|
|
|
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<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<T>, k: Fold<T, Value<R>>): Value<R> {
|
|
|
|
return new 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> {
|
2021-02-24 19:40:49 +00:00
|
|
|
readonly f: (t: T) => Value<R>;
|
2021-02-17 19:55:22 +00:00
|
|
|
|
2021-02-24 19:40:49 +00:00
|
|
|
constructor(f: (t: T) => Value<R>) {
|
2021-02-17 19:55:22 +00:00
|
|
|
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>,
|
2021-02-24 19:40:49 +00:00
|
|
|
f: (t: T) => Value<R>,
|
2021-02-17 19:55:22 +00:00
|
|
|
): Value<R>
|
|
|
|
{
|
|
|
|
return fold(v, new MapFold(f));
|
|
|
|
}
|