Strict tsconfig; more major steps toward ocap style

This commit is contained in:
Tony Garnock-Jones 2021-01-15 13:38:15 +01:00
parent 595a13dfd6
commit 0f6059cd59
17 changed files with 419 additions and 238 deletions

View File

@ -30,7 +30,7 @@ console.time('box-and-client-' + N.toString());
export function boot(thisFacet) {
thisFacet.spawn('box', function (thisFacet) {
thisFacet.actor.dataspace.declareField(this, 'value', 0);
thisFacet.declareField(this, 'value', 0);
thisFacet.addEndpoint(() => {
// console.log('recomputing published BoxState', this.value);
return { assertion: BoxState(this.value), analysis: null };

View File

@ -1,5 +1,5 @@
import {
Item, Items,
Token, Items,
Pattern,
scope, bind, seq, alt, upTo, atom, group, exec,
@ -11,7 +11,7 @@ import * as Matcher from '../syntax/matcher.js';
export type Expr = Items;
export type Statement = Items;
export type Identifier = Item;
export type Identifier = Token;
export const block = (acc?: Items) =>
(acc === void 0)
@ -21,15 +21,6 @@ export const block = (acc?: Items) =>
export const statementBoundary = alt(atom(';'), Matcher.newline, Matcher.end);
export const exprBoundary = alt(atom(';'), atom(','), group('{', discard), Matcher.end);
export interface SpawnStatement {
isDataspace: boolean;
name?: Expr;
initialAssertions: Expr[];
parentIds: Identifier[];
parentInits: Expr[];
bootProcBody: Statement;
}
export const identifier: Pattern<Identifier> = atom();
export function expr(... extraStops: Pattern<any>[]): Pattern<Expr> {
@ -42,8 +33,34 @@ export function statement(acc: Items): Pattern<void> {
map(statementBoundary, i => i ? acc.push(i) : void 0))));
}
export interface FacetAction {
implicitFacet: boolean;
}
export function facetAction<I extends FacetAction, T extends I>(
pattern: (scope: T) => Pattern<any>): Pattern<T>
{
return i => {
const scope = Object.create(null);
scope.implicitFacet = true;
const p = seq(option(map(atom('.'), _ => scope.implicitFacet = false)), pattern(scope));
const r = p(i);
if (r === null) return null;
return [scope, r[1]];
};
}
export interface SpawnStatement extends FacetAction {
isDataspace: boolean;
name?: Expr;
initialAssertions: Expr[];
parentIds: Identifier[];
parentInits: Expr[];
bootProcBody: Statement;
}
export const spawn: Pattern<SpawnStatement> & { headerExpr: Pattern<Expr> } =
Object.assign(scope((o: SpawnStatement) => {
Object.assign(facetAction((o: SpawnStatement) => {
o.isDataspace = false;
o.initialAssertions = [];
o.parentIds = [];
@ -69,38 +86,50 @@ export const spawn: Pattern<SpawnStatement> & { headerExpr: Pattern<Expr> } =
headerExpr: expr(atom(':asserting'), atom(':let')),
});
export interface FieldDeclarationStatement {
export interface FieldDeclarationStatement extends FacetAction {
member: Expr;
expr?: Expr;
}
// Principal: Dataspace, but only for implementation reasons, so really Facet
export const fieldDeclarationStatement: Pattern<FieldDeclarationStatement> =
scope(o => seq(atom('field'),
bind(o, 'member', expr(atom('='))),
option(seq(atom('='), bind(o, 'expr', expr())))));
facetAction(o => seq(atom('field'),
bind(o, 'member', expr(atom('='))),
option(seq(atom('='), bind(o, 'expr', expr()))),
statementBoundary));
export interface AssertionEndpointStatement {
export interface AssertionEndpointStatement extends FacetAction {
isDynamic: boolean,
template: Expr,
test?: Expr,
}
// Principal: Facet
export const assertionEndpointStatement: Pattern<AssertionEndpointStatement> =
scope(o => {
facetAction(o => {
o.isDynamic = true;
return seq(atom('assert'),
option(map(atom(':snapshot'), _ => o.isDynamic = false)),
bind(o, 'template', expr(seq(atom('when'), group('(', discard)))),
option(seq(atom('when'), group('(', bind(o, 'test', expr())))));
option(seq(atom('when'), group('(', bind(o, 'test', expr())))),
statementBoundary);
});
export const dataflowStatement: Pattern<Statement> =
value(o => {
o.value = [];
return seq(atom('dataflow'), statement(o.value));
});
export interface StatementFacetAction extends FacetAction {
body: Statement;
}
export interface EventHandlerEndpointStatement {
export function statementFacetAction(kw: Pattern<any>): Pattern<StatementFacetAction> {
return facetAction(o => {
o.body = [];
return seq(kw, statement(o.body));
});
}
// Principal: Facet
export const dataflowStatement = statementFacetAction(atom('dataflow'));
export interface EventHandlerEndpointStatement extends FacetAction {
terminal: boolean;
triggerType: 'dataflow' | 'start' | 'stop' | 'asserted' | 'retracted' | 'message';
isDynamic: boolean;
@ -108,8 +137,9 @@ export interface EventHandlerEndpointStatement {
body: Statement;
}
// Principal: Facet
export const eventHandlerEndpointStatement: Pattern<EventHandlerEndpointStatement> =
scope(o => {
facetAction(o => {
o.terminal = false;
o.isDynamic = true;
o.body = [];
@ -125,8 +155,9 @@ export const eventHandlerEndpointStatement: Pattern<EventHandlerEndpointStatemen
atom('message')),
e => e.text)),
option(map(atom(':snapshot'), _ => o.isDynamic = false)),
bind(o, 'pattern', expr()),
option(statement(o.body)))));
bind(o, 'pattern', expr(atom('=>'))),
alt(seq(atom('=>'), statement(o.body)),
statementBoundary))));
});
export interface TypeDefinitionStatement {
@ -136,6 +167,7 @@ export interface TypeDefinitionStatement {
wireName?: Expr;
}
// Principal: none
export const typeDefinitionStatement: Pattern<TypeDefinitionStatement> =
scope(o => seq(bind(o, 'expectedUse', map(alt(atom('message'), atom('assertion')), e => e.text)),
atom('type'),
@ -145,24 +177,39 @@ export const typeDefinitionStatement: Pattern<TypeDefinitionStatement> =
bind(o, 'wireName', withoutSpace(upTo(statementBoundary))))),
statementBoundary));
export const messageSendStatement: Pattern<Expr> =
value(o => seq(atom('send'), bind(o, 'value', withoutSpace(upTo(statementBoundary))), statementBoundary));
export interface MessageSendStatement extends FacetAction {
expr: Expr;
}
export interface DuringStatement {
// Principal: Facet
export const messageSendStatement: Pattern<MessageSendStatement> =
facetAction(o => seq(atom('send'),
bind(o, 'expr', withoutSpace(upTo(statementBoundary))),
statementBoundary));
export interface DuringStatement extends FacetAction {
pattern: Expr;
body: Statement;
}
// Principal: Facet
export const duringStatement: Pattern<DuringStatement> =
scope(o => {
facetAction(o => {
o.body = [];
return seq(atom('during'),
bind(o, 'pattern', expr()),
statement(o.body));
});
export const reactStatement: Pattern<Statement> =
// Principal: Facet
export const reactStatement = statementFacetAction(atom('react'));
// Principal: none
export const bootStatement: Pattern<Statement> =
value(o => {
o.value = [];
return seq(atom('react'), statement(o.value));
return seq(atom('boot'), statement(o.value));
});
// Principal: Facet
export const stopStatement = statementFacetAction(atom('stop'));

View File

@ -0,0 +1 @@
export const BootProc = '__SYNDICATE__bootProc';

View File

@ -1,6 +1,8 @@
import fs from 'fs';
import * as S from '../syntax/index.js';
import { ArrayList, Substitution } from '../syntax/index.js';
import * as G from './grammar.js';
import { BootProc } from './internals.js';
export function main(argv: string[]) {
let [ inputFilename ] = argv.slice(2);
@ -12,6 +14,8 @@ export function main(argv: string[]) {
let tree = reader.readToEnd();
let macro = new S.Templates();
tree = macro.template()`import * as __SYNDICATE__ from '@syndicate/core';\n${tree}`;
let passNumber = 1;
let expansionNeeded = true;
function expand<T>(p: S.Pattern<T>, f: (t: T) => S.Items) {
@ -20,29 +24,90 @@ export function main(argv: string[]) {
return f(t);
});
}
function receiverFor(s: G.FacetAction): Substitution {
return (s.implicitFacet) ? 'thisFacet.' : '.';
}
function expandFacetAction<T extends G.FacetAction>(p: S.Pattern<T>, f: (t: T) => S.Items) {
expand(p, t => macro.template()`${receiverFor(t)}${f(t)}`);
}
while (expansionNeeded) {
if (passNumber >= 128) {
throw new Error(`Too many compiler passes (${passNumber})!`);
}
expansionNeeded = false;
expand(G.spawn,
s => macro.template()`SPAWN[${s.name ?? []}][${S.joinItems(s.initialAssertions, ', ')}][[${s.bootProcBody}]]`);
expand(G.fieldDeclarationStatement,
s => macro.template()`FIELD[${s.member}][${s.expr ?? []}]`);
expand(G.assertionEndpointStatement,
s => macro.template()`ASSERT[${''+s.isDynamic}][${s.template}][${s.test ?? []}]`);
expand(G.dataflowStatement,
e => macro.template()`DATAFLOW[${e}]`);
expand(G.eventHandlerEndpointStatement,
s => macro.template()`EVENTHANDLER[${`${s.terminal}/${s.isDynamic}`}][${s.triggerType}][${s.pattern}][${s.body}]`);
expand(G.typeDefinitionStatement,
s => macro.template()`TYPEDEF[${s.expectedUse}][${[s.label]}][${S.joinItems(s.fields.map(f => [f]), ' -- ')}][${s.wireName ?? []}]`);
expand(G.messageSendStatement,
e => macro.template()`SEND[${e}]`);
expand(G.duringStatement,
s => macro.template()`DURING[${s.pattern}][${s.body}]`);
expand(G.reactStatement,
e => macro.template()`REACT[${e}]`);
expandFacetAction(
G.spawn,
s => {
let proc = macro.template()`function (thisFacet) {${s.bootProcBody}}`;
if (s.isDataspace) proc = macro.template()`__SYNDICATE__.inNestedDataspace(${proc})`;
let assertions = (s.initialAssertions.length > 0)
? macro.template()`, new __SYNDICATE__.Set([${S.joinItems(s.initialAssertions, ', ')}])`
: ``;
return macro.template()`_spawn(${s.name ?? 'null'}, ${proc}${assertions});`;
});
expandFacetAction(
G.fieldDeclarationStatement,
s => {
function isArrayProp(): [S.Substitution, S.Substitution] | null {
if (s.member.length === 0) return null;
const x = s.member[s.member.length - 1];
if (!S.isGroup(x)) return null;
if (x.start.text !== '[') return null;
return [s.member.slice(0, s.member.length - 1), x.items];
}
function isSimpleProp(): [S.Substitution, S.Substitution] | null {
if (s.member.length < 2) return null;
const r = S.value<G.Identifier>(o => S.seq(S.atom('.'), S.bind(o, 'value', G.identifier)))(
new ArrayList(s.member, s.member.length - 2));
if (r === null) return null;
const id = r[0];
return [s.member.slice(0, s.member.length - 2),
[ { start: id.start,
end: id.end,
type: S.TokenType.STRING,
text: JSON.stringify(id.text) } ]];
}
const [obj, prop] = isArrayProp()
?? isSimpleProp()
?? ["__SYNDICATE__.INVALID_PROPERTY_SYNTAX", "''"];
return macro.template()`declareField(${obj}, ${prop}, ${s.expr ?? 'void 0'});`;
});
expandFacetAction(
G.assertionEndpointStatement,
s => macro.template()`addEndpoint(thisFacet => (${s.test ?? 'true'}) && (${s.template}), ${''+s.isDynamic});`);
expandFacetAction(
G.dataflowStatement,
s => macro.template()`addDataflow(function (thisFacet) {${s.body}});`);
expandFacetAction(
G.eventHandlerEndpointStatement,
s => {
return macro.template()`EVENTHANDLER[${`${s.terminal}/${s.isDynamic}`}][${s.triggerType}][${s.pattern ?? []}][${s.body}]`;
});
expandFacetAction(
G.duringStatement,
s => macro.template()`DURING[${s.pattern}][${s.body}]`);
expand(
G.typeDefinitionStatement,
s => {
const l = JSON.stringify(s.label.text);
const fs = JSON.stringify(s.fields.map(f => f.text));
return macro.template()`const ${[s.label]} = __SYNDICATE__.Record.makeConstructor(${s.wireName ?? l}, ${fs});`;
});
expandFacetAction(
G.messageSendStatement,
s => macro.template()`_send(${s.expr});`);
expandFacetAction(
G.reactStatement,
s => macro.template()`addChildFacet(function (thisFacet) {${s.body}});`);
expand(
G.bootStatement,
s => macro.template()`export function ${BootProc}(thisFacet) {${s}}`);
expandFacetAction(
G.stopStatement,
s => macro.template()`_stop(function (thisFacet) {${s.body}});`);
}
console.log(S.itemText(tree, { color: true, missing: '\x1b[41m□\x1b[0m' }));

View File

@ -101,10 +101,11 @@ export class Graph<SubjectId, ObjectId> {
}
}
defineObservableProperty(obj: object,
prop: string,
value: any,
options: PropertyOptions<ObjectId>)
defineObservableProperty<T, K extends keyof T>(
obj: T,
prop: K,
value: T[K],
options: PropertyOptions<ObjectId>)
{
const { objectId, noopGuard } = options;
Object.defineProperty(obj, prop, {
@ -125,9 +126,10 @@ export class Graph<SubjectId, ObjectId> {
return objectId;
}
static newScope(o: object): object {
function O() {}
O.prototype = o;
return new O();
static newScope<T, R extends T>(o: T): R {
const Scope: { new (): R, prototype: T } =
(function Scope () {}) as unknown as ({ new (): R, prototype: T });
Scope.prototype = o;
return new Scope();
}
}

View File

@ -82,18 +82,6 @@ export abstract class Dataspace {
this.pendingTurns = [new Turn(null, [new Spawn(null, bootProc, new Set())])];
}
_inScript = true;
withNonScriptContext<T>(task: Task<T>): T {
let savedInScript = this._inScript;
this._inScript = false;
try {
return task();
} finally {
this._inScript = savedInScript;
}
}
abstract start(): this;
abstract ground(): Ground;
@ -101,29 +89,6 @@ export abstract class Dataspace {
return this.ground().backgroundTask();
}
referenceField(obj: DataflowObservableObject, prop: string) {
if (!(prop in obj)) {
this.dataflow.recordObservation([obj, prop]);
}
return obj[prop];
}
declareField(obj: DataflowObservableObject, prop: string, init: any) {
if (prop in obj) {
obj[prop] = init;
} else {
this.dataflow.defineObservableProperty(obj, prop, init, {
objectId: [obj, prop],
noopGuard: is
});
}
}
deleteField(obj: DataflowObservableObject, prop: string) {
this.dataflow.recordDamage([obj, prop]);
delete obj[prop];
}
runTasks() { // TODO: rename?
this.runPendingTasks();
this.performPendingActions();
@ -153,17 +118,15 @@ export abstract class Dataspace {
}
refreshAssertions() {
this.withNonScriptContext(() => {
this.dataflow.repairDamage((ep) => {
let facet = ep.facet;
if (facet.isLive) { // TODO: necessary test, or tautological?
facet.invokeScript(() => ep.refresh());
}
});
this.dataflow.repairDamage((ep) => {
let facet = ep.facet;
if (facet.isLive) { // TODO: necessary test, or tautological?
facet.invokeScript(f => f.withNonScriptContext(() => ep.refresh()));
}
});
}
addActor(name: any, bootProc: Script<void>, initialAssertions: Set, parentActor: Actor | undefined) {
addActor(name: any, bootProc: Script<void>, initialAssertions: Set, parentActor: Actor | null) {
let ac = new Actor(this, name, initialAssertions, parentActor?.id);
// debug('Spawn', ac && ac.toString());
this.applyPatch(ac, ac.adhocAssertions);
@ -178,7 +141,7 @@ export abstract class Dataspace {
applyPatch(ac: Actor, delta: Bag) {
// if (!delta.isEmpty()) debug('applyPatch BEGIN', ac && ac.toString());
let removals = [];
let removals: Array<[number, Value]> = [];
delta.forEach((count, a) => {
if (count > 0) {
// debug('applyPatch +', a && a.toString());
@ -195,7 +158,7 @@ export abstract class Dataspace {
// if (!delta.isEmpty()) debug('applyPatch END');
}
deliverMessage(m: Value, _sendingActor: Actor) {
deliverMessage(m: Value, _sendingActor: Actor | null) {
// debug('deliverMessage', sendingActor && sendingActor.toString(), m.toString());
this.index.deliverMessage(m);
// this.index.deliverMessage(m, (leaf, _m) => {
@ -208,11 +171,11 @@ export abstract class Dataspace {
}
subscribe(handler: Skeleton.Analysis) {
this.index.addHandler(handler, handler.callback);
this.index.addHandler(handler, handler.callback!);
}
unsubscribe(handler: Skeleton.Analysis) {
this.index.removeHandler(handler, handler.callback);
this.index.removeHandler(handler, handler.callback!);
}
endpointHook(_facet: Facet, _endpoint: Endpoint) {
@ -269,7 +232,7 @@ export class Actor {
let tasks = this.pendingTasks;
for (let i = 0; i < Priority._count; i++) {
let q = tasks[i];
if (q.length > 0) return q.shift();
if (q.length > 0) return q.shift()!;
}
return null;
}
@ -287,12 +250,12 @@ export class Actor {
this.pendingTasks[priority].push(task);
}
addFacet(parentFacet: Facet, bootProc: Script<void>, checkInScript: boolean = false) {
if (checkInScript && !this.dataspace._inScript) {
addFacet(parentFacet: Facet | null, bootProc: Script<void>, checkInScript: boolean = false) {
if (checkInScript && parentFacet && !parentFacet.inScript) {
throw new Error("Cannot add facet outside script; are you missing a `react { ... }`?");
}
let f = new Facet(this, parentFacet);
f.invokeScript(f => this.dataspace.withNonScriptContext(() => bootProc.call(f.fields, f)));
f.invokeScript(f => f.withNonScriptContext(() => bootProc.call(f.fields, f)));
this.scheduleTask(() => {
if ((parentFacet && !parentFacet.isLive) || f.isInert()) {
f._terminate();
@ -352,7 +315,7 @@ export class Actor {
}
abstract class Action {
abstract perform(ds: Dataspace, ac: Actor): void;
abstract perform(ds: Dataspace, ac: Actor | null): void;
}
class Patch extends Action {
@ -363,8 +326,8 @@ class Patch extends Action {
this.changes = changes;
}
perform(ds: Dataspace, ac: Actor): void {
ds.applyPatch(ac, this.changes);
perform(ds: Dataspace, ac: Actor | null): void {
ds.applyPatch(ac!, this.changes);
}
adjust(a: Value, count: number) {
@ -380,7 +343,7 @@ class Message extends Action {
this.body = fromJS(body);
}
perform(ds: Dataspace, ac: Actor): void {
perform(ds: Dataspace, ac: Actor | null): void {
ds.deliverMessage(this.body, ac);
}
}
@ -397,7 +360,7 @@ class Spawn extends Action {
this.initialAssertions = initialAssertions;
}
perform(ds: Dataspace, ac: Actor): void {
perform(ds: Dataspace, ac: Actor | null): void {
ds.addActor(this.name, this.bootProc, this.initialAssertions, ac);
}
}
@ -405,7 +368,8 @@ class Spawn extends Action {
class Quit extends Action { // TODO: rename? Perhaps to Cleanup?
// Pseudo-action - not for userland use.
perform(ds: Dataspace, ac: Actor): void {
perform(ds: Dataspace, ac: Actor | null): void {
if (ac === null) throw new Error("Internal error: Quit action with null actor");
ds.applyPatch(ac, ac.cleanupChanges);
ds.actors.delete(ac.id);
// debug('Quit', ac && ac.toString());
@ -420,9 +384,9 @@ class DeferredTurn extends Action {
this.continuation = continuation;
}
perform(_ds: Dataspace, ac: Actor): void {
perform(_ds: Dataspace, ac: Actor | null): void {
// debug('DeferredTurn', ac && ac.toString());
ac.scheduleTask(this.continuation);
ac!.scheduleTask(this.continuation);
}
}
@ -449,6 +413,7 @@ export class Facet {
readonly stopScripts: Array<Script<void>> = [];
readonly children = new IdentitySet<Facet>();
readonly fields: any;
inScript = true;
constructor(actor: Actor, parent: Facet | null) {
this.id = actor.dataspace.nextId++;
@ -467,6 +432,16 @@ export class Facet {
this.fields[DataflowObservableObjectId] = () => this.id;
}
withNonScriptContext<T>(task: Task<T>): T {
let savedInScript = this.inScript;
this.inScript = false;
try {
return task();
} finally {
this.inScript = savedInScript;
}
}
_abort(emitPatches: boolean) {
this.isLive = false;
this.children.forEach(child => child._abort(emitPatches));
@ -517,12 +492,17 @@ export class Facet {
}, Priority.GC);
}
// This alias exists because of the naive expansion done by the parser.
_stop(continuation?: Script<void>) {
this.stop(continuation);
}
stop(continuation?: Script<void>) {
this.parent.invokeScript(() => {
this.parent!.invokeScript(() => {
this.actor.scheduleTask(() => {
this._terminate();
if (continuation) {
this.parent.scheduleScript(parent => continuation.call(this.fields, parent));
this.parent!.scheduleScript(parent => continuation.call(this.fields, parent));
// ^ TODO: is this the correct scope to use??
}
});
@ -539,7 +519,7 @@ export class Facet {
this.stopScripts.push(s);
}
addEndpoint(updateFun: () => EndpointSpec, isDynamic: boolean = true): Endpoint {
addEndpoint(updateFun: Script<EndpointSpec>, isDynamic: boolean = true): Endpoint {
const ep = new Endpoint(this, isDynamic, updateFun);
this.actor.dataspace.endpointHook(this, ep);
return ep;
@ -582,7 +562,7 @@ export class Facet {
this.scheduleScript(() => {
if (this.isLive) {
this.actor.dataspace.dataflow.withSubject(subjectId, () =>
subjectFun.call(this.fields));
subjectFun.call(this.fields, this));
}
}, priority);
return { assertion: void 0, analysis: null };
@ -624,7 +604,7 @@ export class Facet {
}
wrap<T extends Array<any>, R>(fn: (f: Facet, ... args: T) => R): (... args: T) => R {
return (... actuals) => this.invokeScript(f => fn.call(f.fields, f, ... actuals), true);
return (... actuals) => this.invokeScript(f => fn.call(f.fields, f, ... actuals), true)!;
}
wrapExternal<T extends Array<any>>(fn: (f: Facet, ... args: T) => void): (... args: T) => void {
@ -638,22 +618,32 @@ export class Facet {
}
ensureFacetSetup(what: string) {
if (this.actor.dataspace._inScript) {
if (this.inScript) {
throw new Error(`Cannot ${what} outside facet setup; are you missing \`react { ... }\`?`);
}
}
ensureNonFacetSetup(what: string, keyword: string) {
if (!this.actor.dataspace._inScript) {
if (!this.inScript) {
throw new Error(`Cannot ${what} during facet setup; are you missing \`${keyword} { ... }\`?`);
}
}
// This alias exists because of the naive expansion done by the parser.
_send(body: any) {
this.send(body);
}
send(body: any) {
this.ensureNonFacetSetup('`send`', 'on start');
this.enqueueScriptAction(new Message(body));
}
// This alias exists because of the naive expansion done by the parser.
_spawn(name: any, bootProc: Script<void>, initialAssertions?: Set) {
this.spawn(name, bootProc, initialAssertions);
}
spawn(name: any, bootProc: Script<void>, initialAssertions?: Set) {
this.ensureNonFacetSetup('`spawn`', 'on start');
this.enqueueScriptAction(new Spawn(name, bootProc, initialAssertions));
@ -667,6 +657,33 @@ export class Facet {
scheduleScript(script: Script<void>, priority?: Priority) {
this.actor.scheduleTask(this.wrap(script), priority);
}
declareField<T extends DataflowObservableObject, K extends keyof T & string>(obj: T, prop: K, init: T[K]) {
if (prop in obj) {
obj[prop] = init;
} else {
this.actor.dataspace.dataflow.defineObservableProperty(obj, prop, init, {
objectId: [obj, prop],
noopGuard: is
});
}
}
// referenceField(obj: DataflowObservableObject, prop: string) {
// if (!(prop in obj)) {
// this.actor.dataspace.dataflow.recordObservation([obj, prop]);
// }
// return obj[prop];
// }
// deleteField(obj: DataflowObservableObject, prop: string) {
// this.actor.dataspace.dataflow.recordDamage([obj, prop]);
// delete obj[prop];
// }
addChildFacet(bootProc: Script<void>) {
this.actor.addFacet(this, bootProc, true);
}
}
export class Endpoint {
@ -685,6 +702,7 @@ export class Endpoint {
let initialSpec = ds.dataflow.withSubject(isDynamic ? this : undefined,
() => updateFun.call(facet.fields, facet));
this._install(initialSpec);
this.spec = initialSpec; // keeps TypeScript's undefinedness-checker happy
facet.endpoints.set(this.id, this);
}

View File

@ -38,6 +38,6 @@ export function randomId(byteCount: number, hexOutput: boolean = false): string
if (hexOutput) {
return Bytes.from(buf).toHex();
} else {
return _btoa(String.fromCharCode.apply(null, buf)).replace(/=/g,'');
return _btoa(String.fromCharCode.apply(null, buf as unknown as number[])).replace(/=/g,'');
}
}

View File

@ -16,7 +16,7 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.
//---------------------------------------------------------------------------
import { Value } from 'preserves';
import { Value, Record } from 'preserves';
import { $Special } from './special.js';
import { Dataspace, Facet, Actor, Endpoint, Script } from './dataspace.js';
@ -43,44 +43,47 @@ export class NestedDataspace extends Dataspace {
}
endpointHook(facet: Facet, innerEp: Endpoint) {
const innerDs = this;
super.endpointHook(facet, innerEp);
if (Observe.isClassOf(innerEp.spec.assertion) &&
Inbound.isClassOf(innerEp.spec.assertion[0]))
{
// We know that innerEp.spec.assertion is an Observe(Inbound(...)). Also, if
// innerEp.spec.analysis exists, it will be consonant with innerEp.spec.assertion.
// Beware of completely-constant patterns, which cause skeleton to be null!
this.hookEndpointLifecycle(innerEp, this.outerFacet.addEndpoint(() => {
const assertion = Observe(innerEp.spec.assertion[0][0]);
const h = innerEp.spec.analysis;
const innerCallback = h.callback;
const callback = (innerCallback === void 0) ? void 0 :
function (evt: EventType, captures: Array<Value>) {
innerCallback.call(this, evt, captures);
innerDs.start();
};
const analysis: Analysis | null = (h === null) ? null :
(h.skeleton === void 0
? {
skeleton: void 0,
constPaths: h.constPaths,
constVals: h.constVals.map((v) => v[0]),
capturePaths: h.capturePaths.map((p) => p.slice(1)),
assertion,
callback
}
: {
skeleton: h.skeleton[1],
constPaths: h.constPaths.map((p) => p.slice(1)),
constVals: h.constVals,
capturePaths: h.capturePaths.map((p) => p.slice(1)),
assertion,
callback
});
return { assertion, analysis };
}, false));
}
const innerAssertion = innerEp.spec.assertion;
if (!Observe.isClassOf(innerAssertion)) return;
const wrapper = innerAssertion[0];
if (!Inbound.isClassOf(wrapper)) return;
// We know that innerAssertion is an Observe(Inbound(...)). Also, if
// innerEp.spec.analysis exists, it will be consonant with innerAssertion. Beware of
// completely-constant patterns, which cause skeleton to be null!
const innerDs = this;
this.hookEndpointLifecycle(innerEp, this.outerFacet.addEndpoint(() => {
const assertion = Observe(wrapper[0]);
const h = innerEp.spec.analysis!;
const innerCallback = h.callback;
const callback = (innerCallback === void 0) ? void 0 :
function (evt: EventType, captures: Array<Value>) {
innerCallback.call(null, evt, captures);
innerDs.start();
};
const analysis: Analysis | null = (h === null) ? null :
(h.skeleton === null
? {
skeleton: null,
constPaths: h.constPaths,
constVals: h.constVals.map(v => (v as Record)[0]),
capturePaths: h.capturePaths.map(p => p.slice(1)),
assertion,
callback
}
: {
skeleton: h.skeleton.members[0],
constPaths: h.constPaths.map(p => p.slice(1)),
constVals: h.constVals,
capturePaths: h.capturePaths.map(p => p.slice(1)),
assertion,
callback
});
return { assertion, analysis };
}, false));
}
adjustIndex(a: Value, count: number) {

View File

@ -17,7 +17,7 @@
//---------------------------------------------------------------------------
import { IdentitySet } from './idcoll.js';
import { is, Value, Record, Set, Dictionary, canonicalString } from 'preserves';
import { is, Value, Record, Set, Dictionary, canonicalString, preserves } from 'preserves';
import { Bag, ChangeDescription } from './bag.js';
import { Discard, Capture, Observe } from './assertions.js';
@ -33,7 +33,8 @@ export enum EventType {
export type HandlerCallback = (eventType: EventType, bindings: Array<Value>) => void;
export type Shape = string;
export type Skeleton = null | { shape: Shape, members: Skeleton[] };
export type NonEmptySkeleton = { shape: Shape, members: Skeleton[] };
export type Skeleton = null | NonEmptySkeleton;
export type Path = Array<number>;
export interface Analysis {
skeleton: Skeleton;
@ -58,10 +59,10 @@ export class Index {
constValMap = new Dictionary();
continuation.cachedAssertions.forEach((a) => {
const key = projectPaths(a, constPaths);
let leaf = constValMap.get(key);
let leaf = constValMap!.get(key);
if (!leaf) {
leaf = new Leaf();
constValMap.set(key, leaf);
constValMap!.set(key, leaf);
}
leaf.cachedAssertions.add(a);
});
@ -76,7 +77,7 @@ export class Index {
if (!handler) {
const cachedCaptures = new Bag();
leaf.cachedAssertions.forEach((a) =>
cachedCaptures._items.update(projectPaths(a, capturePaths), n => n + 1, 0));
cachedCaptures._items.update(projectPaths(a, capturePaths), n => n! + 1, 0));
handler = new Handler(cachedCaptures);
leaf.handlerMap.set(capturePaths, handler);
}
@ -159,7 +160,7 @@ class Node {
}
extend(skeleton: Skeleton): Continuation {
const path = [];
const path: Path = [];
function walkNode(node: Node,
popCount: number,
@ -279,14 +280,14 @@ class Handler {
}
}
function classOf(v: any): string | null {
function classOf(v: any): string {
if (Record.isRecord(v)) {
const ci = v.getConstructorInfo();
return canonicalString(ci.label) + '/' + ci.arity;
} else if (Array.isArray(v)) {
return '' + v.length;
} else {
return null;
throw new Error(preserves`Cannot route based on ${v}`);
}
}
@ -306,10 +307,10 @@ function projectPaths(v: Value, paths: Array<Path>) {
}
export function analyzeAssertion(a: Value): Analysis {
const constPaths = [];
const constVals = [];
const capturePaths = [];
const path = [];
const constPaths: Path[] = [];
const constVals: Value[] = [];
const capturePaths: Path[] = [];
const path: Path = [];
function walk(a: Value): Skeleton {
if (Capture.isClassOf(a)) {
@ -328,7 +329,7 @@ export function analyzeAssertion(a: Value): Analysis {
let aa = a as Array<Value>;
// ^ We know this is safe because it's either Record or Array
let arity = aa.length;
let result = { shape: cls, members: [] };
let result: NonEmptySkeleton = { shape: cls, members: [] };
path.push(0);
for (let i = 0; i < arity; i++) {
path[path.length - 1] = i;
@ -349,7 +350,7 @@ export function analyzeAssertion(a: Value): Analysis {
}
export function match(p: Value, v: Value): Array<Value> | false {
const captures = [];
const captures: Array<Value> = [];
function walk(p: Value, v: Value): boolean {
if (Capture.isClassOf(p)) {
@ -376,8 +377,8 @@ export function match(p: Value, v: Value): Array<Value> | false {
return walk(p, v) ? captures : false;
}
export function isCompletelyConcrete(p: Value) {
function walk(p: Value) {
export function isCompletelyConcrete(p: Value): boolean {
function walk(p: Value): boolean {
if (Capture.isClassOf(p)) return false;
if (Discard.isClassOf(p)) return false;
@ -388,8 +389,8 @@ export function isCompletelyConcrete(p: Value) {
return walk(p);
}
export function withoutCaptures(p: Value) {
function walk(p: Value) {
export function withoutCaptures(p: Value): Value {
function walk(p: Value): Value {
if (Capture.isClassOf(p)) return walk(p[0]);
if (Discard.isClassOf(p)) return p;

View File

@ -27,8 +27,12 @@ export function push<T>(item: T, rest: Stack<T>): NonEmptyStack<T> {
return { item, rest };
}
export function isEmpty<T>(s: Stack<T>): s is null {
return s === null;
}
export function nonEmpty<T>(s: Stack<T>): s is NonEmptyStack<T> {
return s !== empty();
return s !== null;
}
export function rest<T>(s: Stack<T>): Stack<T> {
@ -46,8 +50,9 @@ export function drop<T>(s: Stack<T>, n: number): Stack<T> {
export function dropNonEmpty<T>(s: NonEmptyStack<T>, n: number): NonEmptyStack<T> {
while (n--) {
s = s.rest;
if (!nonEmpty(s)) throw new Error("dropNonEmpty popped too far");
const next = s.rest;
if (!nonEmpty(next)) throw new Error("dropNonEmpty popped too far");
s = next;
}
return s;
}

View File

@ -12,15 +12,22 @@ export interface SourceMap {
mappings: string;
}
export interface Mapping {
generatedStartColumn?: number; // zero-based
sourceIndex?: number;
sourceStartLine?: number; // zero-based (!!)
sourceStartColumn?: number; // zero-based
nameIndex?: number;
export interface NoSourceMapping {
generatedStartColumn: number; // zero-based
}
export interface SourceMapping extends NoSourceMapping {
sourceIndex: number;
sourceStartLine: number; // zero-based (!!)
sourceStartColumn: number; // zero-based
}
export interface SourceNameMapping extends SourceMapping {
nameIndex: number;
}
function encodeMapping(entry: Mapping): Array<number> {
export type NonEmptyMapping = NoSourceMapping | SourceMapping | SourceNameMapping;
export type Mapping = {} | NonEmptyMapping;
function encodeMapping(entry: NonEmptyMapping): Array<number> {
const a = [entry.generatedStartColumn];
if ('sourceIndex' in entry) {
a.push(entry.sourceIndex);
@ -33,11 +40,9 @@ function encodeMapping(entry: Mapping): Array<number> {
return a;
}
function maybeDelta(newValue: number | undefined, oldValue: number | undefined) {
function maybeDelta(newValue: number, oldValue: number | undefined): number {
// console.log('maybeDelta', oldValue, newValue);
return (newValue === void 0) ? void 0
: (oldValue === void 0) ? newValue
: newValue - oldValue;
return (oldValue === void 0) ? newValue : newValue - oldValue;
}
export class CodeWriter {
@ -45,8 +50,8 @@ export class CodeWriter {
readonly pos: Pos;
readonly sources: Array<string> = [];
readonly chunks: Array<string> = [];
readonly mappings: Array<Array<Mapping>> = [];
previous: Mapping = {};
readonly mappings: Array<Array<NonEmptyMapping>> = [];
previous: Partial<SourceNameMapping> = {};
previousPos: Pos | null = null;
constructor(file: string | null) {
@ -113,17 +118,21 @@ export class CodeWriter {
return;
}
const n: Mapping = {};
n.generatedStartColumn = maybeDelta(this.pos.column, this.previous.generatedStartColumn);
let n: NonEmptyMapping = {
generatedStartColumn: maybeDelta(this.pos.column, this.previous.generatedStartColumn),
};
this.previous.generatedStartColumn = this.pos.column;
if (p.name !== null) {
const sourceIndex = this.sourceIndexFor(p.name);
n.sourceIndex = maybeDelta(sourceIndex, this.previous.sourceIndex);
n = {
... n,
sourceIndex: maybeDelta(sourceIndex, this.previous.sourceIndex),
sourceStartColumn: maybeDelta(p.column, this.previous.sourceStartColumn),
sourceStartLine: maybeDelta(p.line - 1, this.previous.sourceStartLine),
};
this.previous.sourceIndex = sourceIndex;
n.sourceStartColumn = maybeDelta(p.column, this.previous.sourceStartColumn);
this.previous.sourceStartColumn = p.column;
n.sourceStartLine = maybeDelta(p.line - 1, this.previous.sourceStartLine);
this.previous.sourceStartLine = p.line - 1;
}

View File

@ -5,10 +5,14 @@ export interface List<T> extends Iterable<T> {
toArray(): Array<T>;
}
export function atEnd<T>(xs: List<T>): boolean {
export function atEnd<T>(xs: List<T>): xs is (List<T> & { item: null, next: null }) {
return xs.item === null;
}
export function notAtEnd<T>(xs: List<T>): xs is (List<T> & { item: T, next: List<T> }) {
return xs.item !== null;
}
export class ArrayList<T> implements List<T> {
readonly items: Array<T>;
readonly index: number = 0;
@ -35,9 +39,13 @@ export class ArrayList<T> implements List<T> {
let i: List<T> = this;
return {
next(): IteratorResult<T> {
const value = i.item;
if (!atEnd(i)) i = i.next;
return { done: atEnd(i), value };
if (notAtEnd(i)) {
const value = i.item;
i = i.next;
return { done: false, value };
} else {
return { done: true, value: null };
}
}
};
}

View File

@ -1,6 +1,6 @@
import { Token, TokenType, Items, Item, isGroup, isToken, isSpace, isTokenType } from './tokens.js';
import { Pos } from './position.js';
import { List, ArrayList, atEnd } from './list.js';
import { List, ArrayList, atEnd, notAtEnd } from './list.js';
//---------------------------------------------------------------------------
// Patterns over Item
@ -15,21 +15,24 @@ export const succeed: Pattern<void> = i => [void 0, i];
export const discard: Pattern<void> = _i => [void 0, noItems];
export const rest: Pattern<Items> = i => [i.toArray(), noItems];
export const end: Pattern<void> = i => atEnd(i) ? [void 0, noItems] : null;
export const pos: Pattern<Pos> = i => [isGroup(i.item) ? i.item.start.start : i.item.start, i];
export const pos: Pattern<Pos> = i =>
notAtEnd(i)
? [isGroup(i.item) ? i.item.start.start : i.item.start, i]
: null;
export const newline: Pattern<Item> = i => {
while (!atEnd(i) && isTokenType(i.item, TokenType.SPACE)) i = i.next;
if (!isTokenType(i.item, TokenType.NEWLINE)) return null;
while (notAtEnd(i) && isTokenType(i.item, TokenType.SPACE)) i = i.next;
if (!notAtEnd(i) || !isTokenType(i.item, TokenType.NEWLINE)) return null;
return [i.item, i.next];
};
export function skipSpace(i: List<Item>): List<Item> {
while (!atEnd(i) && isSpace(i.item)) i = i.next;
while (notAtEnd(i) && isSpace(i.item)) i = i.next;
return i;
}
export function collectSpace(i: List<Item>, acc: Array<Item>): List<Item> {
while (!atEnd(i) && isSpace(i.item)) {
while (notAtEnd(i) && isSpace(i.item)) {
acc.push(i.item);
i = i.next;
}
@ -95,7 +98,7 @@ export function exec(thunk: (i: List<Item>) => void): Pattern<void> {
};
}
export function map<T, R>(p: Pattern<T>, f: (T) => R): Pattern<R> {
export function map<T, R>(p: Pattern<T>, f: (t: T) => R): Pattern<R> {
return i => {
const r = p(i);
if (r === null) return null;
@ -117,6 +120,7 @@ export interface TokenOptions extends ItemOptions {
export function group<T>(opener: string, items: Pattern<T>, options: GroupOptions = {}): Pattern<T> {
return i => {
if (options.skipSpace ?? true) i = skipSpace(i);
if (!notAtEnd(i)) return null;
if (!isGroup(i.item)) return null;
if (i.item.start.text !== opener) return null;
const r = items(new ArrayList(i.item.items));
@ -129,6 +133,7 @@ export function group<T>(opener: string, items: Pattern<T>, options: GroupOption
export function atom(text?: string | undefined, options: TokenOptions = {}): Pattern<Token> {
return i => {
if (options.skipSpace ?? true) i = skipSpace(i);
if (!notAtEnd(i)) return null;
if (!isToken(i.item)) return null;
if (i.item.type !== (options.tokenType ?? TokenType.ATOM)) return null;
if (text !== void 0 && i.item.text !== text) return null;
@ -139,7 +144,7 @@ export function atom(text?: string | undefined, options: TokenOptions = {}): Pat
export function anything(options: ItemOptions = {}): Pattern<Item> {
return i => {
if (options.skipSpace ?? true) i = skipSpace(i);
if (atEnd(i)) return null;
if (!notAtEnd(i)) return null;
return [i.item, i.next];
};
}
@ -150,7 +155,7 @@ export function upTo(p: Pattern<any>): Pattern<Items> {
while (true) {
const r = p(i);
if (r !== null) return [acc, i];
if (atEnd(i)) break;
if (!notAtEnd(i)) break;
acc.push(i.item);
i = i.next;
}
@ -202,7 +207,7 @@ export function replace<T>(items: Items,
const walkItems = (items: Items): Items => {
let i: List<Item> = new ArrayList(items);
const acc: Items = [];
while (!atEnd(i = collectSpace(i, acc))) {
while (notAtEnd(i = collectSpace(i, acc))) {
const r = p(i);
if (r !== null) {

View File

@ -31,12 +31,12 @@ export class LaxReader implements IterableIterator<Item> {
if (m !== null && !this.stack.some(g => g.start.text === m)) {
if (this.stack.length > 0) {
this.stackTop().items.push(t);
this.stackTop()!.items.push(t);
return 'continue';
}
} else {
while (this.stack.length > 0) {
const inner = this.stack.pop();
const inner = this.stack.pop()!;
if (inner.start.text === m) {
inner.end = t;
}
@ -44,7 +44,7 @@ export class LaxReader implements IterableIterator<Item> {
if (this.stack.length === 0) {
return inner;
} else {
const outer = this.stackTop();
const outer = this.stackTop()!;
outer.items.push(inner);
if (inner.start.text === m) {
return 'continue';
@ -56,35 +56,50 @@ export class LaxReader implements IterableIterator<Item> {
return 'eof';
}
shift(): Token {
return this.scanner.shift() ?? this.scanner.makeToken(this.scanner.mark(), TokenType.CLOSE, '');
peek(): Token {
return this.scanner.peek() ?? this.scanner.makeToken(this.scanner.mark(), TokenType.CLOSE, '');
}
drop() {
this.scanner.drop();
}
read(): Item | null {
while (true) {
let g = this.stackTop();
const t = this.shift();
const t = this.peek();
switch (t.type) {
case TokenType.SPACE:
case TokenType.NEWLINE:
case TokenType.ATOM:
case TokenType.STRING:
if (g === null) return t;
if (g === null) {
this.drop();
return t;
}
if (t.text === ';') {
while ('(['.indexOf(g.start.text) >= 0) {
this.stack.pop();
this.stackTop().items.push(g);
g = this.stackTop();
const outer = this.stackTop();
if (outer === null) {
// do not drop the semicolon here
return g;
}
outer.items.push(g);
g = outer;
}
}
this.drop();
g.items.push(t);
break;
case TokenType.OPEN:
this.drop();
this.stack.push({ start: t, end: null, items: [] });
break;
case TokenType.CLOSE: {
this.drop();
const i = this.popUntilMatch(t);
if (i === 'eof') return null;
if (i === 'continue') break;

View File

@ -5,7 +5,7 @@ export abstract class Scanner implements IterableIterator<Token> {
readonly pos: Pos;
charBuffer: string | null = null;
tokenBuffer: Token | null = null;
delimiters = ' \t\n\r\'"`,;()[]{}/';
delimiters = ' \t\n\r\'"`.,;()[]{}/';
constructor(pos: Pos) {
this.pos = pos;
@ -55,19 +55,19 @@ export abstract class Scanner implements IterableIterator<Token> {
}
_collectSpace(buf = '', start = this.mark()): Token {
this._while(ch => this.isSpace(ch), ch => buf = buf + ch);
this._while(ch => ch !== null && this.isSpace(ch), ch => buf = buf + ch);
return this.makeToken(start, TokenType.SPACE, buf);
}
_punct(type: TokenType): Token {
return this.makeToken(this.mark(), type, this.shiftChar());
return this.makeToken(this.mark(), type, this.shiftChar()!);
}
_str(forbidNewlines: boolean): Token {
const start = this.mark();
const q = this.shiftChar();
const q = this.shiftChar()!;
let buf = q;
let ch: string;
let ch: string | null;
while (true) {
ch = this.shiftChar();
if (ch !== null) buf = buf + ch;
@ -98,7 +98,7 @@ export abstract class Scanner implements IterableIterator<Token> {
}
_atom(start = this.mark(), buf = ''): Token {
let ch: string;
let ch: string | null;
while (true) {
ch = this.peekChar();
if (ch === null || this.isDelimiter(ch)) {
@ -111,7 +111,7 @@ export abstract class Scanner implements IterableIterator<Token> {
_maybeComment(): Token {
const start = this.mark();
let buf = this.shiftChar();
let buf = this.shiftChar()!;
let ch = this.peekChar();
if (ch === null) return this._collectSpace(buf, start);
switch (ch) {
@ -162,6 +162,7 @@ export abstract class Scanner implements IterableIterator<Token> {
case '`':
return this._str(false);
case '.':
case ',':
case ';':
return this._punct(TokenType.ATOM);
@ -170,7 +171,7 @@ export abstract class Scanner implements IterableIterator<Token> {
return this._maybeComment();
default:
return this._atom(this.mark(), this.shiftChar());
return this._atom(this.mark(), this.shiftChar()!);
}
}

View File

@ -7,7 +7,7 @@ export function vlqDecode(s: string): Array<number> {
let shift_amount = 0;
const buf = [];
for (const ch of s) {
const sextet = inverse_alphabet.get(ch);
const sextet = inverse_alphabet.get(ch) ?? 0;
acc |= (sextet & 0x1f) << shift_amount;
shift_amount += 5;
if (!(sextet & 0x20)) {

View File

@ -10,7 +10,8 @@
"esModuleInterop": true,
"moduleResolution": "node",
"module": "es6",
"sourceMap": true
"sourceMap": true,
"strict": true
},
"include": ["src/**/*"]
}