preserves/implementations/javascript/packages/core/src/record.ts

102 lines
4.1 KiB
TypeScript
Raw Normal View History

import { GenericEmbedded } from "./embedded";
import { is } from "./is";
2021-03-17 09:21:48 +00:00
import { Value } from "./values";
2021-02-25 18:37:22 +00:00
export type Tuple<T> = Array<T> | [T];
export type Record<LabelType extends Value<T>, FieldsType extends Tuple<Value<T>>, T = GenericEmbedded>
2021-02-25 22:16:05 +00:00
= FieldsType & { label: LabelType };
2021-02-25 22:16:05 +00:00
export type RecordGetters<Fs, R> = {
[K in string & keyof Fs]: (r: R) => Fs[K];
2021-02-25 18:37:22 +00:00
};
2021-02-25 22:16:05 +00:00
export type CtorTypes<Fs, Names extends Tuple<keyof Fs>> =
2021-02-25 18:37:22 +00:00
{ [K in keyof Names]: Fs[keyof Fs & Names[K]] } & any[];
export interface RecordConstructor<L extends Value<T>, Fs, Names extends Tuple<keyof Fs>, T = GenericEmbedded> {
2021-02-25 22:16:05 +00:00
(...fields: CtorTypes<Fs, Names>): Record<L, CtorTypes<Fs, Names>, T>;
2021-02-25 18:37:22 +00:00
constructorInfo: RecordConstructorInfo<L, T>;
2021-02-25 22:16:05 +00:00
isClassOf(v: any): v is Record<L, CtorTypes<Fs, Names>, T>;
_: RecordGetters<Fs, Record<L, CtorTypes<Fs, Names>, T>>;
2021-02-25 18:37:22 +00:00
};
export interface RecordConstructorInfo<L extends Value<T>, T = GenericEmbedded> {
2021-02-25 18:37:22 +00:00
label: L;
arity: number;
}
export type InferredRecordType<L, FieldsType extends Tuple<any>> =
L extends symbol ? (FieldsType extends Tuple<Value<infer T>>
? (Exclude<T, never> extends symbol ? Record<L, FieldsType, never> : Record<L, FieldsType, T>)
: (FieldsType extends Tuple<Value<never>>
? Record<L, FieldsType, never>
: "TYPE_ERROR_cannotInferFieldsType" & [never])) :
L extends Value<infer T> ? (FieldsType extends Tuple<Value<T>>
? Record<L, FieldsType, T>
: "TYPE_ERROR_cannotMatchFieldsTypeToLabelType" & [never]) :
"TYPE_ERROR_cannotInferEmbeddedType" & [never];
export function Record<L, FieldsType extends Tuple<any>>(
label: L,
fields: FieldsType): InferredRecordType<L, FieldsType>
2021-02-25 18:37:22 +00:00
{
(fields as any).label = label;
return fields as any;
2021-02-25 18:37:22 +00:00
}
2021-02-25 18:37:22 +00:00
export namespace Record {
export function isRecord<L extends Value<T>, FieldsType extends Tuple<Value<T>>, T = GenericEmbedded>(x: any): x is Record<L, FieldsType, T> {
2021-02-25 18:37:22 +00:00
return Array.isArray(x) && 'label' in x;
}
2021-02-25 18:37:22 +00:00
export function fallbackToString (_f: Value<any>): string {
return '<unprintable_preserves_field_value>';
}
export function constructorInfo<L extends Value<T>, FieldsType extends Tuple<Value<T>>, T = GenericEmbedded>(
2021-02-25 22:16:05 +00:00
r: Record<L, FieldsType, T>): RecordConstructorInfo<L, T>
2021-02-25 18:37:22 +00:00
{
return { label: r.label, arity: r.length };
}
export function isClassOf<L extends Value<T>, FieldsType extends Tuple<Value<T>>, T = GenericEmbedded>(
2021-02-25 22:16:05 +00:00
ci: RecordConstructorInfo<L, T>, v: any): v is Record<L, FieldsType, T>
2021-02-25 18:37:22 +00:00
{
return (Record.isRecord(v)) && is(ci.label, v.label) && (ci.arity === v.length);
}
export function makeConstructor<Fs, T = GenericEmbedded>()
2021-02-25 18:37:22 +00:00
: (<L extends Value<T>, Names extends Tuple<keyof Fs>>(label: L, fieldNames: Names) =>
RecordConstructor<L, Fs, Names, T>)
{
return <L extends Value<T>, Names extends Tuple<keyof Fs>>(label: L, fieldNames: Names) => {
const ctor: RecordConstructor<L, Fs, Names, T> =
2021-02-25 22:16:05 +00:00
((...fields: CtorTypes<Fs, Names>) =>
Record(label, fields)) as unknown as RecordConstructor<L, Fs, Names, T>;
2021-02-25 18:37:22 +00:00
const constructorInfo = { label, arity: fieldNames.length };
ctor.constructorInfo = constructorInfo;
ctor.isClassOf = (v: any): v is Record<L, CtorTypes<Fs, Names>, T> => Record.isClassOf<L, CtorTypes<Fs, Names>, T>(constructorInfo, v);
2021-02-25 18:37:22 +00:00
(ctor as any)._ = {};
2021-02-25 22:16:05 +00:00
fieldNames.forEach((name, i) => (ctor._ as any)[name] = (r: Record<L, CtorTypes<Fs, Names>, T>) => r[i]);
2021-02-25 18:37:22 +00:00
return ctor;
};
}
}
Array.prototype.asPreservesText = function (): string {
if ('label' in (this as any)) {
const r = this as Record<Value, Tuple<Value>, 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(', ') + ']';
}
};