syndicate-js/packages/compiler/src/compiler/grammar.ts

497 lines
18 KiB
TypeScript

/// SPDX-License-Identifier: GPL-3.0-or-later
/// SPDX-FileCopyrightText: Copyright © 2016-2021 Tony Garnock-Jones <tonyg@leastfixedpoint.com>
import {
Token, Items,
Pattern,
foldItems, match, anonymousTemplate as template, commaJoin,
scope, bind, seq, alt, upTo, atom, atomString, group,
repeat, option, withoutSpace, map, mapm, rest, discard,
value, succeed, fail, separatedBy, anything, not
} from '../syntax/index.js';
import * as Matcher from '../syntax/matcher.js';
//---------------------------------------------------------------------------
// 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;
isLink: boolean;
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 interface GenericEventEndpointStatement extends StatementTurnAction {
terminal: 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;
}
export interface ReactStatement extends FacetSetupAction {
}
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 PRecord {
type: 'PRecord',
ctor: Expr,
arguments: ValuePattern[],
}
export interface PArray {
type: 'PArray',
elements: ValuePattern[],
}
export interface PDict {
type: 'PDict',
elements: [Expr, ValuePattern][],
}
export type ValuePattern = PCapture | PDiscard | PRecord | PConstant | PArray | PDict;
interface RawCall {
items: Items;
callee: Expr;
arguments: Expr[];
}
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);
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)));
}
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.isLink = false;
o.parentBinders = [];
o.parentInits = [];
o.body = [];
return seq(atom('spawn'),
option(map(atom('linked'), _ => o.isLink = true)),
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.terminal) ? option(p)(i) : p(i);
};
}
// Principal: Turn
readonly eventHandlerEndpointStatement: Pattern<EventHandlerEndpointStatement> =
this.turnAction(o => {
o.terminal = false;
o.isDynamic = true;
o.body = [];
return seq(option(map(atom('stop'), _ => o.terminal = true)),
atom('on'),
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.terminal ? 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(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(atom('=>'))),
seq(atom('=>'), this.statement(o.body)));
});
// Principal: Turn
readonly reactStatement: Pattern<ReactStatement> =
this.turnAction(o => {
o.body = [];
return seq(atom('react'), this.block(o.body));
});
// Principal: Turn
readonly stopStatement = this.blockTurnAction(atom('stop'));
// Principal: none
readonly atStatement: Pattern<AtStatement> =
scope(o => {
o.body = [];
return seq(atom('at'),
bind(o, 'target', this.expr()),
this.statement(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);
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: Pattern<PArray> =
scope(o => {
o.type = 'PArray';
return group(
'[', mapm(bind(o, 'elements', separatedBy(this.valuePattern(), atom(','))),
v => (o.elements.every(p => p.type === 'PConstant') ? fail : succeed(v))));
});
pDict: Pattern<PDict> =
scope(o => {
o.type = 'PDict';
return group(
'{', mapm(bind(o,
'elements',
separatedBy(
scope<[Expr, ValuePattern]>(e =>
seq(bind(e, '0', this.expr(atom(':'))),
atom(':'),
bind(e, '1', this.valuePattern()))),
atom(','))),
v => (o.elements.every(e => e[1].type === 'PConstant') ? fail : succeed(v))));
});
pRawCall(... extraStops: Pattern<any>[]): Pattern<RawCall> {
return scope((o: RawCall) =>
seq(bind(o, 'callee',
this.expr(seq(group('(', discard),
alt(this.exprBoundary, ... extraStops)))),
seq(map(anything({ advance: false }),
g => o.items = [... o.callee, g]),
group('(', bind(o, 'arguments',
separatedBy(this.expr(), atom(',')))))));
}
isConstant(o: RawCall): boolean {
return (!(this.hasCapturesOrDiscards(o.callee) ||
o.arguments.some(a => this.hasCapturesOrDiscards(a))));
}
valuePattern(... extraStops: Pattern<any>[]): Pattern<ValuePattern> {
return alt<ValuePattern>(
scope<PCapture>(o => {
o.type = 'PCapture';
o.inner = { type: 'PDiscard' };
return bind(o, 'binder', this.pCaptureBinder);
}),
scope(o => map(this.pDiscard, _ => o.type = 'PDiscard')),
this.pArray,
this.pDict,
mapm<RawCall, ValuePattern>(
this.pRawCall(... extraStops),
o => {
if (this.isConstant(o)) {
return succeed({ type: 'PConstant', value: o.items });
} else if (this.hasCapturesOrDiscards(o.callee)) {
const r = match(this.pCaptureBinder, o.callee, null);
if (r !== null && o.arguments.length === 1)
{
const argPat = match(this.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(this.valuePattern(), a, null));
if (argPats.some(p => p === null)) return fail;
return succeed({
type: 'PRecord',
ctor: o.callee,
arguments: argPats as ValuePattern[]
});
}
}),
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
const eDiscard: Expr = template`(__SYNDICATE__.Pattern._)`;
const eBind = (e: Expr): Expr => template`(__SYNDICATE__.Pattern.bind(${e}))`;
const eLit = (e : Expr): Expr => template`(__SYNDICATE__.Pattern.lit(${e}))`;
export function compilePattern(pattern: ValuePattern): StaticAnalysis {
const captureBinders: Binder[] = [];
function walk(pattern: ValuePattern): Expr {
switch (pattern.type) {
case 'PDiscard':
return eDiscard;
case 'PCapture': {
captureBinders.push(pattern.binder);
return eBind(walk(pattern.inner));
}
case 'PConstant':
return eLit(pattern.value);
case 'PRecord': {
const pieces = [template`(${pattern.ctor}).constructorInfo.label`,
... pattern.arguments.map(walk)];
return template`(__SYNDICATE__.Pattern.rec(${commaJoin(pieces)}))`;
}
case 'PArray': {
const pieces = pattern.elements.map(walk);
return template`(__SYNDICATE__.Pattern.arr(${commaJoin(pieces)}))`;
}
case 'PDict': {
const pieces = pattern.elements.map(([k, v]) => template`[${k}, ${walk(v)}]`);
return template`(__SYNDICATE__.Pattern.dict(${commaJoin(pieces)}))`;
}
}
}
const skeleton = walk(pattern);
return {
skeleton,
captureBinders,
};
}