2021-03-02 21:43:10 +00:00
|
|
|
import { is } from "./is";
|
|
|
|
import { DefaultPointer, Value } from "./values";
|
2021-02-17 15:52:01 +00:00
|
|
|
|
2021-02-25 18:37:22 +00:00
|
|
|
export type Tuple<T> = Array<T> | [T];
|
2021-02-17 15:52:01 +00:00
|
|
|
|
2021-02-25 22:16:05 +00:00
|
|
|
export type Record<LabelType extends Value<T>, FieldsType extends Tuple<Value<T>>, T extends object = DefaultPointer>
|
|
|
|
= FieldsType & { label: LabelType };
|
2021-02-17 15:52:01 +00:00
|
|
|
|
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-17 15:52:01 +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[];
|
2021-02-17 15:52:01 +00:00
|
|
|
|
2021-02-25 18:37:22 +00:00
|
|
|
export interface RecordConstructor<L extends Value<T>, Fs, Names extends Tuple<keyof Fs>, T extends object = DefaultPointer> {
|
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
|
|
|
};
|
2021-02-17 15:52:01 +00:00
|
|
|
|
2021-02-25 18:37:22 +00:00
|
|
|
export interface RecordConstructorInfo<L extends Value<T>, T extends object = DefaultPointer> {
|
|
|
|
label: L;
|
|
|
|
arity: number;
|
|
|
|
}
|
2021-02-17 15:52:01 +00:00
|
|
|
|
2021-02-25 22:16:05 +00:00
|
|
|
export function Record<L extends Value<T>, FieldsType extends Tuple<Value<T>>, T extends object = DefaultPointer>(
|
|
|
|
label: L, fields: FieldsType): Record<L, FieldsType, T>
|
2021-02-25 18:37:22 +00:00
|
|
|
{
|
|
|
|
(fields as any).label = label;
|
2021-02-25 22:16:05 +00:00
|
|
|
return fields as Record<L, FieldsType, T>;
|
2021-02-25 18:37:22 +00:00
|
|
|
}
|
2021-02-17 15:52:01 +00:00
|
|
|
|
2021-02-25 18:37:22 +00:00
|
|
|
export namespace Record {
|
2021-02-25 22:16:05 +00:00
|
|
|
export function isRecord<L extends Value<T>, FieldsType extends Tuple<Value<T>>, T extends object = DefaultPointer>(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-17 15:52:01 +00:00
|
|
|
}
|
|
|
|
|
2021-02-25 18:37:22 +00:00
|
|
|
export function fallbackToString (_f: Value<any>): string {
|
|
|
|
return '<unprintable_preserves_field_value>';
|
2021-02-17 15:52:01 +00:00
|
|
|
}
|
|
|
|
|
2021-02-25 22:16:05 +00:00
|
|
|
export function constructorInfo<L extends Value<T>, FieldsType extends Tuple<Value<T>>, T extends object = DefaultPointer>(
|
|
|
|
r: Record<L, FieldsType, T>): RecordConstructorInfo<L, T>
|
2021-02-25 18:37:22 +00:00
|
|
|
{
|
|
|
|
return { label: r.label, arity: r.length };
|
2021-02-17 15:52:01 +00:00
|
|
|
}
|
|
|
|
|
2021-02-25 22:16:05 +00:00
|
|
|
export function isClassOf<L extends Value<T>, FieldsType extends Tuple<Value<T>>, T extends object = DefaultPointer>(
|
|
|
|
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);
|
2021-02-17 15:52:01 +00:00
|
|
|
}
|
|
|
|
|
2021-02-25 18:37:22 +00:00
|
|
|
export function makeConstructor<Fs, T extends object = DefaultPointer>()
|
|
|
|
: (<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>) =>
|
2021-02-25 18:37:22 +00:00
|
|
|
Record(label, fields)) as RecordConstructor<L, Fs, Names, T>;
|
|
|
|
const constructorInfo = { label, arity: fieldNames.length };
|
|
|
|
ctor.constructorInfo = constructorInfo;
|
2021-02-25 22:16:05 +00:00
|
|
|
ctor.isClassOf = (v: any): v is Record<L, CtorTypes<Fs, Names>, T> => Record.isClassOf(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;
|
2021-02-17 15:52:01 +00:00
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
2021-03-02 21:43:10 +00:00
|
|
|
|
|
|
|
Array.prototype.asPreservesText = function (): string {
|
|
|
|
if ('label' in (this as any)) {
|
|
|
|
const r = this as Record<Value, Tuple<Value>, DefaultPointer>;
|
2021-03-05 20:16:14 +00:00
|
|
|
return '<' + r.label.asPreservesText() + (r.length > 0 ? ' ': '') +
|
|
|
|
r.map(f => {
|
2021-03-02 21:43:10 +00:00
|
|
|
try {
|
|
|
|
return f.asPreservesText();
|
|
|
|
} catch (e) {
|
|
|
|
return Record.fallbackToString(f);
|
|
|
|
}
|
2021-03-05 20:16:14 +00:00
|
|
|
}).join(' ') + '>';
|
2021-03-02 21:43:10 +00:00
|
|
|
} else {
|
|
|
|
return '[' + this.map(i => i.asPreservesText()).join(', ') + ']';
|
|
|
|
}
|
|
|
|
};
|