Improve protocols for (de)coding JS/Preserves somewhat
This commit is contained in:
parent
ae6eaa663e
commit
e4792ddccd
|
@ -1,10 +1,10 @@
|
|||
import { Encoder } from "./encoder";
|
||||
import { Tag } from "./constants";
|
||||
import { AsPreserve, PreserveOn } from "./symbols";
|
||||
import { Value } from "./values";
|
||||
import { is, isAnnotated, IsPreservesAnnotated } from "./is";
|
||||
import { stringify } from "./text";
|
||||
import { GenericEmbedded } from "./embedded";
|
||||
import type { Preservable } from "./encoder";
|
||||
|
||||
export interface Position {
|
||||
line?: number;
|
||||
|
@ -53,7 +53,7 @@ export function formatPosition(p: Position | null | string): string {
|
|||
}
|
||||
}
|
||||
|
||||
export class Annotated<T = GenericEmbedded> {
|
||||
export class Annotated<T = GenericEmbedded> implements Preservable<T> {
|
||||
readonly annotations: Array<Value<T>>;
|
||||
readonly pos: Position | null;
|
||||
readonly item: Value<T>;
|
||||
|
@ -64,11 +64,15 @@ export class Annotated<T = GenericEmbedded> {
|
|||
this.item = item;
|
||||
}
|
||||
|
||||
[AsPreserve](): Value<T> {
|
||||
__as__preserve__(): Value<T> {
|
||||
return this;
|
||||
}
|
||||
|
||||
[PreserveOn](encoder: Encoder<T>) {
|
||||
static __from_preserve__<T>(v: Value<T>): undefined | Annotated<T> {
|
||||
return isAnnotated<T>(v) ? v : void 0;
|
||||
}
|
||||
|
||||
__preserve_on__(encoder: Encoder<T>): void {
|
||||
if (encoder.includeAnnotations) {
|
||||
for (const a of this.annotations) {
|
||||
encoder.state.emitbyte(Tag.Annotation);
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import { Tag } from './constants';
|
||||
import { AsPreserve, PreserveOn } from './symbols';
|
||||
import { Encoder, Preservable } from './encoder';
|
||||
import { Value } from './values';
|
||||
import { GenericEmbedded } from './embedded';
|
||||
|
@ -127,10 +126,14 @@ export class Bytes implements Preservable<never> {
|
|||
return this.asPreservesText();
|
||||
}
|
||||
|
||||
[AsPreserve]<T = GenericEmbedded>(): Value<T> {
|
||||
__as_preserve__<T = GenericEmbedded>(): Value<T> {
|
||||
return this;
|
||||
}
|
||||
|
||||
static __from_preserve__<T>(v: Value<T>): undefined | Bytes {
|
||||
return Bytes.isBytes(v) ? v : void 0;
|
||||
}
|
||||
|
||||
asPreservesText(): string {
|
||||
return '#"' + this.__asciify() + '"';
|
||||
}
|
||||
|
@ -165,7 +168,7 @@ export class Bytes implements Preservable<never> {
|
|||
return this.toHex();
|
||||
}
|
||||
|
||||
[PreserveOn](encoder: Encoder<never>) {
|
||||
__preserve_on__(encoder: Encoder<never>) {
|
||||
encoder.state.emitbyte(Tag.ByteString);
|
||||
encoder.state.varint(this.length);
|
||||
encoder.state.emitbytes(this._view);
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
import { Encoder, canonicalEncode, canonicalString } from "./encoder";
|
||||
import { Tag } from "./constants";
|
||||
import { FlexMap, FlexSet, _iterMap } from "./flex";
|
||||
import { PreserveOn } from "./symbols";
|
||||
import { stringify } from "./text";
|
||||
import { Value } from "./values";
|
||||
import { Bytes } from './bytes';
|
||||
import { GenericEmbedded } from "./embedded";
|
||||
import type { Preservable } from "./encoder";
|
||||
|
||||
export type DictionaryType = 'Dictionary' | 'Set';
|
||||
export const DictionaryType = Symbol.for('DictionaryType');
|
||||
|
||||
export class KeyedDictionary<K extends Value<T>, V, T = GenericEmbedded> extends FlexMap<K, V> {
|
||||
export class KeyedDictionary<K extends Value<T>, V, T = GenericEmbedded> extends FlexMap<K, V> implements Preservable<T> {
|
||||
get [DictionaryType](): DictionaryType {
|
||||
return 'Dictionary';
|
||||
}
|
||||
|
@ -51,7 +51,7 @@ export class KeyedDictionary<K extends Value<T>, V, T = GenericEmbedded> extends
|
|||
|
||||
get [Symbol.toStringTag]() { return 'Dictionary'; }
|
||||
|
||||
[PreserveOn](encoder: Encoder<T>) {
|
||||
__preserve_on__(encoder: Encoder<T>) {
|
||||
if (encoder.canonical) {
|
||||
const entries = Array.from(this);
|
||||
const pieces = entries.map<[Bytes, number]>(([k, _v], i) => [canonicalEncode(k), i]);
|
||||
|
@ -78,9 +78,13 @@ export class Dictionary<T = GenericEmbedded, V = Value<T>> extends KeyedDictiona
|
|||
static isDictionary<T = GenericEmbedded, V = Value<T>>(x: any): x is Dictionary<T, V> {
|
||||
return x?.[DictionaryType] === 'Dictionary';
|
||||
}
|
||||
|
||||
static __from_preserve__<T>(v: Value<T>): undefined | Dictionary<T> {
|
||||
return Dictionary.isDictionary<T>(v) ? v : void 0;
|
||||
}
|
||||
}
|
||||
|
||||
export class KeyedSet<K extends Value<T>, T = GenericEmbedded> extends FlexSet<K> {
|
||||
export class KeyedSet<K extends Value<T>, T = GenericEmbedded> extends FlexSet<K> implements Preservable<T> {
|
||||
get [DictionaryType](): DictionaryType {
|
||||
return 'Set';
|
||||
}
|
||||
|
@ -119,7 +123,7 @@ export class KeyedSet<K extends Value<T>, T = GenericEmbedded> extends FlexSet<K
|
|||
|
||||
get [Symbol.toStringTag]() { return 'Set'; }
|
||||
|
||||
[PreserveOn](encoder: Encoder<T>) {
|
||||
__preserve_on__(encoder: Encoder<T>) {
|
||||
if (encoder.canonical) {
|
||||
const pieces = Array.from(this).map<[Bytes, K]>(k => [canonicalEncode(k), k]);
|
||||
pieces.sort((a, b) => Bytes.compare(a[0], b[0]));
|
||||
|
@ -134,4 +138,8 @@ export class Set<T = GenericEmbedded> extends KeyedSet<Value<T>, T> {
|
|||
static isSet<T = GenericEmbedded>(x: any): x is Set<T> {
|
||||
return x?.[DictionaryType] === 'Set';
|
||||
}
|
||||
|
||||
static __from_preserve__<T>(v: Value<T>): undefined | Set<T> {
|
||||
return Set.isSet<T>(v) ? v : void 0;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,6 +33,14 @@ export class Embedded<T> {
|
|||
return '#!' + (this.embeddedValue as any).toString();
|
||||
}
|
||||
}
|
||||
|
||||
__as_preserve__<R>(): T extends R ? Value<R> : never {
|
||||
return this as any;
|
||||
}
|
||||
|
||||
static __from_preserve__<T>(v: Value<T>): undefined | Embedded<T> {
|
||||
return isEmbedded<T>(v) ? v : void 0;
|
||||
}
|
||||
}
|
||||
|
||||
export function embed<T>(embeddedValue: T): Embedded<T> {
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import { Tag } from "./constants";
|
||||
import { Bytes } from "./bytes";
|
||||
import { Value } from "./values";
|
||||
import { PreserveOn } from "./symbols";
|
||||
import { EncodeError } from "./codec";
|
||||
import { Record, Tuple } from "./record";
|
||||
import { GenericEmbedded, EmbeddedTypeEncode } from "./embedded";
|
||||
|
@ -10,11 +9,11 @@ export type Encodable<T> =
|
|||
Value<T> | Preservable<T> | Iterable<Value<T>> | ArrayBufferView;
|
||||
|
||||
export interface Preservable<T> {
|
||||
[PreserveOn](encoder: Encoder<T>): void;
|
||||
__preserve_on__(encoder: Encoder<T>): void;
|
||||
}
|
||||
|
||||
export function isPreservable<T>(v: any): v is Preservable<T> {
|
||||
return typeof v === 'object' && v !== null && typeof v[PreserveOn] === 'function';
|
||||
return typeof v === 'object' && v !== null && '__preserve_on__' in v && typeof v.__preserve_on__ === 'function';
|
||||
}
|
||||
|
||||
export interface EncoderOptions {
|
||||
|
@ -211,11 +210,11 @@ export class Encoder<T = object> {
|
|||
}
|
||||
|
||||
push(v: Encodable<T>) {
|
||||
if (isPreservable<never>(v)) {
|
||||
v[PreserveOn](this as unknown as Encoder<never>);
|
||||
if (isPreservable<unknown>(v)) {
|
||||
v.__preserve_on__(this);
|
||||
}
|
||||
else if (isPreservable<T>(v)) {
|
||||
v[PreserveOn](this);
|
||||
v.__preserve_on__(this);
|
||||
}
|
||||
else if (typeof v === 'boolean') {
|
||||
this.state.emitbyte(v ? Tag.True : Tag.False);
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import { Encoder, Preservable } from "./encoder";
|
||||
import { Tag } from "./constants";
|
||||
import { AsPreserve, PreserveOn } from "./symbols";
|
||||
import { Value } from "./values";
|
||||
import { GenericEmbedded } from "./embedded";
|
||||
|
||||
|
@ -44,12 +43,16 @@ export function floatValue(f: any): number {
|
|||
}
|
||||
}
|
||||
|
||||
export class SingleFloat extends Float implements Preservable<never> {
|
||||
[AsPreserve]<T = GenericEmbedded>(): Value<T> {
|
||||
export class SingleFloat extends Float implements Preservable<any> {
|
||||
__as_preserve__<T = GenericEmbedded>(): Value<T> {
|
||||
return this;
|
||||
}
|
||||
|
||||
[PreserveOn](encoder: Encoder<never>) {
|
||||
static __from_preserve__<T>(v: Value<T>): undefined | SingleFloat {
|
||||
return Float.isSingle(v) ? v : void 0;
|
||||
}
|
||||
|
||||
__preserve_on__(encoder: Encoder<any>) {
|
||||
encoder.state.emitbyte(Tag.Float);
|
||||
encoder.state.makeroom(4);
|
||||
encoder.state.view.setFloat32(encoder.state.index, this.value, false);
|
||||
|
@ -69,12 +72,16 @@ export function Single(value: number | Float): SingleFloat {
|
|||
return new SingleFloat(value);
|
||||
}
|
||||
|
||||
export class DoubleFloat extends Float implements Preservable<never> {
|
||||
[AsPreserve]<T = GenericEmbedded>(): Value<T> {
|
||||
export class DoubleFloat extends Float implements Preservable<any> {
|
||||
__as_preserve__<T = GenericEmbedded>(): Value<T> {
|
||||
return this;
|
||||
}
|
||||
|
||||
[PreserveOn](encoder: Encoder<never>) {
|
||||
static __from_preserve__<T>(v: Value<T>): undefined | DoubleFloat {
|
||||
return Float.isDouble(v) ? v : void 0;
|
||||
}
|
||||
|
||||
__preserve_on__(encoder: Encoder<any>) {
|
||||
encoder.state.emitbyte(Tag.Double);
|
||||
encoder.state.makeroom(8);
|
||||
encoder.state.view.setFloat64(encoder.state.index, this.value, false);
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import { embed, GenericEmbedded } from "./embedded";
|
||||
import { Bytes } from "./bytes";
|
||||
import { Record, Tuple } from "./record";
|
||||
import { AsPreserve } from "./symbols";
|
||||
import { Value } from "./values";
|
||||
import { Dictionary, Set } from "./dictionary";
|
||||
|
||||
|
@ -27,8 +26,8 @@ export function fromJS<T = GenericEmbedded>(x: any): Value<T> {
|
|||
if (x === null) {
|
||||
break;
|
||||
}
|
||||
if (typeof x[AsPreserve] === 'function') {
|
||||
return x[AsPreserve]();
|
||||
if (typeof x.__as_preserve__ === 'function') {
|
||||
return x.__as_preserve__();
|
||||
}
|
||||
if (Record.isRecord<Value<T>, Tuple<Value<T>>, T>(x)) {
|
||||
return x;
|
||||
|
|
|
@ -1,6 +1,18 @@
|
|||
export * from './runtime';
|
||||
export * as Constants from './constants';
|
||||
|
||||
import type { Value } from './values';
|
||||
|
||||
declare global {
|
||||
interface ArrayConstructor {
|
||||
__from_preserve__<T>(v: Value<T>): undefined | Array<Value<T>>;
|
||||
}
|
||||
}
|
||||
|
||||
Array.__from_preserve__ = <T>(v: Value<T>) => {
|
||||
return Array.isArray(v) ? v : void 0;
|
||||
};
|
||||
|
||||
const _Array = Array;
|
||||
type _Array<T> = Array<T>;
|
||||
export { _Array as Array };
|
||||
|
|
|
@ -16,6 +16,5 @@ export * from './merge';
|
|||
export * from './reader';
|
||||
export * from './record';
|
||||
export * from './strip';
|
||||
export * from './symbols';
|
||||
export * from './text';
|
||||
export * from './values';
|
||||
|
|
|
@ -1,5 +1,23 @@
|
|||
// Symbols for various Preserves protocols.
|
||||
|
||||
export const PreserveOn = Symbol.for('PreserveOn');
|
||||
export const AsPreserve = Symbol.for('AsPreserve');
|
||||
|
||||
// Previously, we had the following:
|
||||
//
|
||||
// export const PreserveOn = Symbol.for('PreserveOn');
|
||||
// export const AsPreserve = Symbol.for('AsPreserve');
|
||||
// export const FromPreserve = Symbol.for('FromPreserve');
|
||||
//
|
||||
// ... and used them to define methods [PreserveOn] and [AsPreserve]
|
||||
// and functions [FromPreserve] in various namespaces. The methods
|
||||
// worked well, but the functions didn't work with TypeScript as of TS
|
||||
// 4.5 because there's no way to export a symbol-named property from a
|
||||
// `namespace`. See
|
||||
// https://github.com/microsoft/TypeScript/issues/36813.
|
||||
//
|
||||
// So, instead, I've converted them as follows:
|
||||
//
|
||||
// [PreserveOn] ==> __preserve_on__
|
||||
// [AsPreserve] ==> __as_preserve__
|
||||
// [FromPreserve] ==> __from_preserve__
|
||||
//
|
||||
// and emphasised use of functions `fromJS` and `toJS` to get away
|
||||
// from the `__fiddly_details__`.
|
||||
|
|
Loading…
Reference in New Issue