Different approach to pointer codec; support custom schema-driven decode
This commit is contained in:
parent
4ee9f99529
commit
1cc0325007
|
@ -6,20 +6,60 @@ import { DoubleFloat, SingleFloat } from "./float";
|
|||
import { Record } from "./record";
|
||||
import { Bytes, BytesLike, underlying } from "./bytes";
|
||||
import { Value } from "./values";
|
||||
import { is } from "./is";
|
||||
|
||||
export interface DecoderOptions<T> {
|
||||
export interface DecoderOptions {
|
||||
includeAnnotations?: boolean;
|
||||
decodePointer?: (v: Value<T>) => T;
|
||||
}
|
||||
|
||||
export class Decoder<T> {
|
||||
packet: Uint8Array;
|
||||
index: number;
|
||||
options: DecoderOptions<T>;
|
||||
export interface DecoderPointerOptions<T> extends DecoderOptions {
|
||||
decodePointer?(d: TypedDecoder<T>): T | undefined;
|
||||
}
|
||||
|
||||
constructor(packet: BytesLike = new Uint8Array(0), options: DecoderOptions<T> = {}) {
|
||||
export interface TypedDecoder<T> {
|
||||
atEnd(): boolean;
|
||||
|
||||
mark(): any;
|
||||
restoreMark(m: any): void;
|
||||
|
||||
skip(): void;
|
||||
next(): Value<T>;
|
||||
withPointerDecoder<S, R>(decodePointer: (d: TypedDecoder<S>) => S | undefined,
|
||||
body: (d: TypedDecoder<S>) => R): R;
|
||||
|
||||
nextBoolean(): boolean | undefined;
|
||||
nextFloat(): SingleFloat | undefined;
|
||||
nextDouble(): DoubleFloat | undefined;
|
||||
nextPointer(): T | undefined;
|
||||
nextSignedInteger(): number | undefined;
|
||||
nextString(): string | undefined;
|
||||
nextByteString(): Bytes | undefined;
|
||||
nextSymbol(): symbol | undefined;
|
||||
|
||||
openRecord(): boolean;
|
||||
openSequence(): boolean;
|
||||
openSet(): boolean;
|
||||
openDictionary(): boolean;
|
||||
|
||||
closeCompound(): boolean;
|
||||
}
|
||||
|
||||
export function checkIs<T, E extends Value<T>>(actual: Value<T>, expected: E): E | undefined {
|
||||
return is(actual, expected) ? expected : void 0;
|
||||
}
|
||||
|
||||
function _defaultDecodePointer<T>(_d: TypedDecoder<T>): T | undefined {
|
||||
throw new DecodeError("No decodePointer function supplied");
|
||||
}
|
||||
|
||||
export class Decoder<T = never> implements TypedDecoder<T> {
|
||||
packet: Uint8Array;
|
||||
index = 0;
|
||||
options: DecoderOptions;
|
||||
decodePointer: ((d: TypedDecoder<T>) => T | undefined) = _defaultDecodePointer;
|
||||
|
||||
constructor(packet: BytesLike = new Uint8Array(0), options: DecoderOptions = {}) {
|
||||
this.packet = underlying(packet);
|
||||
this.index = 0;
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
|
@ -37,16 +77,24 @@ export class Decoder<T> {
|
|||
}
|
||||
|
||||
nextbyte(): number {
|
||||
if (this.index >= this.packet.length) throw new ShortPacket("Short packet");
|
||||
// ^ NOTE: greater-than-or-equal-to, not greater-than.
|
||||
return this.packet[this.index++];
|
||||
if (this.atEnd()) throw new ShortPacket("Short packet");
|
||||
return this.packet[this.advance()];
|
||||
}
|
||||
|
||||
peekbyte(): number {
|
||||
if (this.atEnd()) throw new ShortPacket("Short packet");
|
||||
return this.packet[this.index];
|
||||
}
|
||||
|
||||
advance(): number {
|
||||
return this.index++;
|
||||
}
|
||||
|
||||
nextbytes(n: number): DataView {
|
||||
const start = this.index;
|
||||
this.index += n;
|
||||
if (this.index > this.packet.length) throw new ShortPacket("Short packet");
|
||||
// ^ NOTE: greater-than, not greater-than-or-equal-to.
|
||||
// ^ NOTE: greater-than, not greater-than-or-equal-to - this makes atEnd() inappropriate
|
||||
return new DataView(this.packet.buffer, this.packet.byteOffset + start, n);
|
||||
}
|
||||
|
||||
|
@ -58,9 +106,7 @@ export class Decoder<T> {
|
|||
}
|
||||
|
||||
peekend(): boolean {
|
||||
const matched = this.nextbyte() === Tag.End;
|
||||
if (!matched) this.index--;
|
||||
return matched;
|
||||
return (this.peekbyte() === Tag.End) && (this.advance(), true);
|
||||
}
|
||||
|
||||
nextvalues(): Value<T>[] {
|
||||
|
@ -112,11 +158,11 @@ export class Decoder<T> {
|
|||
return this.unshiftAnnotation(a, v);
|
||||
}
|
||||
case Tag.Pointer: {
|
||||
const d = this.options.decodePointer;
|
||||
if (d === void 0) {
|
||||
throw new DecodeError("No decodePointer function supplied");
|
||||
const v = this.decodePointer(this);
|
||||
if (v === void 0) {
|
||||
throw new DecodeError("decodePointer function failed");
|
||||
}
|
||||
return this.wrap(d(this.next()));
|
||||
return this.wrap(v);
|
||||
}
|
||||
case Tag.SignedInteger: return this.wrap(this.nextint(this.varint()));
|
||||
case Tag.String: return this.wrap(Bytes.from(this.nextbytes(this.varint())).fromUtf8());
|
||||
|
@ -131,41 +177,219 @@ export class Decoder<T> {
|
|||
case Tag.Set: return this.wrap(new Set(this.nextvalues()));
|
||||
case Tag.Dictionary: return this.wrap(Decoder.dictionaryFromArray(this.nextvalues()));
|
||||
default: {
|
||||
if (tag >= Tag.SmallInteger_lo && tag <= Tag.SmallInteger_lo + 15) {
|
||||
const v = tag - Tag.SmallInteger_lo;
|
||||
return this.wrap(v > 12 ? v - 16 : v);
|
||||
const v = this.nextSmallOrMediumInteger(tag);
|
||||
if (v === void 0) {
|
||||
throw new DecodeError("Unsupported Preserves tag: " + tag);
|
||||
}
|
||||
if (tag >= Tag.MediumInteger_lo && tag <= Tag.MediumInteger_lo + 15) {
|
||||
const n = tag - Tag.MediumInteger_lo;
|
||||
return this.wrap(this.nextint(n + 1));
|
||||
}
|
||||
throw new DecodeError("Unsupported Preserves tag: " + tag);
|
||||
return this.wrap(v);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try_next(): Value<T> | undefined {
|
||||
const start = this.index;
|
||||
nextSmallOrMediumInteger(tag: number): number | undefined {
|
||||
if (tag >= Tag.SmallInteger_lo && tag <= Tag.SmallInteger_lo + 15) {
|
||||
const v = tag - Tag.SmallInteger_lo;
|
||||
return v > 12 ? v - 16 : v;
|
||||
}
|
||||
if (tag >= Tag.MediumInteger_lo && tag <= Tag.MediumInteger_lo + 15) {
|
||||
const n = tag - Tag.MediumInteger_lo;
|
||||
return this.nextint(n + 1);
|
||||
}
|
||||
return void 0;
|
||||
}
|
||||
|
||||
if (start >= this.packet.length) return void 0;
|
||||
shortGuard<R>(body: () => R, short: () => R): R {
|
||||
if (this.atEnd()) return short();
|
||||
// ^ important somewhat-common case optimization - avoid the exception
|
||||
|
||||
const start = this.mark();
|
||||
try {
|
||||
return this.next();
|
||||
return body();
|
||||
} catch (e) {
|
||||
if (ShortPacket.isShortPacket(e)) {
|
||||
this.index = start;
|
||||
return void 0;
|
||||
this.restoreMark(start);
|
||||
return short();
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
try_next(): Value<T> | undefined {
|
||||
return this.shortGuard(() => this.next(), () => void 0);
|
||||
}
|
||||
|
||||
atEnd(): boolean {
|
||||
return this.index >= this.packet.length;
|
||||
}
|
||||
|
||||
mark(): number {
|
||||
return this.index;
|
||||
}
|
||||
|
||||
restoreMark(m: number): void {
|
||||
this.index = m;
|
||||
}
|
||||
|
||||
skip(): void {
|
||||
// TODO: be more efficient
|
||||
this.next();
|
||||
}
|
||||
|
||||
replacePointerDecoder<S>(decodePointer: (d: TypedDecoder<S>) => S | undefined): Decoder<S> {
|
||||
const replacement = new Decoder<S>(this.packet, this.options);
|
||||
replacement.index = this.index;
|
||||
replacement.decodePointer = decodePointer;
|
||||
this.packet = new Uint8Array();
|
||||
this.index = 0;
|
||||
this.decodePointer = _defaultDecodePointer;
|
||||
return replacement;
|
||||
}
|
||||
|
||||
withPointerDecoder<S, R>(decodePointer: (d: TypedDecoder<S>) => S | undefined,
|
||||
body: (d: TypedDecoder<S>) => R): R
|
||||
{
|
||||
const oldDecodePointer = this.decodePointer;
|
||||
const disguised = this as unknown as Decoder<S>;
|
||||
disguised.decodePointer = decodePointer;
|
||||
try {
|
||||
return body(disguised);
|
||||
} finally {
|
||||
this.decodePointer = oldDecodePointer;
|
||||
}
|
||||
}
|
||||
|
||||
skipAnnotations(): void {
|
||||
while (this.peekbyte() === Tag.Annotation) {
|
||||
this.advance();
|
||||
this.skip();
|
||||
}
|
||||
}
|
||||
|
||||
nextBoolean(): boolean | undefined {
|
||||
this.skipAnnotations();
|
||||
switch (this.peekbyte()) {
|
||||
case Tag.False: this.advance(); return false;
|
||||
case Tag.True: this.advance(); return true;
|
||||
default: return void 0;
|
||||
}
|
||||
}
|
||||
|
||||
nextFloat(): SingleFloat | undefined {
|
||||
this.skipAnnotations();
|
||||
switch (this.peekbyte()) {
|
||||
case Tag.Float:
|
||||
this.advance();
|
||||
return new SingleFloat(this.nextbytes(4).getFloat32(0, false));
|
||||
default:
|
||||
return void 0;
|
||||
}
|
||||
}
|
||||
|
||||
nextDouble(): DoubleFloat | undefined {
|
||||
this.skipAnnotations();
|
||||
switch (this.peekbyte()) {
|
||||
case Tag.Double:
|
||||
this.advance();
|
||||
return new DoubleFloat(this.nextbytes(8).getFloat64(0, false));
|
||||
default:
|
||||
return void 0;
|
||||
}
|
||||
}
|
||||
|
||||
nextPointer(): T | undefined {
|
||||
this.skipAnnotations();
|
||||
switch (this.peekbyte()) {
|
||||
case Tag.Pointer: {
|
||||
this.advance();
|
||||
const M = this.mark();
|
||||
const v = this.decodePointer(this);
|
||||
if (v === void 0) this.restoreMark(M);
|
||||
return v;
|
||||
}
|
||||
default:
|
||||
return void 0;
|
||||
}
|
||||
}
|
||||
|
||||
nextSignedInteger(): number | undefined {
|
||||
this.skipAnnotations();
|
||||
const b = this.nextbyte();
|
||||
switch (b) {
|
||||
case Tag.SignedInteger:
|
||||
return this.nextint(this.varint());
|
||||
default: {
|
||||
const v = this.nextSmallOrMediumInteger(b);
|
||||
if (v === void 0) this.index--; // ugh
|
||||
return v;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
nextString(): string | undefined {
|
||||
this.skipAnnotations();
|
||||
switch (this.peekbyte()) {
|
||||
case Tag.String:
|
||||
this.advance();
|
||||
return Bytes.from(this.nextbytes(this.varint())).fromUtf8();
|
||||
default:
|
||||
return void 0;
|
||||
}
|
||||
}
|
||||
|
||||
nextByteString(): Bytes | undefined {
|
||||
this.skipAnnotations();
|
||||
switch (this.peekbyte()) {
|
||||
case Tag.ByteString:
|
||||
this.advance();
|
||||
return Bytes.from(this.nextbytes(this.varint()));
|
||||
default:
|
||||
return void 0;
|
||||
}
|
||||
}
|
||||
|
||||
nextSymbol(): symbol | undefined {
|
||||
this.skipAnnotations();
|
||||
switch (this.peekbyte()) {
|
||||
case Tag.Symbol:
|
||||
this.advance();
|
||||
return Symbol.for(Bytes.from(this.nextbytes(this.varint())).fromUtf8());
|
||||
default:
|
||||
return void 0;
|
||||
}
|
||||
}
|
||||
|
||||
openRecord(): boolean {
|
||||
this.skipAnnotations();
|
||||
return (this.peekbyte() === Tag.Record) && (this.advance(), true);
|
||||
}
|
||||
|
||||
openSequence(): boolean {
|
||||
this.skipAnnotations();
|
||||
return (this.peekbyte() === Tag.Sequence) && (this.advance(), true);
|
||||
}
|
||||
|
||||
openSet(): boolean {
|
||||
this.skipAnnotations();
|
||||
return (this.peekbyte() === Tag.Set) && (this.advance(), true);
|
||||
}
|
||||
|
||||
openDictionary(): boolean {
|
||||
this.skipAnnotations();
|
||||
return (this.peekbyte() === Tag.Dictionary) && (this.advance(), true);
|
||||
}
|
||||
|
||||
closeCompound(): boolean {
|
||||
return this.peekend();
|
||||
}
|
||||
}
|
||||
|
||||
export function decode<T>(bs: BytesLike, options?: DecoderOptions<T>) {
|
||||
return new Decoder(bs, options).next();
|
||||
export function decode<T>(bs: BytesLike, options: DecoderPointerOptions<T> = {}): Value<T> {
|
||||
return new Decoder<T>(bs, options).withPointerDecoder<T, Value<T>>(
|
||||
options.decodePointer ?? _defaultDecodePointer,
|
||||
d => d.next());
|
||||
}
|
||||
|
||||
export function decodeWithAnnotations<T>(bs: BytesLike, options: DecoderOptions<T> = {}): Annotated<T> {
|
||||
export function decodeWithAnnotations<T>(bs: BytesLike,
|
||||
options: DecoderPointerOptions<T> = {}): Annotated<T> {
|
||||
return decode(bs, { ... options, includeAnnotations: true }) as Annotated<T>;
|
||||
}
|
||||
|
|
|
@ -16,10 +16,13 @@ export function isPreservable<T>(v: any): v is Preservable<T> {
|
|||
return typeof v === 'object' && v !== null && typeof v[PreserveOn] === 'function';
|
||||
}
|
||||
|
||||
export interface EncoderOptions<T> {
|
||||
export interface EncoderOptions {
|
||||
canonical?: boolean;
|
||||
includeAnnotations?: boolean;
|
||||
encodePointer?: (v: T) => Value<T>;
|
||||
}
|
||||
|
||||
export interface EncoderPointerOptions<T> extends EncoderOptions {
|
||||
encodePointer?: (e: Encoder<T>, v: T) => void;
|
||||
}
|
||||
|
||||
function chunkStr(bs: Uint8Array): string {
|
||||
|
@ -30,19 +33,38 @@ function isIterable<T>(v: any): v is Iterable<T> {
|
|||
return typeof v === 'object' && v !== null && typeof v[Symbol.iterator] === 'function';
|
||||
}
|
||||
|
||||
function _defaultEncodePointer<T>(e: Encoder<T>, v: T): void {
|
||||
e.push(pointerId(v));
|
||||
}
|
||||
|
||||
export class Encoder<T> {
|
||||
chunks: Array<Uint8Array>;
|
||||
view: DataView;
|
||||
index: number;
|
||||
options: EncoderOptions<T>;
|
||||
options: EncoderOptions;
|
||||
encodePointer: ((e: Encoder<T>, v: T) => void) = _defaultEncodePointer;
|
||||
|
||||
constructor(options: EncoderOptions<T> = {}) {
|
||||
constructor(options: EncoderOptions = {}) {
|
||||
this.chunks = [];
|
||||
this.view = new DataView(new ArrayBuffer(256));
|
||||
this.index = 0;
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
withPointerEncoder<S>(encodePointer: (e: Encoder<S>, v: S) => void,
|
||||
body: (e: Encoder<S>) => void): this
|
||||
{
|
||||
const oldEncodePointer = this.encodePointer;
|
||||
const disguised = this as unknown as Encoder<S>;
|
||||
disguised.encodePointer = encodePointer;
|
||||
try {
|
||||
body(disguised);
|
||||
return this;
|
||||
} finally {
|
||||
this.encodePointer = oldEncodePointer;
|
||||
}
|
||||
}
|
||||
|
||||
get canonical(): boolean {
|
||||
return this.options.canonical ?? true;
|
||||
}
|
||||
|
@ -183,16 +205,17 @@ export class Encoder<T> {
|
|||
this.encodevalues(Tag.Sequence, v as Iterable<Value<T>>);
|
||||
}
|
||||
else {
|
||||
const e = this.options.encodePointer ?? pointerId;
|
||||
this.emitbyte(Tag.Pointer);
|
||||
this.push(e(v));
|
||||
this.encodePointer(this, v);
|
||||
}
|
||||
return this; // for chaining
|
||||
}
|
||||
}
|
||||
|
||||
export function encode<T>(v: Encodable<T>, options?: EncoderOptions<T>): Bytes {
|
||||
return new Encoder(options).push(v).contents();
|
||||
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;
|
||||
|
@ -208,7 +231,8 @@ export function pointerId(v: any): number {
|
|||
|
||||
const _canonicalEncoder = new Encoder({ canonical: true });
|
||||
let _usingCanonicalEncoder = false;
|
||||
export function canonicalEncode(v: Encodable<any>, options?: EncoderOptions<any>): Bytes {
|
||||
export function canonicalEncode(v: Encodable<any>, options?: EncoderPointerOptions<any>): Bytes
|
||||
{
|
||||
if (options === void 0 && !_usingCanonicalEncoder) {
|
||||
_usingCanonicalEncoder = true;
|
||||
const bs = _canonicalEncoder.push(v).contents();
|
||||
|
@ -220,9 +244,17 @@ export function canonicalEncode(v: Encodable<any>, options?: EncoderOptions<any>
|
|||
}
|
||||
|
||||
export function canonicalString(v: Encodable<any>): string {
|
||||
return _canonicalEncoder.push(v).contentsString();
|
||||
if (!_usingCanonicalEncoder) {
|
||||
_usingCanonicalEncoder = true;
|
||||
const s = _canonicalEncoder.push(v).contentsString();
|
||||
_usingCanonicalEncoder = false;
|
||||
return s;
|
||||
} else {
|
||||
return new Encoder({ canonical: true }).push(v).contentsString();
|
||||
}
|
||||
}
|
||||
|
||||
export function encodeWithAnnotations<T>(v: Encodable<T>, options: EncoderOptions<T> = {}): Bytes {
|
||||
export function encodeWithAnnotations<T>(v: Encodable<T>,
|
||||
options: EncoderPointerOptions<T> = {}): Bytes {
|
||||
return encode(v, { ... options, includeAnnotations: true });
|
||||
}
|
||||
|
|
|
@ -168,7 +168,7 @@ export class Reader<T> {
|
|||
if (!Bytes.isBytes(bs)) this.error('ByteString must follow #=',
|
||||
startPos);
|
||||
return decode<T>(bs, {
|
||||
decodePointer: this.options.decodePointer,
|
||||
decodePointer: d => this.options.decodePointer?.(d.next()),
|
||||
includeAnnotations: this.options.includeAnnotations,
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Value } from './values';
|
||||
import type { Value } from './values';
|
||||
|
||||
export function stringify(x: any): string {
|
||||
if (typeof x?.asPreservesText === 'function') {
|
||||
|
|
|
@ -9,6 +9,8 @@ import {
|
|||
preserves,
|
||||
fromJS,
|
||||
Constants,
|
||||
TypedDecoder,
|
||||
Encoder,
|
||||
} from '../src/index';
|
||||
const { Tag } = Constants;
|
||||
import './test-utils';
|
||||
|
@ -104,9 +106,9 @@ describe('encoding and decoding pointers', () => {
|
|||
expect(encode(
|
||||
[A, B],
|
||||
{
|
||||
encodePointer(v: object): Value<object> {
|
||||
encodePointer(e: Encoder<object>, v: object): void {
|
||||
objects.push(v);
|
||||
return objects.length - 1;
|
||||
e.push(objects.length - 1);
|
||||
}
|
||||
})).is(Bytes.from([Tag.Sequence,
|
||||
Tag.Pointer, Tag.SmallInteger_lo,
|
||||
|
@ -124,7 +126,8 @@ describe('encoding and decoding pointers', () => {
|
|||
Tag.Pointer, Tag.SmallInteger_lo + 1,
|
||||
Tag.End
|
||||
]), {
|
||||
decodePointer(v: Value<object>): object {
|
||||
decodePointer(d: TypedDecoder<object>): object {
|
||||
const v = d.next();
|
||||
if (typeof v !== 'number' || v < 0 || v >= objects.length) {
|
||||
throw new Error("Unknown pointer target");
|
||||
}
|
||||
|
@ -135,7 +138,7 @@ describe('encoding and decoding pointers', () => {
|
|||
it('should store pointers embedded in map keys correctly', () => {
|
||||
const A1 = ({a: 1});
|
||||
const A2 = ({a: 1});
|
||||
const m = new Dictionary();
|
||||
const m = new Dictionary<number, object>();
|
||||
m.set([A1], 1);
|
||||
m.set([A2], 2);
|
||||
expect(m.get(A1)).toBeUndefined();
|
||||
|
@ -179,19 +182,19 @@ describe('common test suite', () => {
|
|||
back: 5 },
|
||||
annotation5: {
|
||||
forward: annotate<Pointer>(
|
||||
Record<symbol, any, Pointer>(Symbol.for('R'),
|
||||
[annotate<Pointer>(Symbol.for('f'),
|
||||
Symbol.for('af'))]),
|
||||
Record<symbol, any>(Symbol.for('R'),
|
||||
[annotate<Pointer>(Symbol.for('f'),
|
||||
Symbol.for('af'))]),
|
||||
Symbol.for('ar')),
|
||||
back: Record<Value<Pointer>, any, Pointer>(Symbol.for('R'), [Symbol.for('f')])
|
||||
back: Record<Value<Pointer>, any>(Symbol.for('R'), [Symbol.for('f')])
|
||||
},
|
||||
annotation6: {
|
||||
forward: Record<Value<Pointer>, any, Pointer>(
|
||||
forward: Record<Value<Pointer>, any>(
|
||||
annotate<Pointer>(Symbol.for('R'),
|
||||
Symbol.for('ar')),
|
||||
[annotate<Pointer>(Symbol.for('f'),
|
||||
Symbol.for('af'))]),
|
||||
back: Record<symbol, any, Pointer>(Symbol.for('R'), [Symbol.for('f')])
|
||||
back: Record<symbol, any>(Symbol.for('R'), [Symbol.for('f')])
|
||||
},
|
||||
annotation7: {
|
||||
forward: annotate<Pointer>([], Symbol.for('a'), Symbol.for('b'), Symbol.for('c')),
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { Bytes, Decoder, encode, Reader } from '../src/index';
|
||||
import './test-utils';
|
||||
import { decodePointer, encodePointer } from './test-utils';
|
||||
import { decodePointer, encodePointer, readPointer } from './test-utils';
|
||||
|
||||
import * as fs from 'fs';
|
||||
|
||||
|
@ -9,19 +9,23 @@ describe('reading common test suite', () => {
|
|||
const samples_txt = fs.readFileSync(__dirname + '/../../../../../tests/samples.txt', 'utf-8');
|
||||
|
||||
it('should read equal to decoded binary without annotations', () => {
|
||||
const s1 = new Reader(samples_txt, { decodePointer, includeAnnotations: false }).next();
|
||||
const s2 = new Decoder(samples_bin, { decodePointer, includeAnnotations: false }).next();
|
||||
const s1 = new Reader(samples_txt, { decodePointer: readPointer, includeAnnotations: false }).next();
|
||||
const s2 = new Decoder(samples_bin, { includeAnnotations: false }).withPointerDecoder(
|
||||
decodePointer,
|
||||
d => d.next());
|
||||
expect(s1).is(s2);
|
||||
});
|
||||
|
||||
it('should read equal to decoded binary with annotations', () => {
|
||||
const s1 = new Reader(samples_txt, { decodePointer, includeAnnotations: true }).next();
|
||||
const s2 = new Decoder(samples_bin, { decodePointer, includeAnnotations: true }).next();
|
||||
const s1 = new Reader(samples_txt, { decodePointer: readPointer, includeAnnotations: true }).next();
|
||||
const s2 = new Decoder(samples_bin, { includeAnnotations: true }).withPointerDecoder(
|
||||
decodePointer,
|
||||
d => d.next());
|
||||
expect(s1).is(s2);
|
||||
});
|
||||
|
||||
it('should read and encode back to binary with annotations', () => {
|
||||
const s = new Reader(samples_txt, { decodePointer, includeAnnotations: true }).next();
|
||||
const s = new Reader(samples_txt, { decodePointer: readPointer, includeAnnotations: true }).next();
|
||||
const bs = Bytes.toIO(encode(s, {
|
||||
encodePointer,
|
||||
includeAnnotations: true,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Value, is, preserves, strip } from '../src/index';
|
||||
import { Value, is, preserves, strip, TypedDecoder, Encoder } from '../src/index';
|
||||
import '../src/node_support';
|
||||
|
||||
declare global {
|
||||
|
@ -51,10 +51,14 @@ export class Pointer {
|
|||
}
|
||||
}
|
||||
|
||||
export function decodePointer(v: Value<Pointer>): Pointer {
|
||||
export function readPointer(v: Value<Pointer>): Pointer {
|
||||
return new Pointer(strip(v));
|
||||
}
|
||||
|
||||
export function encodePointer(w: Pointer): Value<Pointer> {
|
||||
return w.v;
|
||||
export function decodePointer(d: TypedDecoder<Pointer>): Pointer {
|
||||
return readPointer(d.next());
|
||||
}
|
||||
|
||||
export function encodePointer(e: Encoder<Pointer>, w: Pointer): void {
|
||||
e.push(w.v);
|
||||
}
|
||||
|
|
|
@ -40,6 +40,119 @@ export function compile(env: Environment, schema: Schema, options: CompilerOptio
|
|||
return varname;
|
||||
}
|
||||
|
||||
function decoderFor(p: Pattern, recordFields = false): Item {
|
||||
switch (p.label) {
|
||||
case M.$atom:
|
||||
switch (p[0]) {
|
||||
case M.$Boolean: return `d.nextBoolean()`;
|
||||
case M.$Float: return `d.nextFloat()`;
|
||||
case M.$Double: return `d.nextDouble()`;
|
||||
case M.$SignedInteger: return `d.nextSignedInteger()`;
|
||||
case M.$String: return `d.nextString()`;
|
||||
case M.$ByteString: return `d.nextByteString()`;
|
||||
case M.$Symbol: return `d.nextSymbol()`;
|
||||
}
|
||||
case M.$lit:
|
||||
switch (typeof p[0]) {
|
||||
case 'boolean': return `_.checkIs(d.nextBoolean(), ${literal(p[0])})`;
|
||||
case 'string': return `_.checkIs(d.nextString(), ${literal(p[0])})`;
|
||||
case 'number': return `_.checkIs(d.nextSignedInteger(), ${literal(p[0])})`;
|
||||
case 'symbol': return `_.checkIs(d.nextSymbol(), ${literal(p[0])})`;
|
||||
default: return `_.checkIs(d.next(), ${literal(p[0])})`;
|
||||
}
|
||||
case M.$ref:
|
||||
return lookup(refPosition(p), p, env,
|
||||
(_p) => `decode${p[1].description!}(d)`,
|
||||
(p) => decoderFor(p),
|
||||
(mod, modPath,_p) => {
|
||||
imports.add([mod, modPath]);
|
||||
return `${mod}.decode${p[1].description!}(d)`;
|
||||
});
|
||||
case M.$or:
|
||||
return opseq('void 0', ' ?? ', ... p[0].map(pp => decoderFor(pp)));
|
||||
case M.$and:
|
||||
switch (p[0].length) {
|
||||
case 0: return `d.next()`;
|
||||
case 1: return decoderFor(p[0][0]);
|
||||
default: {
|
||||
const tmp = gentemp();
|
||||
const [pp0, ... ppN] = p[0];
|
||||
const otherChecks =
|
||||
opseq('false', ' && ', ... ppN.map(pp => predicateFor(tmp, pp)));
|
||||
return seq(`((${tmp} = `, decoderFor(pp0), `) != void 0) && `,
|
||||
otherChecks, ` ? ${tmp} : void 0`);
|
||||
}
|
||||
}
|
||||
case M.$pointer:
|
||||
return `_decodePtr(d)`;
|
||||
case M.$rec:
|
||||
return fnblock(
|
||||
seq(`const M = d.mark()`),
|
||||
seq(`if (!d.openRecord()) return void 0`),
|
||||
seq(`const L = `, decoderFor(p[0])),
|
||||
seq(`if (L === void 0) { d.restoreMark(M); return void 0; }`),
|
||||
seq(`const Fs = (`, decoderFor(p[1], true), `) as any`),
|
||||
seq(`if (Fs === void 0) { d.restoreMark(M); return void 0; }`),
|
||||
seq(`return _.Record`,
|
||||
anglebrackets(typeFor(p[0]), typeFor(p[1])),
|
||||
parens(`L`, `Fs`)));
|
||||
case M.$tuple:
|
||||
return fnblock(
|
||||
seq(`const M = d.mark()`),
|
||||
... recordFields ? [] : [seq(`if (!d.openSequence()) return void 0`)],
|
||||
... p[0].map((pp, i) =>
|
||||
seq(`const v${i} = `, decoderFor(unname(pp)), `; `,
|
||||
`if (v${i} === void 0) { d.restoreMark(M); return void 0; }`)),
|
||||
seq(`if (!d.closeCompound()) { d.restoreMark(M); return void 0; }`),
|
||||
seq(`return [${p[0].map((_pp, i) => `v${i}`).join(', ')}] as `, typeFor(p)));
|
||||
case M.$tuple_STAR_:
|
||||
return fnblock(
|
||||
seq(`const M = d.mark()`),
|
||||
... recordFields ? [] : [seq(`if (!d.openSequence()) return void 0`)],
|
||||
... p[0].map((pp, i) =>
|
||||
seq(`const v${i} = `, decoderFor(unname(pp)), `; `,
|
||||
`if (v${i} === void 0) { d.restoreMark(M); return void 0; }`)),
|
||||
seq(`const vN: Array<`, typeFor(unname(p[1])), `> = []`),
|
||||
seq(`let tmp: undefined | `, typeFor(unname(p[1]))),
|
||||
seq(`while ((tmp = `, decoderFor(unname(p[1])), `) !== void 0) vN.push(tmp)`),
|
||||
seq(`if (!d.closeCompound()) { d.restoreMark(M); return void 0; }`),
|
||||
(p[0].length === 0
|
||||
? seq(`return vN`)
|
||||
: seq(`return [${p[0].map((_pp, i) => `v${i}`).join(', ')}, ... vN] as `,
|
||||
typeFor(p))));
|
||||
case M.$setof:
|
||||
return fnblock(
|
||||
seq(`const M = d.mark()`),
|
||||
seq(`if (!d.openSet()) return void 0`),
|
||||
seq(`const r: `, typeFor(p), ` = new _.KeyedSet()`),
|
||||
seq(`let tmp: undefined | `, typeFor(p[0])),
|
||||
seq(`while ((tmp = `, decoderFor(p[0]), `) !== void 0) r.add(tmp)`),
|
||||
seq(`if (!d.closeCompound()) { d.restoreMark(M); return void 0; }`),
|
||||
seq(`return r`));
|
||||
case M.$dictof:
|
||||
return fnblock(
|
||||
seq(`const M = d.mark()`),
|
||||
seq(`if (!d.openDictionary()) return void 0`),
|
||||
seq(`const r: `, typeFor(p), ` = new _.KeyedDictionary()`),
|
||||
seq(`let K: undefined | `, typeFor(p[0])),
|
||||
seq(`while ((K = `, decoderFor(p[0]), `) !== void 0) `, block(
|
||||
seq(`const V = `, decoderFor(p[1])),
|
||||
seq(`if (V === void 0) { d.restoreMark(M); return void 0; }`),
|
||||
seq(`r.set(K, V)`))),
|
||||
seq(`if (!d.closeCompound()) { d.restoreMark(M); return void 0; }`),
|
||||
seq(`return r`));
|
||||
case M.$dict:
|
||||
return fnblock(
|
||||
seq(`const M = d.mark()`),
|
||||
seq(`const r = d.next()`),
|
||||
seq(`if (!(`, predicateFor('r', p), `)) { d.restoreMark(M); return void 0; }`),
|
||||
seq(`return r`));
|
||||
default:
|
||||
((_p: never) => {})(p);
|
||||
throw new Error("Unreachable");
|
||||
}
|
||||
}
|
||||
|
||||
function typeFor(p: Pattern): Item {
|
||||
switch (p.label) {
|
||||
case M.$atom:
|
||||
|
@ -205,23 +318,37 @@ export function compile(env: Environment, schema: Schema, options: CompilerOptio
|
|||
types.push(
|
||||
seq(`export type ${name.description!} = `, typeFor(pattern), `;`));
|
||||
functions.push(
|
||||
seq('export function ', `is${name.description!}`,
|
||||
seq(`export function is${name.description!}`,
|
||||
'(v: any): v is ', name.description!, ' ',
|
||||
block(
|
||||
... temps.length > 0 ? [seq('let ', commas(... temps), ': any')] : [],
|
||||
seq('return ', recognizer))));
|
||||
functions.push(
|
||||
seq('export function ', `as${name.description!}`,
|
||||
seq(`export function as${name.description!}`,
|
||||
'(v: any): ', name.description!, ' ',
|
||||
block(
|
||||
seq(`if (!is${name.description!}(v)) `,
|
||||
block(`throw new TypeError(\`Invalid ${name.description!}: \${_.stringify(v)}\`)`),
|
||||
' else ',
|
||||
block(`return v`)))));
|
||||
|
||||
temps = [];
|
||||
const decoder = decoderFor(pattern);
|
||||
functions.push(
|
||||
seq(`export function decode${name.description!}`,
|
||||
`(d: _.TypedDecoder<_ptr>): ${name.description!} | undefined `,
|
||||
block(
|
||||
... temps.length > 0 ? [seq('let ', commas(... temps), ': any')] : [],
|
||||
seq(`return `, decoder))));
|
||||
}
|
||||
|
||||
types.push(seq('export type _ptr = ', pointerName === false ? 'never' : typeFor(pointerName), `;`));
|
||||
types.push(`export type _val = _.Value<_ptr>;`);
|
||||
functions.push(seq(`export const _decodePtr = `,
|
||||
(pointerName === false
|
||||
? '() => { throw new _.DecodeError("Pointers forbidden"); }'
|
||||
: seq(`(d: _.TypedDecoder<_ptr>) => `, decoderFor(pointerName))),
|
||||
`;`));
|
||||
|
||||
const f = new Formatter();
|
||||
f.write(`import * as _ from ${JSON.stringify(options.preservesModule ?? '@preserves/core')};\n`);
|
||||
|
|
|
@ -137,18 +137,87 @@ export function asSchema(v: any): Schema {
|
|||
if (!isSchema(v)) {throw new TypeError(`Invalid Schema: ${_.stringify(v)}`);} else {return v;};
|
||||
}
|
||||
|
||||
export function decodeSchema(d: _.TypedDecoder<_ptr>): Schema | undefined {
|
||||
let _tmp0, _tmp1, _tmp2: any;
|
||||
return ((() => {
|
||||
const M = d.mark();
|
||||
if (!d.openRecord()) return void 0;
|
||||
const L = _.checkIs(d.nextSymbol(), $schema);
|
||||
if (L === void 0) { d.restoreMark(M); return void 0; };
|
||||
const Fs = (((() => {
|
||||
const M = d.mark();
|
||||
if (!d.openSequence()) return void 0;
|
||||
const v0 = ((() => {
|
||||
const M = d.mark();
|
||||
const r = d.next();
|
||||
if (!((
|
||||
_.Dictionary.isDictionary<_val, _ptr>(r) &&
|
||||
((_tmp0 = r.get($version)) !== void 0 && isVersion(_tmp0)) &&
|
||||
((_tmp1 = r.get($pointer)) !== void 0 && isPointerName(_tmp1)) &&
|
||||
(
|
||||
(_tmp2 = r.get($definitions)) !== void 0 && (
|
||||
_.Dictionary.isDictionary<_val, _ptr>(_tmp2) &&
|
||||
((() => {
|
||||
for (const e of _tmp2) {
|
||||
if (!(typeof e[0] === 'symbol')) return false;
|
||||
if (!(isPattern(e[1]))) return false;
|
||||
};
|
||||
return true;
|
||||
})())
|
||||
)
|
||||
)
|
||||
))) { d.restoreMark(M); return void 0; };
|
||||
return r;
|
||||
})()); if (v0 === void 0) { d.restoreMark(M); return void 0; };
|
||||
if (!d.closeCompound()) { d.restoreMark(M); return void 0; };
|
||||
return [v0] as [
|
||||
(
|
||||
{
|
||||
get(k: typeof $version): Version;
|
||||
get(k: typeof $pointer): PointerName;
|
||||
get(k: typeof $definitions): _.KeyedDictionary<symbol, Pattern, _ptr>;
|
||||
has(k: typeof $version): true;
|
||||
has(k: typeof $pointer): true;
|
||||
has(k: typeof $definitions): true;
|
||||
} & _.Dictionary<_val, _ptr>
|
||||
)
|
||||
];
|
||||
})())) as any;
|
||||
if (Fs === void 0) { d.restoreMark(M); return void 0; };
|
||||
return _.Record<
|
||||
(typeof $schema),
|
||||
[
|
||||
(
|
||||
{
|
||||
get(k: typeof $version): Version;
|
||||
get(k: typeof $pointer): PointerName;
|
||||
get(k: typeof $definitions): _.KeyedDictionary<symbol, Pattern, _ptr>;
|
||||
has(k: typeof $version): true;
|
||||
has(k: typeof $pointer): true;
|
||||
has(k: typeof $definitions): true;
|
||||
} & _.Dictionary<_val, _ptr>
|
||||
)
|
||||
]
|
||||
>(L, Fs);
|
||||
})());
|
||||
}
|
||||
|
||||
export function isVersion(v: any): v is Version {return _.is(v, $1);}
|
||||
|
||||
export function asVersion(v: any): Version {
|
||||
if (!isVersion(v)) {throw new TypeError(`Invalid Version: ${_.stringify(v)}`);} else {return v;};
|
||||
}
|
||||
|
||||
export function decodeVersion(d: _.TypedDecoder<_ptr>): Version | undefined {return _.checkIs(d.nextSignedInteger(), $1);}
|
||||
|
||||
export function isPointerName(v: any): v is PointerName {return (isRef(v) || _.is(v, __lit5));}
|
||||
|
||||
export function asPointerName(v: any): PointerName {
|
||||
if (!isPointerName(v)) {throw new TypeError(`Invalid PointerName: ${_.stringify(v)}`);} else {return v;};
|
||||
}
|
||||
|
||||
export function decodePointerName(d: _.TypedDecoder<_ptr>): PointerName | undefined {return (decodeRef(d) ?? _.checkIs(d.nextBoolean(), __lit5));}
|
||||
|
||||
export function isPattern(v: any): v is Pattern {
|
||||
return (
|
||||
(
|
||||
|
@ -267,6 +336,254 @@ export function asPattern(v: any): Pattern {
|
|||
if (!isPattern(v)) {throw new TypeError(`Invalid Pattern: ${_.stringify(v)}`);} else {return v;};
|
||||
}
|
||||
|
||||
export function decodePattern(d: _.TypedDecoder<_ptr>): Pattern | undefined {
|
||||
return (
|
||||
((() => {
|
||||
const M = d.mark();
|
||||
if (!d.openRecord()) return void 0;
|
||||
const L = _.checkIs(d.nextSymbol(), $atom);
|
||||
if (L === void 0) { d.restoreMark(M); return void 0; };
|
||||
const Fs = (((() => {
|
||||
const M = d.mark();
|
||||
if (!d.openSequence()) return void 0;
|
||||
const v0 = (
|
||||
_.checkIs(d.nextSymbol(), $Boolean) ??
|
||||
_.checkIs(d.nextSymbol(), $Float) ??
|
||||
_.checkIs(d.nextSymbol(), $Double) ??
|
||||
_.checkIs(d.nextSymbol(), $SignedInteger) ??
|
||||
_.checkIs(d.nextSymbol(), $String) ??
|
||||
_.checkIs(d.nextSymbol(), $ByteString) ??
|
||||
_.checkIs(d.nextSymbol(), $Symbol)
|
||||
); if (v0 === void 0) { d.restoreMark(M); return void 0; };
|
||||
if (!d.closeCompound()) { d.restoreMark(M); return void 0; };
|
||||
return [v0] as [
|
||||
(
|
||||
(typeof $Boolean) |
|
||||
(typeof $Float) |
|
||||
(typeof $Double) |
|
||||
(typeof $SignedInteger) |
|
||||
(typeof $String) |
|
||||
(typeof $ByteString) |
|
||||
(typeof $Symbol)
|
||||
)
|
||||
];
|
||||
})())) as any;
|
||||
if (Fs === void 0) { d.restoreMark(M); return void 0; };
|
||||
return _.Record<
|
||||
(typeof $atom),
|
||||
[
|
||||
(
|
||||
(typeof $Boolean) |
|
||||
(typeof $Float) |
|
||||
(typeof $Double) |
|
||||
(typeof $SignedInteger) |
|
||||
(typeof $String) |
|
||||
(typeof $ByteString) |
|
||||
(typeof $Symbol)
|
||||
)
|
||||
]
|
||||
>(L, Fs);
|
||||
})()) ??
|
||||
((() => {
|
||||
const M = d.mark();
|
||||
if (!d.openRecord()) return void 0;
|
||||
const L = _.checkIs(d.nextSymbol(), $pointer);
|
||||
if (L === void 0) { d.restoreMark(M); return void 0; };
|
||||
const Fs = (((() => {
|
||||
const M = d.mark();
|
||||
if (!d.openSequence()) return void 0;
|
||||
if (!d.closeCompound()) { d.restoreMark(M); return void 0; };
|
||||
return [] as [];
|
||||
})())) as any;
|
||||
if (Fs === void 0) { d.restoreMark(M); return void 0; };
|
||||
return _.Record<(typeof $pointer), []>(L, Fs);
|
||||
})()) ??
|
||||
((() => {
|
||||
const M = d.mark();
|
||||
if (!d.openRecord()) return void 0;
|
||||
const L = _.checkIs(d.nextSymbol(), $lit);
|
||||
if (L === void 0) { d.restoreMark(M); return void 0; };
|
||||
const Fs = (((() => {
|
||||
const M = d.mark();
|
||||
if (!d.openSequence()) return void 0;
|
||||
const v0 = d.next(); if (v0 === void 0) { d.restoreMark(M); return void 0; };
|
||||
if (!d.closeCompound()) { d.restoreMark(M); return void 0; };
|
||||
return [v0] as [_val];
|
||||
})())) as any;
|
||||
if (Fs === void 0) { d.restoreMark(M); return void 0; };
|
||||
return _.Record<(typeof $lit), [_val]>(L, Fs);
|
||||
})()) ??
|
||||
decodeRef(d) ??
|
||||
((() => {
|
||||
const M = d.mark();
|
||||
if (!d.openRecord()) return void 0;
|
||||
const L = _.checkIs(d.nextSymbol(), $or);
|
||||
if (L === void 0) { d.restoreMark(M); return void 0; };
|
||||
const Fs = (((() => {
|
||||
const M = d.mark();
|
||||
if (!d.openSequence()) return void 0;
|
||||
const v0 = ((() => {
|
||||
const M = d.mark();
|
||||
if (!d.openSequence()) return void 0;
|
||||
const vN: Array<Pattern> = [];
|
||||
let tmp: undefined | Pattern;
|
||||
while ((tmp = decodePattern(d)) !== void 0) vN.push(tmp);
|
||||
if (!d.closeCompound()) { d.restoreMark(M); return void 0; };
|
||||
return vN;
|
||||
})()); if (v0 === void 0) { d.restoreMark(M); return void 0; };
|
||||
if (!d.closeCompound()) { d.restoreMark(M); return void 0; };
|
||||
return [v0] as [Array<Pattern>];
|
||||
})())) as any;
|
||||
if (Fs === void 0) { d.restoreMark(M); return void 0; };
|
||||
return _.Record<(typeof $or), [Array<Pattern>]>(L, Fs);
|
||||
})()) ??
|
||||
((() => {
|
||||
const M = d.mark();
|
||||
if (!d.openRecord()) return void 0;
|
||||
const L = _.checkIs(d.nextSymbol(), $and);
|
||||
if (L === void 0) { d.restoreMark(M); return void 0; };
|
||||
const Fs = (((() => {
|
||||
const M = d.mark();
|
||||
if (!d.openSequence()) return void 0;
|
||||
const v0 = ((() => {
|
||||
const M = d.mark();
|
||||
if (!d.openSequence()) return void 0;
|
||||
const vN: Array<Pattern> = [];
|
||||
let tmp: undefined | Pattern;
|
||||
while ((tmp = decodePattern(d)) !== void 0) vN.push(tmp);
|
||||
if (!d.closeCompound()) { d.restoreMark(M); return void 0; };
|
||||
return vN;
|
||||
})()); if (v0 === void 0) { d.restoreMark(M); return void 0; };
|
||||
if (!d.closeCompound()) { d.restoreMark(M); return void 0; };
|
||||
return [v0] as [Array<Pattern>];
|
||||
})())) as any;
|
||||
if (Fs === void 0) { d.restoreMark(M); return void 0; };
|
||||
return _.Record<(typeof $and), [Array<Pattern>]>(L, Fs);
|
||||
})()) ??
|
||||
((() => {
|
||||
const M = d.mark();
|
||||
if (!d.openRecord()) return void 0;
|
||||
const L = _.checkIs(d.nextSymbol(), $rec);
|
||||
if (L === void 0) { d.restoreMark(M); return void 0; };
|
||||
const Fs = (((() => {
|
||||
const M = d.mark();
|
||||
if (!d.openSequence()) return void 0;
|
||||
const v0 = decodePattern(d); if (v0 === void 0) { d.restoreMark(M); return void 0; };
|
||||
const v1 = decodePattern(d); if (v1 === void 0) { d.restoreMark(M); return void 0; };
|
||||
if (!d.closeCompound()) { d.restoreMark(M); return void 0; };
|
||||
return [v0, v1] as [Pattern, Pattern];
|
||||
})())) as any;
|
||||
if (Fs === void 0) { d.restoreMark(M); return void 0; };
|
||||
return _.Record<(typeof $rec), [Pattern, Pattern]>(L, Fs);
|
||||
})()) ??
|
||||
((() => {
|
||||
const M = d.mark();
|
||||
if (!d.openRecord()) return void 0;
|
||||
const L = _.checkIs(d.nextSymbol(), $tuple);
|
||||
if (L === void 0) { d.restoreMark(M); return void 0; };
|
||||
const Fs = (((() => {
|
||||
const M = d.mark();
|
||||
if (!d.openSequence()) return void 0;
|
||||
const v0 = ((() => {
|
||||
const M = d.mark();
|
||||
if (!d.openSequence()) return void 0;
|
||||
const vN: Array<NamedPattern> = [];
|
||||
let tmp: undefined | NamedPattern;
|
||||
while ((tmp = decodeNamedPattern(d)) !== void 0) vN.push(tmp);
|
||||
if (!d.closeCompound()) { d.restoreMark(M); return void 0; };
|
||||
return vN;
|
||||
})()); if (v0 === void 0) { d.restoreMark(M); return void 0; };
|
||||
if (!d.closeCompound()) { d.restoreMark(M); return void 0; };
|
||||
return [v0] as [Array<NamedPattern>];
|
||||
})())) as any;
|
||||
if (Fs === void 0) { d.restoreMark(M); return void 0; };
|
||||
return _.Record<(typeof $tuple), [Array<NamedPattern>]>(L, Fs);
|
||||
})()) ??
|
||||
((() => {
|
||||
const M = d.mark();
|
||||
if (!d.openRecord()) return void 0;
|
||||
const L = _.checkIs(d.nextSymbol(), $tuple_STAR_);
|
||||
if (L === void 0) { d.restoreMark(M); return void 0; };
|
||||
const Fs = (((() => {
|
||||
const M = d.mark();
|
||||
if (!d.openSequence()) return void 0;
|
||||
const v0 = ((() => {
|
||||
const M = d.mark();
|
||||
if (!d.openSequence()) return void 0;
|
||||
const vN: Array<NamedPattern> = [];
|
||||
let tmp: undefined | NamedPattern;
|
||||
while ((tmp = decodeNamedPattern(d)) !== void 0) vN.push(tmp);
|
||||
if (!d.closeCompound()) { d.restoreMark(M); return void 0; };
|
||||
return vN;
|
||||
})()); if (v0 === void 0) { d.restoreMark(M); return void 0; };
|
||||
const v1 = decodeNamedPattern(d); if (v1 === void 0) { d.restoreMark(M); return void 0; };
|
||||
if (!d.closeCompound()) { d.restoreMark(M); return void 0; };
|
||||
return [v0, v1] as [Array<NamedPattern>, NamedPattern];
|
||||
})())) as any;
|
||||
if (Fs === void 0) { d.restoreMark(M); return void 0; };
|
||||
return _.Record<(typeof $tuple_STAR_), [Array<NamedPattern>, NamedPattern]>(L, Fs);
|
||||
})()) ??
|
||||
((() => {
|
||||
const M = d.mark();
|
||||
if (!d.openRecord()) return void 0;
|
||||
const L = _.checkIs(d.nextSymbol(), $setof);
|
||||
if (L === void 0) { d.restoreMark(M); return void 0; };
|
||||
const Fs = (((() => {
|
||||
const M = d.mark();
|
||||
if (!d.openSequence()) return void 0;
|
||||
const v0 = decodePattern(d); if (v0 === void 0) { d.restoreMark(M); return void 0; };
|
||||
if (!d.closeCompound()) { d.restoreMark(M); return void 0; };
|
||||
return [v0] as [Pattern];
|
||||
})())) as any;
|
||||
if (Fs === void 0) { d.restoreMark(M); return void 0; };
|
||||
return _.Record<(typeof $setof), [Pattern]>(L, Fs);
|
||||
})()) ??
|
||||
((() => {
|
||||
const M = d.mark();
|
||||
if (!d.openRecord()) return void 0;
|
||||
const L = _.checkIs(d.nextSymbol(), $dictof);
|
||||
if (L === void 0) { d.restoreMark(M); return void 0; };
|
||||
const Fs = (((() => {
|
||||
const M = d.mark();
|
||||
if (!d.openSequence()) return void 0;
|
||||
const v0 = decodePattern(d); if (v0 === void 0) { d.restoreMark(M); return void 0; };
|
||||
const v1 = decodePattern(d); if (v1 === void 0) { d.restoreMark(M); return void 0; };
|
||||
if (!d.closeCompound()) { d.restoreMark(M); return void 0; };
|
||||
return [v0, v1] as [Pattern, Pattern];
|
||||
})())) as any;
|
||||
if (Fs === void 0) { d.restoreMark(M); return void 0; };
|
||||
return _.Record<(typeof $dictof), [Pattern, Pattern]>(L, Fs);
|
||||
})()) ??
|
||||
((() => {
|
||||
const M = d.mark();
|
||||
if (!d.openRecord()) return void 0;
|
||||
const L = _.checkIs(d.nextSymbol(), $dict);
|
||||
if (L === void 0) { d.restoreMark(M); return void 0; };
|
||||
const Fs = (((() => {
|
||||
const M = d.mark();
|
||||
if (!d.openSequence()) return void 0;
|
||||
const v0 = ((() => {
|
||||
const M = d.mark();
|
||||
if (!d.openDictionary()) return void 0;
|
||||
const r: _.KeyedDictionary<_val, Pattern, _ptr> = new _.KeyedDictionary();
|
||||
let K: undefined | _val;
|
||||
while ((K = d.next()) !== void 0) {
|
||||
const V = decodePattern(d);
|
||||
if (V === void 0) { d.restoreMark(M); return void 0; };
|
||||
r.set(K, V);
|
||||
};
|
||||
if (!d.closeCompound()) { d.restoreMark(M); return void 0; };
|
||||
return r;
|
||||
})()); if (v0 === void 0) { d.restoreMark(M); return void 0; };
|
||||
if (!d.closeCompound()) { d.restoreMark(M); return void 0; };
|
||||
return [v0] as [_.KeyedDictionary<_val, Pattern, _ptr>];
|
||||
})())) as any;
|
||||
if (Fs === void 0) { d.restoreMark(M); return void 0; };
|
||||
return _.Record<(typeof $dict), [_.KeyedDictionary<_val, Pattern, _ptr>]>(L, Fs);
|
||||
})())
|
||||
);
|
||||
}
|
||||
|
||||
export function isNamedPattern(v: any): v is NamedPattern {
|
||||
return (
|
||||
(
|
||||
|
@ -282,6 +599,28 @@ export function asNamedPattern(v: any): NamedPattern {
|
|||
if (!isNamedPattern(v)) {throw new TypeError(`Invalid NamedPattern: ${_.stringify(v)}`);} else {return v;};
|
||||
}
|
||||
|
||||
export function decodeNamedPattern(d: _.TypedDecoder<_ptr>): NamedPattern | undefined {
|
||||
return (
|
||||
((() => {
|
||||
const M = d.mark();
|
||||
if (!d.openRecord()) return void 0;
|
||||
const L = _.checkIs(d.nextSymbol(), $named);
|
||||
if (L === void 0) { d.restoreMark(M); return void 0; };
|
||||
const Fs = (((() => {
|
||||
const M = d.mark();
|
||||
if (!d.openSequence()) return void 0;
|
||||
const v0 = d.nextSymbol(); if (v0 === void 0) { d.restoreMark(M); return void 0; };
|
||||
const v1 = decodePattern(d); if (v1 === void 0) { d.restoreMark(M); return void 0; };
|
||||
if (!d.closeCompound()) { d.restoreMark(M); return void 0; };
|
||||
return [v0, v1] as [symbol, Pattern];
|
||||
})())) as any;
|
||||
if (Fs === void 0) { d.restoreMark(M); return void 0; };
|
||||
return _.Record<(typeof $named), [symbol, Pattern]>(L, Fs);
|
||||
})()) ??
|
||||
decodePattern(d)
|
||||
);
|
||||
}
|
||||
|
||||
export function isRef(v: any): v is Ref {
|
||||
return (
|
||||
_.Record.isRecord<_val, _.Tuple<_val>, _ptr>(v) &&
|
||||
|
@ -294,12 +633,33 @@ export function asRef(v: any): Ref {
|
|||
if (!isRef(v)) {throw new TypeError(`Invalid Ref: ${_.stringify(v)}`);} else {return v;};
|
||||
}
|
||||
|
||||
export function decodeRef(d: _.TypedDecoder<_ptr>): Ref | undefined {
|
||||
return ((() => {
|
||||
const M = d.mark();
|
||||
if (!d.openRecord()) return void 0;
|
||||
const L = _.checkIs(d.nextSymbol(), $ref);
|
||||
if (L === void 0) { d.restoreMark(M); return void 0; };
|
||||
const Fs = (((() => {
|
||||
const M = d.mark();
|
||||
if (!d.openSequence()) return void 0;
|
||||
const v0 = decodeModuleRef(d); if (v0 === void 0) { d.restoreMark(M); return void 0; };
|
||||
const v1 = d.nextSymbol(); if (v1 === void 0) { d.restoreMark(M); return void 0; };
|
||||
if (!d.closeCompound()) { d.restoreMark(M); return void 0; };
|
||||
return [v0, v1] as [ModuleRef, symbol];
|
||||
})())) as any;
|
||||
if (Fs === void 0) { d.restoreMark(M); return void 0; };
|
||||
return _.Record<(typeof $ref), [ModuleRef, symbol]>(L, Fs);
|
||||
})());
|
||||
}
|
||||
|
||||
export function isModuleRef(v: any): v is ModuleRef {return (_.is(v, $thisModule) || isModulePath(v));}
|
||||
|
||||
export function asModuleRef(v: any): ModuleRef {
|
||||
if (!isModuleRef(v)) {throw new TypeError(`Invalid ModuleRef: ${_.stringify(v)}`);} else {return v;};
|
||||
}
|
||||
|
||||
export function decodeModuleRef(d: _.TypedDecoder<_ptr>): ModuleRef | undefined {return (_.checkIs(d.nextSymbol(), $thisModule) ?? decodeModulePath(d));}
|
||||
|
||||
export function isModulePath(v: any): v is ModulePath {
|
||||
return (
|
||||
_.Array.isArray(v) &&
|
||||
|
@ -313,3 +673,17 @@ export function asModulePath(v: any): ModulePath {
|
|||
if (!isModulePath(v)) {throw new TypeError(`Invalid ModulePath: ${_.stringify(v)}`);} else {return v;};
|
||||
}
|
||||
|
||||
export function decodeModulePath(d: _.TypedDecoder<_ptr>): ModulePath | undefined {
|
||||
return ((() => {
|
||||
const M = d.mark();
|
||||
if (!d.openSequence()) return void 0;
|
||||
const vN: Array<symbol> = [];
|
||||
let tmp: undefined | symbol;
|
||||
while ((tmp = d.nextSymbol()) !== void 0) vN.push(tmp);
|
||||
if (!d.closeCompound()) { d.restoreMark(M); return void 0; };
|
||||
return vN;
|
||||
})());
|
||||
}
|
||||
|
||||
export const _decodePtr = () => { throw new _.DecodeError("Pointers forbidden"); };
|
||||
|
||||
|
|
Loading…
Reference in New Issue