import { GenericEmbedded } from "./embedded"; import { is } from "./is"; import { Value } from "./values"; export type Tuple = Array | [T]; export type Record, FieldsType extends Tuple>, T = GenericEmbedded> = 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 = GenericEmbedded> { (...fields: CtorTypes): Record, T>; constructorInfo: RecordConstructorInfo; isClassOf(v: any): v is Record, T>; _: RecordGetters, T>>; }; export interface RecordConstructorInfo, T = GenericEmbedded> { label: L; arity: number; } export type InferredRecordType> = L extends symbol ? (FieldsType extends Tuple> ? (Exclude extends symbol ? Record : Record) : (FieldsType extends Tuple> ? Record : "TYPE_ERROR_cannotInferFieldsType" & [never])) : L extends Value ? (FieldsType extends Tuple> ? Record : "TYPE_ERROR_cannotMatchFieldsTypeToLabelType" & [never]) : "TYPE_ERROR_cannotInferEmbeddedType" & [never]; export function Record>( label: L, fields: FieldsType): InferredRecordType { (fields as any).label = label; return fields as any; } export namespace Record { export function isRecord, FieldsType extends Tuple>, T = GenericEmbedded>(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 = GenericEmbedded>( r: Record): RecordConstructorInfo { return { label: r.label, arity: r.length }; } export function isClassOf, FieldsType extends Tuple>, T = GenericEmbedded>( 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 unknown as RecordConstructor; const constructorInfo = { label, arity: fieldNames.length }; ctor.constructorInfo = constructorInfo; ctor.isClassOf = (v: any): v is Record, T> => Record.isClassOf, T>(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, GenericEmbedded>; 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(', ') + ']'; } };