Fix *almost* all cyclic dependencies in js impl
This commit is contained in:
parent
c8c027f762
commit
6d2120989b
|
@ -1,11 +1,8 @@
|
|||
import { Encoder } from "./codec";
|
||||
import { Tag } from "./constants";
|
||||
import { AsPreserve, PreserveOn } from "./symbols";
|
||||
import { DefaultPointer, is, Value } from "./values";
|
||||
import { Record, Tuple } from './record';
|
||||
import { Dictionary, Set } from './dictionary';
|
||||
|
||||
export const IsPreservesAnnotated = Symbol.for('IsPreservesAnnotated');
|
||||
import { DefaultPointer, Value } from "./values";
|
||||
import { is, isAnnotated, IsPreservesAnnotated } from './is';
|
||||
|
||||
export class Annotated<T extends object = DefaultPointer> {
|
||||
readonly annotations: Array<Value<T>>;
|
||||
|
@ -52,43 +49,10 @@ export class Annotated<T extends object = DefaultPointer> {
|
|||
}
|
||||
|
||||
static isAnnotated<T extends object = DefaultPointer>(x: any): x is Annotated<T> {
|
||||
return !!x?.[IsPreservesAnnotated];
|
||||
return isAnnotated(x);
|
||||
}
|
||||
}
|
||||
|
||||
export function unannotate<T extends object = DefaultPointer>(v: Value<T>): Value<T> {
|
||||
return Annotated.isAnnotated<T>(v) ? v.item : v;
|
||||
}
|
||||
|
||||
export function peel<T extends object = DefaultPointer>(v: Value<T>): Value<T> {
|
||||
return strip(v, 1);
|
||||
}
|
||||
|
||||
export function strip<T extends object = DefaultPointer>(v: Value<T>, depth: number = Infinity): Value<T> {
|
||||
function step(v: Value<T>, depth: number): Value<T> {
|
||||
if (depth === 0) return v;
|
||||
if (!Annotated.isAnnotated<T>(v)) return v;
|
||||
|
||||
const nextDepth = depth - 1;
|
||||
function walk(v: Value<T>): Value<T> { return step(v, nextDepth); }
|
||||
|
||||
if (Record.isRecord<Value<T>, Tuple<Value<T>>, T>(v.item)) {
|
||||
return Record(step(v.item.label, depth), v.item.map(walk));
|
||||
} else if (Array.isArray(v.item)) {
|
||||
return (v.item as Value<T>[]).map(walk);
|
||||
} else if (Set.isSet<T>(v.item)) {
|
||||
return v.item.map(walk);
|
||||
} else if (Dictionary.isDictionary<Value<T>, T>(v.item)) {
|
||||
return v.item.mapEntries((e) => [walk(e[0]), walk(e[1])]);
|
||||
} else if (Annotated.isAnnotated(v.item)) {
|
||||
throw new Error("Improper annotation structure");
|
||||
} else {
|
||||
return v.item;
|
||||
}
|
||||
}
|
||||
return step(v, depth);
|
||||
}
|
||||
|
||||
export function annotate<T extends object = DefaultPointer>(v0: Value<T>, ...anns: Value<T>[]): Annotated<T> {
|
||||
const v = Annotated.isAnnotated<T>(v0) ? v0 : new Annotated(v0);
|
||||
anns.forEach((a) => v.annotations.push(a));
|
||||
|
|
|
@ -1,16 +1,13 @@
|
|||
// Preserves Binary codec.
|
||||
|
||||
import {
|
||||
underlying,
|
||||
Annotated,
|
||||
Dictionary, Set, Bytes, Record, SingleFloat, DoubleFloat,
|
||||
BytesLike,
|
||||
Value,
|
||||
Tuple,
|
||||
} from './values';
|
||||
import { Value } from './values';
|
||||
import { Tag } from './constants';
|
||||
|
||||
import { PreserveOn } from './symbols';
|
||||
import { Bytes, BytesLike, underlying } from './bytes';
|
||||
import { Annotated } from './annotated';
|
||||
import { Set, Dictionary } from './dictionary';
|
||||
import { DoubleFloat, SingleFloat } from './float';
|
||||
import { Record, Tuple } from './record';
|
||||
|
||||
export type ErrorType = 'DecodeError' | 'EncodeError' | 'ShortPacket';
|
||||
export const ErrorType = Symbol.for('ErrorType');
|
||||
|
|
|
@ -1,10 +1,13 @@
|
|||
import { canonicalEncode, canonicalString, Encoder } from "./codec";
|
||||
import type { Encoder } from "./codec";
|
||||
|
||||
import { canonicalEncode, canonicalString } from "./codec";
|
||||
import { Tag } from "./constants";
|
||||
import { FlexMap, FlexSet, _iterMap } from "./flex";
|
||||
import { PreserveOn } from "./symbols";
|
||||
import { stringify } from "./text";
|
||||
import { DefaultPointer, fromJS, Value } from "./values";
|
||||
import { DefaultPointer, Value } from "./values";
|
||||
import { Bytes } from './bytes';
|
||||
import { fromJS } from "./fromjs";
|
||||
|
||||
export type DictionaryType = 'Dictionary' | 'Set';
|
||||
export const DictionaryType = Symbol.for('DictionaryType');
|
||||
|
|
|
@ -1,4 +1,9 @@
|
|||
import { Bytes, Value, Record, Set, Dictionary, Single, Double, Annotated, annotate, Float, Tuple } from "./values";
|
||||
import { Record, Tuple } from "./record";
|
||||
import { Bytes } from "./bytes";
|
||||
import { Value } from "./values";
|
||||
import { Set, Dictionary } from "./dictionary";
|
||||
import { annotate, Annotated } from "./annotated";
|
||||
import { Double, Float, Single } from "./float";
|
||||
|
||||
export type Fold<T extends object, R = Value<T>> = (v: Value<T>) => R;
|
||||
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
import { Bytes } from "./bytes";
|
||||
import { Record, Tuple } from "./record";
|
||||
import { AsPreserve } from "./symbols";
|
||||
import { DefaultPointer, Value } from "./values";
|
||||
|
||||
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<Value<T>, Tuple<Value<T>>, T>(x)) {
|
||||
return x;
|
||||
}
|
||||
if (Array.isArray(x)) {
|
||||
return x.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);
|
||||
}
|
|
@ -1,7 +1,15 @@
|
|||
export * from './flex';
|
||||
export * from './symbols';
|
||||
export * from './annotated';
|
||||
export * from './bytes';
|
||||
export * from './codec';
|
||||
export * from './values';
|
||||
export * from './text';
|
||||
export * from './dictionary';
|
||||
export * from './flex';
|
||||
export * from './float';
|
||||
export * from './fold';
|
||||
export * from './fromjs';
|
||||
export * from './is';
|
||||
export * from './record';
|
||||
export * from './strip';
|
||||
export * from './symbols';
|
||||
export * from './text';
|
||||
export * from './values';
|
||||
export * as Constants from './constants';
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
import type { DefaultPointer } from "./values.js";
|
||||
import type { Annotated } from "./annotated.js";
|
||||
|
||||
export const IsPreservesAnnotated = Symbol.for('IsPreservesAnnotated');
|
||||
|
||||
export function isAnnotated<T extends object = DefaultPointer>(x: any): x is Annotated<T>
|
||||
{
|
||||
return !!x?.[IsPreservesAnnotated];
|
||||
}
|
||||
|
||||
export function is(a: any, b: any): boolean {
|
||||
if (isAnnotated(a)) a = a.item;
|
||||
if (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)) {
|
||||
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;
|
||||
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;
|
||||
}
|
|
@ -1,7 +1,11 @@
|
|||
// Patching to support node.js extensions.
|
||||
|
||||
import { Annotated } from './annotated';
|
||||
import { Bytes } from './bytes';
|
||||
import { Set, Dictionary } from './dictionary';
|
||||
import { Record } from './record';
|
||||
|
||||
import * as util from 'util';
|
||||
import { Record, Bytes, Annotated, Set, Dictionary } from './values';
|
||||
|
||||
[Bytes, Annotated, Set, Dictionary].forEach((C) => {
|
||||
(C as any).prototype[util.inspect.custom] =
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { DefaultPointer, is, Value } from "./values";
|
||||
import { is } from "./is";
|
||||
import { DefaultPointer, Value } from "./values";
|
||||
|
||||
export type Tuple<T> = Array<T> | [T];
|
||||
|
||||
|
@ -69,3 +70,19 @@ export namespace Record {
|
|||
};
|
||||
}
|
||||
}
|
||||
|
||||
Array.prototype.asPreservesText = function (): string {
|
||||
if ('label' in (this as any)) {
|
||||
const r = this as Record<Value, Tuple<Value>, DefaultPointer>;
|
||||
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(', ') + ']';
|
||||
}
|
||||
};
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
import { DefaultPointer, Value } from "./values";
|
||||
import { Annotated } from "./annotated";
|
||||
import { Record, Tuple } from "./record";
|
||||
import { Set, Dictionary } from "./dictionary";
|
||||
|
||||
export function unannotate<T extends object = DefaultPointer>(v: Value<T>): Value<T> {
|
||||
return Annotated.isAnnotated<T>(v) ? v.item : v;
|
||||
}
|
||||
|
||||
export function peel<T extends object = DefaultPointer>(v: Value<T>): Value<T> {
|
||||
return strip(v, 1);
|
||||
}
|
||||
|
||||
export function strip<T extends object = DefaultPointer>(
|
||||
v: Value<T>,
|
||||
depth: number = Infinity): Value<T>
|
||||
{
|
||||
function step(v: Value<T>, depth: number): Value<T> {
|
||||
if (depth === 0) return v;
|
||||
if (!Annotated.isAnnotated<T>(v)) return v;
|
||||
|
||||
const nextDepth = depth - 1;
|
||||
function walk(v: Value<T>): Value<T> { return step(v, nextDepth); }
|
||||
|
||||
if (Record.isRecord<Value<T>, Tuple<Value<T>>, T>(v.item)) {
|
||||
return Record(step(v.item.label, depth), v.item.map(walk));
|
||||
} else if (Array.isArray(v.item)) {
|
||||
return (v.item as Value<T>[]).map(walk);
|
||||
} else if (Set.isSet<T>(v.item)) {
|
||||
return v.item.map(walk);
|
||||
} else if (Dictionary.isDictionary<Value<T>, T>(v.item)) {
|
||||
return v.item.mapEntries((e) => [walk(e[0]), walk(e[1])]);
|
||||
} else if (Annotated.isAnnotated(v.item)) {
|
||||
throw new Error("Improper annotation structure");
|
||||
} else {
|
||||
return v.item;
|
||||
}
|
||||
}
|
||||
return step(v, depth);
|
||||
}
|
|
@ -1,17 +1,9 @@
|
|||
// Preserves Values.
|
||||
|
||||
import { AsPreserve } from './symbols';
|
||||
import { Bytes } from './bytes';
|
||||
import { DoubleFloat, SingleFloat } from './float';
|
||||
import { Record, Tuple } from './record';
|
||||
import { Annotated } from './annotated';
|
||||
import { Set, Dictionary } from './dictionary';
|
||||
|
||||
export * from './bytes';
|
||||
export * from './float';
|
||||
export * from './record';
|
||||
export * from './annotated';
|
||||
export * from './dictionary';
|
||||
import type { Bytes } from './bytes';
|
||||
import type { DoubleFloat, SingleFloat } from './float';
|
||||
import type { Annotated } from './annotated';
|
||||
import type { Set, Dictionary } from './dictionary';
|
||||
|
||||
export type DefaultPointer = object;
|
||||
|
||||
|
@ -39,70 +31,6 @@ export type Compound<T extends object = DefaultPointer> =
|
|||
| 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<Value<T>, Tuple<Value<T>>, T>(x)) {
|
||||
return x;
|
||||
}
|
||||
if (Array.isArray(x)) {
|
||||
return x.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)) {
|
||||
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;
|
||||
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; }
|
||||
}
|
||||
|
@ -129,19 +57,3 @@ Symbol.prototype.asPreservesText = function (): string {
|
|||
// TODO: escaping
|
||||
return this.description ?? '||';
|
||||
};
|
||||
|
||||
Array.prototype.asPreservesText = function (): string {
|
||||
if ('label' in (this as any)) {
|
||||
const r = this as Record<Value, Tuple<Value>, DefaultPointer>;
|
||||
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(', ') + ']';
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Bytes, fromJS } from '../src/values';
|
||||
import { Bytes, fromJS } from '../src/index';
|
||||
import './test-utils';
|
||||
|
||||
describe('immutable byte arrays', () => {
|
||||
|
|
Loading…
Reference in New Issue