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) {
|
export function boot(thisFacet) {
|
||||||
thisFacet.spawn('box', function (thisFacet) {
|
thisFacet.spawn('box', function (thisFacet) {
|
||||||
thisFacet.actor.dataspace.declareField(this, 'value', 0);
|
thisFacet.declareField(this, 'value', 0);
|
||||||
thisFacet.addEndpoint(() => {
|
thisFacet.addEndpoint(() => {
|
||||||
// console.log('recomputing published BoxState', this.value);
|
// console.log('recomputing published BoxState', this.value);
|
||||||
return { assertion: BoxState(this.value), analysis: null };
|
return { assertion: BoxState(this.value), analysis: null };
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import {
|
import {
|
||||||
Item, Items,
|
Token, Items,
|
||||||
Pattern,
|
Pattern,
|
||||||
|
|
||||||
scope, bind, seq, alt, upTo, atom, group, exec,
|
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 Expr = Items;
|
||||||
export type Statement = Items;
|
export type Statement = Items;
|
||||||
export type Identifier = Item;
|
export type Identifier = Token;
|
||||||
|
|
||||||
export const block = (acc?: Items) =>
|
export const block = (acc?: Items) =>
|
||||||
(acc === void 0)
|
(acc === void 0)
|
||||||
|
@ -21,15 +21,6 @@ export const block = (acc?: Items) =>
|
||||||
export const statementBoundary = alt(atom(';'), Matcher.newline, Matcher.end);
|
export const statementBoundary = alt(atom(';'), Matcher.newline, Matcher.end);
|
||||||
export const exprBoundary = alt(atom(';'), atom(','), group('{', discard), 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 const identifier: Pattern<Identifier> = atom();
|
||||||
|
|
||||||
export function expr(... extraStops: Pattern<any>[]): Pattern<Expr> {
|
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))));
|
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> } =
|
export const spawn: Pattern<SpawnStatement> & { headerExpr: Pattern<Expr> } =
|
||||||
Object.assign(scope((o: SpawnStatement) => {
|
Object.assign(facetAction((o: SpawnStatement) => {
|
||||||
o.isDataspace = false;
|
o.isDataspace = false;
|
||||||
o.initialAssertions = [];
|
o.initialAssertions = [];
|
||||||
o.parentIds = [];
|
o.parentIds = [];
|
||||||
|
@ -69,38 +86,50 @@ export const spawn: Pattern<SpawnStatement> & { headerExpr: Pattern<Expr> } =
|
||||||
headerExpr: expr(atom(':asserting'), atom(':let')),
|
headerExpr: expr(atom(':asserting'), atom(':let')),
|
||||||
});
|
});
|
||||||
|
|
||||||
export interface FieldDeclarationStatement {
|
export interface FieldDeclarationStatement extends FacetAction {
|
||||||
member: Expr;
|
member: Expr;
|
||||||
expr?: Expr;
|
expr?: Expr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Principal: Dataspace, but only for implementation reasons, so really Facet
|
||||||
export const fieldDeclarationStatement: Pattern<FieldDeclarationStatement> =
|
export const fieldDeclarationStatement: Pattern<FieldDeclarationStatement> =
|
||||||
scope(o => seq(atom('field'),
|
facetAction(o => seq(atom('field'),
|
||||||
bind(o, 'member', expr(atom('='))),
|
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,
|
isDynamic: boolean,
|
||||||
template: Expr,
|
template: Expr,
|
||||||
test?: Expr,
|
test?: Expr,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Principal: Facet
|
||||||
export const assertionEndpointStatement: Pattern<AssertionEndpointStatement> =
|
export const assertionEndpointStatement: Pattern<AssertionEndpointStatement> =
|
||||||
scope(o => {
|
facetAction(o => {
|
||||||
o.isDynamic = true;
|
o.isDynamic = true;
|
||||||
return seq(atom('assert'),
|
return seq(atom('assert'),
|
||||||
option(map(atom(':snapshot'), _ => o.isDynamic = false)),
|
option(map(atom(':snapshot'), _ => o.isDynamic = false)),
|
||||||
bind(o, 'template', expr(seq(atom('when'), group('(', discard)))),
|
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> =
|
export interface StatementFacetAction extends FacetAction {
|
||||||
value(o => {
|
body: Statement;
|
||||||
o.value = [];
|
}
|
||||||
return seq(atom('dataflow'), statement(o.value));
|
|
||||||
});
|
|
||||||
|
|
||||||
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;
|
terminal: boolean;
|
||||||
triggerType: 'dataflow' | 'start' | 'stop' | 'asserted' | 'retracted' | 'message';
|
triggerType: 'dataflow' | 'start' | 'stop' | 'asserted' | 'retracted' | 'message';
|
||||||
isDynamic: boolean;
|
isDynamic: boolean;
|
||||||
|
@ -108,8 +137,9 @@ export interface EventHandlerEndpointStatement {
|
||||||
body: Statement;
|
body: Statement;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Principal: Facet
|
||||||
export const eventHandlerEndpointStatement: Pattern<EventHandlerEndpointStatement> =
|
export const eventHandlerEndpointStatement: Pattern<EventHandlerEndpointStatement> =
|
||||||
scope(o => {
|
facetAction(o => {
|
||||||
o.terminal = false;
|
o.terminal = false;
|
||||||
o.isDynamic = true;
|
o.isDynamic = true;
|
||||||
o.body = [];
|
o.body = [];
|
||||||
|
@ -125,8 +155,9 @@ export const eventHandlerEndpointStatement: Pattern<EventHandlerEndpointStatemen
|
||||||
atom('message')),
|
atom('message')),
|
||||||
e => e.text)),
|
e => e.text)),
|
||||||
option(map(atom(':snapshot'), _ => o.isDynamic = false)),
|
option(map(atom(':snapshot'), _ => o.isDynamic = false)),
|
||||||
bind(o, 'pattern', expr()),
|
bind(o, 'pattern', expr(atom('=>'))),
|
||||||
option(statement(o.body)))));
|
alt(seq(atom('=>'), statement(o.body)),
|
||||||
|
statementBoundary))));
|
||||||
});
|
});
|
||||||
|
|
||||||
export interface TypeDefinitionStatement {
|
export interface TypeDefinitionStatement {
|
||||||
|
@ -136,6 +167,7 @@ export interface TypeDefinitionStatement {
|
||||||
wireName?: Expr;
|
wireName?: Expr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Principal: none
|
||||||
export const typeDefinitionStatement: Pattern<TypeDefinitionStatement> =
|
export const typeDefinitionStatement: Pattern<TypeDefinitionStatement> =
|
||||||
scope(o => seq(bind(o, 'expectedUse', map(alt(atom('message'), atom('assertion')), e => e.text)),
|
scope(o => seq(bind(o, 'expectedUse', map(alt(atom('message'), atom('assertion')), e => e.text)),
|
||||||
atom('type'),
|
atom('type'),
|
||||||
|
@ -145,24 +177,39 @@ export const typeDefinitionStatement: Pattern<TypeDefinitionStatement> =
|
||||||
bind(o, 'wireName', withoutSpace(upTo(statementBoundary))))),
|
bind(o, 'wireName', withoutSpace(upTo(statementBoundary))))),
|
||||||
statementBoundary));
|
statementBoundary));
|
||||||
|
|
||||||
export const messageSendStatement: Pattern<Expr> =
|
export interface MessageSendStatement extends FacetAction {
|
||||||
value(o => seq(atom('send'), bind(o, 'value', withoutSpace(upTo(statementBoundary))), statementBoundary));
|
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;
|
pattern: Expr;
|
||||||
body: Statement;
|
body: Statement;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Principal: Facet
|
||||||
export const duringStatement: Pattern<DuringStatement> =
|
export const duringStatement: Pattern<DuringStatement> =
|
||||||
scope(o => {
|
facetAction(o => {
|
||||||
o.body = [];
|
o.body = [];
|
||||||
return seq(atom('during'),
|
return seq(atom('during'),
|
||||||
bind(o, 'pattern', expr()),
|
bind(o, 'pattern', expr()),
|
||||||
statement(o.body));
|
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 => {
|
value(o => {
|
||||||
o.value = [];
|
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 fs from 'fs';
|
||||||
import * as S from '../syntax/index.js';
|
import * as S from '../syntax/index.js';
|
||||||
|
import { ArrayList, Substitution } from '../syntax/index.js';
|
||||||
import * as G from './grammar.js';
|
import * as G from './grammar.js';
|
||||||
|
import { BootProc } from './internals.js';
|
||||||
|
|
||||||
export function main(argv: string[]) {
|
export function main(argv: string[]) {
|
||||||
let [ inputFilename ] = argv.slice(2);
|
let [ inputFilename ] = argv.slice(2);
|
||||||
|
@ -12,6 +14,8 @@ export function main(argv: string[]) {
|
||||||
let tree = reader.readToEnd();
|
let tree = reader.readToEnd();
|
||||||
let macro = new S.Templates();
|
let macro = new S.Templates();
|
||||||
|
|
||||||
|
tree = macro.template()`import * as __SYNDICATE__ from '@syndicate/core';\n${tree}`;
|
||||||
|
|
||||||
let passNumber = 1;
|
let passNumber = 1;
|
||||||
let expansionNeeded = true;
|
let expansionNeeded = true;
|
||||||
function expand<T>(p: S.Pattern<T>, f: (t: T) => S.Items) {
|
function expand<T>(p: S.Pattern<T>, f: (t: T) => S.Items) {
|
||||||
|
@ -20,29 +24,90 @@ export function main(argv: string[]) {
|
||||||
return f(t);
|
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) {
|
while (expansionNeeded) {
|
||||||
if (passNumber >= 128) {
|
if (passNumber >= 128) {
|
||||||
throw new Error(`Too many compiler passes (${passNumber})!`);
|
throw new Error(`Too many compiler passes (${passNumber})!`);
|
||||||
}
|
}
|
||||||
expansionNeeded = false;
|
expansionNeeded = false;
|
||||||
expand(G.spawn,
|
expandFacetAction(
|
||||||
s => macro.template()`SPAWN[${s.name ?? []}][${S.joinItems(s.initialAssertions, ', ')}][[${s.bootProcBody}]]`);
|
G.spawn,
|
||||||
expand(G.fieldDeclarationStatement,
|
s => {
|
||||||
s => macro.template()`FIELD[${s.member}][${s.expr ?? []}]`);
|
let proc = macro.template()`function (thisFacet) {${s.bootProcBody}}`;
|
||||||
expand(G.assertionEndpointStatement,
|
if (s.isDataspace) proc = macro.template()`__SYNDICATE__.inNestedDataspace(${proc})`;
|
||||||
s => macro.template()`ASSERT[${''+s.isDynamic}][${s.template}][${s.test ?? []}]`);
|
let assertions = (s.initialAssertions.length > 0)
|
||||||
expand(G.dataflowStatement,
|
? macro.template()`, new __SYNDICATE__.Set([${S.joinItems(s.initialAssertions, ', ')}])`
|
||||||
e => macro.template()`DATAFLOW[${e}]`);
|
: ``;
|
||||||
expand(G.eventHandlerEndpointStatement,
|
return macro.template()`_spawn(${s.name ?? 'null'}, ${proc}${assertions});`;
|
||||||
s => macro.template()`EVENTHANDLER[${`${s.terminal}/${s.isDynamic}`}][${s.triggerType}][${s.pattern}][${s.body}]`);
|
});
|
||||||
expand(G.typeDefinitionStatement,
|
expandFacetAction(
|
||||||
s => macro.template()`TYPEDEF[${s.expectedUse}][${[s.label]}][${S.joinItems(s.fields.map(f => [f]), ' -- ')}][${s.wireName ?? []}]`);
|
G.fieldDeclarationStatement,
|
||||||
expand(G.messageSendStatement,
|
s => {
|
||||||
e => macro.template()`SEND[${e}]`);
|
function isArrayProp(): [S.Substitution, S.Substitution] | null {
|
||||||
expand(G.duringStatement,
|
if (s.member.length === 0) return null;
|
||||||
s => macro.template()`DURING[${s.pattern}][${s.body}]`);
|
const x = s.member[s.member.length - 1];
|
||||||
expand(G.reactStatement,
|
if (!S.isGroup(x)) return null;
|
||||||
e => macro.template()`REACT[${e}]`);
|
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' }));
|
console.log(S.itemText(tree, { color: true, missing: '\x1b[41m□\x1b[0m' }));
|
||||||
|
|
|
@ -101,10 +101,11 @@ export class Graph<SubjectId, ObjectId> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
defineObservableProperty(obj: object,
|
defineObservableProperty<T, K extends keyof T>(
|
||||||
prop: string,
|
obj: T,
|
||||||
value: any,
|
prop: K,
|
||||||
options: PropertyOptions<ObjectId>)
|
value: T[K],
|
||||||
|
options: PropertyOptions<ObjectId>)
|
||||||
{
|
{
|
||||||
const { objectId, noopGuard } = options;
|
const { objectId, noopGuard } = options;
|
||||||
Object.defineProperty(obj, prop, {
|
Object.defineProperty(obj, prop, {
|
||||||
|
@ -125,9 +126,10 @@ export class Graph<SubjectId, ObjectId> {
|
||||||
return objectId;
|
return objectId;
|
||||||
}
|
}
|
||||||
|
|
||||||
static newScope(o: object): object {
|
static newScope<T, R extends T>(o: T): R {
|
||||||
function O() {}
|
const Scope: { new (): R, prototype: T } =
|
||||||
O.prototype = o;
|
(function Scope () {}) as unknown as ({ new (): R, prototype: T });
|
||||||
return new O();
|
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())])];
|
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 start(): this;
|
||||||
abstract ground(): Ground;
|
abstract ground(): Ground;
|
||||||
|
|
||||||
|
@ -101,29 +89,6 @@ export abstract class Dataspace {
|
||||||
return this.ground().backgroundTask();
|
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?
|
runTasks() { // TODO: rename?
|
||||||
this.runPendingTasks();
|
this.runPendingTasks();
|
||||||
this.performPendingActions();
|
this.performPendingActions();
|
||||||
|
@ -153,17 +118,15 @@ export abstract class Dataspace {
|
||||||
}
|
}
|
||||||
|
|
||||||
refreshAssertions() {
|
refreshAssertions() {
|
||||||
this.withNonScriptContext(() => {
|
this.dataflow.repairDamage((ep) => {
|
||||||
this.dataflow.repairDamage((ep) => {
|
let facet = ep.facet;
|
||||||
let facet = ep.facet;
|
if (facet.isLive) { // TODO: necessary test, or tautological?
|
||||||
if (facet.isLive) { // TODO: necessary test, or tautological?
|
facet.invokeScript(f => f.withNonScriptContext(() => ep.refresh()));
|
||||||
facet.invokeScript(() => 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);
|
let ac = new Actor(this, name, initialAssertions, parentActor?.id);
|
||||||
// debug('Spawn', ac && ac.toString());
|
// debug('Spawn', ac && ac.toString());
|
||||||
this.applyPatch(ac, ac.adhocAssertions);
|
this.applyPatch(ac, ac.adhocAssertions);
|
||||||
|
@ -178,7 +141,7 @@ export abstract class Dataspace {
|
||||||
|
|
||||||
applyPatch(ac: Actor, delta: Bag) {
|
applyPatch(ac: Actor, delta: Bag) {
|
||||||
// if (!delta.isEmpty()) debug('applyPatch BEGIN', ac && ac.toString());
|
// if (!delta.isEmpty()) debug('applyPatch BEGIN', ac && ac.toString());
|
||||||
let removals = [];
|
let removals: Array<[number, Value]> = [];
|
||||||
delta.forEach((count, a) => {
|
delta.forEach((count, a) => {
|
||||||
if (count > 0) {
|
if (count > 0) {
|
||||||
// debug('applyPatch +', a && a.toString());
|
// debug('applyPatch +', a && a.toString());
|
||||||
|
@ -195,7 +158,7 @@ export abstract class Dataspace {
|
||||||
// if (!delta.isEmpty()) debug('applyPatch END');
|
// if (!delta.isEmpty()) debug('applyPatch END');
|
||||||
}
|
}
|
||||||
|
|
||||||
deliverMessage(m: Value, _sendingActor: Actor) {
|
deliverMessage(m: Value, _sendingActor: Actor | null) {
|
||||||
// debug('deliverMessage', sendingActor && sendingActor.toString(), m.toString());
|
// debug('deliverMessage', sendingActor && sendingActor.toString(), m.toString());
|
||||||
this.index.deliverMessage(m);
|
this.index.deliverMessage(m);
|
||||||
// this.index.deliverMessage(m, (leaf, _m) => {
|
// this.index.deliverMessage(m, (leaf, _m) => {
|
||||||
|
@ -208,11 +171,11 @@ export abstract class Dataspace {
|
||||||
}
|
}
|
||||||
|
|
||||||
subscribe(handler: Skeleton.Analysis) {
|
subscribe(handler: Skeleton.Analysis) {
|
||||||
this.index.addHandler(handler, handler.callback);
|
this.index.addHandler(handler, handler.callback!);
|
||||||
}
|
}
|
||||||
|
|
||||||
unsubscribe(handler: Skeleton.Analysis) {
|
unsubscribe(handler: Skeleton.Analysis) {
|
||||||
this.index.removeHandler(handler, handler.callback);
|
this.index.removeHandler(handler, handler.callback!);
|
||||||
}
|
}
|
||||||
|
|
||||||
endpointHook(_facet: Facet, _endpoint: Endpoint) {
|
endpointHook(_facet: Facet, _endpoint: Endpoint) {
|
||||||
|
@ -269,7 +232,7 @@ export class Actor {
|
||||||
let tasks = this.pendingTasks;
|
let tasks = this.pendingTasks;
|
||||||
for (let i = 0; i < Priority._count; i++) {
|
for (let i = 0; i < Priority._count; i++) {
|
||||||
let q = tasks[i];
|
let q = tasks[i];
|
||||||
if (q.length > 0) return q.shift();
|
if (q.length > 0) return q.shift()!;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -287,12 +250,12 @@ export class Actor {
|
||||||
this.pendingTasks[priority].push(task);
|
this.pendingTasks[priority].push(task);
|
||||||
}
|
}
|
||||||
|
|
||||||
addFacet(parentFacet: Facet, bootProc: Script<void>, checkInScript: boolean = false) {
|
addFacet(parentFacet: Facet | null, bootProc: Script<void>, checkInScript: boolean = false) {
|
||||||
if (checkInScript && !this.dataspace._inScript) {
|
if (checkInScript && parentFacet && !parentFacet.inScript) {
|
||||||
throw new Error("Cannot add facet outside script; are you missing a `react { ... }`?");
|
throw new Error("Cannot add facet outside script; are you missing a `react { ... }`?");
|
||||||
}
|
}
|
||||||
let f = new Facet(this, parentFacet);
|
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(() => {
|
this.scheduleTask(() => {
|
||||||
if ((parentFacet && !parentFacet.isLive) || f.isInert()) {
|
if ((parentFacet && !parentFacet.isLive) || f.isInert()) {
|
||||||
f._terminate();
|
f._terminate();
|
||||||
|
@ -352,7 +315,7 @@ export class Actor {
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract class Action {
|
abstract class Action {
|
||||||
abstract perform(ds: Dataspace, ac: Actor): void;
|
abstract perform(ds: Dataspace, ac: Actor | null): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
class Patch extends Action {
|
class Patch extends Action {
|
||||||
|
@ -363,8 +326,8 @@ class Patch extends Action {
|
||||||
this.changes = changes;
|
this.changes = changes;
|
||||||
}
|
}
|
||||||
|
|
||||||
perform(ds: Dataspace, ac: Actor): void {
|
perform(ds: Dataspace, ac: Actor | null): void {
|
||||||
ds.applyPatch(ac, this.changes);
|
ds.applyPatch(ac!, this.changes);
|
||||||
}
|
}
|
||||||
|
|
||||||
adjust(a: Value, count: number) {
|
adjust(a: Value, count: number) {
|
||||||
|
@ -380,7 +343,7 @@ class Message extends Action {
|
||||||
this.body = fromJS(body);
|
this.body = fromJS(body);
|
||||||
}
|
}
|
||||||
|
|
||||||
perform(ds: Dataspace, ac: Actor): void {
|
perform(ds: Dataspace, ac: Actor | null): void {
|
||||||
ds.deliverMessage(this.body, ac);
|
ds.deliverMessage(this.body, ac);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -397,7 +360,7 @@ class Spawn extends Action {
|
||||||
this.initialAssertions = initialAssertions;
|
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);
|
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?
|
class Quit extends Action { // TODO: rename? Perhaps to Cleanup?
|
||||||
// Pseudo-action - not for userland use.
|
// 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.applyPatch(ac, ac.cleanupChanges);
|
||||||
ds.actors.delete(ac.id);
|
ds.actors.delete(ac.id);
|
||||||
// debug('Quit', ac && ac.toString());
|
// debug('Quit', ac && ac.toString());
|
||||||
|
@ -420,9 +384,9 @@ class DeferredTurn extends Action {
|
||||||
this.continuation = continuation;
|
this.continuation = continuation;
|
||||||
}
|
}
|
||||||
|
|
||||||
perform(_ds: Dataspace, ac: Actor): void {
|
perform(_ds: Dataspace, ac: Actor | null): void {
|
||||||
// debug('DeferredTurn', ac && ac.toString());
|
// 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 stopScripts: Array<Script<void>> = [];
|
||||||
readonly children = new IdentitySet<Facet>();
|
readonly children = new IdentitySet<Facet>();
|
||||||
readonly fields: any;
|
readonly fields: any;
|
||||||
|
inScript = true;
|
||||||
|
|
||||||
constructor(actor: Actor, parent: Facet | null) {
|
constructor(actor: Actor, parent: Facet | null) {
|
||||||
this.id = actor.dataspace.nextId++;
|
this.id = actor.dataspace.nextId++;
|
||||||
|
@ -467,6 +432,16 @@ export class Facet {
|
||||||
this.fields[DataflowObservableObjectId] = () => this.id;
|
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) {
|
_abort(emitPatches: boolean) {
|
||||||
this.isLive = false;
|
this.isLive = false;
|
||||||
this.children.forEach(child => child._abort(emitPatches));
|
this.children.forEach(child => child._abort(emitPatches));
|
||||||
|
@ -517,12 +492,17 @@ export class Facet {
|
||||||
}, Priority.GC);
|
}, 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>) {
|
stop(continuation?: Script<void>) {
|
||||||
this.parent.invokeScript(() => {
|
this.parent!.invokeScript(() => {
|
||||||
this.actor.scheduleTask(() => {
|
this.actor.scheduleTask(() => {
|
||||||
this._terminate();
|
this._terminate();
|
||||||
if (continuation) {
|
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??
|
// ^ TODO: is this the correct scope to use??
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -539,7 +519,7 @@ export class Facet {
|
||||||
this.stopScripts.push(s);
|
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);
|
const ep = new Endpoint(this, isDynamic, updateFun);
|
||||||
this.actor.dataspace.endpointHook(this, ep);
|
this.actor.dataspace.endpointHook(this, ep);
|
||||||
return ep;
|
return ep;
|
||||||
|
@ -582,7 +562,7 @@ export class Facet {
|
||||||
this.scheduleScript(() => {
|
this.scheduleScript(() => {
|
||||||
if (this.isLive) {
|
if (this.isLive) {
|
||||||
this.actor.dataspace.dataflow.withSubject(subjectId, () =>
|
this.actor.dataspace.dataflow.withSubject(subjectId, () =>
|
||||||
subjectFun.call(this.fields));
|
subjectFun.call(this.fields, this));
|
||||||
}
|
}
|
||||||
}, priority);
|
}, priority);
|
||||||
return { assertion: void 0, analysis: null };
|
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 {
|
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 {
|
wrapExternal<T extends Array<any>>(fn: (f: Facet, ... args: T) => void): (... args: T) => void {
|
||||||
|
@ -638,22 +618,32 @@ export class Facet {
|
||||||
}
|
}
|
||||||
|
|
||||||
ensureFacetSetup(what: string) {
|
ensureFacetSetup(what: string) {
|
||||||
if (this.actor.dataspace._inScript) {
|
if (this.inScript) {
|
||||||
throw new Error(`Cannot ${what} outside facet setup; are you missing \`react { ... }\`?`);
|
throw new Error(`Cannot ${what} outside facet setup; are you missing \`react { ... }\`?`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ensureNonFacetSetup(what: string, keyword: string) {
|
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} { ... }\`?`);
|
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) {
|
send(body: any) {
|
||||||
this.ensureNonFacetSetup('`send`', 'on start');
|
this.ensureNonFacetSetup('`send`', 'on start');
|
||||||
this.enqueueScriptAction(new Message(body));
|
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) {
|
spawn(name: any, bootProc: Script<void>, initialAssertions?: Set) {
|
||||||
this.ensureNonFacetSetup('`spawn`', 'on start');
|
this.ensureNonFacetSetup('`spawn`', 'on start');
|
||||||
this.enqueueScriptAction(new Spawn(name, bootProc, initialAssertions));
|
this.enqueueScriptAction(new Spawn(name, bootProc, initialAssertions));
|
||||||
|
@ -667,6 +657,33 @@ export class Facet {
|
||||||
scheduleScript(script: Script<void>, priority?: Priority) {
|
scheduleScript(script: Script<void>, priority?: Priority) {
|
||||||
this.actor.scheduleTask(this.wrap(script), 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 {
|
export class Endpoint {
|
||||||
|
@ -685,6 +702,7 @@ export class Endpoint {
|
||||||
let initialSpec = ds.dataflow.withSubject(isDynamic ? this : undefined,
|
let initialSpec = ds.dataflow.withSubject(isDynamic ? this : undefined,
|
||||||
() => updateFun.call(facet.fields, facet));
|
() => updateFun.call(facet.fields, facet));
|
||||||
this._install(initialSpec);
|
this._install(initialSpec);
|
||||||
|
this.spec = initialSpec; // keeps TypeScript's undefinedness-checker happy
|
||||||
facet.endpoints.set(this.id, this);
|
facet.endpoints.set(this.id, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -38,6 +38,6 @@ export function randomId(byteCount: number, hexOutput: boolean = false): string
|
||||||
if (hexOutput) {
|
if (hexOutput) {
|
||||||
return Bytes.from(buf).toHex();
|
return Bytes.from(buf).toHex();
|
||||||
} else {
|
} 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/>.
|
// 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 { $Special } from './special.js';
|
||||||
import { Dataspace, Facet, Actor, Endpoint, Script } from './dataspace.js';
|
import { Dataspace, Facet, Actor, Endpoint, Script } from './dataspace.js';
|
||||||
|
@ -43,44 +43,47 @@ export class NestedDataspace extends Dataspace {
|
||||||
}
|
}
|
||||||
|
|
||||||
endpointHook(facet: Facet, innerEp: Endpoint) {
|
endpointHook(facet: Facet, innerEp: Endpoint) {
|
||||||
const innerDs = this;
|
|
||||||
super.endpointHook(facet, innerEp);
|
super.endpointHook(facet, innerEp);
|
||||||
if (Observe.isClassOf(innerEp.spec.assertion) &&
|
|
||||||
Inbound.isClassOf(innerEp.spec.assertion[0]))
|
const innerAssertion = innerEp.spec.assertion;
|
||||||
{
|
if (!Observe.isClassOf(innerAssertion)) return;
|
||||||
// We know that innerEp.spec.assertion is an Observe(Inbound(...)). Also, if
|
const wrapper = innerAssertion[0];
|
||||||
// innerEp.spec.analysis exists, it will be consonant with innerEp.spec.assertion.
|
if (!Inbound.isClassOf(wrapper)) return;
|
||||||
// Beware of completely-constant patterns, which cause skeleton to be null!
|
|
||||||
this.hookEndpointLifecycle(innerEp, this.outerFacet.addEndpoint(() => {
|
// We know that innerAssertion is an Observe(Inbound(...)). Also, if
|
||||||
const assertion = Observe(innerEp.spec.assertion[0][0]);
|
// innerEp.spec.analysis exists, it will be consonant with innerAssertion. Beware of
|
||||||
const h = innerEp.spec.analysis;
|
// completely-constant patterns, which cause skeleton to be null!
|
||||||
const innerCallback = h.callback;
|
|
||||||
const callback = (innerCallback === void 0) ? void 0 :
|
const innerDs = this;
|
||||||
function (evt: EventType, captures: Array<Value>) {
|
this.hookEndpointLifecycle(innerEp, this.outerFacet.addEndpoint(() => {
|
||||||
innerCallback.call(this, evt, captures);
|
const assertion = Observe(wrapper[0]);
|
||||||
innerDs.start();
|
const h = innerEp.spec.analysis!;
|
||||||
};
|
const innerCallback = h.callback;
|
||||||
const analysis: Analysis | null = (h === null) ? null :
|
const callback = (innerCallback === void 0) ? void 0 :
|
||||||
(h.skeleton === void 0
|
function (evt: EventType, captures: Array<Value>) {
|
||||||
? {
|
innerCallback.call(null, evt, captures);
|
||||||
skeleton: void 0,
|
innerDs.start();
|
||||||
constPaths: h.constPaths,
|
};
|
||||||
constVals: h.constVals.map((v) => v[0]),
|
const analysis: Analysis | null = (h === null) ? null :
|
||||||
capturePaths: h.capturePaths.map((p) => p.slice(1)),
|
(h.skeleton === null
|
||||||
assertion,
|
? {
|
||||||
callback
|
skeleton: null,
|
||||||
}
|
constPaths: h.constPaths,
|
||||||
: {
|
constVals: h.constVals.map(v => (v as Record)[0]),
|
||||||
skeleton: h.skeleton[1],
|
capturePaths: h.capturePaths.map(p => p.slice(1)),
|
||||||
constPaths: h.constPaths.map((p) => p.slice(1)),
|
assertion,
|
||||||
constVals: h.constVals,
|
callback
|
||||||
capturePaths: h.capturePaths.map((p) => p.slice(1)),
|
}
|
||||||
assertion,
|
: {
|
||||||
callback
|
skeleton: h.skeleton.members[0],
|
||||||
});
|
constPaths: h.constPaths.map(p => p.slice(1)),
|
||||||
return { assertion, analysis };
|
constVals: h.constVals,
|
||||||
}, false));
|
capturePaths: h.capturePaths.map(p => p.slice(1)),
|
||||||
}
|
assertion,
|
||||||
|
callback
|
||||||
|
});
|
||||||
|
return { assertion, analysis };
|
||||||
|
}, false));
|
||||||
}
|
}
|
||||||
|
|
||||||
adjustIndex(a: Value, count: number) {
|
adjustIndex(a: Value, count: number) {
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
//---------------------------------------------------------------------------
|
//---------------------------------------------------------------------------
|
||||||
|
|
||||||
import { IdentitySet } from './idcoll.js';
|
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 { Bag, ChangeDescription } from './bag.js';
|
||||||
import { Discard, Capture, Observe } from './assertions.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 HandlerCallback = (eventType: EventType, bindings: Array<Value>) => void;
|
||||||
|
|
||||||
export type Shape = string;
|
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 type Path = Array<number>;
|
||||||
export interface Analysis {
|
export interface Analysis {
|
||||||
skeleton: Skeleton;
|
skeleton: Skeleton;
|
||||||
|
@ -58,10 +59,10 @@ export class Index {
|
||||||
constValMap = new Dictionary();
|
constValMap = new Dictionary();
|
||||||
continuation.cachedAssertions.forEach((a) => {
|
continuation.cachedAssertions.forEach((a) => {
|
||||||
const key = projectPaths(a, constPaths);
|
const key = projectPaths(a, constPaths);
|
||||||
let leaf = constValMap.get(key);
|
let leaf = constValMap!.get(key);
|
||||||
if (!leaf) {
|
if (!leaf) {
|
||||||
leaf = new Leaf();
|
leaf = new Leaf();
|
||||||
constValMap.set(key, leaf);
|
constValMap!.set(key, leaf);
|
||||||
}
|
}
|
||||||
leaf.cachedAssertions.add(a);
|
leaf.cachedAssertions.add(a);
|
||||||
});
|
});
|
||||||
|
@ -76,7 +77,7 @@ export class Index {
|
||||||
if (!handler) {
|
if (!handler) {
|
||||||
const cachedCaptures = new Bag();
|
const cachedCaptures = new Bag();
|
||||||
leaf.cachedAssertions.forEach((a) =>
|
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);
|
handler = new Handler(cachedCaptures);
|
||||||
leaf.handlerMap.set(capturePaths, handler);
|
leaf.handlerMap.set(capturePaths, handler);
|
||||||
}
|
}
|
||||||
|
@ -159,7 +160,7 @@ class Node {
|
||||||
}
|
}
|
||||||
|
|
||||||
extend(skeleton: Skeleton): Continuation {
|
extend(skeleton: Skeleton): Continuation {
|
||||||
const path = [];
|
const path: Path = [];
|
||||||
|
|
||||||
function walkNode(node: Node,
|
function walkNode(node: Node,
|
||||||
popCount: number,
|
popCount: number,
|
||||||
|
@ -279,14 +280,14 @@ class Handler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function classOf(v: any): string | null {
|
function classOf(v: any): string {
|
||||||
if (Record.isRecord(v)) {
|
if (Record.isRecord(v)) {
|
||||||
const ci = v.getConstructorInfo();
|
const ci = v.getConstructorInfo();
|
||||||
return canonicalString(ci.label) + '/' + ci.arity;
|
return canonicalString(ci.label) + '/' + ci.arity;
|
||||||
} else if (Array.isArray(v)) {
|
} else if (Array.isArray(v)) {
|
||||||
return '' + v.length;
|
return '' + v.length;
|
||||||
} else {
|
} 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 {
|
export function analyzeAssertion(a: Value): Analysis {
|
||||||
const constPaths = [];
|
const constPaths: Path[] = [];
|
||||||
const constVals = [];
|
const constVals: Value[] = [];
|
||||||
const capturePaths = [];
|
const capturePaths: Path[] = [];
|
||||||
const path = [];
|
const path: Path = [];
|
||||||
|
|
||||||
function walk(a: Value): Skeleton {
|
function walk(a: Value): Skeleton {
|
||||||
if (Capture.isClassOf(a)) {
|
if (Capture.isClassOf(a)) {
|
||||||
|
@ -328,7 +329,7 @@ export function analyzeAssertion(a: Value): Analysis {
|
||||||
let aa = a as Array<Value>;
|
let aa = a as Array<Value>;
|
||||||
// ^ We know this is safe because it's either Record or Array
|
// ^ We know this is safe because it's either Record or Array
|
||||||
let arity = aa.length;
|
let arity = aa.length;
|
||||||
let result = { shape: cls, members: [] };
|
let result: NonEmptySkeleton = { shape: cls, members: [] };
|
||||||
path.push(0);
|
path.push(0);
|
||||||
for (let i = 0; i < arity; i++) {
|
for (let i = 0; i < arity; i++) {
|
||||||
path[path.length - 1] = 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 {
|
export function match(p: Value, v: Value): Array<Value> | false {
|
||||||
const captures = [];
|
const captures: Array<Value> = [];
|
||||||
|
|
||||||
function walk(p: Value, v: Value): boolean {
|
function walk(p: Value, v: Value): boolean {
|
||||||
if (Capture.isClassOf(p)) {
|
if (Capture.isClassOf(p)) {
|
||||||
|
@ -376,8 +377,8 @@ export function match(p: Value, v: Value): Array<Value> | false {
|
||||||
return walk(p, v) ? captures : false;
|
return walk(p, v) ? captures : false;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isCompletelyConcrete(p: Value) {
|
export function isCompletelyConcrete(p: Value): boolean {
|
||||||
function walk(p: Value) {
|
function walk(p: Value): boolean {
|
||||||
if (Capture.isClassOf(p)) return false;
|
if (Capture.isClassOf(p)) return false;
|
||||||
if (Discard.isClassOf(p)) return false;
|
if (Discard.isClassOf(p)) return false;
|
||||||
|
|
||||||
|
@ -388,8 +389,8 @@ export function isCompletelyConcrete(p: Value) {
|
||||||
return walk(p);
|
return walk(p);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function withoutCaptures(p: Value) {
|
export function withoutCaptures(p: Value): Value {
|
||||||
function walk(p: Value) {
|
function walk(p: Value): Value {
|
||||||
if (Capture.isClassOf(p)) return walk(p[0]);
|
if (Capture.isClassOf(p)) return walk(p[0]);
|
||||||
if (Discard.isClassOf(p)) return p;
|
if (Discard.isClassOf(p)) return p;
|
||||||
|
|
||||||
|
|
|
@ -27,8 +27,12 @@ export function push<T>(item: T, rest: Stack<T>): NonEmptyStack<T> {
|
||||||
return { item, rest };
|
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> {
|
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> {
|
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> {
|
export function dropNonEmpty<T>(s: NonEmptyStack<T>, n: number): NonEmptyStack<T> {
|
||||||
while (n--) {
|
while (n--) {
|
||||||
s = s.rest;
|
const next = s.rest;
|
||||||
if (!nonEmpty(s)) throw new Error("dropNonEmpty popped too far");
|
if (!nonEmpty(next)) throw new Error("dropNonEmpty popped too far");
|
||||||
|
s = next;
|
||||||
}
|
}
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,15 +12,22 @@ export interface SourceMap {
|
||||||
mappings: string;
|
mappings: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Mapping {
|
export interface NoSourceMapping {
|
||||||
generatedStartColumn?: number; // zero-based
|
generatedStartColumn: number; // zero-based
|
||||||
sourceIndex?: number;
|
}
|
||||||
sourceStartLine?: number; // zero-based (!!)
|
export interface SourceMapping extends NoSourceMapping {
|
||||||
sourceStartColumn?: number; // zero-based
|
sourceIndex: number;
|
||||||
nameIndex?: 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];
|
const a = [entry.generatedStartColumn];
|
||||||
if ('sourceIndex' in entry) {
|
if ('sourceIndex' in entry) {
|
||||||
a.push(entry.sourceIndex);
|
a.push(entry.sourceIndex);
|
||||||
|
@ -33,11 +40,9 @@ function encodeMapping(entry: Mapping): Array<number> {
|
||||||
return a;
|
return a;
|
||||||
}
|
}
|
||||||
|
|
||||||
function maybeDelta(newValue: number | undefined, oldValue: number | undefined) {
|
function maybeDelta(newValue: number, oldValue: number | undefined): number {
|
||||||
// console.log('maybeDelta', oldValue, newValue);
|
// console.log('maybeDelta', oldValue, newValue);
|
||||||
return (newValue === void 0) ? void 0
|
return (oldValue === void 0) ? newValue : newValue - oldValue;
|
||||||
: (oldValue === void 0) ? newValue
|
|
||||||
: newValue - oldValue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class CodeWriter {
|
export class CodeWriter {
|
||||||
|
@ -45,8 +50,8 @@ export class CodeWriter {
|
||||||
readonly pos: Pos;
|
readonly pos: Pos;
|
||||||
readonly sources: Array<string> = [];
|
readonly sources: Array<string> = [];
|
||||||
readonly chunks: Array<string> = [];
|
readonly chunks: Array<string> = [];
|
||||||
readonly mappings: Array<Array<Mapping>> = [];
|
readonly mappings: Array<Array<NonEmptyMapping>> = [];
|
||||||
previous: Mapping = {};
|
previous: Partial<SourceNameMapping> = {};
|
||||||
previousPos: Pos | null = null;
|
previousPos: Pos | null = null;
|
||||||
|
|
||||||
constructor(file: string | null) {
|
constructor(file: string | null) {
|
||||||
|
@ -113,17 +118,21 @@ export class CodeWriter {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const n: Mapping = {};
|
let n: NonEmptyMapping = {
|
||||||
n.generatedStartColumn = maybeDelta(this.pos.column, this.previous.generatedStartColumn);
|
generatedStartColumn: maybeDelta(this.pos.column, this.previous.generatedStartColumn),
|
||||||
|
};
|
||||||
this.previous.generatedStartColumn = this.pos.column;
|
this.previous.generatedStartColumn = this.pos.column;
|
||||||
|
|
||||||
if (p.name !== null) {
|
if (p.name !== null) {
|
||||||
const sourceIndex = this.sourceIndexFor(p.name);
|
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;
|
this.previous.sourceIndex = sourceIndex;
|
||||||
n.sourceStartColumn = maybeDelta(p.column, this.previous.sourceStartColumn);
|
|
||||||
this.previous.sourceStartColumn = p.column;
|
this.previous.sourceStartColumn = p.column;
|
||||||
n.sourceStartLine = maybeDelta(p.line - 1, this.previous.sourceStartLine);
|
|
||||||
this.previous.sourceStartLine = p.line - 1;
|
this.previous.sourceStartLine = p.line - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,10 +5,14 @@ export interface List<T> extends Iterable<T> {
|
||||||
toArray(): Array<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;
|
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> {
|
export class ArrayList<T> implements List<T> {
|
||||||
readonly items: Array<T>;
|
readonly items: Array<T>;
|
||||||
readonly index: number = 0;
|
readonly index: number = 0;
|
||||||
|
@ -35,9 +39,13 @@ export class ArrayList<T> implements List<T> {
|
||||||
let i: List<T> = this;
|
let i: List<T> = this;
|
||||||
return {
|
return {
|
||||||
next(): IteratorResult<T> {
|
next(): IteratorResult<T> {
|
||||||
const value = i.item;
|
if (notAtEnd(i)) {
|
||||||
if (!atEnd(i)) i = i.next;
|
const value = i.item;
|
||||||
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 { Token, TokenType, Items, Item, isGroup, isToken, isSpace, isTokenType } from './tokens.js';
|
||||||
import { Pos } from './position.js';
|
import { Pos } from './position.js';
|
||||||
import { List, ArrayList, atEnd } from './list.js';
|
import { List, ArrayList, atEnd, notAtEnd } from './list.js';
|
||||||
|
|
||||||
//---------------------------------------------------------------------------
|
//---------------------------------------------------------------------------
|
||||||
// Patterns over Item
|
// 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 discard: Pattern<void> = _i => [void 0, noItems];
|
||||||
export const rest: Pattern<Items> = i => [i.toArray(), noItems];
|
export const rest: Pattern<Items> = i => [i.toArray(), noItems];
|
||||||
export const end: Pattern<void> = i => atEnd(i) ? [void 0, noItems] : null;
|
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 => {
|
export const newline: Pattern<Item> = i => {
|
||||||
while (!atEnd(i) && isTokenType(i.item, TokenType.SPACE)) i = i.next;
|
while (notAtEnd(i) && isTokenType(i.item, TokenType.SPACE)) i = i.next;
|
||||||
if (!isTokenType(i.item, TokenType.NEWLINE)) return null;
|
if (!notAtEnd(i) || !isTokenType(i.item, TokenType.NEWLINE)) return null;
|
||||||
return [i.item, i.next];
|
return [i.item, i.next];
|
||||||
};
|
};
|
||||||
|
|
||||||
export function skipSpace(i: List<Item>): List<Item> {
|
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;
|
return i;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function collectSpace(i: List<Item>, acc: Array<Item>): List<Item> {
|
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);
|
acc.push(i.item);
|
||||||
i = i.next;
|
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 => {
|
return i => {
|
||||||
const r = p(i);
|
const r = p(i);
|
||||||
if (r === null) return null;
|
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> {
|
export function group<T>(opener: string, items: Pattern<T>, options: GroupOptions = {}): Pattern<T> {
|
||||||
return i => {
|
return i => {
|
||||||
if (options.skipSpace ?? true) i = skipSpace(i);
|
if (options.skipSpace ?? true) i = skipSpace(i);
|
||||||
|
if (!notAtEnd(i)) return null;
|
||||||
if (!isGroup(i.item)) return null;
|
if (!isGroup(i.item)) return null;
|
||||||
if (i.item.start.text !== opener) return null;
|
if (i.item.start.text !== opener) return null;
|
||||||
const r = items(new ArrayList(i.item.items));
|
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> {
|
export function atom(text?: string | undefined, options: TokenOptions = {}): Pattern<Token> {
|
||||||
return i => {
|
return i => {
|
||||||
if (options.skipSpace ?? true) i = skipSpace(i);
|
if (options.skipSpace ?? true) i = skipSpace(i);
|
||||||
|
if (!notAtEnd(i)) return null;
|
||||||
if (!isToken(i.item)) return null;
|
if (!isToken(i.item)) return null;
|
||||||
if (i.item.type !== (options.tokenType ?? TokenType.ATOM)) return null;
|
if (i.item.type !== (options.tokenType ?? TokenType.ATOM)) return null;
|
||||||
if (text !== void 0 && i.item.text !== text) 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> {
|
export function anything(options: ItemOptions = {}): Pattern<Item> {
|
||||||
return i => {
|
return i => {
|
||||||
if (options.skipSpace ?? true) i = skipSpace(i);
|
if (options.skipSpace ?? true) i = skipSpace(i);
|
||||||
if (atEnd(i)) return null;
|
if (!notAtEnd(i)) return null;
|
||||||
return [i.item, i.next];
|
return [i.item, i.next];
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -150,7 +155,7 @@ export function upTo(p: Pattern<any>): Pattern<Items> {
|
||||||
while (true) {
|
while (true) {
|
||||||
const r = p(i);
|
const r = p(i);
|
||||||
if (r !== null) return [acc, i];
|
if (r !== null) return [acc, i];
|
||||||
if (atEnd(i)) break;
|
if (!notAtEnd(i)) break;
|
||||||
acc.push(i.item);
|
acc.push(i.item);
|
||||||
i = i.next;
|
i = i.next;
|
||||||
}
|
}
|
||||||
|
@ -202,7 +207,7 @@ export function replace<T>(items: Items,
|
||||||
const walkItems = (items: Items): Items => {
|
const walkItems = (items: Items): Items => {
|
||||||
let i: List<Item> = new ArrayList(items);
|
let i: List<Item> = new ArrayList(items);
|
||||||
const acc: Items = [];
|
const acc: Items = [];
|
||||||
while (!atEnd(i = collectSpace(i, acc))) {
|
while (notAtEnd(i = collectSpace(i, acc))) {
|
||||||
const r = p(i);
|
const r = p(i);
|
||||||
|
|
||||||
if (r !== null) {
|
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 (m !== null && !this.stack.some(g => g.start.text === m)) {
|
||||||
if (this.stack.length > 0) {
|
if (this.stack.length > 0) {
|
||||||
this.stackTop().items.push(t);
|
this.stackTop()!.items.push(t);
|
||||||
return 'continue';
|
return 'continue';
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
while (this.stack.length > 0) {
|
while (this.stack.length > 0) {
|
||||||
const inner = this.stack.pop();
|
const inner = this.stack.pop()!;
|
||||||
if (inner.start.text === m) {
|
if (inner.start.text === m) {
|
||||||
inner.end = t;
|
inner.end = t;
|
||||||
}
|
}
|
||||||
|
@ -44,7 +44,7 @@ export class LaxReader implements IterableIterator<Item> {
|
||||||
if (this.stack.length === 0) {
|
if (this.stack.length === 0) {
|
||||||
return inner;
|
return inner;
|
||||||
} else {
|
} else {
|
||||||
const outer = this.stackTop();
|
const outer = this.stackTop()!;
|
||||||
outer.items.push(inner);
|
outer.items.push(inner);
|
||||||
if (inner.start.text === m) {
|
if (inner.start.text === m) {
|
||||||
return 'continue';
|
return 'continue';
|
||||||
|
@ -56,35 +56,50 @@ export class LaxReader implements IterableIterator<Item> {
|
||||||
return 'eof';
|
return 'eof';
|
||||||
}
|
}
|
||||||
|
|
||||||
shift(): Token {
|
peek(): Token {
|
||||||
return this.scanner.shift() ?? this.scanner.makeToken(this.scanner.mark(), TokenType.CLOSE, '');
|
return this.scanner.peek() ?? this.scanner.makeToken(this.scanner.mark(), TokenType.CLOSE, '');
|
||||||
|
}
|
||||||
|
|
||||||
|
drop() {
|
||||||
|
this.scanner.drop();
|
||||||
}
|
}
|
||||||
|
|
||||||
read(): Item | null {
|
read(): Item | null {
|
||||||
while (true) {
|
while (true) {
|
||||||
let g = this.stackTop();
|
let g = this.stackTop();
|
||||||
const t = this.shift();
|
const t = this.peek();
|
||||||
switch (t.type) {
|
switch (t.type) {
|
||||||
case TokenType.SPACE:
|
case TokenType.SPACE:
|
||||||
case TokenType.NEWLINE:
|
case TokenType.NEWLINE:
|
||||||
case TokenType.ATOM:
|
case TokenType.ATOM:
|
||||||
case TokenType.STRING:
|
case TokenType.STRING:
|
||||||
if (g === null) return t;
|
if (g === null) {
|
||||||
|
this.drop();
|
||||||
|
return t;
|
||||||
|
}
|
||||||
if (t.text === ';') {
|
if (t.text === ';') {
|
||||||
while ('(['.indexOf(g.start.text) >= 0) {
|
while ('(['.indexOf(g.start.text) >= 0) {
|
||||||
this.stack.pop();
|
this.stack.pop();
|
||||||
this.stackTop().items.push(g);
|
const outer = this.stackTop();
|
||||||
g = 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);
|
g.items.push(t);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case TokenType.OPEN:
|
case TokenType.OPEN:
|
||||||
|
this.drop();
|
||||||
this.stack.push({ start: t, end: null, items: [] });
|
this.stack.push({ start: t, end: null, items: [] });
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case TokenType.CLOSE: {
|
case TokenType.CLOSE: {
|
||||||
|
this.drop();
|
||||||
const i = this.popUntilMatch(t);
|
const i = this.popUntilMatch(t);
|
||||||
if (i === 'eof') return null;
|
if (i === 'eof') return null;
|
||||||
if (i === 'continue') break;
|
if (i === 'continue') break;
|
||||||
|
|
|
@ -5,7 +5,7 @@ export abstract class Scanner implements IterableIterator<Token> {
|
||||||
readonly pos: Pos;
|
readonly pos: Pos;
|
||||||
charBuffer: string | null = null;
|
charBuffer: string | null = null;
|
||||||
tokenBuffer: Token | null = null;
|
tokenBuffer: Token | null = null;
|
||||||
delimiters = ' \t\n\r\'"`,;()[]{}/';
|
delimiters = ' \t\n\r\'"`.,;()[]{}/';
|
||||||
|
|
||||||
constructor(pos: Pos) {
|
constructor(pos: Pos) {
|
||||||
this.pos = pos;
|
this.pos = pos;
|
||||||
|
@ -55,19 +55,19 @@ export abstract class Scanner implements IterableIterator<Token> {
|
||||||
}
|
}
|
||||||
|
|
||||||
_collectSpace(buf = '', start = this.mark()): 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);
|
return this.makeToken(start, TokenType.SPACE, buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
_punct(type: TokenType): Token {
|
_punct(type: TokenType): Token {
|
||||||
return this.makeToken(this.mark(), type, this.shiftChar());
|
return this.makeToken(this.mark(), type, this.shiftChar()!);
|
||||||
}
|
}
|
||||||
|
|
||||||
_str(forbidNewlines: boolean): Token {
|
_str(forbidNewlines: boolean): Token {
|
||||||
const start = this.mark();
|
const start = this.mark();
|
||||||
const q = this.shiftChar();
|
const q = this.shiftChar()!;
|
||||||
let buf = q;
|
let buf = q;
|
||||||
let ch: string;
|
let ch: string | null;
|
||||||
while (true) {
|
while (true) {
|
||||||
ch = this.shiftChar();
|
ch = this.shiftChar();
|
||||||
if (ch !== null) buf = buf + ch;
|
if (ch !== null) buf = buf + ch;
|
||||||
|
@ -98,7 +98,7 @@ export abstract class Scanner implements IterableIterator<Token> {
|
||||||
}
|
}
|
||||||
|
|
||||||
_atom(start = this.mark(), buf = ''): Token {
|
_atom(start = this.mark(), buf = ''): Token {
|
||||||
let ch: string;
|
let ch: string | null;
|
||||||
while (true) {
|
while (true) {
|
||||||
ch = this.peekChar();
|
ch = this.peekChar();
|
||||||
if (ch === null || this.isDelimiter(ch)) {
|
if (ch === null || this.isDelimiter(ch)) {
|
||||||
|
@ -111,7 +111,7 @@ export abstract class Scanner implements IterableIterator<Token> {
|
||||||
|
|
||||||
_maybeComment(): Token {
|
_maybeComment(): Token {
|
||||||
const start = this.mark();
|
const start = this.mark();
|
||||||
let buf = this.shiftChar();
|
let buf = this.shiftChar()!;
|
||||||
let ch = this.peekChar();
|
let ch = this.peekChar();
|
||||||
if (ch === null) return this._collectSpace(buf, start);
|
if (ch === null) return this._collectSpace(buf, start);
|
||||||
switch (ch) {
|
switch (ch) {
|
||||||
|
@ -162,6 +162,7 @@ export abstract class Scanner implements IterableIterator<Token> {
|
||||||
case '`':
|
case '`':
|
||||||
return this._str(false);
|
return this._str(false);
|
||||||
|
|
||||||
|
case '.':
|
||||||
case ',':
|
case ',':
|
||||||
case ';':
|
case ';':
|
||||||
return this._punct(TokenType.ATOM);
|
return this._punct(TokenType.ATOM);
|
||||||
|
@ -170,7 +171,7 @@ export abstract class Scanner implements IterableIterator<Token> {
|
||||||
return this._maybeComment();
|
return this._maybeComment();
|
||||||
|
|
||||||
default:
|
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;
|
let shift_amount = 0;
|
||||||
const buf = [];
|
const buf = [];
|
||||||
for (const ch of s) {
|
for (const ch of s) {
|
||||||
const sextet = inverse_alphabet.get(ch);
|
const sextet = inverse_alphabet.get(ch) ?? 0;
|
||||||
acc |= (sextet & 0x1f) << shift_amount;
|
acc |= (sextet & 0x1f) << shift_amount;
|
||||||
shift_amount += 5;
|
shift_amount += 5;
|
||||||
if (!(sextet & 0x20)) {
|
if (!(sextet & 0x20)) {
|
||||||
|
|
|
@ -10,7 +10,8 @@
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"moduleResolution": "node",
|
"moduleResolution": "node",
|
||||||
"module": "es6",
|
"module": "es6",
|
||||||
"sourceMap": true
|
"sourceMap": true,
|
||||||
|
"strict": true
|
||||||
},
|
},
|
||||||
"include": ["src/**/*"]
|
"include": ["src/**/*"]
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue