diff --git a/implementations/javascript/package.json b/implementations/javascript/package.json index 8854316..e07d0a2 100644 --- a/implementations/javascript/package.json +++ b/implementations/javascript/package.json @@ -1,6 +1,6 @@ { "name": "preserves", - "version": "0.5.0", + "version": "0.5.1", "description": "Experimental data serialization format", "homepage": "https://gitlab.com/preserves/preserves", "license": "Apache-2.0", diff --git a/implementations/javascript/src/codec.ts b/implementations/javascript/src/codec.ts index 8ccbd35..02d0e8d 100644 --- a/implementations/javascript/src/codec.ts +++ b/implementations/javascript/src/codec.ts @@ -135,8 +135,8 @@ export class Decoder { return this.includeAnnotations ? new Annotated(v) : v; } - static dictionaryFromArray(vs: Value[]): Dictionary> { - const d = new Dictionary>(); + static dictionaryFromArray(vs: Value[]): Dictionary, T> { + const d = new Dictionary, T>(); if (vs.length % 2) throw new DecodeError("Missing dictionary value"); for (let i = 0; i < vs.length; i += 2) { d.set(vs[i], vs[i+1]); diff --git a/implementations/javascript/src/values.ts b/implementations/javascript/src/values.ts index b699816..24856bb 100644 --- a/implementations/javascript/src/values.ts +++ b/implementations/javascript/src/values.ts @@ -9,15 +9,17 @@ import { _iterMap, FlexMap, FlexSet } from './flex'; const textEncoder = new TextEncoder(); const textDecoder = new TextDecoder(); -export type Value = Atom | Compound | T | Annotated; +export type DefaultPointer = object + +export type Value = Atom | Compound | T | Annotated; export type Atom = boolean | Single | Double | number | string | Bytes | symbol; -export type Compound = Record | Array> | Set | Dictionary>; +export type Compound = Record | Array> | Set | Dictionary, T>; export const IsPreservesRecord = Symbol.for('IsPreservesRecord'); export const IsPreservesBytes = Symbol.for('IsPreservesBytes'); export const IsPreservesAnnotated = Symbol.for('IsPreservesAnnotated'); -export function fromJS(x: any): Value { +export function fromJS(x: any): Value { switch (typeof x) { case 'number': if (!Number.isInteger(x)) { @@ -95,7 +97,7 @@ export abstract class Float { } export class Single extends Float implements Preservable { - [AsPreserve](): Value { + [AsPreserve](): Value { return this; } @@ -116,7 +118,7 @@ export class Single extends Float implements Preservable { } export class Double extends Float implements Preservable { - [AsPreserve](): Value { + [AsPreserve](): Value { return this; } @@ -254,7 +256,7 @@ export class Bytes implements Preservable { return this.asPreservesText(); } - [AsPreserve](): Value { + [AsPreserve](): Value { return this; } @@ -416,7 +418,7 @@ keys lastIndexOf reduce reduceRight some toLocaleString values`.split(/\s+/)) Bytes.prototype[Symbol.iterator] = function () { return this._view[Symbol.iterator](); }; })(); -export class Record extends Array> { +export class Record extends Array> { readonly label: Value; constructor(label: Value, fieldsJS: any[]) { @@ -482,11 +484,11 @@ export class Record extends Array> { }).join(', ') + ')'; } - static makeConstructor(labelSymbolText: string, fieldNames: string[]): RecordConstructor { + static makeConstructor(labelSymbolText: string, fieldNames: string[]): RecordConstructor { return Record.makeBasicConstructor(Symbol.for(labelSymbolText), fieldNames); } - static makeBasicConstructor(label0: any, fieldNames: string[]): RecordConstructor { + static makeBasicConstructor(label0: any, fieldNames: string[]): RecordConstructor { const label = fromJS(label0); const arity = fieldNames.length; const ctor: RecordConstructor = (...fields: any[]): Record => { @@ -523,23 +525,23 @@ export class Record extends Array> { return true; } - static isRecord(x: any): x is Record { + static isRecord(x: any): x is Record { return !!x?.[IsPreservesRecord]; } - static isClassOf(ci: RecordConstructorInfo, v: any): v is Record { + static isClassOf(ci: RecordConstructorInfo, v: any): v is Record { return (Record.isRecord(v)) && is(ci.label, v.label) && (ci.arity === v.length); } } -export interface RecordConstructor { +export interface RecordConstructor { (...fields: any[]): Record; constructorInfo: RecordConstructorInfo; isClassOf(v: any): v is Record; _: { [getter: string]: (r: any) => Value | undefined }; } -export interface RecordConstructorInfo { +export interface RecordConstructorInfo { label: Value; arity: number; } @@ -564,18 +566,18 @@ export function is(a: any, b: any): boolean { export type DictionaryType = 'Dictionary' | 'Set'; export const DictionaryType = Symbol.for('DictionaryType'); -export class Dictionary extends FlexMap, V> { +export class Dictionary extends FlexMap, V> { get [DictionaryType](): DictionaryType { return 'Dictionary'; } - static isDictionary(x: any): x is Dictionary { + static isDictionary(x: any): x is Dictionary { return x?.[DictionaryType] === 'Dictionary'; } - static fromJS(x: object): Dictionary> { - if (Dictionary.isDictionary(x)) return x as Dictionary>; - const d = new Dictionary>(); + static fromJS(x: object): Dictionary, T> { + if (Dictionary.isDictionary(x)) return x as Dictionary, T>; + const d = new Dictionary, T>(); Object.entries(x).forEach(([key, value]) => d.set(key, fromJS(value))); return d; } @@ -585,8 +587,8 @@ export class Dictionary extends FlexMap, V> { super(canonicalString, iter === void 0 ? void 0 : _iterMap(iter, ([k,v]) => [fromJS(k), v])); } - mapEntries(f: (entry: [Value, V]) => [Value, W]): Dictionary { - const result = new Dictionary(); + mapEntries(f: (entry: [Value, V]) => [Value, W]): Dictionary { + const result = new Dictionary(); for (let oldEntry of this.entries()) { const newEntry = f(oldEntry); result.set(newEntry[0], newEntry[1]) @@ -601,7 +603,7 @@ export class Dictionary extends FlexMap, V> { '}'; } - clone(): Dictionary { + clone(): Dictionary { return new Dictionary(this); } @@ -628,12 +630,12 @@ export class Dictionary extends FlexMap, V> { } } -export class Set extends FlexSet> { +export class Set extends FlexSet> { get [DictionaryType](): DictionaryType { return 'Set'; } - static isSet(x: any): x is Set { + static isSet(x: any): x is Set { return x?.[DictionaryType] === 'Set'; } @@ -642,7 +644,7 @@ export class Set extends FlexSet> { super(canonicalString, iter === void 0 ? void 0 : _iterMap>(iter, fromJS)); } - map(f: (value: Value) => Value): Set { + map(f: (value: Value) => Value): Set { return new Set(_iterMap(this[Symbol.iterator](), f)); } @@ -679,7 +681,7 @@ export class Set extends FlexSet> { } } -export class Annotated { +export class Annotated { readonly annotations: Array>; readonly item: Value; @@ -723,16 +725,16 @@ export class Annotated { return true; } - static isAnnotated(x: any): x is Annotated { + static isAnnotated(x: any): x is Annotated { return !!x?.[IsPreservesAnnotated]; } } -export function peel(v: Value): Value { +export function peel(v: Value): Value { return strip(v, 1); } -export function strip(v: Value, depth: number = Infinity): Value { +export function strip(v: Value, depth: number = Infinity): Value { function step(v: Value, depth: number): Value { if (depth === 0) return v; if (!Annotated.isAnnotated(v)) return v; @@ -746,7 +748,7 @@ export function strip(v: Value, depth: number = Infinity): return v.item.map(walk); } else if (Set.isSet(v.item)) { return v.item.map(walk); - } else if (Dictionary.isDictionary>(v.item)) { + } else if (Dictionary.isDictionary, T>(v.item)) { return v.item.mapEntries((e) => [walk(e[0]), walk(e[1])]); } else if (Annotated.isAnnotated(v.item)) { throw new Error("Improper annotation structure"); @@ -757,7 +759,7 @@ export function strip(v: Value, depth: number = Infinity): return step(v, depth); } -export function annotate(v0: Value, ...anns: Value[]): Annotated { +export function annotate(v0: Value, ...anns: Value[]): Annotated { const v = Annotated.isAnnotated(v0) ? v0 : new Annotated(v0); anns.forEach((a) => v.annotations.push(a)); return v; diff --git a/implementations/javascript/test/codec.test.ts b/implementations/javascript/test/codec.test.ts index b437e55..10eeff3 100644 --- a/implementations/javascript/test/codec.test.ts +++ b/implementations/javascript/test/codec.test.ts @@ -151,7 +151,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(); m.set([A1], 1); m.set([A2], 2); expect(m.get(A1)).toBeUndefined(); @@ -241,7 +241,7 @@ describe('common test suite', () => { }); } - const tests = peel(TestCases._.cases(peel(samples))!) as Dictionary>; + const tests = peel(TestCases._.cases(peel(samples))!) as Dictionary, Pointer>; tests.forEach((t0: Value, tName0: Value) => { const tName = Symbol.keyFor(strip(tName0) as symbol)!; const t = peel(t0) as Record;