582 lines
21 KiB
TypeScript
582 lines
21 KiB
TypeScript
/// SPDX-License-Identifier: GPL-3.0-or-later
|
|
/// SPDX-FileCopyrightText: Copyright © 2016-2024 Tony Garnock-Jones <tonyg@leastfixedpoint.com>
|
|
|
|
import {
|
|
Token, Items, TokenBase, TokenType,
|
|
Pattern,
|
|
foldItems, match, anonymousTemplate as template, commaJoin,
|
|
|
|
scope, bind, seq, seqTuple, alt, upTo, atom, atomString, group,
|
|
repeat, option, withoutSpace, map, mapm, rest, discard,
|
|
value, succeed, fail, separatedOrTerminatedBy, not,
|
|
} from '../syntax/index';
|
|
import * as Matcher from '../syntax/matcher';
|
|
|
|
//---------------------------------------------------------------------------
|
|
// AST types
|
|
|
|
export type Expr = Items;
|
|
export type Statement = Items;
|
|
export type Identifier = Token;
|
|
export type Type = Items;
|
|
export type Binder = { id: Identifier, type?: Type };
|
|
|
|
export interface TurnAction {
|
|
}
|
|
|
|
export interface FacetSetupAction extends TurnAction {
|
|
body: Statement;
|
|
}
|
|
|
|
export interface SpawnStatement extends FacetSetupAction {
|
|
name?: Expr;
|
|
linkedToken: TokenBase | null;
|
|
parentBinders: Binder[];
|
|
parentInits: Expr[];
|
|
}
|
|
|
|
export interface FieldDeclarationStatement extends TurnAction {
|
|
field: Binder;
|
|
init?: Expr;
|
|
}
|
|
|
|
export interface AssertionEndpointStatement extends TurnAction {
|
|
isDynamic: boolean,
|
|
template: Expr,
|
|
test?: Expr,
|
|
}
|
|
|
|
export interface StatementTurnAction extends TurnAction {
|
|
body: Statement;
|
|
}
|
|
|
|
export type FacetToStop = 'default' | Expr;
|
|
|
|
export interface StopStatement extends StatementTurnAction {
|
|
facetToStop: FacetToStop;
|
|
}
|
|
|
|
export interface GenericEventEndpointStatement extends StatementTurnAction {
|
|
facetToStop: FacetToStop | 'none' | 'once-wrapper';
|
|
once: boolean;
|
|
isDynamic: boolean;
|
|
}
|
|
|
|
export interface DataflowEndpointStatement extends GenericEventEndpointStatement {
|
|
triggerType: 'dataflow';
|
|
predicate: Expr;
|
|
}
|
|
|
|
export interface PseudoEventEndpointStatement extends GenericEventEndpointStatement {
|
|
triggerType: 'stop';
|
|
}
|
|
|
|
export interface AssertionEventEndpointStatement extends GenericEventEndpointStatement {
|
|
triggerType: 'asserted' | 'retracted' | 'message';
|
|
pattern: ValuePattern;
|
|
}
|
|
|
|
export type EventHandlerEndpointStatement =
|
|
DataflowEndpointStatement | PseudoEventEndpointStatement | AssertionEventEndpointStatement;
|
|
|
|
export interface TypeDefinitionStatement {
|
|
expectedUse: 'message' | 'assertion';
|
|
label: Identifier;
|
|
fields: Binder[];
|
|
wireName?: Expr;
|
|
}
|
|
|
|
export interface MessageSendStatement extends TurnAction {
|
|
expr: Expr;
|
|
}
|
|
|
|
export interface DuringStatement extends FacetSetupAction {
|
|
pattern: ValuePattern;
|
|
test?: Expr,
|
|
}
|
|
|
|
export interface ReactStatement extends FacetSetupAction {
|
|
label: Identifier | null;
|
|
}
|
|
|
|
export interface AtStatement {
|
|
target: Expr;
|
|
body: Statement;
|
|
}
|
|
|
|
export interface CreateExpression {
|
|
entity: Expr;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
// Value pattern AST types
|
|
|
|
export interface PCapture {
|
|
type: 'PCapture',
|
|
binder: Binder,
|
|
inner: ValuePattern,
|
|
}
|
|
|
|
export interface PDiscard {
|
|
type: 'PDiscard',
|
|
}
|
|
|
|
export interface PConstant {
|
|
type: 'PConstant',
|
|
value: Expr,
|
|
}
|
|
|
|
export interface PConstructor {
|
|
type: 'PConstructor',
|
|
ctor: Expr,
|
|
arguments: ValuePattern[],
|
|
}
|
|
|
|
export interface PArray {
|
|
type: 'PArray',
|
|
elements: ValuePattern[],
|
|
}
|
|
|
|
export interface PDict {
|
|
type: 'PDict',
|
|
elements: [Expr, ValuePattern][],
|
|
}
|
|
|
|
export interface PUnquote {
|
|
type: 'PUnquote',
|
|
unquoted: ValuePattern,
|
|
}
|
|
|
|
export interface POuterUnquote {
|
|
type: 'POuterUnquote',
|
|
outer: Expr,
|
|
};
|
|
|
|
export interface PQuote {
|
|
type: 'PQuote',
|
|
quoted: ValuePattern,
|
|
}
|
|
|
|
export type ValuePattern =
|
|
| PCapture | PDiscard
|
|
| PConstant
|
|
| PConstructor | PArray | PDict
|
|
| PUnquote | POuterUnquote | PQuote
|
|
;
|
|
|
|
export interface StaticAnalysis {
|
|
skeleton: Expr; // constructs a P.Pattern
|
|
captureBinders: Binder[];
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
// Parsers
|
|
|
|
function kw(text: string): Pattern<Token> {
|
|
return value(o => seq(atom(':'), bind(o, 'value', atom(text, { skipSpace: false }))));
|
|
}
|
|
|
|
export class SyndicateParser {
|
|
block(acc?: Items): Pattern<Items> {
|
|
return group('{', map(rest, items => (acc?.push(... items), items)));
|
|
}
|
|
|
|
readonly statementBoundary = alt<any>(
|
|
atom(';'),
|
|
Matcher.newline,
|
|
seq(Matcher.end, i => {
|
|
if (i.context === null || i.context === '{') return discard(i);
|
|
// ^ toplevel, or inside braces, so presumably statement context
|
|
return fail(i); // otherwise, parens or brackets presumably, so not statement context
|
|
}),
|
|
);
|
|
readonly exprBoundary = alt<any>(atom(';'), atom(','), group('{', discard), Matcher.end);
|
|
|
|
readonly identifier: Pattern<Identifier> = atom();
|
|
get binder(): Pattern<Binder> { return scope(o => bind(o, 'id', this.identifier)); }
|
|
|
|
expr(... extraStops: Pattern<any>[]): Pattern<Expr> {
|
|
return withoutSpace(upTo(alt(this.exprBoundary, ... extraStops)));
|
|
}
|
|
|
|
expr1(... extraStops: Pattern<any>[]): Pattern<Expr> {
|
|
return mapm(this.expr(... extraStops), e => e.length ? succeed(e) : fail);
|
|
}
|
|
|
|
propertyNameExpr(): Pattern<Expr> {
|
|
const dq = template`"`;
|
|
return alt<Expr>(
|
|
map(atom(), name => [... dq, name, ... dq]),
|
|
map(atom(void 0, { tokenType: TokenType.STRING }), str => [str]),
|
|
group('[', this.expr()));
|
|
}
|
|
|
|
readonly type: (... extraStops: Pattern<any>[]) => Pattern<Type> = this.expr;
|
|
|
|
statement(acc: Items): Pattern<any> {
|
|
return alt<any>(this.block(acc),
|
|
withoutSpace(seq(map(upTo(this.statementBoundary),
|
|
items => acc.push(... items)),
|
|
map(this.statementBoundary,
|
|
i => i ? acc.push(i) : void 0))));
|
|
}
|
|
|
|
turnAction<T extends TurnAction>(pattern: (scope: T) => Pattern<any>): Pattern<T> {
|
|
return i => {
|
|
const scope = Object.create(null);
|
|
const p = pattern(scope);
|
|
const r = p(i);
|
|
if (r === null) return null;
|
|
return [scope, r[1]];
|
|
};
|
|
}
|
|
|
|
readonly headerExpr = this.expr(kw('asserting'), kw('let'));
|
|
|
|
// Principal: Turn
|
|
readonly spawn: Pattern<SpawnStatement> =
|
|
this.turnAction(o => {
|
|
o.linkedToken = null;
|
|
o.parentBinders = [];
|
|
o.parentInits = [];
|
|
o.body = [];
|
|
return seq(atom('spawn'),
|
|
option(map(atom('linked'), tok => o.linkedToken = tok)),
|
|
option(seq(atom('named'), bind(o, 'name', this.headerExpr))),
|
|
repeat(alt(
|
|
/* seq(kw('asserting'), map(this.headerExpr, e => o.initialAssertions.push(e))), */
|
|
map(scope(
|
|
(l: { b: Binder, init: Expr }) =>
|
|
seq(kw('let'),
|
|
bind(l, 'b', this.binder),
|
|
atom('='),
|
|
bind(l, 'init', this.headerExpr))),
|
|
l => {
|
|
o.parentBinders.push(l.b);
|
|
o.parentInits.push(l.init);
|
|
}))),
|
|
this.block(o.body));
|
|
});
|
|
|
|
// Principal: Turn
|
|
readonly fieldDeclarationStatement: Pattern<FieldDeclarationStatement> =
|
|
this.turnAction(o => {
|
|
return seq(atom('field'),
|
|
bind(o, 'field', this.binder),
|
|
option(seq(atom('='), bind(o, 'init', this.expr()))),
|
|
this.statementBoundary);
|
|
});
|
|
|
|
// Principal: Turn
|
|
readonly assertionEndpointStatement: Pattern<AssertionEndpointStatement> =
|
|
this.turnAction(o => {
|
|
o.isDynamic = true;
|
|
return seq(atom('assert'),
|
|
option(map(kw('snapshot'), _ => o.isDynamic = false)),
|
|
bind(o, 'template', this.expr(seq(atom('when'), group('(', discard)))),
|
|
option(seq(atom('when'), group('(', bind(o, 'test', this.expr())))),
|
|
this.statementBoundary);
|
|
});
|
|
|
|
blockTurnAction(kw: Pattern<any>): Pattern<StatementTurnAction> {
|
|
return this.turnAction(o => {
|
|
o.body = [];
|
|
return seq(kw, this.block(o.body));
|
|
});
|
|
}
|
|
|
|
// Principal: Turn
|
|
readonly dataflowStatement = this.blockTurnAction(atom('dataflow'));
|
|
|
|
mandatoryIfNotTerminal(o: GenericEventEndpointStatement, p: Pattern<any>): Pattern<any> {
|
|
return i => {
|
|
return (o.facetToStop !== 'none') ? option(p)(i) : p(i);
|
|
};
|
|
}
|
|
|
|
// Principal: Turn
|
|
readonly eventHandlerEndpointStatement: Pattern<EventHandlerEndpointStatement> =
|
|
this.turnAction(o => {
|
|
o.facetToStop = 'none';
|
|
o.once = false;
|
|
o.isDynamic = true;
|
|
o.body = [];
|
|
return seq(alt(seq(option(seq(atom('stop'),
|
|
map(option(this.expr1(atom('on'))), es => {
|
|
o.facetToStop = es.length ? es[0] : 'default';
|
|
}))),
|
|
atom('on')),
|
|
map(atom('once'), _ => {
|
|
o.once = true;
|
|
o.facetToStop = 'once-wrapper';
|
|
})),
|
|
alt<any>(seq(map(group('(', bind(o as DataflowEndpointStatement, 'predicate',
|
|
this.expr())),
|
|
_ => o.triggerType = 'dataflow'),
|
|
this.mandatoryIfNotTerminal(o, this.statement(o.body))),
|
|
mapm(seq(bind(o, 'triggerType', atomString('stop')),
|
|
option(this.statement(o.body))),
|
|
v => ((o.facetToStop !== 'none') || o.once) ? fail : succeed(v)),
|
|
seq(bind(o, 'triggerType',
|
|
alt(atomString('asserted'),
|
|
atomString('retracted'),
|
|
atomString('message'))),
|
|
option(map(kw('snapshot'), _ => o.isDynamic = false)),
|
|
bind(o as AssertionEventEndpointStatement, 'pattern',
|
|
this.valuePattern(1, atom('=>'))),
|
|
this.mandatoryIfNotTerminal(
|
|
o, seq(atom('=>'), this.statement(o.body))))));
|
|
});
|
|
|
|
// Principal: none
|
|
readonly typeDefinitionStatement: Pattern<TypeDefinitionStatement> =
|
|
scope(o => seq(bind(o, 'expectedUse', alt(atomString('message'), atomString('assertion'))),
|
|
atom('type'),
|
|
bind(o, 'label', this.identifier),
|
|
group('(', bind(o, 'fields', repeat(this.binder, { separator: atom(',') }))),
|
|
option(seq(atom('='),
|
|
bind(o, 'wireName', withoutSpace(upTo(this.statementBoundary))))),
|
|
this.statementBoundary));
|
|
|
|
// Principal: Turn
|
|
readonly messageSendStatement: Pattern<MessageSendStatement> =
|
|
this.turnAction(o => seq(atom('send'),
|
|
atom('message'),
|
|
not(this.statementBoundary),
|
|
bind(o, 'expr', withoutSpace(upTo(this.statementBoundary))),
|
|
this.statementBoundary));
|
|
|
|
// Principal: Turn
|
|
readonly duringStatement: Pattern<DuringStatement> =
|
|
this.turnAction(o => {
|
|
o.body = [];
|
|
return seq(atom('during'),
|
|
bind(o, 'pattern',
|
|
this.valuePattern(1, atom('=>'), seq(atom('when'), group('(', discard)))),
|
|
option(seq(atom('when'), group('(', bind(o, 'test', this.expr())))),
|
|
seq(atom('=>'), this.statement(o.body)));
|
|
});
|
|
|
|
// Principal: Turn
|
|
readonly reactStatement: Pattern<ReactStatement> =
|
|
this.turnAction(o => {
|
|
o.label = null;
|
|
o.body = [];
|
|
return seq(option(map(seqTuple(this.identifier, atom(':')),
|
|
([i, _colon]) => o.label = i)),
|
|
atom('react'),
|
|
this.block(o.body));
|
|
});
|
|
|
|
// Principal: Turn
|
|
readonly stopStatement: Pattern<StopStatement> =
|
|
this.turnAction(o => {
|
|
o.facetToStop = 'default';
|
|
o.body = [];
|
|
return seq(atom('stop'),
|
|
option(map(this.expr1(), e => o.facetToStop = e)),
|
|
alt(this.block(o.body), this.statementBoundary));
|
|
});
|
|
|
|
// Principal: none
|
|
readonly atStatement: Pattern<AtStatement> =
|
|
scope(o => {
|
|
o.body = [];
|
|
return seq(atom('at'),
|
|
bind(o, 'target', this.expr()),
|
|
this.block(o.body));
|
|
});
|
|
|
|
// Principal: none
|
|
readonly createExpression: Pattern<CreateExpression> =
|
|
scope(o => seq(atom('create'), bind(o, 'entity', this.expr())));
|
|
|
|
//---------------------------------------------------------------------------
|
|
// Syntax of patterns over Value, used in endpoints
|
|
|
|
// $id - capture of discard
|
|
// _ - discard
|
|
//
|
|
// expr(pat, ...) - record ctor
|
|
// $id(pat) - nested capture
|
|
// [pat, ...] - array pat
|
|
// {expr: pat, ...} - dict pat
|
|
//
|
|
// expr(expr, ...) - constant
|
|
// [expr, ...] - constant
|
|
// {expr: expr, ...} - constant
|
|
// other - constant
|
|
|
|
readonly pCaptureBinder: Pattern<Binder> =
|
|
mapm(this.binder, i => {
|
|
return i.id.text.startsWith('$')
|
|
? succeed({ id: { ... i.id, text: i.id.text.slice(1) }, type: i.type })
|
|
: fail;
|
|
});
|
|
|
|
readonly pDiscard: Pattern<void> =
|
|
mapm(this.identifier, i => i.text === '_' ? succeed(void 0) : fail);
|
|
|
|
pUnquote(level: number, extraStops: Pattern<any>[]): Pattern<PUnquote | POuterUnquote> {
|
|
if (level == 1) {
|
|
return scope<POuterUnquote>(o => {
|
|
o.type = 'POuterUnquote';
|
|
return seq(atom('\\'), bind(o, 'outer', this.expr(... extraStops)));
|
|
});
|
|
} else {
|
|
return scope<PUnquote>(o => {
|
|
o.type = 'PUnquote';
|
|
return seq(atom('\\'), bind(o, 'unquoted', this.valuePattern(level - 1, ... extraStops)));
|
|
});
|
|
}
|
|
}
|
|
|
|
pQuote(level: number, extraStops: Pattern<any>[]): Pattern<PQuote> {
|
|
return scope(o => {
|
|
o.type = 'PQuote';
|
|
return seq(kw('pattern'), bind(o, 'quoted', this.valuePattern(level + 1, ... extraStops)));
|
|
});
|
|
}
|
|
|
|
hasCapturesOrDiscards(e: Expr): boolean {
|
|
return foldItems(e,
|
|
t => match(alt<any>(this.pCaptureBinder, this.pDiscard), [t], null, '(') !== null,
|
|
(_g, b, _k) => b,
|
|
bs => bs.some(b => b));
|
|
}
|
|
|
|
pArray(level: number): Pattern<PArray> {
|
|
return scope(o => {
|
|
o.type = 'PArray';
|
|
return group(
|
|
'[', mapm(bind(o, 'elements',
|
|
separatedOrTerminatedBy(this.valuePattern(level), atom(','))),
|
|
v => (o.elements.every(p => p.type === 'PConstant') ? fail : succeed(v))));
|
|
});
|
|
}
|
|
|
|
pDict(level: number): Pattern<PDict> {
|
|
return scope(o => {
|
|
o.type = 'PDict';
|
|
return group(
|
|
'{', mapm(bind(o,
|
|
'elements',
|
|
separatedOrTerminatedBy(
|
|
value<[Expr, ValuePattern]>(e => {
|
|
e.value = [] as any;
|
|
return seq(
|
|
bind(e.value, '0', this.propertyNameExpr()),
|
|
atom(':'),
|
|
bind(e.value, '1', this.valuePattern(level)));
|
|
}),
|
|
atom(','))),
|
|
v => (o.elements.every(e => e[1].type === 'PConstant') ? fail : succeed(v))));
|
|
});
|
|
}
|
|
|
|
pConstructor(level: number, extraStops: Pattern<any>[]): Pattern<PConstructor> {
|
|
return scope(o => {
|
|
o.type = 'PConstructor';
|
|
return seq(bind(o, 'ctor', this.expr(... extraStops, group('(', discard))),
|
|
// map(anything({ advance: false }), g => o.argumentGroup = g),
|
|
group('(', bind(o, 'arguments',
|
|
separatedOrTerminatedBy(
|
|
this.valuePattern(level, ... extraStops),
|
|
atom(',')))));
|
|
});
|
|
}
|
|
|
|
valuePattern(level: number, ... extraStops: Pattern<any>[]): Pattern<ValuePattern> {
|
|
return withoutSpace(alt<ValuePattern>(
|
|
scope(o => map(this.pDiscard, _ => o.type = 'PDiscard')),
|
|
this.pArray(level),
|
|
this.pDict(level),
|
|
this.pQuote(level, extraStops),
|
|
this.pUnquote(level, extraStops),
|
|
mapm<PConstructor, PConstant | PCapture | PConstructor>(
|
|
this.pConstructor(level, extraStops),
|
|
o => {
|
|
// if (o.arguments.every(a => a.type === 'PConstant')) {
|
|
// return succeed({
|
|
// type: 'PConstant',
|
|
// value: [... o.ctor, o.argumentGroup],
|
|
// });
|
|
// } else
|
|
if (this.hasCapturesOrDiscards(o.ctor)) {
|
|
const r = match(this.pCaptureBinder, o.ctor, null, '(');
|
|
if (r !== null && o.arguments.length === 1) {
|
|
return succeed({
|
|
type: 'PCapture',
|
|
inner: o.arguments[0],
|
|
binder: r
|
|
});
|
|
} else {
|
|
return fail;
|
|
}
|
|
} else {
|
|
return succeed(o);
|
|
}
|
|
}),
|
|
scope<PCapture>(o => {
|
|
o.type = 'PCapture';
|
|
o.inner = { type: 'PDiscard' };
|
|
return bind(o, 'binder', this.pCaptureBinder);
|
|
}),
|
|
map(this.expr(... extraStops), e => ({ type: 'PConstant', value: e }))
|
|
));
|
|
}
|
|
}
|
|
|
|
export class SyndicateTypedParser extends SyndicateParser {
|
|
get binder(): Pattern<Binder> {
|
|
return scope(o => seq(bind(o, 'id', this.identifier),
|
|
option(seq(atom(':'),
|
|
bind(o, 'type', this.type(atom('=')))))));
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
// Value pattern utilities
|
|
|
|
export function compilePattern(pattern: ValuePattern): StaticAnalysis {
|
|
const captureBinders: Binder[] = [];
|
|
|
|
function walk(pattern: ValuePattern): Expr {
|
|
switch (pattern.type) {
|
|
case 'PDiscard':
|
|
return template`(__SYNDICATE__.QuasiValue._)`;
|
|
case 'PCapture': {
|
|
captureBinders.push(pattern.binder);
|
|
return template`(__SYNDICATE__.QuasiValue.bind(${walk(pattern.inner)}))`;
|
|
}
|
|
case 'PConstant':
|
|
return template`(__SYNDICATE__.QuasiValue.lit(__SYNDICATE__.fromJS(${pattern.value})))`;
|
|
case 'PConstructor': {
|
|
const pieces = [template`${pattern.ctor}`, ... pattern.arguments.map(walk)];
|
|
return template`(__SYNDICATE__.QuasiValue.ctor(${commaJoin(pieces)}))`;
|
|
}
|
|
case 'PArray': {
|
|
const pieces = pattern.elements.map(walk);
|
|
return template`(__SYNDICATE__.QuasiValue.arr(${commaJoin(pieces)}))`;
|
|
}
|
|
case 'PDict': {
|
|
const pieces = pattern.elements.map(([k, v]) => template`[${k}, ${walk(v)}]`);
|
|
return template`(__SYNDICATE__.QuasiValue.dict(${commaJoin(pieces)}))`;
|
|
}
|
|
case 'PQuote':
|
|
return template`(__SYNDICATE__.QuasiValue.quote(${walk(pattern.quoted)}))`;
|
|
case 'PUnquote':
|
|
return template`(__SYNDICATE__.QuasiValue.unquote(${walk(pattern.unquoted)}))`;
|
|
case 'POuterUnquote':
|
|
return template`(__SYNDICATE__.QuasiValue.unquote(${pattern.outer}))`;
|
|
}
|
|
}
|
|
|
|
const skeleton = walk(pattern);
|
|
|
|
return {
|
|
skeleton,
|
|
captureBinders,
|
|
};
|
|
}
|