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 { Embedded } from "./embedded"; export type Fold> = (v: Value) => R; export interface FoldMethods { 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, Tuple>, T>, k: Fold): R; array(a: Array>, k: Fold): R; set(s: Set, k: Fold): R; dictionary(d: Dictionary, k: Fold): R; annotated(a: Annotated, k: Fold): R; embedded(t: Embedded, k: Fold): R; } export abstract class ValueFold implements FoldMethods> { boolean(b: boolean): Value { return b; } single(f: number): Value { return Single(f); } double(f: number): Value { return Double(f); } integer(i: number): Value { return i; } string(s: string): Value { return s; } bytes(b: Bytes): Value { return b; } symbol(s: symbol): Value { return s; } record(r: Record, Tuple>, T>, k: Fold>): Value { return Record(k(r.label), r.map(k)); } array(a: Value[], k: Fold>): Value { return a.map(k); } set(s: Set, k: Fold>): Value { return s.map(k); } dictionary(d: Dictionary, k: Fold>): Value { return d.mapEntries(([key, value]) => [k(key), k(value)]); } annotated(a: Annotated, k: Fold>): Value { return annotate(k(a.item), ...a.annotations.map(k)); } abstract embedded(t: Embedded, k: Fold>): Value; } export class IdentityFold extends ValueFold { embedded(t: Embedded, _k: Fold>): Value { return t; } } export class MapFold extends ValueFold { readonly f: (t: T) => Value; constructor(f: (t: T) => Value) { super(); this.f = f; } embedded(t: Embedded, _k: Fold>): Value { return this.f(t.embeddedValue); } } export const IDENTITY_FOLD = new IdentityFold(); export function fold(v: Value, o: FoldMethods): R { const walk = (v: Value): 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, Tuple>, T>(v)) { return o.record(v, walk); } else if (Array.isArray(v)) { return o.array(v, walk); } else if (Set.isSet(v)) { return o.set(v, walk); } else if (Dictionary.isDictionary(v)) { return o.dictionary(v, walk); } else if (Annotated.isAnnotated(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.embedded(v, walk); } default: ((_v: never): never => { throw new Error("Internal error"); })(v); } }; return walk(v); } export function mapEmbeddeds( v: Value, f: (t: T) => Value, ): Value { return fold(v, new MapFold(f)); }