Improve protocols for (de)coding JS/Preserves somewhat

This commit is contained in:
Tony Garnock-Jones 2022-01-22 23:38:02 +01:00
parent ae6eaa663e
commit e4792ddccd
10 changed files with 89 additions and 32 deletions

View File

@ -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);

View File

@ -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);

View File

@ -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;
}
}

View File

@ -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> {

View File

@ -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);

View File

@ -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);

View File

@ -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;

View File

@ -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 };

View File

@ -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';

View File

@ -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__`.