2021-01-07 16:41:46 +00:00
|
|
|
// Preserves Values.
|
|
|
|
|
2021-02-17 15:52:01 +00:00
|
|
|
import { AsPreserve } from './symbols';
|
|
|
|
import { Bytes } from './bytes';
|
|
|
|
import { DoubleFloat, SingleFloat } from './float';
|
2021-02-25 22:16:05 +00:00
|
|
|
import { Record, Tuple } from './record';
|
2021-02-17 15:52:01 +00:00
|
|
|
import { Annotated } from './annotated';
|
|
|
|
import { Set, Dictionary } from './dictionary';
|
|
|
|
|
|
|
|
export * from './bytes';
|
|
|
|
export * from './float';
|
|
|
|
export * from './record';
|
|
|
|
export * from './annotated';
|
|
|
|
export * from './dictionary';
|
2021-01-07 16:41:46 +00:00
|
|
|
|
2021-02-25 18:37:22 +00:00
|
|
|
export type DefaultPointer = object;
|
2021-01-29 14:35:07 +00:00
|
|
|
|
2021-03-01 08:19:32 +00:00
|
|
|
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> =
|
|
|
|
| (Array<Value<T>> | [Value<T>]) & { label: Value<T> }
|
|
|
|
// ^ expanded from definition of Record<> in record.ts,
|
|
|
|
// because if we use Record<Value<T>, Tuple<Value<T>>, T>,
|
|
|
|
// TypeScript currently complains about circular use of Value<T>,
|
|
|
|
// and if we use Record<any, any, T>, it accepts it but collapses
|
|
|
|
// Value<T> to any.
|
|
|
|
| Array<Value<T>>
|
|
|
|
| Set<T>
|
|
|
|
| Dictionary<Value<T>, T>;
|
2021-01-07 16:41:46 +00:00
|
|
|
|
2021-01-29 14:35:07 +00:00
|
|
|
export function fromJS<T extends object = DefaultPointer>(x: any): Value<T> {
|
2021-01-07 16:41:46 +00:00
|
|
|
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':
|
2021-01-29 11:03:28 +00:00
|
|
|
case 'bigint':
|
2021-01-07 16:41:46 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
case 'object':
|
|
|
|
if (x === null) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (typeof x[AsPreserve] === 'function') {
|
|
|
|
return x[AsPreserve]();
|
|
|
|
}
|
2021-02-25 22:16:05 +00:00
|
|
|
if (Record.isRecord<Value<T>, Tuple<Value<T>>, T>(x)) {
|
2021-01-07 16:41:46 +00:00
|
|
|
return x;
|
|
|
|
}
|
|
|
|
if (Array.isArray(x)) {
|
2021-02-25 18:37:22 +00:00
|
|
|
return x.map<Value<T>>(fromJS);
|
2021-01-07 16:41:46 +00:00
|
|
|
}
|
|
|
|
if (ArrayBuffer.isView(x) || x instanceof ArrayBuffer) {
|
|
|
|
return Bytes.from(x);
|
|
|
|
}
|
2021-01-29 11:03:28 +00:00
|
|
|
// Just... assume it's a T.
|
|
|
|
return (x as T);
|
|
|
|
|
|
|
|
default:
|
|
|
|
break;
|
2021-01-07 16:41:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
throw new TypeError("Cannot represent JavaScript value as Preserves: " + x);
|
|
|
|
}
|
|
|
|
|
2021-02-17 15:52:01 +00:00
|
|
|
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)) {
|
2021-02-25 18:37:22 +00:00
|
|
|
const isRecord = 'label' in a;
|
|
|
|
if (isRecord !== 'label' in b) return false;
|
|
|
|
if (isRecord && !is((a as any).label, (b as any).label)) return false;
|
2021-02-17 15:52:01 +00:00
|
|
|
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;
|
2021-01-07 16:41:46 +00:00
|
|
|
}
|
2021-01-11 15:54:52 +00:00
|
|
|
}
|
2021-02-17 15:52:01 +00:00
|
|
|
return false;
|
2021-01-07 16:41:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
declare global {
|
2021-01-29 11:03:28 +00:00
|
|
|
interface Object { asPreservesText(): string; }
|
2021-01-07 16:41:46 +00:00
|
|
|
}
|
|
|
|
|
2021-01-29 11:03:28 +00:00
|
|
|
Object.defineProperty(Object.prototype, 'asPreservesText', {
|
|
|
|
enumerable: false,
|
|
|
|
writable: true,
|
|
|
|
value: function(): string { return '#!' + JSON.stringify(this); }
|
|
|
|
});
|
|
|
|
|
2021-01-07 16:41:46 +00:00
|
|
|
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
|
2021-01-15 13:01:14 +00:00
|
|
|
return this.description ?? '||';
|
2021-01-07 16:41:46 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
Array.prototype.asPreservesText = function (): string {
|
2021-02-25 18:37:22 +00:00
|
|
|
if ('label' in (this as any)) {
|
2021-02-25 22:16:05 +00:00
|
|
|
const r = this as Record<Value, Tuple<Value>, DefaultPointer>;
|
2021-02-25 18:37:22 +00:00
|
|
|
return r.label.asPreservesText() +
|
|
|
|
'(' + r.map(f => {
|
|
|
|
try {
|
|
|
|
return f.asPreservesText();
|
|
|
|
} catch (e) {
|
|
|
|
return Record.fallbackToString(f);
|
|
|
|
}
|
|
|
|
}).join(', ') + ')';
|
|
|
|
} else {
|
|
|
|
return '[' + this.map(i => i.asPreservesText()).join(', ') + ']';
|
|
|
|
}
|
2021-01-07 16:41:46 +00:00
|
|
|
};
|