Much better duck typing
This commit is contained in:
parent
10351b5369
commit
329cee7bd6
|
@ -12,27 +12,30 @@ import { Tag } from './constants';
|
||||||
import { PreserveOn } from './symbols';
|
import { PreserveOn } from './symbols';
|
||||||
|
|
||||||
export type ErrorType = 'DecodeError' | 'EncodeError' | 'ShortPacket';
|
export type ErrorType = 'DecodeError' | 'EncodeError' | 'ShortPacket';
|
||||||
|
export const ErrorType = Symbol.for('ErrorType');
|
||||||
export function isCodecError(e: any, t?: ErrorType): e is PreservesCodecError {
|
|
||||||
return typeof e === 'object' && e !== null &&
|
|
||||||
'_codecErrorType' in e &&
|
|
||||||
(!t || e._codecErrorType() === t);
|
|
||||||
}
|
|
||||||
|
|
||||||
export const isDecodeError = (e: any): e is DecodeError => isCodecError(e, 'DecodeError');
|
|
||||||
export const isEncodeError = (e: any): e is EncodeError => isCodecError(e, 'EncodeError');
|
|
||||||
export const isShortPacket = (e: any): e is ShortPacket => isCodecError(e, 'ShortPacket');
|
|
||||||
|
|
||||||
export abstract class PreservesCodecError {
|
export abstract class PreservesCodecError {
|
||||||
abstract _codecErrorType(): ErrorType;
|
abstract get [ErrorType](): ErrorType;
|
||||||
|
|
||||||
|
static isCodecError(e: any, t: ErrorType): e is PreservesCodecError {
|
||||||
|
return (e?.[ErrorType] === t);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class DecodeError extends Error {
|
export class DecodeError extends Error {
|
||||||
_codecErrorType(): ErrorType { return 'DecodeError' }
|
get [ErrorType](): ErrorType { return 'DecodeError' }
|
||||||
|
|
||||||
|
static isDecodeError(e: any): e is DecodeError {
|
||||||
|
return PreservesCodecError.isCodecError(e, 'DecodeError');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class EncodeError extends Error {
|
export class EncodeError extends Error {
|
||||||
_codecErrorType(): ErrorType { return 'EncodeError' }
|
get [ErrorType](): ErrorType { return 'EncodeError' }
|
||||||
|
|
||||||
|
static isEncodeError(e: any): e is EncodeError {
|
||||||
|
return PreservesCodecError.isCodecError(e, 'EncodeError');
|
||||||
|
}
|
||||||
|
|
||||||
readonly irritant: any;
|
readonly irritant: any;
|
||||||
|
|
||||||
|
@ -43,7 +46,11 @@ export class EncodeError extends Error {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ShortPacket extends DecodeError {
|
export class ShortPacket extends DecodeError {
|
||||||
_codecErrorType(): ErrorType { return 'ShortPacket' }
|
get [ErrorType](): ErrorType { return 'ShortPacket' }
|
||||||
|
|
||||||
|
static isShortPacket(e: any): e is ShortPacket {
|
||||||
|
return PreservesCodecError.isCodecError(e, 'ShortPacket');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DecoderOptions {
|
export interface DecoderOptions {
|
||||||
|
@ -176,7 +183,7 @@ export class Decoder {
|
||||||
try {
|
try {
|
||||||
return this.next();
|
return this.next();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e instanceof ShortPacket) {
|
if (ShortPacket.isShortPacket(e)) {
|
||||||
this.index = start;
|
this.index = start;
|
||||||
return void 0;
|
return void 0;
|
||||||
}
|
}
|
||||||
|
@ -266,7 +273,7 @@ export class Encoder {
|
||||||
this.emitbyte(Tag.SignedInteger);
|
this.emitbyte(Tag.SignedInteger);
|
||||||
this.varint(bytecount);
|
this.varint(bytecount);
|
||||||
}
|
}
|
||||||
const enc = (n, x) => {
|
const enc = (n: number, x: number) => {
|
||||||
if (n > 0) {
|
if (n > 0) {
|
||||||
enc(n - 1, x >> 8);
|
enc(n - 1, x >> 8);
|
||||||
this.emitbyte(x & 255);
|
this.emitbyte(x & 255);
|
||||||
|
@ -294,7 +301,7 @@ export class Encoder {
|
||||||
}
|
}
|
||||||
|
|
||||||
push(v: any) {
|
push(v: any) {
|
||||||
if (typeof v === 'object' && v !== null && typeof v[PreserveOn] === 'function') {
|
if (typeof v?.[PreserveOn] === 'function') {
|
||||||
v[PreserveOn](this);
|
v[PreserveOn](this);
|
||||||
}
|
}
|
||||||
else if (typeof v === 'boolean') {
|
else if (typeof v === 'boolean') {
|
||||||
|
@ -326,7 +333,7 @@ export class Encoder {
|
||||||
else if (Array.isArray(v)) {
|
else if (Array.isArray(v)) {
|
||||||
this.encodevalues(Tag.Sequence, v);
|
this.encodevalues(Tag.Sequence, v);
|
||||||
}
|
}
|
||||||
else if (typeof v === 'object' && v !== null && typeof v[Symbol.iterator] === 'function') {
|
else if (typeof v?.[Symbol.iterator] === 'function') {
|
||||||
this.encodevalues(Tag.Sequence, v as Iterable<Value>);
|
this.encodevalues(Tag.Sequence, v as Iterable<Value>);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|
|
@ -22,6 +22,20 @@ export type IdentitySet<V> = Set<V>;
|
||||||
export const IdentityMap = Map;
|
export const IdentityMap = Map;
|
||||||
export const IdentitySet = Set;
|
export const IdentitySet = Set;
|
||||||
|
|
||||||
|
export const IsMap = Symbol.for('IsMap');
|
||||||
|
export const IsSet = Symbol.for('IsSet');
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface Map<K, V> { [IsMap]: boolean; }
|
||||||
|
interface MapConstructor { isMap<K, V>(x: any): x is Map<K, V>; }
|
||||||
|
interface Set<T> { [IsSet]: boolean; }
|
||||||
|
interface SetConstructor { isSet<T>(x: any): x is Set<T>; }
|
||||||
|
}
|
||||||
|
Object.defineProperty(Map.prototype, IsMap, { get() { return true; } });
|
||||||
|
Map.isMap = <K,V> (x: any): x is Map<K, V> => !!x?.[IsMap];
|
||||||
|
Object.defineProperty(Set.prototype, IsSet, { get() { return true; } });
|
||||||
|
Set.isSet = <T> (x: any): x is Set<T> => !!x?.[IsSet];
|
||||||
|
|
||||||
export function _iterMap<S,T>(i: Iterator<S> | undefined, f : (s: S) => T): IterableIterator<T> {
|
export function _iterMap<S,T>(i: Iterator<S> | undefined, f : (s: S) => T): IterableIterator<T> {
|
||||||
if (!i) return void 0;
|
if (!i) return void 0;
|
||||||
const _f = (r: IteratorResult<S>): IteratorResult<T> => {
|
const _f = (r: IteratorResult<S>): IteratorResult<T> => {
|
||||||
|
@ -139,6 +153,10 @@ export class FlexMap<K, V> implements Map<K, V> {
|
||||||
canonicalKeys(): IterableIterator<string> {
|
canonicalKeys(): IterableIterator<string> {
|
||||||
return this.items.keys();
|
return this.items.keys();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get [IsMap](): boolean {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class FlexSet<V> implements Set<V> {
|
export class FlexSet<V> implements Set<V> {
|
||||||
|
@ -238,4 +256,8 @@ export class FlexSet<V> implements Set<V> {
|
||||||
for (let k of this) if (!other.has(k)) result.add(k);
|
for (let k of this) if (!other.has(k)) result.add(k);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get [IsSet](): boolean {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { Value } from './values';
|
import { Value } from './values';
|
||||||
|
|
||||||
export function stringify(x: any): string {
|
export function stringify(x: any): string {
|
||||||
if (typeof x === 'object' && x !== null && 'asPreservesText' in x) {
|
if (typeof x?.asPreservesText === 'function') {
|
||||||
return x.asPreservesText();
|
return x.asPreservesText();
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { PreserveOn, AsPreserve } from './symbols';
|
||||||
import { Tag } from './constants';
|
import { Tag } from './constants';
|
||||||
import { Encoder, encode } from './codec';
|
import { Encoder, encode } from './codec';
|
||||||
import { stringify } from './text';
|
import { stringify } from './text';
|
||||||
import { _iterMap, FlexMap, FlexSet, IdentityMap, IdentitySet } from './flex';
|
import { _iterMap, FlexMap, FlexSet } from './flex';
|
||||||
|
|
||||||
const textEncoder = new TextEncoder();
|
const textEncoder = new TextEncoder();
|
||||||
const textDecoder = new TextDecoder();
|
const textDecoder = new TextDecoder();
|
||||||
|
@ -13,9 +13,9 @@ export type Value = Atom | Compound | Annotated;
|
||||||
export type Atom = boolean | Single | Double | number | string | Bytes | symbol;
|
export type Atom = boolean | Single | Double | number | string | Bytes | symbol;
|
||||||
export type Compound = Record | Array<Value> | Set | Dictionary<Value>;
|
export type Compound = Record | Array<Value> | Set | Dictionary<Value>;
|
||||||
|
|
||||||
export function isRecord(x: any): x is Record {
|
export const IsPreservesRecord = Symbol.for('IsPreservesRecord');
|
||||||
return Array.isArray(x) && 'label' in x;
|
export const IsPreservesBytes = Symbol.for('IsPreservesBytes');
|
||||||
}
|
export const IsPreservesAnnotated = Symbol.for('IsPreservesAnnotated');
|
||||||
|
|
||||||
export function fromJS(x: any): Value {
|
export function fromJS(x: any): Value {
|
||||||
switch (typeof x) {
|
switch (typeof x) {
|
||||||
|
@ -41,7 +41,7 @@ export function fromJS(x: any): Value {
|
||||||
if (typeof x[AsPreserve] === 'function') {
|
if (typeof x[AsPreserve] === 'function') {
|
||||||
return x[AsPreserve]();
|
return x[AsPreserve]();
|
||||||
}
|
}
|
||||||
if (isRecord(x)) {
|
if (Record.isRecord(x)) {
|
||||||
return x;
|
return x;
|
||||||
}
|
}
|
||||||
if (Array.isArray(x)) {
|
if (Array.isArray(x)) {
|
||||||
|
@ -57,6 +57,7 @@ export function fromJS(x: any): Value {
|
||||||
}
|
}
|
||||||
|
|
||||||
export type FloatType = 'Single' | 'Double';
|
export type FloatType = 'Single' | 'Double';
|
||||||
|
export const FloatType = Symbol.for('FloatType');
|
||||||
|
|
||||||
export abstract class Float {
|
export abstract class Float {
|
||||||
readonly value: number;
|
readonly value: number;
|
||||||
|
@ -78,6 +79,14 @@ export abstract class Float {
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract asPreservesText(): string;
|
abstract asPreservesText(): string;
|
||||||
|
abstract get [FloatType](): FloatType;
|
||||||
|
|
||||||
|
static isFloat(x: any, t: FloatType): x is Float {
|
||||||
|
return (x?.[FloatType] === t);
|
||||||
|
}
|
||||||
|
|
||||||
|
static isSingle = (x: any): x is Single => Float.isFloat(x, 'Single');
|
||||||
|
static isDouble = (x: any): x is Double => Float.isFloat(x, 'Double');
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Single extends Float {
|
export class Single extends Float {
|
||||||
|
@ -92,7 +101,7 @@ export class Single extends Float {
|
||||||
encoder.index += 4;
|
encoder.index += 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
_floatType(): FloatType {
|
get [FloatType](): FloatType {
|
||||||
return 'Single';
|
return 'Single';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -113,7 +122,7 @@ export class Double extends Float {
|
||||||
encoder.index += 8;
|
encoder.index += 8;
|
||||||
}
|
}
|
||||||
|
|
||||||
_floatType(): FloatType {
|
get [FloatType](): FloatType {
|
||||||
return 'Double';
|
return 'Double';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -122,22 +131,13 @@ export class Double extends Float {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function isFloat(x: any, t: FloatType): x is Float {
|
|
||||||
return typeof x === 'object' && x !== null &&
|
|
||||||
'value' in x && typeof x.value === 'number' &&
|
|
||||||
'_floatType' in x && x._floatType() === t;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const isSingle = (x: any): x is Single => isFloat(x, 'Single');
|
|
||||||
export const isDouble = (x: any): x is Double => isFloat(x, 'Double');
|
|
||||||
|
|
||||||
export type BytesLike = Bytes | Uint8Array;
|
export type BytesLike = Bytes | Uint8Array;
|
||||||
|
|
||||||
export class Bytes {
|
export class Bytes {
|
||||||
readonly _view: Uint8Array;
|
readonly _view: Uint8Array;
|
||||||
|
|
||||||
constructor(maybeByteIterable: any = new Uint8Array()) {
|
constructor(maybeByteIterable: any = new Uint8Array()) {
|
||||||
if (isBytes(maybeByteIterable)) {
|
if (Bytes.isBytes(maybeByteIterable)) {
|
||||||
this._view = maybeByteIterable._view;
|
this._view = maybeByteIterable._view;
|
||||||
} else if (ArrayBuffer.isView(maybeByteIterable)) {
|
} else if (ArrayBuffer.isView(maybeByteIterable)) {
|
||||||
this._view = new Uint8Array(maybeByteIterable.buffer,
|
this._view = new Uint8Array(maybeByteIterable.buffer,
|
||||||
|
@ -182,13 +182,13 @@ export class Bytes {
|
||||||
|
|
||||||
static fromIO(io: string | BytesLike): string | Bytes {
|
static fromIO(io: string | BytesLike): string | Bytes {
|
||||||
if (typeof io === 'string') return io;
|
if (typeof io === 'string') return io;
|
||||||
if (isBytes(io)) return io;
|
if (Bytes.isBytes(io)) return io;
|
||||||
if (io instanceof Uint8Array) return new Bytes(io);
|
if (io instanceof Uint8Array) return new Bytes(io);
|
||||||
}
|
}
|
||||||
|
|
||||||
static toIO(b : string | BytesLike): string | Uint8Array {
|
static toIO(b : string | BytesLike): string | Uint8Array {
|
||||||
if (typeof b === 'string') return b;
|
if (typeof b === 'string') return b;
|
||||||
if (isBytes(b)) return b._view;
|
if (Bytes.isBytes(b)) return b._view;
|
||||||
if (b instanceof Uint8Array) return b;
|
if (b instanceof Uint8Array) return b;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -211,7 +211,7 @@ export class Bytes {
|
||||||
}
|
}
|
||||||
|
|
||||||
equals(other: any): boolean {
|
equals(other: any): boolean {
|
||||||
if (!isBytes(other)) return false;
|
if (!Bytes.isBytes(other)) return false;
|
||||||
if (other.length !== this.length) return false;
|
if (other.length !== this.length) return false;
|
||||||
const va = this._view;
|
const va = this._view;
|
||||||
const vb = other._view;
|
const vb = other._view;
|
||||||
|
@ -287,6 +287,14 @@ export class Bytes {
|
||||||
encoder.varint(this.length);
|
encoder.varint(this.length);
|
||||||
encoder.emitbytes(this._view);
|
encoder.emitbytes(this._view);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get [IsPreservesBytes](): boolean {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static isBytes(x: any): x is Bytes {
|
||||||
|
return !!x?.[IsPreservesBytes];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function hexDigit(n: number): string {
|
export function hexDigit(n: number): string {
|
||||||
|
@ -300,12 +308,6 @@ export function unhexDigit(asciiCode: number) {
|
||||||
throw new Error("Invalid hex digit: " + String.fromCharCode(asciiCode));
|
throw new Error("Invalid hex digit: " + String.fromCharCode(asciiCode));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isBytes(x: any): x is Bytes {
|
|
||||||
return typeof x === 'object' && x !== null &&
|
|
||||||
'_view' in x &&
|
|
||||||
x._view instanceof Uint8Array;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function underlying(b: Bytes | Uint8Array): Uint8Array {
|
export function underlying(b: Bytes | Uint8Array): Uint8Array {
|
||||||
return (b instanceof Uint8Array) ? b : b._view;
|
return (b instanceof Uint8Array) ? b : b._view;
|
||||||
}
|
}
|
||||||
|
@ -437,7 +439,7 @@ export class Record extends Array<Value> {
|
||||||
}
|
}
|
||||||
|
|
||||||
equals(other: any): boolean {
|
equals(other: any): boolean {
|
||||||
return isRecord(other) &&
|
return Record.isRecord(other) &&
|
||||||
is(this.label, other.label) &&
|
is(this.label, other.label) &&
|
||||||
this.every((f, i) => is(f, other.get(i)));
|
this.every((f, i) => is(f, other.get(i)));
|
||||||
}
|
}
|
||||||
|
@ -483,9 +485,9 @@ export class Record extends Array<Value> {
|
||||||
}
|
}
|
||||||
return new Record(label, fields);
|
return new Record(label, fields);
|
||||||
};
|
};
|
||||||
ctor.constructorInfo = { label, arity };
|
const constructorInfo = { label, arity };
|
||||||
ctor.isClassOf =
|
ctor.constructorInfo = constructorInfo;
|
||||||
(v: any): v is Record => (isRecord(v) && is(label, v.label) && v.length === arity);
|
ctor.isClassOf = (v: any): v is Record => Record.isClassOf(constructorInfo, v);
|
||||||
ctor._ = {};
|
ctor._ = {};
|
||||||
fieldNames.forEach((name, i) => {
|
fieldNames.forEach((name, i) => {
|
||||||
ctor._[name] = function (r: any): Value {
|
ctor._[name] = function (r: any): Value {
|
||||||
|
@ -505,6 +507,18 @@ export class Record extends Array<Value> {
|
||||||
this.forEach((f) => encoder.push(f));
|
this.forEach((f) => encoder.push(f));
|
||||||
encoder.emitbyte(Tag.End);
|
encoder.emitbyte(Tag.End);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get [IsPreservesRecord](): boolean {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static isRecord(x: any): x is Record {
|
||||||
|
return !!x?.[IsPreservesRecord];
|
||||||
|
}
|
||||||
|
|
||||||
|
static isClassOf(ci: RecordConstructorInfo, v: any): v is Record {
|
||||||
|
return (Record.isRecord(v)) && is(ci.label, v.label) && (ci.arity === v.length);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RecordConstructor {
|
export interface RecordConstructor {
|
||||||
|
@ -520,8 +534,8 @@ export interface RecordConstructorInfo {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function is(a: any, b: any): boolean {
|
export function is(a: any, b: any): boolean {
|
||||||
if (isAnnotated(a)) a = a.item;
|
if (Annotated.isAnnotated(a)) a = a.item;
|
||||||
if (isAnnotated(b)) b = b.item;
|
if (Annotated.isAnnotated(b)) b = b.item;
|
||||||
if (Object.is(a, b)) return true;
|
if (Object.is(a, b)) return true;
|
||||||
if (typeof a !== typeof b) return false;
|
if (typeof a !== typeof b) return false;
|
||||||
if (typeof a === 'object') {
|
if (typeof a === 'object') {
|
||||||
|
@ -540,18 +554,8 @@ export function hash(a: Value): number {
|
||||||
throw new Error("shouldBeImplemented"); // TODO
|
throw new Error("shouldBeImplemented"); // TODO
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isClassOf(ci: RecordConstructorInfo, v: any): v is Record {
|
|
||||||
return (isRecord(v)) && is(ci.label, v.label) && (ci.arity === v.length);
|
|
||||||
}
|
|
||||||
|
|
||||||
export type DictionaryType = 'Dictionary' | 'Set';
|
export type DictionaryType = 'Dictionary' | 'Set';
|
||||||
|
export const DictionaryType = Symbol.for('DictionaryType');
|
||||||
export function is_Dictionary(x: any, t: DictionaryType): boolean {
|
|
||||||
return typeof x === 'object' && x !== null && x[Symbol.toStringTag] === t;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const isDictionary = <T> (x: any): x is Dictionary<T> => is_Dictionary(x, 'Dictionary');
|
|
||||||
export const isSet = (x: any): x is Set => is_Dictionary(x, 'Set');
|
|
||||||
|
|
||||||
export function _canonicalString(item: Value): string {
|
export function _canonicalString(item: Value): string {
|
||||||
const bs = encode(item, { canonical: true })._view;
|
const bs = encode(item, { canonical: true })._view;
|
||||||
|
@ -560,8 +564,16 @@ export function _canonicalString(item: Value): string {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Dictionary<T> extends FlexMap<Value, T> {
|
export class Dictionary<T> extends FlexMap<Value, T> {
|
||||||
|
get [DictionaryType](): DictionaryType {
|
||||||
|
return 'Dictionary';
|
||||||
|
}
|
||||||
|
|
||||||
|
static isDictionary<T>(x: any): x is Dictionary<T> {
|
||||||
|
return x?.[DictionaryType] === 'Dictionary';
|
||||||
|
}
|
||||||
|
|
||||||
static fromJS(x: object): Dictionary<Value> {
|
static fromJS(x: object): Dictionary<Value> {
|
||||||
if (isDictionary(x)) return x as Dictionary<Value>;
|
if (Dictionary.isDictionary(x)) return x as Dictionary<Value>;
|
||||||
const d = new Dictionary<Value>();
|
const d = new Dictionary<Value>();
|
||||||
for (let key in x) {
|
for (let key in x) {
|
||||||
const value = x[key];
|
const value = x[key];
|
||||||
|
@ -619,6 +631,14 @@ export class Dictionary<T> extends FlexMap<Value, T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Set extends FlexSet<Value> {
|
export class Set extends FlexSet<Value> {
|
||||||
|
get [DictionaryType](): DictionaryType {
|
||||||
|
return 'Set';
|
||||||
|
}
|
||||||
|
|
||||||
|
static isSet(x: any): x is Set {
|
||||||
|
return x?.[DictionaryType] === 'Set';
|
||||||
|
}
|
||||||
|
|
||||||
constructor(items?: Iterable<any>) {
|
constructor(items?: Iterable<any>) {
|
||||||
super(_canonicalString, _iterMap(items?.[Symbol.iterator](), fromJS));
|
super(_canonicalString, _iterMap(items?.[Symbol.iterator](), fromJS));
|
||||||
}
|
}
|
||||||
|
@ -656,13 +676,6 @@ export class Set extends FlexSet<Value> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isAnnotated(x: any): x is Annotated {
|
|
||||||
return typeof x === 'object' && x !== null &&
|
|
||||||
x.constructor.name === 'Annotated' &&
|
|
||||||
'annotations' in x &&
|
|
||||||
'item' in x;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Annotated {
|
export class Annotated {
|
||||||
readonly annotations: Array<Value>;
|
readonly annotations: Array<Value>;
|
||||||
readonly item: Value;
|
readonly item: Value;
|
||||||
|
@ -687,7 +700,7 @@ export class Annotated {
|
||||||
}
|
}
|
||||||
|
|
||||||
equals(other: any): boolean {
|
equals(other: any): boolean {
|
||||||
return is(this.item, isAnnotated(other) ? other.item : other);
|
return is(this.item, Annotated.isAnnotated(other) ? other.item : other);
|
||||||
}
|
}
|
||||||
|
|
||||||
hashCode(): number {
|
hashCode(): number {
|
||||||
|
@ -702,6 +715,14 @@ export class Annotated {
|
||||||
const anns = this.annotations.map((a) => '@' + a.asPreservesText()).join(' ');
|
const anns = this.annotations.map((a) => '@' + a.asPreservesText()).join(' ');
|
||||||
return (anns ? anns + ' ' : anns) + this.item.asPreservesText();
|
return (anns ? anns + ' ' : anns) + this.item.asPreservesText();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get [IsPreservesAnnotated](): boolean {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static isAnnotated(x: any): x is Annotated {
|
||||||
|
return !!x?.[IsPreservesAnnotated];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function peel(v: Value): Value {
|
export function peel(v: Value): Value {
|
||||||
|
@ -711,20 +732,20 @@ export function peel(v: Value): Value {
|
||||||
export function strip(v: Value, depth: number = Infinity) {
|
export function strip(v: Value, depth: number = Infinity) {
|
||||||
function step(v: Value, depth: number): Value {
|
function step(v: Value, depth: number): Value {
|
||||||
if (depth === 0) return v;
|
if (depth === 0) return v;
|
||||||
if (!isAnnotated(v)) return v;
|
if (!Annotated.isAnnotated(v)) return v;
|
||||||
|
|
||||||
const nextDepth = depth - 1;
|
const nextDepth = depth - 1;
|
||||||
function walk(v: Value) { return step(v, nextDepth); }
|
function walk(v: Value) { return step(v, nextDepth); }
|
||||||
|
|
||||||
if (isRecord(v.item)) {
|
if (Record.isRecord(v.item)) {
|
||||||
return new Record(step(v.item.label, depth), v.item.map(walk));
|
return new Record(step(v.item.label, depth), v.item.map(walk));
|
||||||
} else if (Array.isArray(v.item)) {
|
} else if (Array.isArray(v.item)) {
|
||||||
return v.item.map(walk);
|
return v.item.map(walk);
|
||||||
} else if (isSet(v.item)) {
|
} else if (Set.isSet(v.item)) {
|
||||||
return v.item.map(walk);
|
return v.item.map(walk);
|
||||||
} else if (isDictionary(v.item)) {
|
} else if (Dictionary.isDictionary(v.item)) {
|
||||||
return v.item.mapEntries((e) => [walk(e[0]), walk(e[1])]);
|
return v.item.mapEntries((e) => [walk(e[0]), walk(e[1])]);
|
||||||
} else if (isAnnotated(v.item)) {
|
} else if (Annotated.isAnnotated(v.item)) {
|
||||||
throw new Error("Improper annotation structure");
|
throw new Error("Improper annotation structure");
|
||||||
} else {
|
} else {
|
||||||
return v.item;
|
return v.item;
|
||||||
|
@ -734,7 +755,7 @@ export function strip(v: Value, depth: number = Infinity) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function annotate(v0: Value, ...anns: Value[]) {
|
export function annotate(v0: Value, ...anns: Value[]) {
|
||||||
const v = isAnnotated(v0) ? v0 : new Annotated(v0);
|
const v = Annotated.isAnnotated(v0) ? v0 : new Annotated(v0);
|
||||||
anns.forEach((a) => v.annotations.push(a));
|
anns.forEach((a) => v.annotations.push(a));
|
||||||
return v;
|
return v;
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,68 +13,70 @@ describe('immutable byte arrays', () => {
|
||||||
expect(bs.every((b) => b !== 50)).toBe(true);
|
expect(bs.every((b) => b !== 50)).toBe(true);
|
||||||
expect(!(bs.every((b) => b !== 20))).toBe(true);
|
expect(!(bs.every((b) => b !== 20))).toBe(true);
|
||||||
});
|
});
|
||||||
// it('should implement find', () => {
|
it('should implement find', () => {
|
||||||
// assert.strictEqual(bs.find((b) => b > 20), 30);
|
expect(bs.find((b) => b > 20)).toBe(30);
|
||||||
// assert.strictEqual(bs.find((b) => b > 50), void 0);
|
expect(bs.find((b) => b > 50)).toBe(void 0);
|
||||||
// });
|
});
|
||||||
// it('should implement findIndex', () => {
|
it('should implement findIndex', () => {
|
||||||
// assert.strictEqual(bs.findIndex((b) => b > 20), 2);
|
expect(bs.findIndex((b) => b > 20)).toBe(2);
|
||||||
// assert.strictEqual(bs.findIndex((b) => b > 50), -1);
|
expect(bs.findIndex((b) => b > 50)).toBe(-1);
|
||||||
// });
|
});
|
||||||
// it('should implement forEach', () => {
|
it('should implement forEach', () => {
|
||||||
// const vs = [];
|
const vs = [];
|
||||||
// bs.forEach((b) => vs.push(b));
|
bs.forEach((b) => vs.push(b));
|
||||||
// assert(is(fromJS(vs), fromJS([10, 20, 30, 40])));
|
expect(fromJS(vs)).is(fromJS([10, 20, 30, 40]));
|
||||||
// });
|
});
|
||||||
// it('should implement includes', () => {
|
it('should implement includes', () => {
|
||||||
// assert(bs.includes(20));
|
expect(bs.includes(20)).toBe(true);
|
||||||
// assert(!bs.includes(50));
|
expect(!bs.includes(50)).toBe(true);
|
||||||
// });
|
});
|
||||||
// it('should implement indexOf', () => {
|
it('should implement indexOf', () => {
|
||||||
// assert.strictEqual(bs.indexOf(20), 1);
|
expect(bs.indexOf(20)).toBe(1);
|
||||||
// assert.strictEqual(bs.indexOf(50), -1);
|
expect(bs.indexOf(50)).toBe(-1);
|
||||||
// });
|
});
|
||||||
// it('should implement join', () => assert.strictEqual(bs.join('-'), '10-20-30-40'));
|
it('should implement join', () => {
|
||||||
// it('should implement keys', () => {
|
expect(bs.join('-')).toBe('10-20-30-40');
|
||||||
// assert(is(fromJS(Array.from(bs.keys())), fromJS([0,1,2,3])));
|
});
|
||||||
// });
|
it('should implement keys', () => {
|
||||||
// it('should implement values', () => {
|
expect(fromJS(Array.from(bs.keys()))).is(fromJS([0,1,2,3]));
|
||||||
// assert(is(fromJS(Array.from(bs.values())), fromJS([10,20,30,40])));
|
});
|
||||||
// });
|
it('should implement values', () => {
|
||||||
// it('should implement filter', () => {
|
expect(fromJS(Array.from(bs.values()))).is(fromJS([10,20,30,40]));
|
||||||
// assert(is(bs.filter((b) => b !== 30), Bytes.of(10,20,40)));
|
});
|
||||||
// });
|
it('should implement filter', () => {
|
||||||
// it('should implement slice', () => {
|
expect(bs.filter((b) => b !== 30)).is(Bytes.of(10,20,40));
|
||||||
// const vs = bs.slice(2);
|
});
|
||||||
// assert(!Object.is(vs._view.buffer, bs._view.buffer));
|
it('should implement slice', () => {
|
||||||
// assert.strictEqual(vs._view.buffer.byteLength, 2);
|
const vs = bs.slice(2);
|
||||||
// assert.strictEqual(vs.get(0), 30);
|
expect(Object.is(vs._view.buffer, bs._view.buffer)).toBe(false);
|
||||||
// assert.strictEqual(vs.get(1), 40);
|
expect(vs._view.buffer.byteLength).toBe(2);
|
||||||
// assert.strictEqual(vs.size, 2);
|
expect(vs.get(0)).toBe(30);
|
||||||
// });
|
expect(vs.get(1)).toBe(40);
|
||||||
// it('should implement subarray', () => {
|
expect(vs.length).toBe(2);
|
||||||
// const vs = bs.subarray(2);
|
});
|
||||||
// assert(Object.is(vs._view.buffer, bs._view.buffer));
|
it('should implement subarray', () => {
|
||||||
// assert.strictEqual(vs._view.buffer.byteLength, 4);
|
const vs = bs.subarray(2);
|
||||||
// assert.strictEqual(vs.get(0), 30);
|
expect(Object.is(vs._view.buffer, bs._view.buffer)).toBe(true);
|
||||||
// assert.strictEqual(vs.get(1), 40);
|
expect(vs._view.buffer.byteLength).toBe(4);
|
||||||
// assert.strictEqual(vs.size, 2);
|
expect(vs.get(0)).toBe(30);
|
||||||
// });
|
expect(vs.get(1)).toBe(40);
|
||||||
// it('should implement reverse', () => {
|
expect(vs.length).toBe(2);
|
||||||
// const vs = bs.reverse();
|
});
|
||||||
// assert(!Object.is(vs._view.buffer, bs._view.buffer));
|
it('should implement reverse', () => {
|
||||||
// assert.strictEqual(bs.get(0), 10);
|
const vs = bs.reverse();
|
||||||
// assert.strictEqual(bs.get(3), 40);
|
expect(Object.is(vs._view.buffer, bs._view.buffer)).toBe(false);
|
||||||
// assert.strictEqual(vs.get(0), 40);
|
expect(bs.get(0)).toBe(10);
|
||||||
// assert.strictEqual(vs.get(3), 10);
|
expect(bs.get(3)).toBe(40);
|
||||||
// });
|
expect(vs.get(0)).toBe(40);
|
||||||
// it('should implement sort', () => {
|
expect(vs.get(3)).toBe(10);
|
||||||
// const vs = bs.reverse().sort();
|
});
|
||||||
// assert(!Object.is(vs._view.buffer, bs._view.buffer));
|
it('should implement sort', () => {
|
||||||
// assert.strictEqual(bs.get(0), 10);
|
const vs = bs.reverse().sort();
|
||||||
// assert.strictEqual(bs.get(3), 40);
|
expect(Object.is(vs._view.buffer, bs._view.buffer)).toBe(false);
|
||||||
// assert.strictEqual(vs.get(0), 10);
|
expect(bs.get(0)).toBe(10);
|
||||||
// assert.strictEqual(vs.get(3), 40);
|
expect(bs.get(3)).toBe(40);
|
||||||
// });
|
expect(vs.get(0)).toBe(10);
|
||||||
|
expect(vs.get(3)).toBe(40);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -2,7 +2,7 @@ import {
|
||||||
Value,
|
Value,
|
||||||
Dictionary,
|
Dictionary,
|
||||||
decode, decodeWithAnnotations, encodeWithAnnotations,
|
decode, decodeWithAnnotations, encodeWithAnnotations,
|
||||||
isDecodeError, isShortPacket,
|
DecodeError, ShortPacket,
|
||||||
Bytes, Record,
|
Bytes, Record,
|
||||||
annotate,
|
annotate,
|
||||||
strip, peel,
|
strip, peel,
|
||||||
|
@ -150,7 +150,9 @@ describe('common test suite', () => {
|
||||||
describe(tName, () => {
|
describe(tName, () => {
|
||||||
it('should fail with DecodeError', () => {
|
it('should fail with DecodeError', () => {
|
||||||
expect(() => D(strip(t[0]) as Bytes))
|
expect(() => D(strip(t[0]) as Bytes))
|
||||||
.toThrowFilter(e => isDecodeError(e) && !isShortPacket(e));
|
.toThrowFilter(e =>
|
||||||
|
DecodeError.isDecodeError(e) &&
|
||||||
|
!ShortPacket.isShortPacket(e));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
@ -159,7 +161,7 @@ describe('common test suite', () => {
|
||||||
describe(tName, () => {
|
describe(tName, () => {
|
||||||
it('should fail with ShortPacket', () => {
|
it('should fail with ShortPacket', () => {
|
||||||
expect(() => D(strip(t[0]) as Bytes))
|
expect(() => D(strip(t[0]) as Bytes))
|
||||||
.toThrowFilter(e => isShortPacket(e));
|
.toThrowFilter(e => ShortPacket.isShortPacket(e));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
|
Loading…
Reference in New Issue