import { Bytes, Value, Record, Set, Dictionary, Single, Double, Annotated, annotate, Float } from "./values"; 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, k: Fold): R; array(a: Array>, k: Fold): R; set(s: Set, k: Fold): R; dictionary(d: Dictionary, T>, k: Fold): R; annotated(a: Annotated, k: Fold): R; pointer(t: T, 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, 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, T>, 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 pointer(t: T, k: Fold>): Value; } export class IdentityFold extends ValueFold { pointer(t: T, _k: Fold>): Value { return t; } } export class MapFold extends ValueFold { readonly f: (t: T) => R; constructor(f: (t: T) => R) { super(); this.f = f; } pointer(t: T, _k: Fold>): Value { return this.f(t); } } 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(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, T>(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 { /* fall through */ } default: return o.pointer(v, walk); } }; return walk(v); } export function mapPointers( v: Value, f: (t: T) => R, ): Value { return fold(v, new MapFold(f)); }