import { is } from "./is"; import { DefaultPointer, Value } from "./values"; export type Tuple = Array | [T]; export type Record, FieldsType extends Tuple>, T extends object = DefaultPointer> = FieldsType & { label: LabelType }; export type RecordGetters = { [K in string & keyof Fs]: (r: R) => Fs[K]; }; export type CtorTypes> = { [K in keyof Names]: Fs[keyof Fs & Names[K]] } & any[]; export interface RecordConstructor, Fs, Names extends Tuple, T extends object = DefaultPointer> { (...fields: CtorTypes): Record, T>; constructorInfo: RecordConstructorInfo; isClassOf(v: any): v is Record, T>; _: RecordGetters, T>>; }; export interface RecordConstructorInfo, T extends object = DefaultPointer> { label: L; arity: number; } export function Record, FieldsType extends Tuple>, T extends object = DefaultPointer>( label: L, fields: FieldsType): Record { (fields as any).label = label; return fields as Record; } export namespace Record { export function isRecord, FieldsType extends Tuple>, T extends object = DefaultPointer>(x: any): x is Record { return Array.isArray(x) && 'label' in x; } export function fallbackToString (_f: Value): string { return ''; } export function constructorInfo, FieldsType extends Tuple>, T extends object = DefaultPointer>( r: Record): RecordConstructorInfo { return { label: r.label, arity: r.length }; } export function isClassOf, FieldsType extends Tuple>, T extends object = DefaultPointer>( ci: RecordConstructorInfo, v: any): v is Record { return (Record.isRecord(v)) && is(ci.label, v.label) && (ci.arity === v.length); } export function makeConstructor() : (, Names extends Tuple>(label: L, fieldNames: Names) => RecordConstructor) { return , Names extends Tuple>(label: L, fieldNames: Names) => { const ctor: RecordConstructor = ((...fields: CtorTypes) => Record(label, fields)) as RecordConstructor; const constructorInfo = { label, arity: fieldNames.length }; ctor.constructorInfo = constructorInfo; ctor.isClassOf = (v: any): v is Record, T> => Record.isClassOf(constructorInfo, v); (ctor as any)._ = {}; fieldNames.forEach((name, i) => (ctor._ as any)[name] = (r: Record, T>) => r[i]); return ctor; }; } } Array.prototype.asPreservesText = function (): string { if ('label' in (this as any)) { const r = this as Record, DefaultPointer>; return '<' + r.label.asPreservesText() + (r.length > 0 ? ' ': '') + r.map(f => { try { return f.asPreservesText(); } catch (e) { return Record.fallbackToString(f); } }).join(' ') + '>'; } else { return '[' + this.map(i => i.asPreservesText()).join(', ') + ']'; } };