diff --git a/package.json b/package.json index b5deba4..392f4c3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,7 @@ { "devDependencies": { "@types/node": "^14.14.31", + "rollup": "^2.40.0", "typescript": "^4.1.5" }, "dependencies": { @@ -9,6 +10,8 @@ "scripts": { "compile": "npx tsc", "compile:watch": "npx tsc -w", - "clean": "rm -rf lib" + "rollup": "npx rollup -c", + "rollup:watch": "npx rollup -c -w", + "clean": "rm -rf lib index.js" } } diff --git a/src/actor.ts b/src/actor.ts index 8f2fa06..f724f6b 100644 --- a/src/actor.ts +++ b/src/actor.ts @@ -1,4 +1,5 @@ import { Dictionary, IdentitySet, Record, Tuple, Value, is, IdentityMap } from 'preserves'; +import { Attenuation, runRewrites } from './rewrite.js'; import { queueTask } from './task.js'; //--------------------------------------------------------------------------- @@ -25,74 +26,6 @@ export interface Ref { readonly attenuation?: Attenuation; } -export type Attenuation = Array; // array of stages, each a list of alternatives -export type RewriteStage = Array; -export type Rewrite = { pattern: Pattern, template: Template }; - -export const _CRec = Symbol.for('rec'); -export const CRec = Record.makeConstructor<{label: Value, arity: number}, never>()( - _CRec, ['label', 'arity']); - -export const _CArr = Symbol.for('arr'); -export const CArr = Record.makeConstructor<{arity: number}, never>()( - _CArr, ['arity']); - -export const _CDict = Symbol.for('dict'); -export const CDict = Record.makeConstructor<{}, never>()( - _CDict, []); - -export type ConstructorSpec = - | Record, number], never> - | Record - | Record; - -export const _PDiscard = Symbol.for('_'); -export const PDiscard = Record.makeConstructor<{}, never>()( - _PDiscard, []); - -export const _PBind = Symbol.for('bind'); -export const PBind = Record.makeConstructor<{name: string, pattern: Pattern}, never>()( - _PBind, ['name', 'pattern']); - -export const _PAnd = Symbol.for('and'); -export const PAnd = Record.makeConstructor<{patterns: Array}, never>()( - _PAnd, ['patterns']); - -export const _PNot = Symbol.for('not'); -export const PNot = Record.makeConstructor<{pattern: Pattern}, never>()( - _PNot, ['pattern']); - -export const _Lit = Symbol.for('lit'); -export const Lit = Record.makeConstructor<{value: Value}, never>()( - _Lit, ['value']); - -export const _PCompound = Symbol.for('compound'); -export const PCompound = - Record.makeConstructor<{ctor: ConstructorSpec, members: Dictionary}, never>()( - _PCompound, ['ctor', 'members']); - -export type Pattern = - | Record - | Record - | Record - | Record - | Record], never> - | Record], never>; - -export const _TRef = Symbol.for('ref'); -export const TRef = Record.makeConstructor<{name: string}, never>()( - _TRef, ['name']); - -export const _TCompound = Symbol.for('compound'); -export const TCompound = - Record.makeConstructor<{ctor: ConstructorSpec, members: Dictionary}, never>()( - _TCompound, ['ctor', 'members']); - -export type Template = - | Record - | Record], never> - | Record], never>; - export type Bindings = { [name: string]: Assertion }; //--------------------------------------------------------------------------- @@ -133,9 +66,6 @@ export class Actor { } let nextHandle = 0; -function allocateHandle(): Handle { - return nextHandle++; -} export function _sync_impl(turn: Turn, e: Partial, peer: Ref): void { e.sync ? e.sync!(turn, peer) : turn.message(peer, true); @@ -188,7 +118,7 @@ export class Turn { } assert(ref: Ref, assertion: Assertion): Handle { - const h = allocateHandle(); + const h = nextHandle++; this._assert(ref, assertion, h); return h; } @@ -251,182 +181,3 @@ export class Turn { Turn.for(this.actor, a); } } - -//--------------------------------------------------------------------------- - -export function match(p: Pattern, v: Assertion): Bindings | null { - let bindings: Bindings = {}; - - function walk(p: Pattern, v: Assertion): boolean { - switch (p.label) { - case _PDiscard: - return true; - case _PBind: - if (walk(PBind._.pattern(p), v)) { - bindings[PBind._.name(p)] = v; - return true; - } - return false; - case _PAnd: - for (const pp of PAnd._.patterns(p)) { - if (!walk(pp, v)) return false; - } - return true; - case _PNot: { - const savedBindings = bindings; - bindings = {}; - const result = !walk(PNot._.pattern(p), v) - bindings = savedBindings; - return result; - } - case _Lit: - return is(Lit._.value(p), v); - case _PCompound: { - const ctor = PCompound._.ctor(p); - const members = PCompound._.members(p); - switch (ctor.label) { - case _CRec: - if (!Record.isRecord, Ref>(v)) return false; - if (!is(CRec._.label(ctor), v.label)) return false; - if (CRec._.arity(ctor) !== v.length) return false; - for (const [key, pp] of members) { - if (typeof key !== 'number') return false; - if (!walk(pp, v[key])) return false; - } - return true; - case _CArr: - if (!Array.isArray(v)) return false; - if ('label' in v) return false; - if (CArr._.arity(ctor) !== v.length) return false; - for (const [key, pp] of members) { - if (typeof key !== 'number') return false; - if (!walk(pp, v[key])) return false; - } - return true; - case _CDict: - if (!Dictionary.isDictionary(v)) return false; - for (const [key, pp] of members) { - const vv = v.get(key as Assertion); - if (vv === void 0) return false; - if (!walk(pp, vv)) return false; - } - return true; - } - } - } - } - - return walk(p, v) ? bindings : null; -} - -export function instantiate(t: Template, b: Bindings): Assertion { - function walk(t: Template): Assertion { - switch (t.label) { - case _TRef: { - const v = b[TRef._.name(t)]; - if (v === void 0) throw new Error(`Unbound reference: ${TRef._.name(t)}`); - return v; - } - case _Lit: - return Lit._.value(t) as Assertion; - case _TCompound: { - const ctor = TCompound._.ctor(t); - const members = TCompound._.members(t); - switch (ctor.label) { - case _CRec: { - const v = Record( - CRec._.label(ctor) as Assertion, - [], - ); - v.length = CRec._.arity(ctor); - for (const [key, tt] of members) { - v[key as number] = walk(tt); - } - return v; - } - case _CArr: { - const v = []; - v.length = CArr._.arity(ctor); - for (const [key, tt] of members) { - v[key as number] = walk(tt); - } - return v; - } - case _CDict: { - const v = new Dictionary(); - for (const [key, tt] of members) { - v.set(key as Assertion, walk(tt)); - } - return v; - } - } - } - } - } - - return walk(t); -} - -export function rewrite(r: Rewrite, v: Assertion): Assertion | null { - const bindings = match(r.pattern, v); - if (bindings === null) return null; - return instantiate(r.template, bindings); -} - -export function examineAlternatives(alternatives: RewriteStage, v: Assertion): Assertion | null { - for (const r of alternatives) { - const w = rewrite(r, v); - if (w !== null) return w; - } - return null; -} - -export function runRewrites(a: Attenuation | undefined, v: Assertion): Assertion | null { - if (a !== void 0) { - for (const stage of a) { - const w = examineAlternatives(stage, v); - if (w === null) return null; - v = w; - } - } - return v; -} - -export function rfilter(... patterns: Pattern[]): RewriteStage { - return patterns.map(p => ({ pattern: PBind('a', p), template: TRef('a') })); -} - -export function attenuate(ref: Ref, ... a: Attenuation): Ref { - return { ... ref, attenuation: [... a, ... (ref.attenuation ?? [])] }; -} - -export function forwarder(t: Turn, ref: Ref): { proxy: Ref, revoker: Ref } { - let underlying: Ref | null = ref; - let handleMap = new IdentityMap(); - let proxy = t.ref({ - assert(turn: Turn, assertion: Assertion, handle: Handle): void { - if (underlying === null) return; - handleMap.set(handle, turn.assert(underlying, assertion)); - }, - retract(turn: Turn, handle: Handle): void { - if (underlying === null) return; - turn.retract(handleMap.get(handle)); - handleMap.delete(handle); - }, - message(turn: Turn, body: Assertion): void { - if (underlying === null) return; - turn.message(underlying, body); - }, - sync(turn: Turn, peer: Ref): void { - if (underlying === null) return; - turn._sync(underlying, peer); - }, - }); - let revoker = t.ref({ - message(turn: Turn, _body: Assertion): void { - underlying = null; - handleMap.forEach(h => turn.retract(h)); - }, - }); - return { proxy, revoker }; -} diff --git a/src/main.ts b/src/main.ts index 90817b7..52c374b 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,9 +1,10 @@ -import { Actor, Assertion, attenuate, CRec, Lit, Pattern, PCompound, rfilter, Turn } from './actor.js'; +import { Actor, Assertion, Turn } from './actor.js'; import { Dictionary, Record } from 'preserves'; import { Dataspace, Observe } from './dataspace.js'; import { Worker } from 'worker_threads'; import { Relay, spawnRelay } from './relay.js'; import { BoxState, SetBox } from './box-protocol.js'; +import { attenuate, CRec, Lit, Pattern, PCompound, rfilter } from './rewrite.js'; import path from 'path'; const Instance = Record.makeConstructor<{moduleName: string, arg: Assertion}>()( diff --git a/src/protocol.ts b/src/protocol.ts index 6abe1ab..a936621 100644 --- a/src/protocol.ts +++ b/src/protocol.ts @@ -1,4 +1,5 @@ -import { Attenuation, Handle, Pattern, Ref, Template } from './actor.js'; +import { Handle, Ref } from './actor.js'; +import { Attenuation, Pattern, Template } from './rewrite.js'; import { Record, Value } from 'preserves'; export const _Assert = Symbol.for('assert'); diff --git a/src/relay.ts b/src/relay.ts index b6d958e..edda73d 100644 --- a/src/relay.ts +++ b/src/relay.ts @@ -1,4 +1,4 @@ -import { Actor, Assertion, attenuate, Entity, Handle, Ref, Turn } from './actor.js'; +import { Actor, Assertion, Entity, Handle, Ref, Turn } from './actor.js'; import { BytesLike, canonicalString, Decoder, encode, FlexMap, IdentityMap, mapPointers, underlying, Value } from 'preserves'; import { EncodedAttenuation, @@ -18,6 +18,7 @@ import { yourRef, } from './protocol.js'; import { queueTask } from './task.js'; +import { attenuate } from './rewrite.js'; export class SyncPeerEntity implements Entity { readonly relay: Relay; diff --git a/src/rewrite.ts b/src/rewrite.ts new file mode 100644 index 0000000..f4ff1a0 --- /dev/null +++ b/src/rewrite.ts @@ -0,0 +1,247 @@ +import type { Assertion, Bindings, Handle, Ref, Turn } from "./actor.js"; +import { Dictionary, IdentityMap, is, Record, Tuple, Value } from "preserves"; + +export type Attenuation = Array; // array of stages, each a list of alternatives +export type RewriteStage = Array; +export type Rewrite = { pattern: Pattern, template: Template }; + +export const _CRec = Symbol.for('rec'); +export const CRec = Record.makeConstructor<{label: Value, arity: number}, never>()( + _CRec, ['label', 'arity']); + +export const _CArr = Symbol.for('arr'); +export const CArr = Record.makeConstructor<{arity: number}, never>()( + _CArr, ['arity']); + +export const _CDict = Symbol.for('dict'); +export const CDict = Record.makeConstructor<{}, never>()( + _CDict, []); + +export type ConstructorSpec = + | Record, number], never> + | Record + | Record; + +export const _PDiscard = Symbol.for('_'); +export const PDiscard = Record.makeConstructor<{}, never>()( + _PDiscard, []); + +export const _PBind = Symbol.for('bind'); +export const PBind = Record.makeConstructor<{name: string, pattern: Pattern}, never>()( + _PBind, ['name', 'pattern']); + +export const _PAnd = Symbol.for('and'); +export const PAnd = Record.makeConstructor<{patterns: Array}, never>()( + _PAnd, ['patterns']); + +export const _PNot = Symbol.for('not'); +export const PNot = Record.makeConstructor<{pattern: Pattern}, never>()( + _PNot, ['pattern']); + +export const _Lit = Symbol.for('lit'); +export const Lit = Record.makeConstructor<{value: Value}, never>()( + _Lit, ['value']); + +export const _PCompound = Symbol.for('compound'); +export const PCompound = + Record.makeConstructor<{ctor: ConstructorSpec, members: Dictionary}, never>()( + _PCompound, ['ctor', 'members']); + +export type Pattern = + | Record + | Record + | Record + | Record + | Record], never> + | Record], never>; + +export const _TRef = Symbol.for('ref'); +export const TRef = Record.makeConstructor<{name: string}, never>()( + _TRef, ['name']); + +export const _TCompound = Symbol.for('compound'); +export const TCompound = + Record.makeConstructor<{ctor: ConstructorSpec, members: Dictionary}, never>()( + _TCompound, ['ctor', 'members']); + +export type Template = + | Record + | Record], never> + | Record], never>; + +export function match(p: Pattern, v: Assertion): Bindings | null { + let bindings: Bindings = {}; + + function walk(p: Pattern, v: Assertion): boolean { + switch (p.label) { + case _PDiscard: + return true; + case _PBind: + if (walk(PBind._.pattern(p), v)) { + bindings[PBind._.name(p)] = v; + return true; + } + return false; + case _PAnd: + for (const pp of PAnd._.patterns(p)) { + if (!walk(pp, v)) return false; + } + return true; + case _PNot: { + const savedBindings = bindings; + bindings = {}; + const result = !walk(PNot._.pattern(p), v) + bindings = savedBindings; + return result; + } + case _Lit: + return is(Lit._.value(p), v); + case _PCompound: { + const ctor = PCompound._.ctor(p); + const members = PCompound._.members(p); + switch (ctor.label) { + case _CRec: + if (!Record.isRecord, Ref>(v)) return false; + if (!is(CRec._.label(ctor), v.label)) return false; + if (CRec._.arity(ctor) !== v.length) return false; + for (const [key, pp] of members) { + if (typeof key !== 'number') return false; + if (!walk(pp, v[key])) return false; + } + return true; + case _CArr: + if (!Array.isArray(v)) return false; + if ('label' in v) return false; + if (CArr._.arity(ctor) !== v.length) return false; + for (const [key, pp] of members) { + if (typeof key !== 'number') return false; + if (!walk(pp, v[key])) return false; + } + return true; + case _CDict: + if (!Dictionary.isDictionary(v)) return false; + for (const [key, pp] of members) { + const vv = v.get(key as Assertion); + if (vv === void 0) return false; + if (!walk(pp, vv)) return false; + } + return true; + } + } + } + } + + return walk(p, v) ? bindings : null; +} + +export function instantiate(t: Template, b: Bindings): Assertion { + function walk(t: Template): Assertion { + switch (t.label) { + case _TRef: { + const v = b[TRef._.name(t)]; + if (v === void 0) throw new Error(`Unbound reference: ${TRef._.name(t)}`); + return v; + } + case _Lit: + return Lit._.value(t) as Assertion; + case _TCompound: { + const ctor = TCompound._.ctor(t); + const members = TCompound._.members(t); + switch (ctor.label) { + case _CRec: { + const v = Record( + CRec._.label(ctor) as Assertion, + [], + ); + v.length = CRec._.arity(ctor); + for (const [key, tt] of members) { + v[key as number] = walk(tt); + } + return v; + } + case _CArr: { + const v = []; + v.length = CArr._.arity(ctor); + for (const [key, tt] of members) { + v[key as number] = walk(tt); + } + return v; + } + case _CDict: { + const v = new Dictionary(); + for (const [key, tt] of members) { + v.set(key as Assertion, walk(tt)); + } + return v; + } + } + } + } + } + + return walk(t); +} + +export function rewrite(r: Rewrite, v: Assertion): Assertion | null { + const bindings = match(r.pattern, v); + if (bindings === null) return null; + return instantiate(r.template, bindings); +} + +export function examineAlternatives(alternatives: RewriteStage, v: Assertion): Assertion | null { + for (const r of alternatives) { + const w = rewrite(r, v); + if (w !== null) return w; + } + return null; +} + +export function runRewrites(a: Attenuation | undefined, v: Assertion): Assertion | null { + if (a !== void 0) { + for (const stage of a) { + const w = examineAlternatives(stage, v); + if (w === null) return null; + v = w; + } + } + return v; +} + +export function rfilter(... patterns: Pattern[]): RewriteStage { + return patterns.map(p => ({ pattern: PBind('a', p), template: TRef('a') })); +} + +export function attenuate(ref: Ref, ... a: Attenuation): Ref { + return { ... ref, attenuation: [... a, ... (ref.attenuation ?? [])] }; +} + +export function forwarder(t: Turn, ref: Ref): { proxy: Ref, revoker: Ref } { + let underlying: Ref | null = ref; + let handleMap = new IdentityMap(); + let proxy = t.ref({ + assert(turn: Turn, assertion: Assertion, handle: Handle): void { + if (underlying === null) return; + handleMap.set(handle, turn.assert(underlying, assertion)); + }, + retract(turn: Turn, handle: Handle): void { + if (underlying === null) return; + turn.retract(handleMap.get(handle)); + handleMap.delete(handle); + }, + message(turn: Turn, body: Assertion): void { + if (underlying === null) return; + turn.message(underlying, body); + }, + sync(turn: Turn, peer: Ref): void { + if (underlying === null) return; + turn._sync(underlying, peer); + }, + }); + let revoker = t.ref({ + message(turn: Turn, _body: Assertion): void { + underlying = null; + handleMap.forEach(h => turn.retract(h)); + }, + }); + return { proxy, revoker }; +} diff --git a/tsconfig.json b/tsconfig.json index 3d3ecc6..2a9798e 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -9,7 +9,7 @@ "declarationDir": "./lib", "esModuleInterop": true, "moduleResolution": "node", - "module": "commonjs", + "module": "esnext", "sourceMap": true, "strict": true },