Fix module cycles (largely by splitting PointerType in two)

This commit is contained in:
Tony Garnock-Jones 2021-04-25 10:42:21 +02:00
parent 854a2bc41c
commit 8442718f96
11 changed files with 172 additions and 130 deletions

View File

@ -7,14 +7,14 @@ import { Record } from "./record";
import { Bytes, BytesLike, underlying } from "./bytes";
import { Value } from "./values";
import { is } from "./is";
import { embed, neverPointerType, Pointer, PointerType } from "./pointer";
import { embed, GenericPointer, Pointer, PointerTypeDecode } from "./pointer";
export interface DecoderOptions {
includeAnnotations?: boolean;
}
export interface DecoderPointerOptions<T> extends DecoderOptions {
pointerType?: PointerType<T>;
pointerDecode?: PointerTypeDecode<T>;
}
export interface TypedDecoder<T> {
@ -25,8 +25,8 @@ export interface TypedDecoder<T> {
skip(): void;
next(): Value<T>;
withPointerType<S, R>(
pointerType: PointerType<S>,
withPointerDecode<S, R>(
pointerDecode: PointerTypeDecode<S>,
body: (d: TypedDecoder<S>) => R): R;
nextBoolean(): boolean | undefined;
@ -161,23 +161,33 @@ export class DecoderState {
}
}
export const neverPointerTypeDecode: PointerTypeDecode<never> = {
decode(_s: DecoderState): never {
throw new Error("Pointers not permitted at this point in Preserves document");
},
fromValue(_v: Value<GenericPointer>): never {
throw new Error("Pointers not permitted at this point in Preserves document");
},
};
export class Decoder<T = never> implements TypedDecoder<T> {
state: DecoderState;
pointerType: PointerType<T>;
pointerDecode: PointerTypeDecode<T>;
constructor(state: DecoderState, pointerType?: PointerType<T>);
constructor(state: DecoderState, pointerDecode?: PointerTypeDecode<T>);
constructor(packet?: BytesLike, options?: DecoderPointerOptions<T>);
constructor(
packet_or_state: (DecoderState | BytesLike) = new Uint8Array(0),
options_or_pointerType?: (DecoderPointerOptions<T> | PointerType<T>))
options_or_pointerDecode?: (DecoderPointerOptions<T> | PointerTypeDecode<T>))
{
if (packet_or_state instanceof DecoderState) {
this.state = packet_or_state;
this.pointerType = (options_or_pointerType as PointerType<T>) ?? neverPointerType;
this.pointerDecode = (options_or_pointerDecode as PointerTypeDecode<T>) ?? neverPointerTypeDecode;
} else {
const options = (options_or_pointerType as DecoderPointerOptions<T>) ?? {};
const options = (options_or_pointerDecode as DecoderPointerOptions<T>) ?? {};
this.state = new DecoderState(packet_or_state, options);
this.pointerType = options.pointerType ?? neverPointerType;
this.pointerDecode = options.pointerDecode ?? neverPointerTypeDecode;
}
}
@ -213,7 +223,7 @@ export class Decoder<T = never> implements TypedDecoder<T> {
const v = this.next() as Annotated<T>;
return this.state.unshiftAnnotation(a, v);
}
case Tag.Pointer: return this.state.wrap<T>(embed(this.pointerType.decode(this.state)));
case Tag.Pointer: return this.state.wrap<T>(embed(this.pointerDecode.decode(this.state)));
case Tag.SignedInteger: return this.state.wrap<T>(this.state.nextint(this.state.varint()));
case Tag.String: return this.state.wrap<T>(Bytes.from(this.state.nextbytes(this.state.varint())).fromUtf8());
case Tag.ByteString: return this.state.wrap<T>(Bytes.from(this.state.nextbytes(this.state.varint())));
@ -257,11 +267,11 @@ export class Decoder<T = never> implements TypedDecoder<T> {
this.next();
}
withPointerType<S, R>(
pointerType: PointerType<S>,
withPointerDecode<S, R>(
pointerDecode: PointerTypeDecode<S>,
body: (d: TypedDecoder<S>) => R): R
{
return body(new Decoder(this.state, pointerType));
return body(new Decoder(this.state, pointerDecode));
}
skipAnnotations(): void {
@ -299,7 +309,7 @@ export class Decoder<T = never> implements TypedDecoder<T> {
nextPointer(): Pointer<T> | undefined {
this.skipAnnotations();
switch (this.state.nextbyte()) {
case Tag.Pointer: return embed(this.pointerType.decode(this.state));
case Tag.Pointer: return embed(this.pointerDecode.decode(this.state));
default: return void 0;
}
}

View File

@ -5,7 +5,6 @@ import { PreserveOn } from "./symbols";
import { stringify } from "./text";
import { Value } from "./values";
import { Bytes } from './bytes';
import { fromJS } from "./fromjs";
import { GenericPointer } from "./pointer";
export type DictionaryType = 'Dictionary' | 'Set';
@ -79,13 +78,6 @@ export class Dictionary<T = GenericPointer, V = Value<T>> extends KeyedDictionar
static isDictionary<T = GenericPointer, V = Value<T>>(x: any): x is Dictionary<T, V> {
return x?.[DictionaryType] === 'Dictionary';
}
static fromJS<T = GenericPointer, V = GenericPointer>(x: object): Dictionary<T, Value<V>> {
if (Dictionary.isDictionary<T, Value<V>>(x)) return x;
const d = new Dictionary<T, Value<V>>();
Object.entries(x).forEach(([key, value]) => d.set(key, fromJS(value)));
return d;
}
}
export class KeyedSet<K extends Value<T>, T = GenericPointer> extends FlexSet<K> {

View File

@ -4,7 +4,7 @@ import { Value } from "./values";
import { PreserveOn } from "./symbols";
import { EncodeError } from "./codec";
import { Record, Tuple } from "./record";
import { identityPointerType, PointerType } from "./pointer";
import { GenericPointer, PointerTypeEncode } from "./pointer";
export type Encodable<T> =
Value<T> | Preservable<T> | Iterable<Value<T>> | ArrayBufferView;
@ -23,7 +23,7 @@ export interface EncoderOptions {
}
export interface EncoderPointerOptions<T> extends EncoderOptions {
pointerType?: PointerType<T>;
pointerEncode?: PointerTypeEncode<T>;
}
export function asLatin1(bs: Uint8Array): string {
@ -34,6 +34,27 @@ function isIterable<T>(v: any): v is Iterable<T> {
return typeof v === 'object' && v !== null && typeof v[Symbol.iterator] === 'function';
}
let _nextId = 0;
const _registry = new WeakMap<object, number>();
export function pointerId(v: any): number {
let id = _registry.get(v);
if (id === void 0) {
id = _nextId++;
_registry.set(v, id);
}
return id;
}
export const identityPointerTypeEncode: PointerTypeEncode<any> = {
encode(s: EncoderState, v: any): void {
new Encoder(s, this).push(pointerId(v));
},
toValue(v: any): Value<GenericPointer> {
return pointerId(v);
}
};
export class EncoderState {
chunks: Array<Uint8Array>;
view: DataView;
@ -138,28 +159,28 @@ export class EncoderState {
export class Encoder<T = object> {
state: EncoderState;
pointerType: PointerType<T> | undefined;
pointerEncode: PointerTypeEncode<T>;
constructor(options: EncoderPointerOptions<T>);
constructor(state: EncoderState, pointerType?: PointerType<T>);
constructor(state: EncoderState, pointerEncode?: PointerTypeEncode<T>);
constructor(
state_or_options: (EncoderState | EncoderPointerOptions<T>) = {},
pointerType?: PointerType<T>)
pointerEncode?: PointerTypeEncode<T>)
{
if (state_or_options instanceof EncoderState) {
this.state = state_or_options;
this.pointerType = pointerType;
this.pointerEncode = pointerEncode ?? identityPointerTypeEncode;
} else {
this.state = new EncoderState(state_or_options);
this.pointerType = state_or_options.pointerType;
this.pointerEncode = state_or_options.pointerEncode ?? identityPointerTypeEncode;
}
}
withPointerType<S>(
pointerType: PointerType<S>,
withPointerEncode<S>(
pointerEncode: PointerTypeEncode<S>,
body: (e: Encoder<S>) => void): this
{
body(new Encoder(this.state, pointerType));
body(new Encoder(this.state, pointerEncode));
return this;
}
@ -232,7 +253,7 @@ export class Encoder<T = object> {
}
else {
this.state.emitbyte(Tag.Pointer);
(this.pointerType ?? identityPointerType).encode(this.state, v.embeddedValue);
this.pointerEncode.encode(this.state, v.embeddedValue);
}
return this; // for chaining
}

View File

@ -58,3 +58,16 @@ export function fromJS<T = GenericPointer>(x: any): Value<T> {
throw new TypeError("Cannot represent JavaScript value as Preserves: " + x);
}
declare module "./dictionary" {
namespace Dictionary {
export function fromJS<T = GenericPointer, V = GenericPointer>(x: object): Dictionary<T, Value<V>>;
}
}
Dictionary.fromJS = function <T = GenericPointer, V = GenericPointer>(x: object): Dictionary<T, Value<V>> {
if (Dictionary.isDictionary<T, Value<V>>(x)) return x;
const d = new Dictionary<T, Value<V>>();
Object.entries(x).forEach(([key, value]) => d.set(key, fromJS(value)));
return d;
};

View File

@ -1,16 +1,19 @@
import { Encoder, EncoderState } from "./encoder";
import { Decoder, DecoderState } from "./decoder";
import type { EncoderState } from "./encoder";
import type { DecoderState } from "./decoder";
import type { Value } from "./values";
import { strip } from "./strip";
export type PointerType<T> = {
decode(s: DecoderState): T;
export type PointerTypeEncode<T> = {
encode(s: EncoderState, v: T): void;
fromValue(v: Value<GenericPointer>): T;
toValue(v: T): Value<GenericPointer>;
}
export type PointerTypeDecode<T> = {
decode(s: DecoderState): T;
fromValue(v: Value<GenericPointer>): T;
}
export type PointerType<T> = PointerTypeEncode<T> & PointerTypeDecode<T>;
export class Pointer<T> {
embeddedValue: T;
@ -50,68 +53,3 @@ export class GenericPointer {
return this.generic.asPreservesText();
}
}
export const genericPointerType: PointerType<GenericPointer> = {
decode(s: DecoderState): GenericPointer {
return new GenericPointer(new Decoder(s, this).next());
},
encode(s: EncoderState, v: GenericPointer): void {
new Encoder(s, this).push(v.generic);
},
fromValue(v: Value<GenericPointer>): GenericPointer {
return new GenericPointer(strip(v));
},
toValue(v: GenericPointer): Value<GenericPointer> {
return v.generic;
}
};
export const neverPointerType: PointerType<never> = {
decode(_s: DecoderState): never {
throw new Error("Pointers not permitted at this point in Preserves document");
},
encode(_s: EncoderState, _v: never): void {
throw new Error("Pointers not permitted encoding Preserves document");
},
fromValue(_v: Value<GenericPointer>): never {
throw new Error("Pointers not permitted at this point in Preserves document");
},
toValue(_v: never): Value<GenericPointer> {
throw new Error("Pointers not permitted encoding Preserves document");
}
};
let _nextId = 0;
const _registry = new WeakMap<object, number>();
export function pointerId(v: any): number {
let id = _registry.get(v);
if (id === void 0) {
id = _nextId++;
_registry.set(v, id);
}
return id;
}
export const identityPointerType: PointerType<any> = {
decode(_s: DecoderState): any {
throw new Error("Cannot decode identityPointerType");
},
encode(s: EncoderState, v: any): void {
new Encoder(s, this).push(pointerId(v));
},
fromValue(_v: Value<GenericPointer>): any {
throw new Error("Cannot decode identityPointerType");
},
toValue(v: any): Value<GenericPointer> {
return pointerId(v);
}
};

View File

@ -0,0 +1,50 @@
import { GenericPointer, PointerType, PointerTypeDecode, PointerTypeEncode } from "./pointer";
import { Encoder, EncoderState, identityPointerTypeEncode } from "./encoder";
import { genericPointerTypeDecode } from "./reader";
import { Value } from "./values";
import { DecoderState, neverPointerTypeDecode } from "./decoder";
export const genericPointerTypeEncode: PointerTypeEncode<GenericPointer> = {
encode(s: EncoderState, v: GenericPointer): void {
new Encoder(s, this).push(v.generic);
},
toValue(v: GenericPointer): Value<GenericPointer> {
return v.generic;
}
};
export const genericPointerType: PointerType<GenericPointer> =
Object.assign({},
genericPointerTypeDecode,
genericPointerTypeEncode);
export const neverPointerTypeEncode: PointerTypeEncode<never> = {
encode(_s: EncoderState, _v: never): void {
throw new Error("Pointers not permitted encoding Preserves document");
},
toValue(_v: never): Value<GenericPointer> {
throw new Error("Pointers not permitted encoding Preserves document");
}
};
export const neverPointerType: PointerType<never> =
Object.assign({},
neverPointerTypeDecode,
neverPointerTypeEncode);
export const identityPointerTypeDecode: PointerTypeDecode<any> = {
decode(_s: DecoderState): any {
throw new Error("Cannot decode identityPointerType");
},
fromValue(_v: Value<GenericPointer>): any {
throw new Error("Cannot decode identityPointerType");
},
};
export const identityPointerType: PointerType<any> =
Object.assign({},
identityPointerTypeDecode,
identityPointerTypeEncode);

View File

@ -3,14 +3,14 @@
import type { Value } from './values';
import { DecodeError, ShortPacket } from './codec';
import { Dictionary, Set } from './dictionary';
import { unannotate } from './strip';
import { strip, unannotate } from './strip';
import { Bytes, unhexDigit } from './bytes';
import { decode } from './decoder';
import { decode, Decoder, DecoderState, neverPointerTypeDecode } from './decoder';
import { Record } from './record';
import { Annotated, newPosition, Position, updatePosition } from './annotated';
import { Double, DoubleFloat, Single, SingleFloat } from './float';
import { stringify } from './text';
import { embed, GenericPointer, genericPointerType, neverPointerType, PointerType } from './pointer';
import { embed, GenericPointer, PointerTypeDecode } from './pointer';
export interface ReaderStateOptions {
includeAnnotations?: boolean;
@ -18,7 +18,7 @@ export interface ReaderStateOptions {
}
export interface ReaderOptions<T> extends ReaderStateOptions {
pointerType?: PointerType<T>;
pointerDecode?: PointerTypeDecode<T>;
}
type IntOrFloat = 'int' | 'float';
@ -275,23 +275,33 @@ export class ReaderState {
}
}
export const genericPointerTypeDecode: PointerTypeDecode<GenericPointer> = {
decode(s: DecoderState): GenericPointer {
return new GenericPointer(new Decoder(s, this).next());
},
fromValue(v: Value<GenericPointer>): GenericPointer {
return new GenericPointer(strip(v));
},
};
export class Reader<T> {
state: ReaderState;
pointerType: PointerType<T>;
pointerType: PointerTypeDecode<T>;
constructor(state: ReaderState, pointerType: PointerType<T>);
constructor(state: ReaderState, pointerType: PointerTypeDecode<T>);
constructor(buffer: string, options?: ReaderOptions<T>);
constructor(
state_or_buffer: (ReaderState | string) = '',
pointerType_or_options?: (PointerType<T> | ReaderOptions<T>))
pointerType_or_options?: (PointerTypeDecode<T> | ReaderOptions<T>))
{
if (state_or_buffer instanceof ReaderState) {
this.state = state_or_buffer;
this.pointerType = pointerType_or_options as PointerType<T>;
this.pointerType = pointerType_or_options as PointerTypeDecode<T>;
} else {
const options = (pointerType_or_options as ReaderOptions<T>) ?? {};
this.state = new ReaderState(state_or_buffer, options);
this.pointerType = options.pointerType ?? neverPointerType;
this.pointerType = options.pointerDecode ?? neverPointerTypeDecode;
}
}
@ -378,12 +388,12 @@ export class Reader<T> {
if (!Bytes.isBytes(bs)) this.state.error('ByteString must follow #=',
startPos);
return decode<T>(bs, {
pointerType: this.pointerType,
pointerDecode: this.pointerType,
includeAnnotations: this.state.options.includeAnnotations,
});
}
case '!': return embed(this.pointerType.fromValue(
new Reader<GenericPointer>(this.state, genericPointerType).next()));
new Reader<GenericPointer>(this.state, genericPointerTypeDecode).next()));
default:
this.state.error(`Invalid # syntax: ${c}`, startPos);
}

View File

@ -12,6 +12,7 @@ export * from './fromjs';
export * from './is';
export * from './merge';
export * from './pointer';
export * from './pointerTypes';
export * from './reader';
export * from './record';
export * from './strip';

View File

@ -17,7 +17,8 @@ import {
Decoder,
Pointer,
embed,
genericPointerType,
genericPointerTypeDecode,
genericPointerTypeEncode,
} from '../src/index';
const { Tag } = Constants;
import './test-utils';
@ -144,7 +145,7 @@ describe('encoding and decoding pointers', () => {
const pt = new LookasidePointerType(objects);
const A = embed({a: 1});
const B = embed({b: 2});
expect(encode([A, B], { pointerType: pt })).is(
expect(encode([A, B], { pointerEncode: pt })).is(
Bytes.from([Tag.Sequence,
Tag.Pointer, Tag.SmallInteger_lo,
Tag.Pointer, Tag.SmallInteger_lo + 1,
@ -163,7 +164,7 @@ describe('encoding and decoding pointers', () => {
Tag.Pointer, Tag.SmallInteger_lo,
Tag.Pointer, Tag.SmallInteger_lo + 1,
Tag.End
]), { pointerType: pt })).is([X, Y]);
]), { pointerDecode: pt })).is([X, Y]);
});
it('should store pointers embedded in map keys correctly', () => {
const A1a = {a: 1};
@ -183,7 +184,7 @@ describe('encoding and decoding pointers', () => {
describe('common test suite', () => {
const samples_bin = fs.readFileSync(__dirname + '/../../../../../tests/samples.bin');
const samples = decodeWithAnnotations(samples_bin, { pointerType: genericPointerType });
const samples = decodeWithAnnotations(samples_bin, { pointerDecode: genericPointerTypeDecode });
const TestCases = Record.makeConstructor<{
cases: Dictionary<GenericPointer>
@ -191,13 +192,13 @@ describe('common test suite', () => {
type TestCases = ReturnType<typeof TestCases>;
function DS(bs: Bytes) {
return decode(bs, { pointerType: genericPointerType });
return decode(bs, { pointerDecode: genericPointerTypeDecode });
}
function D(bs: Bytes) {
return decodeWithAnnotations(bs, { pointerType: genericPointerType });
return decodeWithAnnotations(bs, { pointerDecode: genericPointerTypeDecode });
}
function E(v: Value<GenericPointer>) {
return encodeWithAnnotations(v, { pointerType: genericPointerType });
return encodeWithAnnotations(v, { pointerEncode: genericPointerTypeEncode });
}
interface ExpectedValues {

View File

@ -8,21 +8,21 @@ describe('reading common test suite', () => {
const samples_pr = fs.readFileSync(__dirname + '/../../../../../tests/samples.pr', 'utf-8');
it('should read equal to decoded binary without annotations', () => {
const s1 = new Reader(samples_pr, { pointerType: genericPointerType, includeAnnotations: false }).next();
const s2 = new Decoder(samples_bin, { pointerType: genericPointerType, includeAnnotations: false }).next();
const s1 = new Reader(samples_pr, { pointerDecode: genericPointerType, includeAnnotations: false }).next();
const s2 = new Decoder(samples_bin, { pointerDecode: genericPointerType, includeAnnotations: false }).next();
expect(s1).is(s2);
});
it('should read equal to decoded binary with annotations', () => {
const s1 = new Reader(samples_pr, { pointerType: genericPointerType, includeAnnotations: true }).next();
const s2 = new Decoder(samples_bin, { pointerType: genericPointerType, includeAnnotations: true }).next();
const s1 = new Reader(samples_pr, { pointerDecode: genericPointerType, includeAnnotations: true }).next();
const s2 = new Decoder(samples_bin, { pointerDecode: genericPointerType, includeAnnotations: true }).next();
expect(s1).is(s2);
});
it('should read and encode back to binary with annotations', () => {
const s = new Reader(samples_pr, { pointerType: genericPointerType, includeAnnotations: true }).next();
const s = new Reader(samples_pr, { pointerDecode: genericPointerType, includeAnnotations: true }).next();
const bs = Bytes.toIO(encode(s, {
pointerType: genericPointerType,
pointerEncode: genericPointerType,
includeAnnotations: true,
canonical: true,
}));

View File

@ -36,3 +36,9 @@ describe('fold', () => {
expect(mapPointers(v, _t => embed(w1))).is(v1);
});
});
describe('fromJS', () => {
it('should map integers to themselves', () => {
expect(fromJS(1)).toBe(1);
});
});