/// SPDX-License-Identifier: GPL-3.0-or-later /// SPDX-FileCopyrightText: Copyright © 2016-2023 Tony Garnock-Jones 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, separatedOrTerminatedBy, not, TokenBase } 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; 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 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; test?: Expr, } 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 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 { return value(o => seq(atom(':'), bind(o, 'value', atom(text, { skipSpace: false })))); } export class SyndicateParser { block(acc?: Items): Pattern { return group('{', map(rest, items => (acc?.push(... items), items))); } readonly statementBoundary = alt(atom(';'), Matcher.newline); readonly exprBoundary = alt(atom(';'), atom(','), group('{', discard), Matcher.end); readonly identifier: Pattern = atom(); get binder(): Pattern { return scope(o => bind(o, 'id', this.identifier)); } expr(... extraStops: Pattern[]): Pattern { return withoutSpace(upTo(alt(this.exprBoundary, ... extraStops))); } readonly type: (... extraStops: Pattern[]) => Pattern = this.expr; statement(acc: Items): Pattern { return alt(this.block(acc), withoutSpace(seq(map(upTo(this.statementBoundary), items => acc.push(... items)), map(this.statementBoundary, i => i ? acc.push(i) : void 0)))); } turnAction(pattern: (scope: T) => Pattern): Pattern { 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 = 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 = 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 = 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): Pattern { 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): Pattern { return i => { return (o.terminal) ? option(p)(i) : p(i); }; } // Principal: Turn readonly eventHandlerEndpointStatement: Pattern = this.turnAction(o => { o.terminal = false; o.isDynamic = true; o.body = []; return seq(option(map(atom('stop'), _ => o.terminal = true)), atom('on'), alt(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(1, atom('=>'))), this.mandatoryIfNotTerminal( o, seq(atom('=>'), this.statement(o.body)))))); }); // Principal: none readonly typeDefinitionStatement: Pattern = 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 = 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 = 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 = 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 = scope(o => { o.body = []; return seq(atom('at'), bind(o, 'target', this.expr()), this.block(o.body)); }); // Principal: none readonly createExpression: Pattern = 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 = 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 = mapm(this.identifier, i => i.text === '_' ? succeed(void 0) : fail); pUnquote(level: number, extraStops: Pattern[]): Pattern { if (level == 1) { return scope(o => { o.type = 'POuterUnquote'; return seq(atom('\\'), bind(o, 'outer', this.expr(... extraStops))); }); } else { return scope(o => { o.type = 'PUnquote'; return seq(atom('\\'), bind(o, 'unquoted', this.valuePattern(level - 1, ... extraStops))); }); } } pQuote(level: number, extraStops: Pattern[]): Pattern { 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(this.pCaptureBinder, this.pDiscard), [t], null) !== null, (_g, b, _k) => b, bs => bs.some(b => b)); } pArray(level: number): Pattern { 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 { 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.expr(atom(':'))), 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[]): Pattern { 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[]): Pattern { return withoutSpace(alt( scope(o => { o.type = 'PCapture'; o.inner = { type: 'PDiscard' }; return bind(o, 'binder', this.pCaptureBinder); }), scope(o => map(this.pDiscard, _ => o.type = 'PDiscard')), this.pArray(level), this.pDict(level), this.pQuote(level, extraStops), this.pUnquote(level, extraStops), mapm( 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); } }), map(this.expr(... extraStops), e => ({ type: 'PConstant', value: e })) )); } } export class SyndicateTypedParser extends SyndicateParser { get binder(): Pattern { 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, }; }