First run of new compiler output!

This commit is contained in:
Tony Garnock-Jones 2021-01-16 17:46:18 +01:00
parent ed12c1c69c
commit fc23d1b779
17 changed files with 508 additions and 99 deletions

View File

@ -60,11 +60,11 @@ export function boot(thisFacet) {
thisFacet.spawn('client', function (thisFacet) { thisFacet.spawn('client', function (thisFacet) {
thisFacet.addEndpoint(() => { thisFacet.addEndpoint(() => {
let analysis = Skeleton.analyzeAssertion(BoxState(_$)); let analysis = Skeleton.analyzeAssertion(BoxState(_$));
analysis.callback = thisFacet.wrap((thisFacet, evt, vs) => { analysis.callback = thisFacet.wrap((thisFacet, evt, [v]) => {
if (evt === Skeleton.EventType.ADDED) { if (evt === Skeleton.EventType.ADDED) {
thisFacet.scheduleScript(() => { thisFacet.scheduleScript(() => {
// console.log('client sending SetBox', vs[0] + 1); // console.log('client sending SetBox', v + 1);
thisFacet.send(SetBox(vs[0] + 1)); thisFacet.send(SetBox(v + 1));
}); });
} }
}); });

View File

@ -28,13 +28,7 @@ boot {
spawn named 'box' { spawn named 'box' {
field this.value = 0; field this.value = 0;
assert BoxState(this.value); assert BoxState(this.value);
dataflow { stop on (this.value === N) console.log('terminated box root facet');
if (this.value === N) {
stop {
console.log('terminated box root facet');
}
}
}
on message SetBox($v) => this.value = v; on message SetBox($v) => this.value = v;
} }
@ -46,3 +40,5 @@ boot {
thisFacet.actor.dataspace.addStopHandler(() => thisFacet.actor.dataspace.addStopHandler(() =>
console.timeEnd('box-and-client-' + N.toString())); console.timeEnd('box-and-client-' + N.toString()));
} }
new __SYNDICATE__.Ground(__SYNDICATE__bootProc).start();

View File

@ -1,13 +1,15 @@
import { import {
Token, Items, TokenType, Token, Items,
Pattern, Pattern,
foldItems, match, anonymousTemplate as template, commaJoin,
startPos,
scope, bind, seq, alt, upTo, atom, group, exec, scope, bind, seq, alt, upTo, atom, atomString, group, exec,
repeat, option, withoutSpace, map, rest, discard, repeat, option, withoutSpace, map, mapm, rest, discard,
value, value, succeed, fail, separatedBy, anything,
} from '../syntax/index.js'; } from '../syntax/index.js';
import * as Matcher from '../syntax/matcher.js'; import * as Matcher from '../syntax/matcher.js';
import { Path, Skeleton } from '../runtime/api.js';
export type Expr = Items; export type Expr = Items;
export type Statement = Items; export type Statement = Items;
@ -18,8 +20,8 @@ export const block = (acc?: Items) =>
? group('{', discard) ? group('{', discard)
: group('{', map(rest, items => acc.push(... items))); : group('{', map(rest, items => acc.push(... items)));
export const statementBoundary = alt(atom(';'), Matcher.newline, Matcher.end); export const statementBoundary = alt<any>(atom(';'), Matcher.newline, Matcher.end);
export const exprBoundary = alt(atom(';'), atom(','), group('{', discard), Matcher.end); export const exprBoundary = alt<any>(atom(';'), atom(','), group('{', discard), Matcher.end);
export const identifier: Pattern<Identifier> = atom(); export const identifier: Pattern<Identifier> = atom();
@ -28,9 +30,9 @@ export function expr(... extraStops: Pattern<any>[]): Pattern<Expr> {
} }
export function statement(acc: Items): Pattern<void> { export function statement(acc: Items): Pattern<void> {
return alt(group('{', map(rest, items => acc.push(... items))), return alt<any>(group('{', map(rest, items => acc.push(... items))),
withoutSpace(seq(map(upTo(statementBoundary), items => acc.push(... items)), withoutSpace(seq(map(upTo(statementBoundary), items => acc.push(... items)),
map(statementBoundary, i => i ? acc.push(i) : void 0)))); map(statementBoundary, i => i ? acc.push(i) : void 0))));
} }
export interface FacetAction { export interface FacetAction {
@ -135,14 +137,28 @@ export function statementFacetAction(kw: Pattern<any>): Pattern<StatementFacetAc
// Principal: Facet // Principal: Facet
export const dataflowStatement = statementFacetAction(atom('dataflow')); export const dataflowStatement = statementFacetAction(atom('dataflow'));
export interface EventHandlerEndpointStatement extends FacetAction { export interface GenericEventEndpointStatement extends StatementFacetAction {
terminal: boolean; terminal: boolean;
triggerType: 'dataflow' | 'start' | 'stop' | 'asserted' | 'retracted' | 'message';
isDynamic: boolean; isDynamic: boolean;
pattern?: Expr;
body: Statement;
} }
export interface DataflowEndpointStatement extends GenericEventEndpointStatement {
triggerType: 'dataflow';
predicate: Expr;
}
export interface PseudoEventEndpointStatement extends GenericEventEndpointStatement {
triggerType: 'start' | 'stop';
}
export interface AssertionEventEndpointStatement extends GenericEventEndpointStatement {
triggerType: 'asserted' | 'retracted' | 'message';
pattern: ValuePattern;
}
export type EventHandlerEndpointStatement =
DataflowEndpointStatement | PseudoEventEndpointStatement | AssertionEventEndpointStatement;
// Principal: Facet // Principal: Facet
export const eventHandlerEndpointStatement: Pattern<EventHandlerEndpointStatement> = export const eventHandlerEndpointStatement: Pattern<EventHandlerEndpointStatement> =
facetAction(o => { facetAction(o => {
@ -151,19 +167,23 @@ export const eventHandlerEndpointStatement: Pattern<EventHandlerEndpointStatemen
o.body = []; o.body = [];
return seq(option(map(atom('stop'), _ => o.terminal = true)), return seq(option(map(atom('stop'), _ => o.terminal = true)),
atom('on'), atom('on'),
alt(map(group('(', bind(o, 'pattern', expr())), _ => o.triggerType = 'dataflow'), alt<any>(seq(map(group('(', bind(o as DataflowEndpointStatement, 'predicate',
seq(bind(o, 'triggerType', expr())),
map(alt(atom('start'), atom('stop')), e => e.text)), _ => o.triggerType = 'dataflow'),
option(statement(o.body))), option(statement(o.body))),
seq(bind(o, 'triggerType', mapm(seq(bind(o, 'triggerType',
map(alt(atom('asserted'), alt(atomString('start'), atomString('stop'))),
atom('retracted'), option(statement(o.body))),
atom('message')), v => o.terminal ? fail : succeed(v)),
e => e.text)), seq(bind(o, 'triggerType',
option(map(atom(':snapshot'), _ => o.isDynamic = false)), alt(atomString('asserted'),
bind(o, 'pattern', expr(atom('=>'))), atomString('retracted'),
alt(seq(atom('=>'), statement(o.body)), atomString('message'))),
statementBoundary)))); option(map(atom(':snapshot'), _ => o.isDynamic = false)),
bind(o as AssertionEventEndpointStatement, 'pattern',
valuePattern(atom('=>'))),
alt<any>(seq(atom('=>'), statement(o.body)),
statementBoundary))));
}); });
export interface TypeDefinitionStatement { export interface TypeDefinitionStatement {
@ -175,7 +195,7 @@ export interface TypeDefinitionStatement {
// Principal: none // 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', alt(atomString('message'), atomString('assertion'))),
atom('type'), atom('type'),
bind(o, 'label', identifier), bind(o, 'label', identifier),
group('(', bind(o, 'fields', repeat(identifier, { separator: atom(',') }))), group('(', bind(o, 'fields', repeat(identifier, { separator: atom(',') }))),
@ -194,7 +214,7 @@ export const messageSendStatement: Pattern<MessageSendStatement> =
statementBoundary)); statementBoundary));
export interface DuringStatement extends FacetAction { export interface DuringStatement extends FacetAction {
pattern: Expr; pattern: ValuePattern;
body: Statement; body: Statement;
} }
@ -203,8 +223,9 @@ export const duringStatement: Pattern<DuringStatement> =
facetAction(o => { facetAction(o => {
o.body = []; o.body = [];
return seq(atom('during'), return seq(atom('during'),
bind(o, 'pattern', expr()), bind(o, 'pattern', valuePattern(atom('=>'))),
statement(o.body)); alt(seq(atom('=>'), statement(o.body)),
statementBoundary));
}); });
// Principal: Facet // Principal: Facet
@ -219,3 +240,230 @@ export const bootStatement: Pattern<Statement> =
// Principal: Facet // Principal: Facet
export const stopStatement = statementFacetAction(atom('stop')); export const stopStatement = statementFacetAction(atom('stop'));
//---------------------------------------------------------------------------
// Syntax of patterns over Value, used in endpoints
export interface PCapture {
type: 'PCapture',
binder: Identifier,
inner: ValuePattern,
}
export interface PDiscard {
type: 'PDiscard',
}
export interface PConstructor {
type: 'PConstructor',
ctor: Expr,
arguments: ValuePattern[],
}
export interface PConstant {
type: 'PConstant',
value: Expr,
}
export interface PArray {
type: 'PArray',
elements: ValuePattern[],
}
export type ValuePattern = PCapture | PDiscard | PConstructor | PConstant | PArray;
const pCaptureId: Pattern<Identifier> =
mapm(identifier, i => i.text.startsWith('$')
? succeed({ ... i, text: i.text.slice(1) })
: fail);
const pDiscard: Pattern<void> = mapm(identifier, i => i.text === '_' ? succeed(void 0) : fail);
function hasCapturesOrDiscards(e: Expr): boolean {
return foldItems(e,
t => match(alt<any>(pCaptureId, pDiscard), [t], null) !== null,
(_s, _e, b, _k) => b,
bs => bs.some(b => b));
}
// $id - capture of discard
// _ - discard
//
// expr(pat, ...) - record ctor
// $id(pat) - nested capture
// [pat, ...] - array pat
//
// expr(expr, ...) - constant
// [expr, ...] - constant
// other - constant
interface RawCall {
items: Items;
callee: Expr;
arguments: Expr[];
}
function pRawCall(... extraStops: Pattern<any>[]): Pattern<RawCall> {
return scope((o: RawCall) => seq(bind(o, 'callee',
expr(seq(group('(', discard),
alt(exprBoundary, ... extraStops)))),
seq(map(anything({ advance: false }),
g => o.items = [... o.callee, g]),
group('(', bind(o, 'arguments',
separatedBy(expr(), atom(',')))))));
}
function isConstant(o: RawCall) {
return (!(hasCapturesOrDiscards(o.callee) || o.arguments.some(hasCapturesOrDiscards)));
}
export function valuePattern(... extraStops: Pattern<any>[]): Pattern<ValuePattern> {
return alt<ValuePattern>(
scope<PCapture>(o => {
o.type = 'PCapture';
o.inner = { type: 'PDiscard' };
return bind(o, 'binder', pCaptureId);
}),
scope(o => map(pDiscard, _ => o.type = 'PDiscard')),
mapm<RawCall, ValuePattern>(
pRawCall(... extraStops),
o => {
if (isConstant(o)) {
return succeed({ type: 'PConstant', value: o.items });
} else if (hasCapturesOrDiscards(o.callee)) {
const r = match(pCaptureId, o.callee, null);
if (r !== null && o.arguments.length === 1)
{
const argPat = match(valuePattern(), o.arguments[0], null);
if (argPat === null) return fail;
return succeed({
type: 'PCapture',
inner: argPat,
binder: r
});
} else {
return fail;
}
} else {
const argPats = o.arguments.map(a => match(valuePattern(), a, null));
if (argPats.some(p => p === null)) return fail;
return succeed({
type: 'PConstructor',
ctor: o.callee,
arguments: argPats as ValuePattern[]
});
}
}),
map(expr(), e => ({ type: 'PConstant', value: e }))
);
}
export function patternText(p: ValuePattern): Items {
switch (p.type) {
case 'PDiscard': return template`_`;
case 'PConstant': return p.value;
case 'PCapture':
{
const binder = { ... p.binder, text: '$' + p.binder.text };
if (p.inner.type === 'PDiscard') {
return [binder];
} else {
return template`${[binder]}(${patternText(p.inner)})`;
}
}
case 'PArray': return template`[${commaJoin(p.elements.map(patternText))}]`;
case 'PConstructor': return template`${p.ctor}(${commaJoin(p.arguments.map(patternText))})`;
}
}
export interface StaticAnalysis {
skeleton: Expr;
constPaths: Path[];
constVals: Expr[];
capturePaths: Path[];
captureIds: Identifier[];
assertion: Expr;
}
const eDiscard: Expr = template`(__SYNDICATE__.Discard._instance)`;
const eCapture = (e: Expr): Expr => template`(__SYNDICATE__.Capture(${e}))`;
export function compilePattern(pattern: ValuePattern): StaticAnalysis {
const constPaths: Path[] = [];
const constVals: Expr[] = [];
const capturePaths: Path[] = [];
const captureIds: Identifier[] = [];
const currentPath: Path = [];
function walk(pattern: ValuePattern): [Skeleton<Expr>, Expr] {
switch (pattern.type) {
case 'PDiscard':
return [null, eDiscard];
case 'PCapture': {
capturePaths.push(currentPath.slice());
captureIds.push(pattern.binder);
const [s, a] = walk(pattern.inner);
return [s, eCapture(a)];
}
case 'PConstant':
constVals.push(pattern.value);
return [null, pattern.value];
case 'PConstructor': {
const skel: Skeleton<Expr> = {
shape: template`__SYNDICATE__.Skeleton.constructorInfoSignature((${pattern.ctor}).constructorInfo)`,
members: [],
};
const assertionArgs: Expr[] = [];
pattern.arguments.forEach((argPat, i) => {
currentPath.push(i);
const [s, a] = walk(argPat);
skel.members.push(s);
assertionArgs.push(a);
currentPath.pop();
});
return [skel, template`(${pattern.ctor}(${commaJoin(assertionArgs)}))`];
}
case 'PArray': {
const skel: Skeleton<Expr> = {
shape: [ {
start: startPos(null),
end: startPos(null),
type: TokenType.STRING,
text: JSON.stringify(pattern.elements.length.toString()),
} ],
members: []
};
const elements: Expr[] = [];
pattern.elements.forEach((elemPat, i) => {
currentPath.push(i);
const [s, a] = walk(elemPat);
skel.members.push(s);
elements.push(a);
currentPath.pop();
});
return [skel, template`[${commaJoin(elements)}]`];
}
}
}
const [skeletonStructure, assertion] = walk(pattern);
const skeleton = renderSkeleton(skeletonStructure);
return {
skeleton,
constPaths,
constVals,
capturePaths,
captureIds,
assertion,
};
}
function renderSkeleton(skel: Skeleton<Expr>): Expr {
if (skel === null) {
return template`null`;
} else {
return template`({shape:${skel.shape}, members: [${commaJoin(skel.members.map(renderSkeleton))}]})`;
}
}

View File

@ -1,9 +1,18 @@
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 { Substitution } from '../syntax/index.js';
import * as G from './grammar.js'; import * as G from './grammar.js';
import { BootProc } from './internals.js'; import { BootProc } from './internals.js';
export function stripShebang(items: S.Items): S.Items {
if ((items.length > 0) &&
S.isToken(items[0]) &&
items[0].text.startsWith('#!')) {
while (items.length > 0 && !S.isTokenType(items[0], S.TokenType.NEWLINE)) items.shift();
}
return items;
}
export function main(argv: string[]) { export function main(argv: string[]) {
let [ inputFilename ] = argv.slice(2); let [ inputFilename ] = argv.slice(2);
inputFilename = inputFilename ?? '/dev/stdin'; inputFilename = inputFilename ?? '/dev/stdin';
@ -11,12 +20,12 @@ export function main(argv: string[]) {
const scanner = new S.StringScanner(S.startPos(inputFilename), source); const scanner = new S.StringScanner(S.startPos(inputFilename), source);
const reader = new S.LaxReader(scanner); const reader = new S.LaxReader(scanner);
let tree = reader.readToEnd(); let tree = stripShebang(reader.readToEnd());
let macro = new S.Templates(); let macro = new S.Templates();
tree = macro.template()`import * as __SYNDICATE__ from '@syndicate/core';\n${tree}`; tree = macro.template()`import * as __SYNDICATE__ from '@syndicate/core';\n${tree}`;
let passNumber = 1; let passNumber = 0;
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) {
tree = S.replace(tree, p, t => { tree = S.replace(tree, p, t => {
@ -33,10 +42,22 @@ export function main(argv: string[]) {
expand(p, t => macro.template()`${receiverFor(t)}${f(t)}`); expand(p, t => macro.template()`${receiverFor(t)}${f(t)}`);
} }
function terminalWrap(isTerminal: boolean, body: G.Statement): G.Statement {
if (isTerminal) {
return macro.template()`thisFacet._stop(function (thisFacet) {${body}})`
} else {
return body;
}
}
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})!`);
} }
// console.log(`\n\n\n======================================== PASS ${passNumber}\n`);
// console.log(S.itemText(tree, { color: true, missing: '\x1b[41m□\x1b[0m' }));
expansionNeeded = false; expansionNeeded = false;
expandFacetAction( expandFacetAction(
G.spawn, G.spawn,
@ -44,7 +65,7 @@ export function main(argv: string[]) {
let proc = macro.template()`function (thisFacet) {${s.bootProcBody}}`; let proc = macro.template()`function (thisFacet) {${s.bootProcBody}}`;
if (s.isDataspace) proc = macro.template()`__SYNDICATE__.inNestedDataspace(${proc})`; if (s.isDataspace) proc = macro.template()`__SYNDICATE__.inNestedDataspace(${proc})`;
let assertions = (s.initialAssertions.length > 0) let assertions = (s.initialAssertions.length > 0)
? macro.template()`, new __SYNDICATE__.Set([${S.joinItems(s.initialAssertions, ', ')}])` ? macro.template()`, new __SYNDICATE__.Set([${S.commaJoin(s.initialAssertions)}])`
: ``; : ``;
return macro.template()`_spawn(${s.name ?? 'null'}, ${proc}${assertions});`; return macro.template()`_spawn(${s.name ?? 'null'}, ${proc}${assertions});`;
}); });
@ -61,18 +82,76 @@ export function main(argv: string[]) {
}); });
expandFacetAction( expandFacetAction(
G.assertionEndpointStatement, G.assertionEndpointStatement,
s => macro.template()`addEndpoint(thisFacet => (${s.test ?? 'true'}) && (${s.template}), ${''+s.isDynamic});`); s => {
if (s.test == void 0) {
return macro.template()`addEndpoint(thisFacet => ({ assertion: ${s.template}, analysis: null }));`;
} else {
return macro.template()`addEndpoint(thisFacet => (${s.test ?? 'true'})
? ({ assertion: ${s.template}, analysis: null })
: ({ assertion: void 0, analysis: null }), ${''+s.isDynamic});`;
}
});
expandFacetAction( expandFacetAction(
G.dataflowStatement, G.dataflowStatement,
s => macro.template()`addDataflow(function (thisFacet) {${s.body}});`); s => macro.template()`addDataflow(function (thisFacet) {${s.body}});`);
expandFacetAction( expandFacetAction(
G.eventHandlerEndpointStatement, G.eventHandlerEndpointStatement,
s => { s => {
return macro.template()`EVENTHANDLER[${`${s.terminal}/${s.isDynamic}`}][${s.triggerType}][${s.pattern ?? []}][${s.body}]`; switch (s.triggerType) {
case 'dataflow':
return macro.template()`withSelfDo(function (thisFacet) { dataflow { if (${s.predicate}) { ${terminalWrap(s.terminal, s.body)} } } });`;
case 'start':
case 'stop': {
const m = s.triggerType === 'start' ? 'addStartScript' : 'addStopScript';
return macro.template()`${m}(function (thisFacet) {${s.body}});`;
}
case 'asserted':
case 'retracted':
case 'message': {
const sa = G.compilePattern(s.pattern);
const expectedEvt = ({
'asserted': 'ADDED',
'retracted': 'REMOVED',
'message': 'MESSAGE',
})[s.triggerType];
return macro.template()`addEndpoint(thisFacet => ({
assertion: __SYNDICATE__.Observe(${sa.assertion}),
analysis: {
skeleton: ${sa.skeleton},
constPaths: ${JSON.stringify(sa.constPaths)},
constVals: [${S.commaJoin(sa.constVals)}],
capturePaths: ${JSON.stringify(sa.capturePaths)},
callback: thisFacet.wrap((thisFacet, __Evt, [${S.commaJoin(sa.captureIds.map(i=>[i]))}]) => {
if (__Evt === __SYNDICATE__.Skeleton.EventType.${expectedEvt}) {
thisFacet.scheduleScript(() => {${terminalWrap(s.terminal, s.body)}});
}
})
}
}), ${'' + s.isDynamic});`;
}
}
}); });
expandFacetAction( expandFacetAction(
G.duringStatement, G.duringStatement,
s => macro.template()`DURING[${s.pattern}][${s.body}]`); s => {
// TODO: spawn during
const sa = G.compilePattern(s.pattern);
return macro.template()`withSelfDo(function (thisFacet) {
const _Facets = new __SYNDICATE__.Dictionary();
on asserted ${G.patternText(s.pattern)} => react {
_Facets.set([${S.commaJoin(sa.captureIds.map(t=>[t]))}], thisFacet);
dataflow void 0; // TODO: horrible hack to keep the facet alive if no other endpoints
${s.body}
}
on retracted ${G.patternText(s.pattern)} => {
const _Key = [${S.commaJoin(sa.captureIds.map(t=>[t]))}];
_Facets.get(_Key)._stop();
_Facets.delete(_Key);
}
});`;
});
expand( expand(
G.typeDefinitionStatement, G.typeDefinitionStatement,
s => { s => {
@ -94,7 +173,8 @@ export function main(argv: string[]) {
s => macro.template()`_stop(function (thisFacet) {${s.body}});`); s => macro.template()`_stop(function (thisFacet) {${s.body}});`);
} }
console.log(S.itemText(tree, { color: true, missing: '\x1b[41m□\x1b[0m' })); // console.log(`\n\n\n======================================== FINAL OUTPUT\n`);
console.log(S.itemText(tree));
const cw = new S.CodeWriter(inputFilename); const cw = new S.CodeWriter(inputFilename);
cw.emit(tree); cw.emit(tree);

View File

@ -21,6 +21,7 @@ export * from 'preserves';
export * from './runtime/randomid.js'; export * from './runtime/randomid.js';
export * from './runtime/assertions.js'; export * from './runtime/assertions.js';
export * from './runtime/bag.js'; export * from './runtime/bag.js';
export * as API from './runtime/api.js';
export * as Skeleton from './runtime/skeleton.js'; export * as Skeleton from './runtime/skeleton.js';
export * from './runtime/dataspace.js'; export * from './runtime/dataspace.js';
export * from './runtime/ground.js'; export * from './runtime/ground.js';

View File

@ -0,0 +1,3 @@
export type NonEmptySkeleton<Shape> = { shape: Shape, members: Skeleton<Shape>[] };
export type Skeleton<Shape> = null | NonEmptySkeleton<Shape>;
export type Path = Array<number>;

View File

@ -89,7 +89,7 @@ export abstract class Dataspace {
return this.ground().backgroundTask(); return this.ground().backgroundTask();
} }
runTasks() { // TODO: rename? runTasks(): boolean { // TODO: rename?
this.runPendingTasks(); this.runPendingTasks();
this.performPendingActions(); this.performPendingActions();
return this.runnable.length > 0 || this.pendingTurns.length > 0; return this.runnable.length > 0 || this.pendingTurns.length > 0;
@ -684,6 +684,10 @@ export class Facet {
addChildFacet(bootProc: Script<void>) { addChildFacet(bootProc: Script<void>) {
this.actor.addFacet(this, bootProc, true); this.actor.addFacet(this, bootProc, true);
} }
withSelfDo(t: Script<void>) {
t(this);
}
} }
export class Endpoint { export class Endpoint {

View File

@ -71,7 +71,6 @@ export class NestedDataspace extends Dataspace {
constPaths: h.constPaths, constPaths: h.constPaths,
constVals: h.constVals.map(v => (v as Record)[0]), constVals: h.constVals.map(v => (v as Record)[0]),
capturePaths: h.capturePaths.map(p => p.slice(1)), capturePaths: h.capturePaths.map(p => p.slice(1)),
assertion,
callback callback
} }
: { : {
@ -79,7 +78,6 @@ export class NestedDataspace extends Dataspace {
constPaths: h.constPaths.map(p => p.slice(1)), constPaths: h.constPaths.map(p => p.slice(1)),
constVals: h.constVals, constVals: h.constVals,
capturePaths: h.capturePaths.map(p => p.slice(1)), capturePaths: h.capturePaths.map(p => p.slice(1)),
assertion,
callback callback
}); });
return { assertion, analysis }; return { assertion, analysis };

View File

@ -17,13 +17,15 @@
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
import { IdentitySet } from './idcoll.js'; import { IdentitySet } from './idcoll.js';
import { is, Value, Record, Set, Dictionary, canonicalString, preserves } from 'preserves'; import { is, Value, Record, Set, Dictionary, canonicalString, preserves, RecordConstructorInfo } 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';
import * as Stack from './stack.js'; import * as Stack from './stack.js';
import { Path, NonEmptySkeleton, Skeleton } from './api.js';
export enum EventType { export enum EventType {
ADDED = +1, ADDED = +1,
REMOVED = -1, REMOVED = -1,
@ -33,15 +35,12 @@ 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 NonEmptySkeleton = { shape: Shape, members: Skeleton[] };
export type Skeleton = null | NonEmptySkeleton;
export type Path = Array<number>;
export interface Analysis { export interface Analysis {
skeleton: Skeleton; skeleton: Skeleton<Shape>;
constPaths: Array<Path>; constPaths: Array<Path>;
constVals: Array<Value>; constVals: Array<Value>;
capturePaths: Array<Path>; capturePaths: Array<Path>;
assertion: Value;
callback?: HandlerCallback; callback?: HandlerCallback;
} }
@ -159,13 +158,13 @@ class Node {
this.continuation = continuation; this.continuation = continuation;
} }
extend(skeleton: Skeleton): Continuation { extend(skeleton: Skeleton<Shape>): Continuation {
const path: Path = []; const path: Path = [];
function walkNode(node: Node, function walkNode(node: Node,
popCount: number, popCount: number,
index: number, index: number,
skeleton: Skeleton): [number, Node] skeleton: Skeleton<Shape>): [number, Node]
{ {
if (skeleton === null) { if (skeleton === null) {
return [popCount, node]; return [popCount, node];
@ -280,10 +279,13 @@ class Handler {
} }
} }
function classOf(v: any): string { export function constructorInfoSignature(ci: RecordConstructorInfo): string {
return canonicalString(ci.label) + '/' + ci.arity;
}
function classOf(v: any): Shape {
if (Record.isRecord(v)) { if (Record.isRecord(v)) {
const ci = v.getConstructorInfo(); return constructorInfoSignature(v.getConstructorInfo());
return canonicalString(ci.label) + '/' + ci.arity;
} else if (Array.isArray(v)) { } else if (Array.isArray(v)) {
return '' + v.length; return '' + v.length;
} else { } else {
@ -312,7 +314,7 @@ export function analyzeAssertion(a: Value): Analysis {
const capturePaths: Path[] = []; const capturePaths: Path[] = [];
const path: Path = []; const path: Path = [];
function walk(a: Value): Skeleton { function walk(a: Value): Skeleton<Shape> {
if (Capture.isClassOf(a)) { if (Capture.isClassOf(a)) {
// NB. isUnrestricted relies on the specific order that // NB. isUnrestricted relies on the specific order that
// capturePaths is computed here. // capturePaths is computed here.
@ -329,7 +331,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: NonEmptySkeleton = { shape: cls, members: [] }; let result: NonEmptySkeleton<Shape> = { 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;
@ -346,7 +348,7 @@ export function analyzeAssertion(a: Value): Analysis {
let skeleton = walk(a); let skeleton = walk(a);
return { skeleton, constPaths, constVals, capturePaths, assertion: Observe(a) }; return { skeleton, constPaths, constVals, capturePaths };
} }
export function match(p: Value, v: Value): Array<Value> | false { export function match(p: Value, v: Value): Array<Value> | false {

View File

@ -8,6 +8,13 @@ import { List, ArrayList, atEnd, notAtEnd } from './list.js';
export type PatternResult<T> = [T, List<Item>] | null; export type PatternResult<T> = [T, List<Item>] | null;
export type Pattern<T> = (i: List<Item>) => PatternResult<T>; export type Pattern<T> = (i: List<Item>) => PatternResult<T>;
export function match<T,F>(p: Pattern<T>, items: Items, failure: F): T | F {
const r = p(new ArrayList(items));
if (r === null) return failure;
if (notAtEnd(r[1])) return failure;
return r[0];
}
export const noItems = new ArrayList<Item>([]); export const noItems = new ArrayList<Item>([]);
export const fail: Pattern<never> = _i => null; export const fail: Pattern<never> = _i => null;
@ -15,7 +22,7 @@ export function succeed<T>(t: T): Pattern<T> { return i => [t, 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(skipSpace(i)) ? [void 0, noItems] : null;
export const pos: Pattern<Pos> = i => export const pos: Pattern<Pos> = i =>
notAtEnd(i) notAtEnd(i)
? [isGroup(i.item) ? i.item.start.start : i.item.start, i] ? [isGroup(i.item) ? i.item.start.start : i.item.start, i]
@ -55,7 +62,7 @@ export function seq(... patterns: Pattern<any>[]): Pattern<void> {
}; };
} }
export function alt(... alts: Pattern<any>[]): Pattern<any> { export function alt<T>(... alts: Pattern<T>[]): Pattern<T> {
return i => { return i => {
for (const a of alts) { for (const a of alts) {
const r = a(i); const r = a(i);
@ -65,7 +72,7 @@ export function alt(... alts: Pattern<any>[]): Pattern<any> {
}; };
} }
export function scope<I, T extends I, R>(pf: (scope: T) => Pattern<R>): Pattern<T> { export function scope<T>(pf: (scope: T) => Pattern<any>): Pattern<T> {
return i => { return i => {
const scope = Object.create(null); const scope = Object.create(null);
const r = pf(scope)(i); const r = pf(scope)(i);
@ -107,8 +114,17 @@ export function map<T, R>(p: Pattern<T>, f: (t: T) => R): Pattern<R> {
}; };
} }
export function mapm<T, R>(p: Pattern<T>, f: (t: T) => Pattern<R>): Pattern<R> {
return i => {
const r = p(i);
if (r === null) return null;
return f(r[0])(r[1]);
};
}
export interface ItemOptions { export interface ItemOptions {
skipSpace?: boolean, // default: true skipSpace?: boolean, // default: true
advance?: boolean, // default: true
} }
export interface GroupOptions extends ItemOptions { export interface GroupOptions extends ItemOptions {
@ -127,18 +143,22 @@ export function group<T>(opener: string, items: Pattern<T>, options: GroupOption
const r = items(new ArrayList(i.item.items)); const r = items(new ArrayList(i.item.items));
if (r === null) return null; if (r === null) return null;
if (!atEnd(r[1])) return null; if (!atEnd(r[1])) return null;
return [r[0], i.next]; return [r[0], (options.advance ?? true) ? i.next : i];
}; };
} }
export function atom(text?: string | undefined, options: TokenOptions = {}): Pattern<Token> { export function atomString<T extends string>(text: T, options: TokenOptions = {}): Pattern<T> {
return map(atom(text, options), t => text);
}
export function atom(text?: string, 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 (!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;
return [i.item, i.next]; return [i.item, (options.advance ?? true) ? i.next : i];
} }
} }
@ -146,7 +166,7 @@ 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 (!notAtEnd(i)) return null; if (!notAtEnd(i)) return null;
return [i.item, i.next]; return [i.item, (options.advance ?? true) ? i.next : i];
}; };
} }
@ -164,6 +184,29 @@ export function upTo(p: Pattern<any>): Pattern<Items> {
}; };
} }
export function separatedBy<T>(itemPattern: Pattern<T>, separator: Pattern<any>): Pattern<T[]> {
return i => {
const acc: T[] = [];
if (end(i) !== null) return [acc, noItems];
while (true) {
{
const r = itemPattern(i);
if (r === null) return null;
acc.push(r[0]);
i = r[1];
}
{
const r = separator(i);
if (r === null) {
if (end(i) !== null) return [acc, noItems];
return null;
}
i = r[1];
}
}
};
}
export interface RepeatOptions { export interface RepeatOptions {
min?: number; min?: number;
max?: number; max?: number;

View File

@ -1,5 +1,6 @@
import { TokenType, Token, Group, Item, Items } from './tokens.js'; import { TokenType, Token, Group, Item, Items } from './tokens.js';
import { Scanner } from './scanner.js'; import { Pos, startPos } from './position.js';
import { Scanner, StringScanner } from './scanner.js';
function matchingParen(c: string): string | null { function matchingParen(c: string): string | null {
switch (c) { switch (c) {
@ -122,3 +123,17 @@ export class LaxReader implements IterableIterator<Item> {
} }
} }
} }
export interface LaxReadOptions {
start?: Pos,
name?: string,
extraDelimiters?: string,
}
export function laxRead(source: string, options: LaxReadOptions = {}): Items {
const start = options.start ?? startPos(options.name ?? null);
const scanner = new StringScanner(start, source);
if (options.extraDelimiters) scanner.addDelimiters(options.extraDelimiters);
const reader = new LaxReader(scanner);
return reader.readToEnd();
}

View File

@ -8,7 +8,7 @@ export abstract class Scanner implements IterableIterator<Token> {
delimiters = ' \t\n\r\'"`.,;()[]{}/'; delimiters = ' \t\n\r\'"`.,;()[]{}/';
constructor(pos: Pos) { constructor(pos: Pos) {
this.pos = pos; this.pos = { ... pos };
} }
[Symbol.iterator](): IterableIterator<Token> { [Symbol.iterator](): IterableIterator<Token> {

View File

@ -1,7 +1,6 @@
import { Items, TokenType } from './tokens.js'; import { Items, TokenType } from './tokens.js';
import { Pos, startPos } from './position.js'; import { Pos, startPos } from './position.js';
import { StringScanner } from './scanner.js'; import { laxRead } from './reader.js';
import { LaxReader } from './reader.js';
import * as M from './matcher.js'; import * as M from './matcher.js';
const substPat = M.scope((o: { pos: Pos }) => const substPat = M.scope((o: { pos: Pos }) =>
@ -11,7 +10,7 @@ const substPat = M.scope((o: { pos: Pos }) =>
export type Substitution = Items | string; export type Substitution = Items | string;
function toItems(s: Substitution, pos: Pos): Items { function toItems(s: Substitution, pos: Pos): Items {
return typeof s === 'string' ? [{ type: TokenType.ATOM, text: s, start: pos, end: pos }] : s; return typeof s === 'string' ? laxRead(s) : s;
} }
export class Templates { export class Templates {
@ -32,10 +31,10 @@ export class Templates {
} }
this.sources[start.name] = source; this.sources[start.name] = source;
} }
const reader = new LaxReader(new StringScanner(start, source));
reader.scanner.addDelimiters('$');
let i = 0; let i = 0;
return M.replace(reader.readToEnd(), substPat, sub => toItems(vars[i++], sub.pos)); return M.replace(laxRead(source, { start, extraDelimiters: '$' }),
substPat,
sub => toItems(vars[i++], sub.pos));
}; };
} }
@ -54,6 +53,12 @@ export function joinItems(itemss: Items[], separator0: Substitution): Items {
return acc; return acc;
} }
export function commaJoin(itemss: Items[]): Items {
return joinItems(itemss, ', ');
}
export const anonymousTemplate = (new Templates()).template();
// const lib = new Templates(); // const lib = new Templates();
// const t = (o: {xs: Items}) => lib.template('testTemplate')`YOYOYOYO ${o.xs}><`; // const t = (o: {xs: Items}) => lib.template('testTemplate')`YOYOYOYO ${o.xs}><`;
// console.log(t({xs: lib.template()`hello there`})); // console.log(t({xs: lib.template()`hello there`}));

View File

@ -60,12 +60,25 @@ export type ItemTextOptions = {
color?: boolean, color?: boolean,
}; };
export function itemText(i: Items, options: ItemTextOptions = {}): string { export function foldItems<T>(i: Items,
const walkItems = (i: Items): string => i.map(walk).join(''); fToken: (t: Token) => T,
const walk = (i: Item): string => { fGroup: (start: Token, end: Token | null, t: T, k: (t: Token) => T) => T,
fItems: (ts: T[]) => T): T
{
const walk = (i: Item): T => {
if (isGroup(i)) { if (isGroup(i)) {
return walk(i.start) + walkItems(i.items) + (i.end ? walk(i.end) : options.missing ?? ''); return fGroup(i.start, i.end, fItems(i.items.map(walk)), walk);
} else { } else {
return fToken(i);
}
};
return fItems(i.map(walk));
}
export function itemText(items: Items, options: ItemTextOptions = {}): string {
return foldItems(
items,
i => {
if (options.color ?? false) { if (options.color ?? false) {
switch (i.type) { switch (i.type) {
case TokenType.SPACE: case TokenType.SPACE:
@ -79,7 +92,7 @@ export function itemText(i: Items, options: ItemTextOptions = {}): string {
} else { } else {
return i.text; return i.text;
} }
} },
}; (start, end, inner, k) => k(start) + inner + (end ? k(end) : options.missing ?? ''),
return walkItems(i); strs => strs.join(''));
} }

View File

@ -10,6 +10,7 @@ spawn named 'test' {
during M.Discovered(M.Service($name, '_syndicate+testing._tcp'), during M.Discovered(M.Service($name, '_syndicate+testing._tcp'),
$host, $port, _, $addr, "IPv4", $ifName) $host, $port, _, $addr, "IPv4", $ifName)
=>
{ {
on start { this.count++; console.log('+', name, host, port, addr, ifName); } on start { this.count++; console.log('+', name, host, port, addr, ifName); }
on stop { this.count--; console.log('-', name, host, port, addr, ifName); } on stop { this.count--; console.log('-', name, host, port, addr, ifName); }

View File

@ -24,18 +24,18 @@ const stdin = genUuid('stdin');
const stdout = genUuid('stdout'); const stdout = genUuid('stdout');
spawn named 'stdioServer' { spawn named 'stdioServer' {
during Observe(S.Stream(stdin, S.Readable())) during Observe(S.Stream(stdin, S.Readable()))
spawn S.readableStreamBehaviour(stdin, process.stdin); => spawn S.readableStreamBehaviour(stdin, process.stdin);
during Observe(S.Stream(stdout, S.Writable())) during Observe(S.Stream(stdout, S.Writable()))
spawn S.writableStreamBehaviour(stdout, process.stdout); => spawn S.writableStreamBehaviour(stdout, process.stdout);
} }
spawn named 'chatclient' { spawn named 'chatclient' {
const id = genUuid('tcpconn'); const id = genUuid('tcpconn');
assert S.Stream(id, S.Outgoing(S.TcpAddress('localhost', 5999))); assert S.Stream(id, S.Outgoing(S.TcpAddress('localhost', 5999)));
stop on message S.Stream(id, S.Rejected($err)) { stop on message S.Stream(id, S.Rejected($err)) => {
console.error('Connection rejected', err); console.error('Connection rejected', err);
} }
stop on message S.Stream(id, S.Accepted()) { stop on message S.Stream(id, S.Accepted()) => {
react { react {
stop on retracted S.Stream(id, S.Duplex()); stop on retracted S.Stream(id, S.Duplex());
stop on retracted S.Stream(stdin, S.Readable()); stop on retracted S.Stream(stdin, S.Readable());
@ -44,10 +44,10 @@ spawn named 'chatclient' {
assert S.Stream(stdin, S.BackPressure(id)); assert S.Stream(stdin, S.BackPressure(id));
assert S.Stream(id, S.BackPressure(stdout)); assert S.Stream(id, S.BackPressure(stdout));
on message S.Stream(stdin, S.Line($line)) { on message S.Stream(stdin, S.Line($line)) => {
send S.Stream(id, S.Push(line.fromUtf8() + '\n', false)); send S.Stream(id, S.Push(line.fromUtf8() + '\n', false));
} }
on message S.Stream(id, S.Line($line)) { on message S.Stream(id, S.Line($line)) => {
send S.Stream(stdout, S.Push(line.fromUtf8() + '\n', false)); send S.Stream(stdout, S.Push(line.fromUtf8() + '\n', false));
} }
} }

View File

@ -44,9 +44,9 @@ spawn named 'view' {
return <td>{text}</td>; return <td>{text}</td>;
} }
on message SetSortColumn($c) { this.orderColumn = c; } on message SetSortColumn($c) => this.orderColumn = c;
during Person($id, $firstName, $lastName, $address, $age) { during Person($id, $firstName, $lastName, $address, $age) => {
assert ui.context(id) assert ui.context(id)
.html('table#the-table tbody', .html('table#the-table tbody',
<tr>{[id, firstName, lastName, address, age].map(cell)}</tr>, <tr>{[id, firstName, lastName, address, age].map(cell)}</tr>,
@ -55,7 +55,7 @@ spawn named 'view' {
} }
spawn named 'controller' { spawn named 'controller' {
on message UI.GlobalEvent('table#the-table th', 'click', $e) { on message UI.GlobalEvent('table#the-table th', 'click', $e) => {
send SetSortColumn(JSON.parse(e.target.dataset.column)); send SetSortColumn(JSON.parse(e.target.dataset.column));
} }
} }
@ -64,7 +64,7 @@ spawn named 'alerter' {
let ui = new UI.Anchor(); let ui = new UI.Anchor();
assert ui.html('#extra', <button>Click me</button>); assert ui.html('#extra', <button>Click me</button>);
on message UI.UIEvent(ui.fragmentId, '.', 'click', $e) { on message UI.UIEvent(ui.fragmentId, '.', 'click', $e) => {
alert("Hello!"); alert("Hello!");
} }
} }