115 lines
3.5 KiB
TypeScript
115 lines
3.5 KiB
TypeScript
// Preserves Values.
|
|
|
|
import { AsPreserve } from './symbols';
|
|
import { Bytes } from './bytes';
|
|
import { DoubleFloat, SingleFloat } from './float';
|
|
import { Record } from './record';
|
|
import { Annotated } from './annotated';
|
|
import { Set, Dictionary } from './dictionary';
|
|
import { Tuple } from './tuple';
|
|
|
|
export * from './bytes';
|
|
export * from './float';
|
|
export * from './record';
|
|
export * from './annotated';
|
|
export * from './dictionary';
|
|
export * from './tuple';
|
|
|
|
export type DefaultPointer = object
|
|
|
|
export type Value<T extends object = DefaultPointer> = Atom | Compound<T> | T | Annotated<T>;
|
|
export type Atom = boolean | SingleFloat | DoubleFloat | number | string | Bytes | symbol;
|
|
export type Compound<T extends object = DefaultPointer> = Record<any, Tuple<any>, T> | Array<Value<T>> | Set<T> | Dictionary<Value<T>, T>;
|
|
|
|
export function fromJS<T extends object = DefaultPointer>(x: any): Value<T> {
|
|
switch (typeof x) {
|
|
case 'number':
|
|
if (!Number.isInteger(x)) {
|
|
// We require that clients be explicit about integer vs. non-integer types.
|
|
throw new TypeError("Refusing to autoconvert non-integer number to Single or Double");
|
|
}
|
|
// FALL THROUGH
|
|
case 'string':
|
|
case 'symbol':
|
|
case 'boolean':
|
|
return x;
|
|
|
|
case 'undefined':
|
|
case 'function':
|
|
case 'bigint':
|
|
break;
|
|
|
|
case 'object':
|
|
if (x === null) {
|
|
break;
|
|
}
|
|
if (typeof x[AsPreserve] === 'function') {
|
|
return x[AsPreserve]();
|
|
}
|
|
if (Record.isRecord<T>(x)) {
|
|
return x;
|
|
}
|
|
if (Array.isArray(x)) {
|
|
return (x as Array<Value<T>>).map<Value<T>>(fromJS);
|
|
}
|
|
if (ArrayBuffer.isView(x) || x instanceof ArrayBuffer) {
|
|
return Bytes.from(x);
|
|
}
|
|
// Just... assume it's a T.
|
|
return (x as T);
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
throw new TypeError("Cannot represent JavaScript value as Preserves: " + x);
|
|
}
|
|
|
|
export function is(a: any, b: any): boolean {
|
|
if (Annotated.isAnnotated(a)) a = a.item;
|
|
if (Annotated.isAnnotated(b)) b = b.item;
|
|
if (Object.is(a, b)) return true;
|
|
if (typeof a !== typeof b) return false;
|
|
if (typeof a === 'object') {
|
|
if (a === null || b === null) return false;
|
|
if ('equals' in a && typeof a.equals === 'function') return a.equals(b, is);
|
|
if (Array.isArray(a) && Array.isArray(b)) {
|
|
if (a.length !== b.length) return false;
|
|
for (let i = 0; i < a.length; i++) if (!is(a[i], b[i])) return false;
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
declare global {
|
|
interface Object { asPreservesText(): string; }
|
|
}
|
|
|
|
Object.defineProperty(Object.prototype, 'asPreservesText', {
|
|
enumerable: false,
|
|
writable: true,
|
|
value: function(): string { return '#!' + JSON.stringify(this); }
|
|
});
|
|
|
|
Boolean.prototype.asPreservesText = function (): string {
|
|
return this ? '#t' : '#f';
|
|
};
|
|
|
|
Number.prototype.asPreservesText = function (): string {
|
|
return '' + this;
|
|
};
|
|
|
|
String.prototype.asPreservesText = function (): string {
|
|
return JSON.stringify(this);
|
|
};
|
|
|
|
Symbol.prototype.asPreservesText = function (): string {
|
|
// TODO: escaping
|
|
return this.description ?? '||';
|
|
};
|
|
|
|
Array.prototype.asPreservesText = function (): string {
|
|
return '[' + this.map((i: Value<any>) => i.asPreservesText()).join(', ') + ']';
|
|
};
|