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 { Encoder } from "./encoder";
import { Tag } from "./constants"; import { Tag } from "./constants";
import { AsPreserve, PreserveOn } from "./symbols";
import { Value } from "./values"; import { Value } from "./values";
import { is, isAnnotated, IsPreservesAnnotated } from "./is"; import { is, isAnnotated, IsPreservesAnnotated } from "./is";
import { stringify } from "./text"; import { stringify } from "./text";
import { GenericEmbedded } from "./embedded"; import { GenericEmbedded } from "./embedded";
import type { Preservable } from "./encoder";
export interface Position { export interface Position {
line?: number; 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 annotations: Array<Value<T>>;
readonly pos: Position | null; readonly pos: Position | null;
readonly item: Value<T>; readonly item: Value<T>;
@ -64,11 +64,15 @@ export class Annotated<T = GenericEmbedded> {
this.item = item; this.item = item;
} }
[AsPreserve](): Value<T> { __as__preserve__(): Value<T> {
return this; 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) { if (encoder.includeAnnotations) {
for (const a of this.annotations) { for (const a of this.annotations) {
encoder.state.emitbyte(Tag.Annotation); encoder.state.emitbyte(Tag.Annotation);

View File

@ -1,5 +1,4 @@
import { Tag } from './constants'; import { Tag } from './constants';
import { AsPreserve, PreserveOn } from './symbols';
import { Encoder, Preservable } from './encoder'; import { Encoder, Preservable } from './encoder';
import { Value } from './values'; import { Value } from './values';
import { GenericEmbedded } from './embedded'; import { GenericEmbedded } from './embedded';
@ -127,10 +126,14 @@ export class Bytes implements Preservable<never> {
return this.asPreservesText(); return this.asPreservesText();
} }
[AsPreserve]<T = GenericEmbedded>(): Value<T> { __as_preserve__<T = GenericEmbedded>(): Value<T> {
return this; return this;
} }
static __from_preserve__<T>(v: Value<T>): undefined | Bytes {
return Bytes.isBytes(v) ? v : void 0;
}
asPreservesText(): string { asPreservesText(): string {
return '#"' + this.__asciify() + '"'; return '#"' + this.__asciify() + '"';
} }
@ -165,7 +168,7 @@ export class Bytes implements Preservable<never> {
return this.toHex(); return this.toHex();
} }
[PreserveOn](encoder: Encoder<never>) { __preserve_on__(encoder: Encoder<never>) {
encoder.state.emitbyte(Tag.ByteString); encoder.state.emitbyte(Tag.ByteString);
encoder.state.varint(this.length); encoder.state.varint(this.length);
encoder.state.emitbytes(this._view); encoder.state.emitbytes(this._view);

View File

@ -1,16 +1,16 @@
import { Encoder, canonicalEncode, canonicalString } from "./encoder"; import { Encoder, canonicalEncode, canonicalString } from "./encoder";
import { Tag } from "./constants"; import { Tag } from "./constants";
import { FlexMap, FlexSet, _iterMap } from "./flex"; import { FlexMap, FlexSet, _iterMap } from "./flex";
import { PreserveOn } from "./symbols";
import { stringify } from "./text"; import { stringify } from "./text";
import { Value } from "./values"; import { Value } from "./values";
import { Bytes } from './bytes'; import { Bytes } from './bytes';
import { GenericEmbedded } from "./embedded"; import { GenericEmbedded } from "./embedded";
import type { Preservable } from "./encoder";
export type DictionaryType = 'Dictionary' | 'Set'; export type DictionaryType = 'Dictionary' | 'Set';
export const DictionaryType = Symbol.for('DictionaryType'); 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 { get [DictionaryType](): DictionaryType {
return 'Dictionary'; return 'Dictionary';
} }
@ -51,7 +51,7 @@ export class KeyedDictionary<K extends Value<T>, V, T = GenericEmbedded> extends
get [Symbol.toStringTag]() { return 'Dictionary'; } get [Symbol.toStringTag]() { return 'Dictionary'; }
[PreserveOn](encoder: Encoder<T>) { __preserve_on__(encoder: Encoder<T>) {
if (encoder.canonical) { if (encoder.canonical) {
const entries = Array.from(this); const entries = Array.from(this);
const pieces = entries.map<[Bytes, number]>(([k, _v], i) => [canonicalEncode(k), i]); 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> { static isDictionary<T = GenericEmbedded, V = Value<T>>(x: any): x is Dictionary<T, V> {
return x?.[DictionaryType] === 'Dictionary'; 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 { get [DictionaryType](): DictionaryType {
return 'Set'; return 'Set';
} }
@ -119,7 +123,7 @@ export class KeyedSet<K extends Value<T>, T = GenericEmbedded> extends FlexSet<K
get [Symbol.toStringTag]() { return 'Set'; } get [Symbol.toStringTag]() { return 'Set'; }
[PreserveOn](encoder: Encoder<T>) { __preserve_on__(encoder: Encoder<T>) {
if (encoder.canonical) { if (encoder.canonical) {
const pieces = Array.from(this).map<[Bytes, K]>(k => [canonicalEncode(k), k]); const pieces = Array.from(this).map<[Bytes, K]>(k => [canonicalEncode(k), k]);
pieces.sort((a, b) => Bytes.compare(a[0], b[0])); 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> { static isSet<T = GenericEmbedded>(x: any): x is Set<T> {
return x?.[DictionaryType] === 'Set'; 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(); 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> { export function embed<T>(embeddedValue: T): Embedded<T> {

View File

@ -1,7 +1,6 @@
import { Tag } from "./constants"; import { Tag } from "./constants";
import { Bytes } from "./bytes"; import { Bytes } from "./bytes";
import { Value } from "./values"; import { Value } from "./values";
import { PreserveOn } from "./symbols";
import { EncodeError } from "./codec"; import { EncodeError } from "./codec";
import { Record, Tuple } from "./record"; import { Record, Tuple } from "./record";
import { GenericEmbedded, EmbeddedTypeEncode } from "./embedded"; import { GenericEmbedded, EmbeddedTypeEncode } from "./embedded";
@ -10,11 +9,11 @@ export type Encodable<T> =
Value<T> | Preservable<T> | Iterable<Value<T>> | ArrayBufferView; Value<T> | Preservable<T> | Iterable<Value<T>> | ArrayBufferView;
export interface Preservable<T> { 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> { 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 { export interface EncoderOptions {
@ -211,11 +210,11 @@ export class Encoder<T = object> {
} }
push(v: Encodable<T>) { push(v: Encodable<T>) {
if (isPreservable<never>(v)) { if (isPreservable<unknown>(v)) {
v[PreserveOn](this as unknown as Encoder<never>); v.__preserve_on__(this);
} }
else if (isPreservable<T>(v)) { else if (isPreservable<T>(v)) {
v[PreserveOn](this); v.__preserve_on__(this);
} }
else if (typeof v === 'boolean') { else if (typeof v === 'boolean') {
this.state.emitbyte(v ? Tag.True : Tag.False); this.state.emitbyte(v ? Tag.True : Tag.False);

View File

@ -1,6 +1,5 @@
import { Encoder, Preservable } from "./encoder"; import { Encoder, Preservable } from "./encoder";
import { Tag } from "./constants"; import { Tag } from "./constants";
import { AsPreserve, PreserveOn } from "./symbols";
import { Value } from "./values"; import { Value } from "./values";
import { GenericEmbedded } from "./embedded"; import { GenericEmbedded } from "./embedded";
@ -44,12 +43,16 @@ export function floatValue(f: any): number {
} }
} }
export class SingleFloat extends Float implements Preservable<never> { export class SingleFloat extends Float implements Preservable<any> {
[AsPreserve]<T = GenericEmbedded>(): Value<T> { __as_preserve__<T = GenericEmbedded>(): Value<T> {
return this; 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.emitbyte(Tag.Float);
encoder.state.makeroom(4); encoder.state.makeroom(4);
encoder.state.view.setFloat32(encoder.state.index, this.value, false); 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); return new SingleFloat(value);
} }
export class DoubleFloat extends Float implements Preservable<never> { export class DoubleFloat extends Float implements Preservable<any> {
[AsPreserve]<T = GenericEmbedded>(): Value<T> { __as_preserve__<T = GenericEmbedded>(): Value<T> {
return this; 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.emitbyte(Tag.Double);
encoder.state.makeroom(8); encoder.state.makeroom(8);
encoder.state.view.setFloat64(encoder.state.index, this.value, false); encoder.state.view.setFloat64(encoder.state.index, this.value, false);

View File

@ -1,7 +1,6 @@
import { embed, GenericEmbedded } from "./embedded"; import { embed, GenericEmbedded } from "./embedded";
import { Bytes } from "./bytes"; import { Bytes } from "./bytes";
import { Record, Tuple } from "./record"; import { Record, Tuple } from "./record";
import { AsPreserve } from "./symbols";
import { Value } from "./values"; import { Value } from "./values";
import { Dictionary, Set } from "./dictionary"; import { Dictionary, Set } from "./dictionary";
@ -27,8 +26,8 @@ export function fromJS<T = GenericEmbedded>(x: any): Value<T> {
if (x === null) { if (x === null) {
break; break;
} }
if (typeof x[AsPreserve] === 'function') { if (typeof x.__as_preserve__ === 'function') {
return x[AsPreserve](); return x.__as_preserve__();
} }
if (Record.isRecord<Value<T>, Tuple<Value<T>>, T>(x)) { if (Record.isRecord<Value<T>, Tuple<Value<T>>, T>(x)) {
return x; return x;

View File

@ -1,6 +1,18 @@
export * from './runtime'; export * from './runtime';
export * as Constants from './constants'; 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; const _Array = Array;
type _Array<T> = Array<T>; type _Array<T> = Array<T>;
export { _Array as Array }; export { _Array as Array };

View File

@ -16,6 +16,5 @@ export * from './merge';
export * from './reader'; export * from './reader';
export * from './record'; export * from './record';
export * from './strip'; export * from './strip';
export * from './symbols';
export * from './text'; export * from './text';
export * from './values'; export * from './values';

View File

@ -1,5 +1,23 @@
// Symbols for various Preserves protocols. // Symbols for various Preserves protocols.
export const PreserveOn = Symbol.for('PreserveOn'); // Previously, we had the following:
export const AsPreserve = Symbol.for('AsPreserve'); //
// 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__`.