Strict tsconfig; more major steps toward ocap style
This commit is contained in:
parent
595a13dfd6
commit
0f6059cd59
|
@ -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 };
|
||||
|
|
|
@ -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'),
|
||||
facetAction(o => seq(atom('field'),
|
||||
bind(o, 'member', expr(atom('='))),
|
||||
option(seq(atom('='), bind(o, 'expr', expr())))));
|
||||
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'));
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
export const BootProc = '__SYNDICATE__bootProc';
|
|
@ -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,
|
||||
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.reactStatement,
|
||||
e => macro.template()`REACT[${e}]`);
|
||||
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' }));
|
||||
|
|
|
@ -101,9 +101,10 @@ export class Graph<SubjectId, ObjectId> {
|
|||
}
|
||||
}
|
||||
|
||||
defineObservableProperty(obj: object,
|
||||
prop: string,
|
||||
value: any,
|
||||
defineObservableProperty<T, K extends keyof T>(
|
||||
obj: T,
|
||||
prop: K,
|
||||
value: T[K],
|
||||
options: PropertyOptions<ObjectId>)
|
||||
{
|
||||
const { objectId, noopGuard } = options;
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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,'');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,45 +43,48 @@ 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!
|
||||
|
||||
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(innerEp.spec.assertion[0][0]);
|
||||
const h = innerEp.spec.analysis;
|
||||
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(this, evt, captures);
|
||||
innerCallback.call(null, evt, captures);
|
||||
innerDs.start();
|
||||
};
|
||||
const analysis: Analysis | null = (h === null) ? null :
|
||||
(h.skeleton === void 0
|
||||
(h.skeleton === null
|
||||
? {
|
||||
skeleton: void 0,
|
||||
skeleton: null,
|
||||
constPaths: h.constPaths,
|
||||
constVals: h.constVals.map((v) => v[0]),
|
||||
capturePaths: h.capturePaths.map((p) => p.slice(1)),
|
||||
constVals: h.constVals.map(v => (v as Record)[0]),
|
||||
capturePaths: h.capturePaths.map(p => p.slice(1)),
|
||||
assertion,
|
||||
callback
|
||||
}
|
||||
: {
|
||||
skeleton: h.skeleton[1],
|
||||
constPaths: h.constPaths.map((p) => p.slice(1)),
|
||||
skeleton: h.skeleton.members[0],
|
||||
constPaths: h.constPaths.map(p => p.slice(1)),
|
||||
constVals: h.constVals,
|
||||
capturePaths: h.capturePaths.map((p) => p.slice(1)),
|
||||
capturePaths: h.capturePaths.map(p => p.slice(1)),
|
||||
assertion,
|
||||
callback
|
||||
});
|
||||
return { assertion, analysis };
|
||||
}, false));
|
||||
}
|
||||
}
|
||||
|
||||
adjustIndex(a: Value, count: number) {
|
||||
const net = super.adjustIndex(a, count);
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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> {
|
||||
if (notAtEnd(i)) {
|
||||
const value = i.item;
|
||||
if (!atEnd(i)) i = i.next;
|
||||
return { done: atEnd(i), value };
|
||||
i = i.next;
|
||||
return { done: false, value };
|
||||
} else {
|
||||
return { done: true, value: null };
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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()!);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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)) {
|
||||
|
|
|
@ -10,7 +10,8 @@
|
|||
"esModuleInterop": true,
|
||||
"moduleResolution": "node",
|
||||
"module": "es6",
|
||||
"sourceMap": true
|
||||
"sourceMap": true,
|
||||
"strict": true
|
||||
},
|
||||
"include": ["src/**/*"]
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue