forked from syndicate-lang/preserves
Explicit wrapper for Pointers
This commit is contained in:
parent
aef970dc2d
commit
8c783dbc7d
|
@ -7,13 +7,14 @@ import { Record } from "./record";
|
||||||
import { Bytes, BytesLike, underlying } from "./bytes";
|
import { Bytes, BytesLike, underlying } from "./bytes";
|
||||||
import { Value } from "./values";
|
import { Value } from "./values";
|
||||||
import { is } from "./is";
|
import { is } from "./is";
|
||||||
|
import { embed, neverPointerType, Pointer, PointerType } from "./pointer";
|
||||||
|
|
||||||
export interface DecoderOptions {
|
export interface DecoderOptions {
|
||||||
includeAnnotations?: boolean;
|
includeAnnotations?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DecoderPointerOptions<T> extends DecoderOptions {
|
export interface DecoderPointerOptions<T> extends DecoderOptions {
|
||||||
decodePointer?(d: TypedDecoder<never>): T;
|
pointerType?: PointerType<T>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TypedDecoder<T> {
|
export interface TypedDecoder<T> {
|
||||||
|
@ -24,13 +25,14 @@ export interface TypedDecoder<T> {
|
||||||
|
|
||||||
skip(): void;
|
skip(): void;
|
||||||
next(): Value<T>;
|
next(): Value<T>;
|
||||||
withPointerDecoder<S, R>(decodePointer: (d: TypedDecoder<T>) => S,
|
withPointerType<S, R>(
|
||||||
body: (d: TypedDecoder<S>) => R): R;
|
pointerType: PointerType<S>,
|
||||||
|
body: (d: TypedDecoder<S>) => R): R;
|
||||||
|
|
||||||
nextBoolean(): boolean | undefined;
|
nextBoolean(): boolean | undefined;
|
||||||
nextFloat(): SingleFloat | undefined;
|
nextFloat(): SingleFloat | undefined;
|
||||||
nextDouble(): DoubleFloat | undefined;
|
nextDouble(): DoubleFloat | undefined;
|
||||||
nextPointer(): T | undefined;
|
nextPointer(): Pointer<T> | undefined;
|
||||||
nextSignedInteger(): number | undefined;
|
nextSignedInteger(): number | undefined;
|
||||||
nextString(): string | undefined;
|
nextString(): string | undefined;
|
||||||
nextByteString(): Bytes | 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;
|
return is(actual, expected) ? expected : void 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
function _defaultDecodePointer<T>(): T {
|
|
||||||
throw new DecodeError("No decodePointer function supplied");
|
|
||||||
}
|
|
||||||
|
|
||||||
export class DecoderState {
|
export class DecoderState {
|
||||||
packet: Uint8Array;
|
packet: Uint8Array;
|
||||||
index = 0;
|
index = 0;
|
||||||
|
@ -163,25 +161,23 @@ export class DecoderState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type DecoderFn<T> = (d: DecoderState) => T;
|
|
||||||
|
|
||||||
export class Decoder<T = never> implements TypedDecoder<T> {
|
export class Decoder<T = never> implements TypedDecoder<T> {
|
||||||
state: DecoderState;
|
state: DecoderState;
|
||||||
decodePointer: DecoderFn<T> = _defaultDecodePointer;
|
pointerType: PointerType<T>;
|
||||||
|
|
||||||
constructor(state: DecoderState, decodePointer: DecoderFn<T>);
|
constructor(state: DecoderState, pointerType?: PointerType<T>);
|
||||||
constructor(packet?: BytesLike, options?: DecoderOptions);
|
constructor(packet?: BytesLike, options?: DecoderPointerOptions<T>);
|
||||||
constructor(
|
constructor(
|
||||||
packet_or_state: (DecoderState | BytesLike) = new Uint8Array(0),
|
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) {
|
if (packet_or_state instanceof DecoderState) {
|
||||||
this.state = packet_or_state;
|
this.state = packet_or_state;
|
||||||
this.decodePointer = options_or_decoder as DecoderFn<T>;
|
this.pointerType = (options_or_pointerType as PointerType<T>) ?? neverPointerType;
|
||||||
} else {
|
} else {
|
||||||
this.state = new DecoderState(
|
const options = (options_or_pointerType as DecoderPointerOptions<T>) ?? {};
|
||||||
packet_or_state,
|
this.state = new DecoderState(packet_or_state, options);
|
||||||
(options_or_decoder as DecoderOptions) ?? {});
|
this.pointerType = options.pointerType ?? neverPointerType;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -217,7 +213,7 @@ export class Decoder<T = never> implements TypedDecoder<T> {
|
||||||
const v = this.next() as Annotated<T>;
|
const v = this.next() as Annotated<T>;
|
||||||
return this.state.unshiftAnnotation(a, v);
|
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.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.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())));
|
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();
|
this.next();
|
||||||
}
|
}
|
||||||
|
|
||||||
pushPointerDecoder<S>(decodePointer: (d: TypedDecoder<T>) => S): Decoder<S> {
|
withPointerType<S, R>(
|
||||||
return new Decoder<S>(this.state, (_s: DecoderState) => decodePointer(this));
|
pointerType: PointerType<S>,
|
||||||
}
|
body: (d: TypedDecoder<S>) => R): R
|
||||||
|
|
||||||
withPointerDecoder<S, R>(decodePointer: (d: TypedDecoder<T>) => S,
|
|
||||||
body: (d: TypedDecoder<S>) => R): R
|
|
||||||
{
|
{
|
||||||
return body(this.pushPointerDecoder(decodePointer));
|
return body(new Decoder(this.state, pointerType));
|
||||||
}
|
}
|
||||||
|
|
||||||
skipAnnotations(): void {
|
skipAnnotations(): void {
|
||||||
|
@ -303,10 +296,10 @@ export class Decoder<T = never> implements TypedDecoder<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
nextPointer(): T | undefined {
|
nextPointer(): Pointer<T> | undefined {
|
||||||
this.skipAnnotations();
|
this.skipAnnotations();
|
||||||
switch (this.state.nextbyte()) {
|
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;
|
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> {
|
export function decode<T>(bs: BytesLike, options: DecoderPointerOptions<T> = {}): Value<T> {
|
||||||
return new Decoder<never>(bs, options).withPointerDecoder<T, Value<T>>(
|
return new Decoder(bs, options).next();
|
||||||
options.decodePointer ?? _defaultDecodePointer,
|
|
||||||
d => d.next());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function decodeWithAnnotations<T>(bs: BytesLike,
|
export function decodeWithAnnotations<T>(bs: BytesLike,
|
||||||
|
|
|
@ -4,6 +4,7 @@ import { Value } from "./values";
|
||||||
import { PreserveOn } from "./symbols";
|
import { PreserveOn } from "./symbols";
|
||||||
import { EncodeError } from "./codec";
|
import { EncodeError } from "./codec";
|
||||||
import { Record, Tuple } from "./record";
|
import { Record, Tuple } from "./record";
|
||||||
|
import { identityPointerType, PointerType } from "./pointer";
|
||||||
|
|
||||||
export type Encodable<T> =
|
export type Encodable<T> =
|
||||||
Value<T> | Preservable<T> | Iterable<Value<T>> | ArrayBufferView;
|
Value<T> | Preservable<T> | Iterable<Value<T>> | ArrayBufferView;
|
||||||
|
@ -22,7 +23,7 @@ export interface EncoderOptions {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface EncoderPointerOptions<T> extends EncoderOptions {
|
export interface EncoderPointerOptions<T> extends EncoderOptions {
|
||||||
encodePointer?: EncodePointerFunction<T>;
|
pointerType?: PointerType<T>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function asLatin1(bs: Uint8Array): string {
|
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';
|
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 {
|
export class EncoderState {
|
||||||
chunks: Array<Uint8Array>;
|
chunks: Array<Uint8Array>;
|
||||||
view: DataView;
|
view: DataView;
|
||||||
|
@ -139,31 +136,30 @@ export class EncoderState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export type EncodePointerFunction<T> = (s: EncoderState, v: T) => void;
|
export class Encoder<T = object> {
|
||||||
|
|
||||||
export class Encoder<T> {
|
|
||||||
state: EncoderState;
|
state: EncoderState;
|
||||||
encodePointer: EncodePointerFunction<T>;
|
pointerType: PointerType<T> | undefined;
|
||||||
|
|
||||||
constructor(options: EncoderOptions);
|
constructor(options: EncoderPointerOptions<T>);
|
||||||
constructor(state: EncoderState, encodePointer?: EncodePointerFunction<T>);
|
constructor(state: EncoderState, pointerType?: PointerType<T>);
|
||||||
constructor(
|
constructor(
|
||||||
state_or_options: (EncoderState | EncoderOptions) = {},
|
state_or_options: (EncoderState | EncoderPointerOptions<T>) = {},
|
||||||
encodePointer?: EncodePointerFunction<T>)
|
pointerType?: PointerType<T>)
|
||||||
{
|
{
|
||||||
if (state_or_options instanceof EncoderState) {
|
if (state_or_options instanceof EncoderState) {
|
||||||
this.state = state_or_options;
|
this.state = state_or_options;
|
||||||
this.encodePointer = encodePointer ?? _defaultEncodePointer;
|
this.pointerType = pointerType;
|
||||||
} else {
|
} else {
|
||||||
this.state = new EncoderState(state_or_options);
|
this.state = new EncoderState(state_or_options);
|
||||||
this.encodePointer = _defaultEncodePointer;
|
this.pointerType = state_or_options.pointerType;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
withPointerEncoder<S>(encodePointer: EncodePointerFunction<S>,
|
withPointerType<S>(
|
||||||
body: (e: Encoder<S>) => void): this
|
pointerType: PointerType<S>,
|
||||||
|
body: (e: Encoder<S>) => void): this
|
||||||
{
|
{
|
||||||
body(new Encoder(this.state, encodePointer));
|
body(new Encoder(this.state, pointerType));
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -236,33 +232,25 @@ export class Encoder<T> {
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
this.state.emitbyte(Tag.Pointer);
|
this.state.emitbyte(Tag.Pointer);
|
||||||
this.encodePointer(this.state, v);
|
(this.pointerType ?? identityPointerType).encode(this.state, v.embeddedValue);
|
||||||
}
|
}
|
||||||
return this; // for chaining
|
return this; // for chaining
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function encode<T>(v: Encodable<T>, options: EncoderPointerOptions<T> = {}): Bytes {
|
export function encode<T>(
|
||||||
return new Encoder(options).withPointerEncoder<T>(
|
v: Encodable<T>,
|
||||||
options.encodePointer ?? _defaultEncodePointer,
|
options: EncoderPointerOptions<T> = {}): Bytes
|
||||||
e => e.push(v)).contents();
|
{
|
||||||
}
|
return new Encoder(options).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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const _canonicalEncoder = new Encoder({ canonical: true });
|
const _canonicalEncoder = new Encoder({ canonical: true });
|
||||||
let _usingCanonicalEncoder = false;
|
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) {
|
if (options === void 0 && !_usingCanonicalEncoder) {
|
||||||
_usingCanonicalEncoder = true;
|
_usingCanonicalEncoder = true;
|
||||||
const bs = _canonicalEncoder.push(v).contents();
|
const bs = _canonicalEncoder.push(v).contents();
|
||||||
|
|
|
@ -4,6 +4,7 @@ import { Value } from "./values";
|
||||||
import { Set, Dictionary } from "./dictionary";
|
import { Set, Dictionary } from "./dictionary";
|
||||||
import { annotate, Annotated } from "./annotated";
|
import { annotate, Annotated } from "./annotated";
|
||||||
import { Double, Float, Single } from "./float";
|
import { Double, Float, Single } from "./float";
|
||||||
|
import { Pointer } from "./pointer";
|
||||||
|
|
||||||
export type Fold<T, R = Value<T>> = (v: Value<T>) => R;
|
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;
|
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>> {
|
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> {
|
annotated(a: Annotated<T>, k: Fold<T, Value<R>>): Value<R> {
|
||||||
return annotate(k(a.item), ...a.annotations.map(k));
|
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> {
|
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;
|
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>;
|
readonly f: (t: T) => Value<R>;
|
||||||
|
|
||||||
constructor(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;
|
this.f = f;
|
||||||
}
|
}
|
||||||
|
|
||||||
pointer(t: T, _k: Fold<T, Value<R>>): Value<R> {
|
pointer(t: Pointer<T>, _k: Fold<T, Value<R>>): Value<R> {
|
||||||
return this.f(t);
|
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 {
|
export function fold<T, R>(v: Value<T>, o: FoldMethods<T, R>): R {
|
||||||
const walk = (v: Value<T>): 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)) {
|
} else if (Float.isDouble(v)) {
|
||||||
return o.double(v.value);
|
return o.double(v.value);
|
||||||
} else {
|
} else {
|
||||||
/* fall through */
|
return o.pointer(v, walk);
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
return o.pointer(v, walk);
|
((_v: never): never => { throw new Error("Internal error"); })(v);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
return walk(v);
|
return walk(v);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function mapPointers<T, R extends object>(
|
export function mapPointers<T, R>(
|
||||||
v: Value<T>,
|
v: Value<T>,
|
||||||
f: (t: T) => Value<R>,
|
f: (t: T) => Value<R>,
|
||||||
): Value<R>
|
): Value<R>
|
||||||
{
|
{
|
||||||
return fold(v, new MapFold(f));
|
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; },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
import { DefaultPointer } from "./pointer";
|
import { embed, DefaultPointer } from "./pointer";
|
||||||
import { Bytes } from "./bytes";
|
import { Bytes } from "./bytes";
|
||||||
import { Record, Tuple } from "./record";
|
import { Record, Tuple } from "./record";
|
||||||
import { AsPreserve } from "./symbols";
|
import { AsPreserve } from "./symbols";
|
||||||
import { Value } from "./values";
|
import { Value } from "./values";
|
||||||
|
import { Dictionary, Set } from "./dictionary";
|
||||||
|
|
||||||
export function fromJS<T = DefaultPointer>(x: any): Value<T> {
|
export function fromJS<T = DefaultPointer>(x: any): Value<T> {
|
||||||
switch (typeof x) {
|
switch (typeof x) {
|
||||||
|
@ -38,8 +39,18 @@ export function fromJS<T = DefaultPointer>(x: any): Value<T> {
|
||||||
if (ArrayBuffer.isView(x) || x instanceof ArrayBuffer) {
|
if (ArrayBuffer.isView(x) || x instanceof ArrayBuffer) {
|
||||||
return Bytes.from(x);
|
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.
|
// Just... assume it's a T.
|
||||||
return (x as T);
|
return embed(x as T);
|
||||||
|
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import type { DefaultPointer } from "./pointer.js";
|
import type { DefaultPointer } from "./pointer";
|
||||||
import type { Annotated } from "./annotated.js";
|
import type { Annotated } from "./annotated";
|
||||||
|
|
||||||
export const IsPreservesAnnotated = Symbol.for('IsPreservesAnnotated');
|
export const IsPreservesAnnotated = Symbol.for('IsPreservesAnnotated');
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
import { Record, Tuple } from "./record";
|
import { Record, Tuple } from "./record";
|
||||||
import { Bytes } from "./bytes";
|
import { Bytes } from "./bytes";
|
||||||
import { fold, isPointer } from "./fold";
|
import { fold } from "./fold";
|
||||||
import { is } from "./is";
|
import { is } from "./is";
|
||||||
import { Value } from "./values";
|
import { Value } from "./values";
|
||||||
import { Set, Dictionary } from "./dictionary";
|
import { Set, Dictionary } from "./dictionary";
|
||||||
import { Annotated } from "./annotated";
|
import { Annotated } from "./annotated";
|
||||||
import { unannotate } from "./strip";
|
import { unannotate } from "./strip";
|
||||||
|
import { embed, isPointer, Pointer } from "./pointer";
|
||||||
|
|
||||||
export function merge<T>(
|
export function merge<T>(
|
||||||
mergePointers: (a: T, b: T) => T | undefined,
|
mergePointers: (a: T, b: T) => T | undefined,
|
||||||
|
@ -53,11 +54,11 @@ export function merge<T>(
|
||||||
return walk(a, unannotate(b));
|
return walk(a, unannotate(b));
|
||||||
},
|
},
|
||||||
|
|
||||||
pointer(t: T) {
|
pointer(t: Pointer<T>) {
|
||||||
if (!isPointer(b)) die();
|
if (!isPointer<T>(b)) die();
|
||||||
const r = mergePointers(t, b);
|
const r = mergePointers(t.embeddedValue, b.embeddedValue);
|
||||||
if (r === void 0) die();
|
if (r === void 0) die();
|
||||||
return r;
|
return embed(r);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,33 +1,117 @@
|
||||||
import { Encoder, EncoderState } from "./encoder";
|
import { Encoder, EncoderState } from "./encoder";
|
||||||
import type { TypedDecoder } from "./decoder";
|
import { Decoder, DecoderState } from "./decoder";
|
||||||
import type { Value } from "./values";
|
import type { Value } from "./values";
|
||||||
|
|
||||||
import { strip } from "./strip";
|
import { strip } from "./strip";
|
||||||
|
|
||||||
export class DefaultPointer {
|
export type PointerType<T> = {
|
||||||
v: Value<DefaultPointer>;
|
decode(s: DecoderState): T;
|
||||||
|
encode(s: EncoderState, v: T): void;
|
||||||
|
|
||||||
constructor(v: Value<DefaultPointer>) {
|
fromValue(v: Value<DefaultPointer>): T;
|
||||||
this.v = v;
|
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) {
|
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 {
|
asPreservesText(): string {
|
||||||
return '#!' + this.v.asPreservesText();
|
return '#!' + (this.embeddedValue as any).asPreservesText();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function readDefaultPointer(v: Value<DefaultPointer>): DefaultPointer {
|
export function embed<T>(embeddedValue: T): Pointer<T> {
|
||||||
return new DefaultPointer(strip(v));
|
return new Pointer(embeddedValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function decodeDefaultPointer<T>(d: TypedDecoder<T>): DefaultPointer {
|
export function isPointer<T>(v: Value<T>): v is Pointer<T> {
|
||||||
return readDefaultPointer(d.withPointerDecoder(decodeDefaultPointer, d => d.next()));
|
return typeof v === 'object' && 'embeddedValue' in v;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function encodeDefaultPointer(e: EncoderState, w: DefaultPointer): void {
|
export class DefaultPointer {
|
||||||
new Encoder(e, encodeDefaultPointer).push(w.v);
|
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);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
|
@ -10,17 +10,15 @@ import { Record } from './record';
|
||||||
import { Annotated, newPosition, Position, updatePosition } from './annotated';
|
import { Annotated, newPosition, Position, updatePosition } from './annotated';
|
||||||
import { Double, DoubleFloat, Single, SingleFloat } from './float';
|
import { Double, DoubleFloat, Single, SingleFloat } from './float';
|
||||||
import { stringify } from './text';
|
import { stringify } from './text';
|
||||||
import { decodeDefaultPointer, DefaultPointer, readDefaultPointer } from './pointer';
|
import { embed, DefaultPointer, genericPointerType, neverPointerType, PointerType } from './pointer';
|
||||||
|
|
||||||
export interface ReaderStateOptions {
|
export interface ReaderStateOptions {
|
||||||
includeAnnotations?: boolean;
|
includeAnnotations?: boolean;
|
||||||
name?: string | Position;
|
name?: string | Position;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type DecodePointerFunction<T> = (v: Value<DefaultPointer>) => T;
|
|
||||||
|
|
||||||
export interface ReaderOptions<T> extends ReaderStateOptions {
|
export interface ReaderOptions<T> extends ReaderStateOptions {
|
||||||
decodePointer?: DecodePointerFunction<T>;
|
pointerType?: PointerType<T>;
|
||||||
}
|
}
|
||||||
|
|
||||||
type IntOrFloat = 'int' | 'float';
|
type IntOrFloat = 'int' | 'float';
|
||||||
|
@ -279,21 +277,21 @@ export class ReaderState {
|
||||||
|
|
||||||
export class Reader<T> {
|
export class Reader<T> {
|
||||||
state: ReaderState;
|
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(buffer: string, options?: ReaderOptions<T>);
|
||||||
constructor(
|
constructor(
|
||||||
state_or_buffer: (ReaderState | string) = '',
|
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) {
|
if (state_or_buffer instanceof ReaderState) {
|
||||||
this.state = state_or_buffer;
|
this.state = state_or_buffer;
|
||||||
this.decodePointer = decodePointer_or_options as DecodePointerFunction<T>;
|
this.pointerType = pointerType_or_options as PointerType<T>;
|
||||||
} else {
|
} 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.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 #=',
|
if (!Bytes.isBytes(bs)) this.state.error('ByteString must follow #=',
|
||||||
startPos);
|
startPos);
|
||||||
return decode<T>(bs, {
|
return decode<T>(bs, {
|
||||||
decodePointer: <never>decodeDefaultPointer,
|
pointerType: this.pointerType,
|
||||||
includeAnnotations: this.state.options.includeAnnotations,
|
includeAnnotations: this.state.options.includeAnnotations,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
case '!': {
|
case '!': return embed(this.pointerType.fromValue(
|
||||||
if (this.decodePointer === void 0) {
|
new Reader<DefaultPointer>(this.state, genericPointerType).next()));
|
||||||
this.state.error("No decodePointer function supplied", startPos);
|
|
||||||
}
|
|
||||||
return this.decodePointer(new Reader(this.state, readDefaultPointer).next());
|
|
||||||
}
|
|
||||||
default:
|
default:
|
||||||
this.state.error(`Invalid # syntax: ${c}`, startPos);
|
this.state.error(`Invalid # syntax: ${c}`, startPos);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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]];
|
const result = [pieces[0]];
|
||||||
values.forEach((v, i) => {
|
values.forEach((v, i) => {
|
||||||
result.push(stringify(v));
|
result.push(stringify(v));
|
||||||
|
@ -29,7 +29,9 @@ declare global {
|
||||||
Object.defineProperty(Object.prototype, 'asPreservesText', {
|
Object.defineProperty(Object.prototype, 'asPreservesText', {
|
||||||
enumerable: false,
|
enumerable: false,
|
||||||
writable: true,
|
writable: true,
|
||||||
value: function(): string { return '#!' + JSON.stringify(this); }
|
value: function(): string {
|
||||||
|
return JSON.stringify(this);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Boolean.prototype.asPreservesText = function (): string {
|
Boolean.prototype.asPreservesText = function (): string {
|
||||||
|
|
|
@ -4,12 +4,12 @@ import type { Bytes } from './bytes';
|
||||||
import type { DoubleFloat, SingleFloat } from './float';
|
import type { DoubleFloat, SingleFloat } from './float';
|
||||||
import type { Annotated } from './annotated';
|
import type { Annotated } from './annotated';
|
||||||
import type { Set, Dictionary } from './dictionary';
|
import type { Set, Dictionary } from './dictionary';
|
||||||
import type { DefaultPointer } from './pointer';
|
import type { Pointer, DefaultPointer } from './pointer';
|
||||||
|
|
||||||
export type Value<T = DefaultPointer> =
|
export type Value<T = DefaultPointer> =
|
||||||
| Atom
|
| Atom
|
||||||
| Compound<T>
|
| Compound<T>
|
||||||
| T
|
| Pointer<T>
|
||||||
| Annotated<T>;
|
| Annotated<T>;
|
||||||
export type Atom =
|
export type Atom =
|
||||||
| boolean
|
| boolean
|
||||||
|
|
|
@ -9,12 +9,15 @@ import {
|
||||||
preserves,
|
preserves,
|
||||||
fromJS,
|
fromJS,
|
||||||
Constants,
|
Constants,
|
||||||
TypedDecoder,
|
|
||||||
Encoder,
|
Encoder,
|
||||||
DefaultPointer,
|
DefaultPointer,
|
||||||
decodeDefaultPointer,
|
|
||||||
encodeDefaultPointer,
|
|
||||||
EncoderState,
|
EncoderState,
|
||||||
|
PointerType,
|
||||||
|
DecoderState,
|
||||||
|
Decoder,
|
||||||
|
Pointer,
|
||||||
|
embed,
|
||||||
|
genericPointerType,
|
||||||
} from '../src/index';
|
} from '../src/index';
|
||||||
const { Tag } = Constants;
|
const { Tag } = Constants;
|
||||||
import './test-utils';
|
import './test-utils';
|
||||||
|
@ -84,9 +87,43 @@ describe('reusing buffer space', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('encoding and decoding pointers', () => {
|
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', () => {
|
it('should encode using pointerId when no function has been supplied', () => {
|
||||||
const A1 = ({a: 1});
|
const A1 = embed({a: 1});
|
||||||
const A2 = ({a: 1});
|
const A2 = embed({a: 1});
|
||||||
const bs1 = canonicalEncode(A1);
|
const bs1 = canonicalEncode(A1);
|
||||||
const bs2 = canonicalEncode(A2);
|
const bs2 = canonicalEncode(A2);
|
||||||
const bs3 = canonicalEncode(A1);
|
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', () => {
|
it('should refuse to decode pointers when no function has been supplied', () => {
|
||||||
expect(() => decode(Bytes.from([Tag.Pointer, Tag.SmallInteger_lo])))
|
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', () => {
|
it('should encode properly', () => {
|
||||||
const objects: object[] = [];
|
const objects: object[] = [];
|
||||||
const A = {a: 1};
|
const pt = new LookasidePointerType(objects);
|
||||||
const B = {b: 2};
|
const A = embed({a: 1});
|
||||||
expect(encode(
|
const B = embed({b: 2});
|
||||||
[A, B],
|
expect(encode([A, B], { pointerType: pt })).is(
|
||||||
{
|
Bytes.from([Tag.Sequence,
|
||||||
encodePointer(e: EncoderState, v: object): void {
|
Tag.Pointer, Tag.SmallInteger_lo,
|
||||||
objects.push(v);
|
Tag.Pointer, Tag.SmallInteger_lo + 1,
|
||||||
new Encoder(e, encodeDefaultPointer).push(objects.length - 1);
|
Tag.End]));
|
||||||
}
|
expect(objects).toEqual([A.embeddedValue, B.embeddedValue]);
|
||||||
})).is(Bytes.from([Tag.Sequence,
|
|
||||||
Tag.Pointer, Tag.SmallInteger_lo,
|
|
||||||
Tag.Pointer, Tag.SmallInteger_lo + 1,
|
|
||||||
Tag.End]));
|
|
||||||
expect(objects).is([A, B]);
|
|
||||||
});
|
});
|
||||||
it('should decode properly', () => {
|
it('should decode properly', () => {
|
||||||
const X = {x: 123};
|
const objects: object[] = [];
|
||||||
const Y = {y: 456};
|
const pt = new LookasidePointerType(objects);
|
||||||
const objects: object[] = [X, Y];
|
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([
|
expect(decode(Bytes.from([
|
||||||
Tag.Sequence,
|
Tag.Sequence,
|
||||||
Tag.Pointer, Tag.SmallInteger_lo,
|
Tag.Pointer, Tag.SmallInteger_lo,
|
||||||
Tag.Pointer, Tag.SmallInteger_lo + 1,
|
Tag.Pointer, Tag.SmallInteger_lo + 1,
|
||||||
Tag.End
|
Tag.End
|
||||||
]), {
|
]), { pointerType: pt })).is([X, Y]);
|
||||||
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]);
|
|
||||||
});
|
});
|
||||||
it('should store pointers embedded in map keys correctly', () => {
|
it('should store pointers embedded in map keys correctly', () => {
|
||||||
const A1 = ({a: 1});
|
const A1a = {a: 1};
|
||||||
const A2 = ({a: 1});
|
const A1: Pointer<object> = embed(A1a);
|
||||||
|
const A2: Pointer<object> = embed({a: 1});
|
||||||
const m = new Dictionary<object, number>();
|
const m = new Dictionary<object, number>();
|
||||||
m.set([A1], 1);
|
m.set([A1], 1);
|
||||||
m.set([A2], 2);
|
m.set([A2], 2);
|
||||||
expect(m.get(A1)).toBeUndefined();
|
expect(m.get(A1)).toBeUndefined();
|
||||||
expect(m.get([A1])).toBe(1);
|
expect(m.get([A1])).toBe(1);
|
||||||
expect(m.get([A2])).toBe(2);
|
expect(m.get([A2])).toBe(2);
|
||||||
expect(m.get([{a: 1}])).toBeUndefined();
|
expect(m.get([embed({a: 1})])).toBeUndefined();
|
||||||
A1.a = 3;
|
A1a.a = 3;
|
||||||
expect(m.get([A1])).toBe(1);
|
expect(m.get([A1])).toBe(1);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('common test suite', () => {
|
describe('common test suite', () => {
|
||||||
const samples_bin = fs.readFileSync(__dirname + '/../../../../../tests/samples.bin');
|
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<{
|
const TestCases = Record.makeConstructor<{
|
||||||
cases: Dictionary<DefaultPointer>
|
cases: Dictionary<DefaultPointer>
|
||||||
|
@ -163,13 +191,13 @@ describe('common test suite', () => {
|
||||||
type TestCases = ReturnType<typeof TestCases>;
|
type TestCases = ReturnType<typeof TestCases>;
|
||||||
|
|
||||||
function DS(bs: Bytes) {
|
function DS(bs: Bytes) {
|
||||||
return decode(bs, { decodePointer: decodeDefaultPointer });
|
return decode(bs, { pointerType: genericPointerType });
|
||||||
}
|
}
|
||||||
function D(bs: Bytes) {
|
function D(bs: Bytes) {
|
||||||
return decodeWithAnnotations(bs, { decodePointer: decodeDefaultPointer });
|
return decodeWithAnnotations(bs, { pointerType: genericPointerType });
|
||||||
}
|
}
|
||||||
function E(v: Value<DefaultPointer>) {
|
function E(v: Value<DefaultPointer>) {
|
||||||
return encodeWithAnnotations(v, { encodePointer: encodeDefaultPointer });
|
return encodeWithAnnotations(v, { pointerType: genericPointerType });
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ExpectedValues {
|
interface ExpectedValues {
|
||||||
|
|
|
@ -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 './test-utils';
|
||||||
|
|
||||||
import * as fs from 'fs';
|
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');
|
const samples_pr = fs.readFileSync(__dirname + '/../../../../../tests/samples.pr', 'utf-8');
|
||||||
|
|
||||||
it('should read equal to decoded binary without annotations', () => {
|
it('should read equal to decoded binary without annotations', () => {
|
||||||
const s1 = new Reader(samples_pr, { decodePointer: readDefaultPointer, includeAnnotations: false }).next();
|
const s1 = new Reader(samples_pr, { pointerType: genericPointerType, includeAnnotations: false }).next();
|
||||||
const s2 = new Decoder(samples_bin, { includeAnnotations: false }).withPointerDecoder(
|
const s2 = new Decoder(samples_bin, { pointerType: genericPointerType, includeAnnotations: false }).next();
|
||||||
decodeDefaultPointer,
|
|
||||||
d => d.next());
|
|
||||||
expect(s1).is(s2);
|
expect(s1).is(s2);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should read equal to decoded binary with annotations', () => {
|
it('should read equal to decoded binary with annotations', () => {
|
||||||
const s1 = new Reader(samples_pr, { decodePointer: readDefaultPointer, includeAnnotations: true }).next();
|
const s1 = new Reader(samples_pr, { pointerType: genericPointerType, includeAnnotations: true }).next();
|
||||||
const s2 = new Decoder(samples_bin, { includeAnnotations: true }).withPointerDecoder(
|
const s2 = new Decoder(samples_bin, { pointerType: genericPointerType, includeAnnotations: true }).next();
|
||||||
decodeDefaultPointer,
|
|
||||||
d => d.next());
|
|
||||||
expect(s1).is(s2);
|
expect(s1).is(s2);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should read and encode back to binary with annotations', () => {
|
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, {
|
const bs = Bytes.toIO(encode(s, {
|
||||||
encodePointer: encodeDefaultPointer,
|
pointerType: genericPointerType,
|
||||||
includeAnnotations: true,
|
includeAnnotations: true,
|
||||||
canonical: true,
|
canonical: true,
|
||||||
}));
|
}));
|
||||||
|
|
|
@ -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';
|
import '../src/node_support';
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
namespace jest {
|
namespace jest {
|
||||||
interface Matchers<R> {
|
interface Matchers<R> {
|
||||||
is<T extends object>(expected: Value<T>): R;
|
is<T>(expected: Value<T>): R;
|
||||||
toThrowFilter(f: (e: Error) => boolean): R;
|
toThrowFilter(f: (e: Error) => boolean): R;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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';
|
import './test-utils';
|
||||||
|
|
||||||
describe('Single', () => {
|
describe('Single', () => {
|
||||||
|
@ -33,6 +33,6 @@ describe('fold', () => {
|
||||||
const w1 = new Date();
|
const w1 = new Date();
|
||||||
const v1 = mkv(w1);
|
const v1 = mkv(w1);
|
||||||
expect(fold(v, IDENTITY_FOLD)).not.is(v1);
|
expect(fold(v, IDENTITY_FOLD)).not.is(v1);
|
||||||
expect(mapPointers(v, _t => w1)).is(v1);
|
expect(mapPointers(v, _t => embed(w1))).is(v1);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -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";
|
import { brackets, Item, parens, seq } from "./block";
|
||||||
|
|
||||||
export function sourceCodeFor(v: Value<any>): Item {
|
export function sourceCodeFor(v: Value<never>): Item {
|
||||||
return fold(v, {
|
return fold(v, {
|
||||||
boolean(b: boolean): Item { return b.toString(); },
|
boolean(b: boolean): Item { return b.toString(); },
|
||||||
single(f: number): Item { return f.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!)})`; },
|
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))));
|
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));
|
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))));
|
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]) =>
|
return seq('new _.Dictionary<_ptr>', parens(brackets(... Array.from(d).map(([kk,vv]) =>
|
||||||
brackets(k(kk), k(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)));
|
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)}`);
|
throw new Error(`Cannot emit source code for construction of pointer ${stringify(t)}`);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue