Explicit wrapper for Pointers

This commit is contained in:
Tony Garnock-Jones 2021-04-24 21:59:52 +02:00
parent aef970dc2d
commit 8c783dbc7d
15 changed files with 280 additions and 209 deletions

View File

@ -7,13 +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";
export interface DecoderOptions {
includeAnnotations?: boolean;
}
export interface DecoderPointerOptions<T> extends DecoderOptions {
decodePointer?(d: TypedDecoder<never>): T;
pointerType?: PointerType<T>;
}
export interface TypedDecoder<T> {
@ -24,13 +25,14 @@ export interface TypedDecoder<T> {
skip(): void;
next(): Value<T>;
withPointerDecoder<S, R>(decodePointer: (d: TypedDecoder<T>) => S,
body: (d: TypedDecoder<S>) => R): R;
withPointerType<S, R>(
pointerType: PointerType<S>,
body: (d: TypedDecoder<S>) => R): R;
nextBoolean(): boolean | undefined;
nextFloat(): SingleFloat | undefined;
nextDouble(): DoubleFloat | undefined;
nextPointer(): T | undefined;
nextPointer(): Pointer<T> | undefined;
nextSignedInteger(): number | undefined;
nextString(): string | undefined;
nextByteString(): Bytes | undefined;
@ -51,10 +53,6 @@ export function asLiteral<T, E extends Exclude<Value<T>, Annotated<T>>>(
return is(actual, expected) ? expected : void 0;
}
function _defaultDecodePointer<T>(): T {
throw new DecodeError("No decodePointer function supplied");
}
export class DecoderState {
packet: Uint8Array;
index = 0;
@ -163,25 +161,23 @@ export class DecoderState {
}
}
type DecoderFn<T> = (d: DecoderState) => T;
export class Decoder<T = never> implements TypedDecoder<T> {
state: DecoderState;
decodePointer: DecoderFn<T> = _defaultDecodePointer;
pointerType: PointerType<T>;
constructor(state: DecoderState, decodePointer: DecoderFn<T>);
constructor(packet?: BytesLike, options?: DecoderOptions);
constructor(state: DecoderState, pointerType?: PointerType<T>);
constructor(packet?: BytesLike, options?: DecoderPointerOptions<T>);
constructor(
packet_or_state: (DecoderState | BytesLike) = new Uint8Array(0),
options_or_decoder?: (DecoderOptions | DecoderFn<T>))
options_or_pointerType?: (DecoderPointerOptions<T> | PointerType<T>))
{
if (packet_or_state instanceof DecoderState) {
this.state = packet_or_state;
this.decodePointer = options_or_decoder as DecoderFn<T>;
this.pointerType = (options_or_pointerType as PointerType<T>) ?? neverPointerType;
} else {
this.state = new DecoderState(
packet_or_state,
(options_or_decoder as DecoderOptions) ?? {});
const options = (options_or_pointerType as DecoderPointerOptions<T>) ?? {};
this.state = new DecoderState(packet_or_state, options);
this.pointerType = options.pointerType ?? neverPointerType;
}
}
@ -217,7 +213,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>(this.decodePointer(this.state));
case Tag.Pointer: return this.state.wrap<T>(embed(this.pointerType.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())));
@ -261,14 +257,11 @@ export class Decoder<T = never> implements TypedDecoder<T> {
this.next();
}
pushPointerDecoder<S>(decodePointer: (d: TypedDecoder<T>) => S): Decoder<S> {
return new Decoder<S>(this.state, (_s: DecoderState) => decodePointer(this));
}
withPointerDecoder<S, R>(decodePointer: (d: TypedDecoder<T>) => S,
body: (d: TypedDecoder<S>) => R): R
withPointerType<S, R>(
pointerType: PointerType<S>,
body: (d: TypedDecoder<S>) => R): R
{
return body(this.pushPointerDecoder(decodePointer));
return body(new Decoder(this.state, pointerType));
}
skipAnnotations(): void {
@ -303,10 +296,10 @@ export class Decoder<T = never> implements TypedDecoder<T> {
}
}
nextPointer(): T | undefined {
nextPointer(): Pointer<T> | undefined {
this.skipAnnotations();
switch (this.state.nextbyte()) {
case Tag.Pointer: return this.decodePointer(this.state);
case Tag.Pointer: return embed(this.pointerType.decode(this.state));
default: return void 0;
}
}
@ -372,9 +365,7 @@ export class Decoder<T = never> implements TypedDecoder<T> {
}
export function decode<T>(bs: BytesLike, options: DecoderPointerOptions<T> = {}): Value<T> {
return new Decoder<never>(bs, options).withPointerDecoder<T, Value<T>>(
options.decodePointer ?? _defaultDecodePointer,
d => d.next());
return new Decoder(bs, options).next();
}
export function decodeWithAnnotations<T>(bs: BytesLike,

View File

@ -4,6 +4,7 @@ import { Value } from "./values";
import { PreserveOn } from "./symbols";
import { EncodeError } from "./codec";
import { Record, Tuple } from "./record";
import { identityPointerType, PointerType } from "./pointer";
export type Encodable<T> =
Value<T> | Preservable<T> | Iterable<Value<T>> | ArrayBufferView;
@ -22,7 +23,7 @@ export interface EncoderOptions {
}
export interface EncoderPointerOptions<T> extends EncoderOptions {
encodePointer?: EncodePointerFunction<T>;
pointerType?: PointerType<T>;
}
export function asLatin1(bs: Uint8Array): string {
@ -33,10 +34,6 @@ function isIterable<T>(v: any): v is Iterable<T> {
return typeof v === 'object' && v !== null && typeof v[Symbol.iterator] === 'function';
}
function _defaultEncodePointer<T>(s: EncoderState, v: T): void {
new Encoder(s).push(pointerId(v));
}
export class EncoderState {
chunks: Array<Uint8Array>;
view: DataView;
@ -139,31 +136,30 @@ export class EncoderState {
}
}
export type EncodePointerFunction<T> = (s: EncoderState, v: T) => void;
export class Encoder<T> {
export class Encoder<T = object> {
state: EncoderState;
encodePointer: EncodePointerFunction<T>;
pointerType: PointerType<T> | undefined;
constructor(options: EncoderOptions);
constructor(state: EncoderState, encodePointer?: EncodePointerFunction<T>);
constructor(options: EncoderPointerOptions<T>);
constructor(state: EncoderState, pointerType?: PointerType<T>);
constructor(
state_or_options: (EncoderState | EncoderOptions) = {},
encodePointer?: EncodePointerFunction<T>)
state_or_options: (EncoderState | EncoderPointerOptions<T>) = {},
pointerType?: PointerType<T>)
{
if (state_or_options instanceof EncoderState) {
this.state = state_or_options;
this.encodePointer = encodePointer ?? _defaultEncodePointer;
this.pointerType = pointerType;
} else {
this.state = new EncoderState(state_or_options);
this.encodePointer = _defaultEncodePointer;
this.pointerType = state_or_options.pointerType;
}
}
withPointerEncoder<S>(encodePointer: EncodePointerFunction<S>,
body: (e: Encoder<S>) => void): this
withPointerType<S>(
pointerType: PointerType<S>,
body: (e: Encoder<S>) => void): this
{
body(new Encoder(this.state, encodePointer));
body(new Encoder(this.state, pointerType));
return this;
}
@ -236,33 +232,25 @@ export class Encoder<T> {
}
else {
this.state.emitbyte(Tag.Pointer);
this.encodePointer(this.state, v);
(this.pointerType ?? identityPointerType).encode(this.state, v.embeddedValue);
}
return this; // for chaining
}
}
export function encode<T>(v: Encodable<T>, options: EncoderPointerOptions<T> = {}): Bytes {
return new Encoder(options).withPointerEncoder<T>(
options.encodePointer ?? _defaultEncodePointer,
e => e.push(v)).contents();
}
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 function encode<T>(
v: Encodable<T>,
options: EncoderPointerOptions<T> = {}): Bytes
{
return new Encoder(options).push(v).contents();
}
const _canonicalEncoder = new Encoder({ canonical: true });
let _usingCanonicalEncoder = false;
export function canonicalEncode(v: Encodable<any>, options?: EncoderPointerOptions<any>): Bytes
{
export function canonicalEncode(v: Encodable<never>, options?: EncoderPointerOptions<never>): Bytes;
export function canonicalEncode(v: Encodable<any>, options?: EncoderPointerOptions<any>): Bytes;
export function canonicalEncode(v: any, options?: EncoderPointerOptions<any>): Bytes {
if (options === void 0 && !_usingCanonicalEncoder) {
_usingCanonicalEncoder = true;
const bs = _canonicalEncoder.push(v).contents();

View File

@ -4,6 +4,7 @@ import { Value } from "./values";
import { Set, Dictionary } from "./dictionary";
import { annotate, Annotated } from "./annotated";
import { Double, Float, Single } from "./float";
import { Pointer } from "./pointer";
export type Fold<T, R = Value<T>> = (v: Value<T>) => R;
@ -23,7 +24,7 @@ export interface FoldMethods<T, R> {
annotated(a: Annotated<T>, k: Fold<T, R>): R;
pointer(t: T, k: Fold<T, R>): R;
pointer(t: Pointer<T>, k: Fold<T, R>): R;
}
export abstract class ValueFold<T, R = T> implements FoldMethods<T, Value<R>> {
@ -63,16 +64,16 @@ export abstract class ValueFold<T, R = T> implements FoldMethods<T, Value<R>> {
annotated(a: Annotated<T>, k: Fold<T, Value<R>>): Value<R> {
return annotate(k(a.item), ...a.annotations.map(k));
}
abstract pointer(t: T, k: Fold<T, Value<R>>): Value<R>;
abstract pointer(t: Pointer<T>, k: Fold<T, Value<R>>): Value<R>;
}
export class IdentityFold<T> extends ValueFold<T, T> {
pointer(t: T, _k: Fold<T, Value<T>>): Value<T> {
pointer(t: Pointer<T>, _k: Fold<T, Value<T>>): Value<T> {
return t;
}
}
export class MapFold<T, R extends object> extends ValueFold<T, R> {
export class MapFold<T, R> extends ValueFold<T, R> {
readonly f: (t: T) => Value<R>;
constructor(f: (t: T) => Value<R>) {
@ -80,12 +81,12 @@ export class MapFold<T, R extends object> extends ValueFold<T, R> {
this.f = f;
}
pointer(t: T, _k: Fold<T, Value<R>>): Value<R> {
return this.f(t);
pointer(t: Pointer<T>, _k: Fold<T, Value<R>>): Value<R> {
return this.f(t.embeddedValue);
}
}
export const IDENTITY_FOLD = new IdentityFold();
export const IDENTITY_FOLD = new IdentityFold<any>();
export function fold<T, R>(v: Value<T>, o: FoldMethods<T, R>): R {
const walk = (v: Value<T>): R => {
@ -121,44 +122,19 @@ export function fold<T, R>(v: Value<T>, o: FoldMethods<T, R>): R {
} else if (Float.isDouble(v)) {
return o.double(v.value);
} else {
/* fall through */
return o.pointer(v, walk);
}
default:
return o.pointer(v, walk);
((_v: never): never => { throw new Error("Internal error"); })(v);
}
};
return walk(v);
}
export function mapPointers<T, R extends object>(
export function mapPointers<T, R>(
v: Value<T>,
f: (t: T) => Value<R>,
): Value<R>
{
return fold(v, new MapFold(f));
}
export function isPointer<T>(v: Value<T>): v is T {
return fold(v, {
boolean(_b: boolean): boolean { return false; },
single(_f: number): boolean { return false; },
double(_f: number): boolean { return false; },
integer(_i: number): boolean { return false; },
string(_s: string): boolean { return false; },
bytes(_b: Bytes): boolean { return false; },
symbol(_s: symbol): boolean { return false; },
record(_r: Record<Value<T>, Tuple<Value<T>>, T>, _k: Fold<T, boolean>): boolean {
return false;
},
array(_a: Array<Value<T>>, _k: Fold<T, boolean>): boolean { return false; },
set(_s: Set<T>, _k: Fold<T, boolean>): boolean { return false; },
dictionary(_d: Dictionary<T>, _k: Fold<T, boolean>): boolean {
return false;
},
annotated(_a: Annotated<T>, _k: Fold<T, boolean>): boolean { return false; },
pointer(_t: T, _k: Fold<T, boolean>): boolean { return true; },
});
}

View File

@ -1,8 +1,9 @@
import { DefaultPointer } from "./pointer";
import { embed, DefaultPointer } from "./pointer";
import { Bytes } from "./bytes";
import { Record, Tuple } from "./record";
import { AsPreserve } from "./symbols";
import { Value } from "./values";
import { Dictionary, Set } from "./dictionary";
export function fromJS<T = DefaultPointer>(x: any): Value<T> {
switch (typeof x) {
@ -38,8 +39,18 @@ export function fromJS<T = DefaultPointer>(x: any): Value<T> {
if (ArrayBuffer.isView(x) || x instanceof ArrayBuffer) {
return Bytes.from(x);
}
if (Map.isMap(x)) {
const d = new Dictionary<T>();
x.forEach((v, k) => d.set(fromJS(k), fromJS(v)));
return d;
}
if (Set.isSet(x)) {
const s = new Set<T>();
x.forEach(v => s.add(fromJS(v)));
return s;
}
// Just... assume it's a T.
return (x as T);
return embed(x as T);
default:
break;

View File

@ -1,5 +1,5 @@
import type { DefaultPointer } from "./pointer.js";
import type { Annotated } from "./annotated.js";
import type { DefaultPointer } from "./pointer";
import type { Annotated } from "./annotated";
export const IsPreservesAnnotated = Symbol.for('IsPreservesAnnotated');

View File

@ -1,11 +1,12 @@
import { Record, Tuple } from "./record";
import { Bytes } from "./bytes";
import { fold, isPointer } from "./fold";
import { fold } from "./fold";
import { is } from "./is";
import { Value } from "./values";
import { Set, Dictionary } from "./dictionary";
import { Annotated } from "./annotated";
import { unannotate } from "./strip";
import { embed, isPointer, Pointer } from "./pointer";
export function merge<T>(
mergePointers: (a: T, b: T) => T | undefined,
@ -53,11 +54,11 @@ export function merge<T>(
return walk(a, unannotate(b));
},
pointer(t: T) {
if (!isPointer(b)) die();
const r = mergePointers(t, b);
pointer(t: Pointer<T>) {
if (!isPointer<T>(b)) die();
const r = mergePointers(t.embeddedValue, b.embeddedValue);
if (r === void 0) die();
return r;
return embed(r);
},
});
}

View File

@ -1,33 +1,117 @@
import { Encoder, EncoderState } from "./encoder";
import type { TypedDecoder } from "./decoder";
import { Decoder, DecoderState } from "./decoder";
import type { Value } from "./values";
import { strip } from "./strip";
export class DefaultPointer {
v: Value<DefaultPointer>;
export type PointerType<T> = {
decode(s: DecoderState): T;
encode(s: EncoderState, v: T): void;
constructor(v: Value<DefaultPointer>) {
this.v = v;
fromValue(v: Value<DefaultPointer>): T;
toValue(v: T): Value<DefaultPointer>;
}
export class Pointer<T> {
embeddedValue: T;
constructor(embeddedValue: T) {
this.embeddedValue = embeddedValue;
}
equals(other: any, is: (a: any, b: any) => boolean) {
return Object.is(other.constructor, this.constructor) && is(this.v, other.v);
return isPointer<T>(other) && is(this.embeddedValue, other.embeddedValue);
}
asPreservesText(): string {
return '#!' + this.v.asPreservesText();
return '#!' + (this.embeddedValue as any).asPreservesText();
}
}
export function readDefaultPointer(v: Value<DefaultPointer>): DefaultPointer {
return new DefaultPointer(strip(v));
export function embed<T>(embeddedValue: T): Pointer<T> {
return new Pointer(embeddedValue);
}
export function decodeDefaultPointer<T>(d: TypedDecoder<T>): DefaultPointer {
return readDefaultPointer(d.withPointerDecoder(decodeDefaultPointer, d => d.next()));
export function isPointer<T>(v: Value<T>): v is Pointer<T> {
return typeof v === 'object' && 'embeddedValue' in v;
}
export function encodeDefaultPointer(e: EncoderState, w: DefaultPointer): void {
new Encoder(e, encodeDefaultPointer).push(w.v);
export class DefaultPointer {
generic: Value;
constructor(generic: Value) {
this.generic = generic;
}
equals(other: any, is: (a: any, b: any) => boolean) {
return typeof other === 'object' && 'generic' in other && is(this.generic, other.generic);
}
asPreservesText(): string {
return this.generic.asPreservesText();
}
}
export const genericPointerType: PointerType<DefaultPointer> = {
decode(s: DecoderState): DefaultPointer {
return new DefaultPointer(new Decoder(s, this).next());
},
encode(s: EncoderState, v: DefaultPointer): void {
new Encoder(s, this).push(v.generic);
},
fromValue(v: Value<DefaultPointer>): DefaultPointer {
return new DefaultPointer(strip(v));
},
toValue(v: DefaultPointer): Value<DefaultPointer> {
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<DefaultPointer>): never {
throw new Error("Pointers not permitted at this point in Preserves document");
},
toValue(_v: never): Value<DefaultPointer> {
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<DefaultPointer>): any {
throw new Error("Cannot decode identityPointerType");
},
toValue(v: any): Value<DefaultPointer> {
return pointerId(v);
}
};

View File

@ -10,17 +10,15 @@ import { Record } from './record';
import { Annotated, newPosition, Position, updatePosition } from './annotated';
import { Double, DoubleFloat, Single, SingleFloat } from './float';
import { stringify } from './text';
import { decodeDefaultPointer, DefaultPointer, readDefaultPointer } from './pointer';
import { embed, DefaultPointer, genericPointerType, neverPointerType, PointerType } from './pointer';
export interface ReaderStateOptions {
includeAnnotations?: boolean;
name?: string | Position;
}
export type DecodePointerFunction<T> = (v: Value<DefaultPointer>) => T;
export interface ReaderOptions<T> extends ReaderStateOptions {
decodePointer?: DecodePointerFunction<T>;
pointerType?: PointerType<T>;
}
type IntOrFloat = 'int' | 'float';
@ -279,21 +277,21 @@ export class ReaderState {
export class Reader<T> {
state: ReaderState;
decodePointer?: DecodePointerFunction<T>;
pointerType: PointerType<T>;
constructor(state: ReaderState, decodePointer?: DecodePointerFunction<T>);
constructor(state: ReaderState, pointerType: PointerType<T>);
constructor(buffer: string, options?: ReaderOptions<T>);
constructor(
state_or_buffer: (ReaderState | string) = '',
decodePointer_or_options?: (DecodePointerFunction<T> | ReaderOptions<T>))
pointerType_or_options?: (PointerType<T> | ReaderOptions<T>))
{
if (state_or_buffer instanceof ReaderState) {
this.state = state_or_buffer;
this.decodePointer = decodePointer_or_options as DecodePointerFunction<T>;
this.pointerType = pointerType_or_options as PointerType<T>;
} else {
const options = (decodePointer_or_options as ReaderOptions<T>) ?? {};
const options = (pointerType_or_options as ReaderOptions<T>) ?? {};
this.state = new ReaderState(state_or_buffer, options);
this.decodePointer = options.decodePointer;
this.pointerType = options.pointerType ?? neverPointerType;
}
}
@ -380,16 +378,12 @@ export class Reader<T> {
if (!Bytes.isBytes(bs)) this.state.error('ByteString must follow #=',
startPos);
return decode<T>(bs, {
decodePointer: <never>decodeDefaultPointer,
pointerType: this.pointerType,
includeAnnotations: this.state.options.includeAnnotations,
});
}
case '!': {
if (this.decodePointer === void 0) {
this.state.error("No decodePointer function supplied", startPos);
}
return this.decodePointer(new Reader(this.state, readDefaultPointer).next());
}
case '!': return embed(this.pointerType.fromValue(
new Reader<DefaultPointer>(this.state, genericPointerType).next()));
default:
this.state.error(`Invalid # syntax: ${c}`, startPos);
}

View File

@ -12,7 +12,7 @@ export function stringify(x: any): string {
}
}
export function preserves(pieces: TemplateStringsArray, ...values: Value<any>[]): string {
export function preserves<T>(pieces: TemplateStringsArray, ...values: Value<T>[]): string {
const result = [pieces[0]];
values.forEach((v, i) => {
result.push(stringify(v));
@ -29,7 +29,9 @@ declare global {
Object.defineProperty(Object.prototype, 'asPreservesText', {
enumerable: false,
writable: true,
value: function(): string { return '#!' + JSON.stringify(this); }
value: function(): string {
return JSON.stringify(this);
}
});
Boolean.prototype.asPreservesText = function (): string {

View File

@ -4,12 +4,12 @@ import type { Bytes } from './bytes';
import type { DoubleFloat, SingleFloat } from './float';
import type { Annotated } from './annotated';
import type { Set, Dictionary } from './dictionary';
import type { DefaultPointer } from './pointer';
import type { Pointer, DefaultPointer } from './pointer';
export type Value<T = DefaultPointer> =
| Atom
| Compound<T>
| T
| Pointer<T>
| Annotated<T>;
export type Atom =
| boolean

View File

@ -9,12 +9,15 @@ import {
preserves,
fromJS,
Constants,
TypedDecoder,
Encoder,
DefaultPointer,
decodeDefaultPointer,
encodeDefaultPointer,
EncoderState,
PointerType,
DecoderState,
Decoder,
Pointer,
embed,
genericPointerType,
} from '../src/index';
const { Tag } = Constants;
import './test-utils';
@ -84,9 +87,43 @@ describe('reusing buffer space', () => {
});
describe('encoding and decoding pointers', () => {
class LookasidePointerType implements PointerType<object> {
readonly objects: object[];
constructor(objects: object[]) {
this.objects = objects;
}
decode(d: DecoderState): object {
return this.fromValue(new Decoder<DefaultPointer>(d).next());
}
encode(e: EncoderState, v: object): void {
new Encoder(e).push(this.toValue(v));
}
equals(a: object, b: object): boolean {
return Object.is(a, b);
}
fromValue(v: Value<DefaultPointer>): object {
if (typeof v !== 'number' || v < 0 || v >= this.objects.length) {
throw new Error("Unknown pointer target");
}
return this.objects[v];
}
toValue(v: object): number {
let i = this.objects.indexOf(v);
if (i !== -1) return i;
this.objects.push(v);
return this.objects.length - 1;
}
}
it('should encode using pointerId when no function has been supplied', () => {
const A1 = ({a: 1});
const A2 = ({a: 1});
const A1 = embed({a: 1});
const A2 = embed({a: 1});
const bs1 = canonicalEncode(A1);
const bs2 = canonicalEncode(A2);
const bs3 = canonicalEncode(A1);
@ -100,62 +137,53 @@ describe('encoding and decoding pointers', () => {
});
it('should refuse to decode pointers when no function has been supplied', () => {
expect(() => decode(Bytes.from([Tag.Pointer, Tag.SmallInteger_lo])))
.toThrow('No decodePointer function supplied');
.toThrow("Pointers not permitted at this point in Preserves document");
});
it('should encode properly', () => {
const objects: object[] = [];
const A = {a: 1};
const B = {b: 2};
expect(encode(
[A, B],
{
encodePointer(e: EncoderState, v: object): void {
objects.push(v);
new Encoder(e, encodeDefaultPointer).push(objects.length - 1);
}
})).is(Bytes.from([Tag.Sequence,
Tag.Pointer, Tag.SmallInteger_lo,
Tag.Pointer, Tag.SmallInteger_lo + 1,
Tag.End]));
expect(objects).is([A, B]);
const pt = new LookasidePointerType(objects);
const A = embed({a: 1});
const B = embed({b: 2});
expect(encode([A, B], { pointerType: pt })).is(
Bytes.from([Tag.Sequence,
Tag.Pointer, Tag.SmallInteger_lo,
Tag.Pointer, Tag.SmallInteger_lo + 1,
Tag.End]));
expect(objects).toEqual([A.embeddedValue, B.embeddedValue]);
});
it('should decode properly', () => {
const X = {x: 123};
const Y = {y: 456};
const objects: object[] = [X, Y];
const objects: object[] = [];
const pt = new LookasidePointerType(objects);
const X: Pointer<object> = embed({x: 123});
const Y: Pointer<object> = embed({y: 456});
objects.push(X.embeddedValue);
objects.push(Y.embeddedValue);
expect(decode(Bytes.from([
Tag.Sequence,
Tag.Pointer, Tag.SmallInteger_lo,
Tag.Pointer, Tag.SmallInteger_lo + 1,
Tag.End
]), {
decodePointer(d: TypedDecoder<never>): object {
const v = d.next();
if (typeof v !== 'number' || v < 0 || v >= objects.length) {
throw new Error("Unknown pointer target");
}
return objects[v];
}
})).is([X, Y]);
]), { pointerType: pt })).is([X, Y]);
});
it('should store pointers embedded in map keys correctly', () => {
const A1 = ({a: 1});
const A2 = ({a: 1});
const A1a = {a: 1};
const A1: Pointer<object> = embed(A1a);
const A2: Pointer<object> = embed({a: 1});
const m = new Dictionary<object, number>();
m.set([A1], 1);
m.set([A2], 2);
expect(m.get(A1)).toBeUndefined();
expect(m.get([A1])).toBe(1);
expect(m.get([A2])).toBe(2);
expect(m.get([{a: 1}])).toBeUndefined();
A1.a = 3;
expect(m.get([embed({a: 1})])).toBeUndefined();
A1a.a = 3;
expect(m.get([A1])).toBe(1);
});
});
describe('common test suite', () => {
const samples_bin = fs.readFileSync(__dirname + '/../../../../../tests/samples.bin');
const samples = decodeWithAnnotations(samples_bin, { decodePointer: decodeDefaultPointer });
const samples = decodeWithAnnotations(samples_bin, { pointerType: genericPointerType });
const TestCases = Record.makeConstructor<{
cases: Dictionary<DefaultPointer>
@ -163,13 +191,13 @@ describe('common test suite', () => {
type TestCases = ReturnType<typeof TestCases>;
function DS(bs: Bytes) {
return decode(bs, { decodePointer: decodeDefaultPointer });
return decode(bs, { pointerType: genericPointerType });
}
function D(bs: Bytes) {
return decodeWithAnnotations(bs, { decodePointer: decodeDefaultPointer });
return decodeWithAnnotations(bs, { pointerType: genericPointerType });
}
function E(v: Value<DefaultPointer>) {
return encodeWithAnnotations(v, { encodePointer: encodeDefaultPointer });
return encodeWithAnnotations(v, { pointerType: genericPointerType });
}
interface ExpectedValues {

View File

@ -1,4 +1,4 @@
import { Bytes, decodeDefaultPointer, Decoder, encode, encodeDefaultPointer, readDefaultPointer, Reader } from '../src/index';
import { Bytes, Decoder, genericPointerType, encode, Reader } from '../src/index';
import './test-utils';
import * as fs from 'fs';
@ -8,25 +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, { decodePointer: readDefaultPointer, includeAnnotations: false }).next();
const s2 = new Decoder(samples_bin, { includeAnnotations: false }).withPointerDecoder(
decodeDefaultPointer,
d => d.next());
const s1 = new Reader(samples_pr, { pointerType: genericPointerType, includeAnnotations: false }).next();
const s2 = new Decoder(samples_bin, { pointerType: genericPointerType, includeAnnotations: false }).next();
expect(s1).is(s2);
});
it('should read equal to decoded binary with annotations', () => {
const s1 = new Reader(samples_pr, { decodePointer: readDefaultPointer, includeAnnotations: true }).next();
const s2 = new Decoder(samples_bin, { includeAnnotations: true }).withPointerDecoder(
decodeDefaultPointer,
d => d.next());
const s1 = new Reader(samples_pr, { pointerType: genericPointerType, includeAnnotations: true }).next();
const s2 = new Decoder(samples_bin, { pointerType: genericPointerType, includeAnnotations: true }).next();
expect(s1).is(s2);
});
it('should read and encode back to binary with annotations', () => {
const s = new Reader(samples_pr, { decodePointer: readDefaultPointer, includeAnnotations: true }).next();
const s = new Reader(samples_pr, { pointerType: genericPointerType, includeAnnotations: true }).next();
const bs = Bytes.toIO(encode(s, {
encodePointer: encodeDefaultPointer,
pointerType: genericPointerType,
includeAnnotations: true,
canonical: true,
}));

View File

@ -1,10 +1,10 @@
import { Value, is, preserves, strip, TypedDecoder, Encoder } from '../src/index';
import { Value, is, preserves } from '../src/index';
import '../src/node_support';
declare global {
namespace jest {
interface Matchers<R> {
is<T extends object>(expected: Value<T>): R;
is<T>(expected: Value<T>): R;
toThrowFilter(f: (e: Error) => boolean): R;
}
}

View File

@ -1,4 +1,4 @@
import { Single, Double, fromJS, Dictionary, IDENTITY_FOLD, fold, mapPointers, Value } from '../src/index';
import { Single, Double, fromJS, Dictionary, IDENTITY_FOLD, fold, mapPointers, Value, embed } from '../src/index';
import './test-utils';
describe('Single', () => {
@ -33,6 +33,6 @@ describe('fold', () => {
const w1 = new Date();
const v1 = mkv(w1);
expect(fold(v, IDENTITY_FOLD)).not.is(v1);
expect(mapPointers(v, _t => w1)).is(v1);
expect(mapPointers(v, _t => embed(w1))).is(v1);
});
});

View File

@ -1,7 +1,7 @@
import { Annotated, Bytes, Dictionary, Fold, fold, Record, Tuple, Value, stringify } from "@preserves/core";
import { Annotated, Bytes, Set, Dictionary, Fold, fold, Record, Tuple, Value, stringify } from "@preserves/core";
import { brackets, Item, parens, seq } from "./block";
export function sourceCodeFor(v: Value<any>): Item {
export function sourceCodeFor(v: Value<never>): Item {
return fold(v, {
boolean(b: boolean): Item { return b.toString(); },
single(f: number): Item { return f.toString(); },
@ -13,25 +13,25 @@ export function sourceCodeFor(v: Value<any>): Item {
},
symbol(s: symbol): Item { return `Symbol.for(${JSON.stringify(s.description!)})`; },
record(r: Record<Value<any>, Tuple<Value<any>>, any>, k: Fold<any, Item>): Item {
record(r: Record<Value<never>, Tuple<Value<never>>, never>, k: Fold<never, Item>): Item {
return seq(`_.Record<_val, _.Tuple<_val>, _ptr>`, parens(k(r.label), brackets(... r.map(k))));
},
array(a: Array<Value<any>>, k: Fold<any, Item>): Item {
array(a: Array<Value<never>>, k: Fold<never, Item>): Item {
return brackets(... a.map(k));
},
set(s: Set<any>, k: Fold<any, Item>): Item {
set(s: Set<never>, k: Fold<never, Item>): Item {
return seq('new _.Set<_val>', parens(brackets(... Array.from(s).map(k))));
},
dictionary(d: Dictionary<any>, k: Fold<any, Item>): Item {
dictionary(d: Dictionary<never>, k: Fold<never, Item>): Item {
return seq('new _.Dictionary<_ptr>', parens(brackets(... Array.from(d).map(([kk,vv]) =>
brackets(k(kk), k(vv))))));
},
annotated(a: Annotated<any>, k: Fold<any, Item>): Item {
annotated(a: Annotated<never>, k: Fold<never, Item>): Item {
return seq('_.annotate<_ptr>', parens(k(a.item), ... a.annotations.map(k)));
},
pointer(t: any, _k: Fold<any, Item>): Item {
pointer(t: never, _k: Fold<never, Item>): Item {
throw new Error(`Cannot emit source code for construction of pointer ${stringify(t)}`);
},
});