import { TokenType, Token, Items, Pattern, foldItems, match, anonymousTemplate as template, commaJoin, startPos, scope, bind, seq, alt, upTo, atom, atomString, group, exec, repeat, option, withoutSpace, map, mapm, rest, discard, value, succeed, fail, separatedBy, anything, not, } from '../syntax/index.js'; import * as Matcher from '../syntax/matcher.js'; import { Path, Skeleton } from './internals.js'; export type Expr = Items; export type Statement = Items; export type Identifier = Token; export const block = (acc: Items) => group('{', map(rest, items => acc.push(... items))); export const statementBoundary = alt(atom(';'), Matcher.newline); export const exprBoundary = alt(atom(';'), atom(','), group('{', discard), Matcher.end); export const identifier: Pattern = atom(); export function expr(... extraStops: Pattern[]): Pattern { return withoutSpace(upTo(alt(exprBoundary, ... extraStops))); } export function statement(acc: Items): Pattern { return alt(block(acc), withoutSpace(seq(map(upTo(statementBoundary), items => acc.push(... items)), map(statementBoundary, i => i ? acc.push(i) : void 0)))); } export interface FacetAction { implicitFacet: boolean; } export function facetAction( pattern: (scope: T) => Pattern): Pattern { 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 & { headerExpr: Pattern } = Object.assign(facetAction((o: SpawnStatement) => { o.isDataspace = false; o.initialAssertions = []; o.parentIds = []; o.parentInits = []; o.bootProcBody = []; return seq(atom('spawn'), option(seq(atom('dataspace'), exec(() => o.isDataspace = true))), option(seq(atom('named'), bind(o, 'name', spawn.headerExpr))), repeat(alt(seq(atom(':asserting'), map(spawn.headerExpr, e => o.initialAssertions.push(e))), map(scope((l: { id: Identifier, init: Expr }) => seq(atom(':let'), bind(l, 'id', identifier), atom('='), bind(l, 'init', spawn.headerExpr))), l => { o.parentIds.push(l.id); o.parentInits.push(l.init); }))), block(o.bootProcBody)); }), { headerExpr: expr(atom(':asserting'), atom(':let')), }); export interface FieldDeclarationStatement extends FacetAction { target: Expr; property: { name: Identifier } | { expr: Expr }; init?: Expr; } // Principal: Dataspace, but only for implementation reasons, so really Facet export const fieldDeclarationStatement: Pattern = facetAction(o => { const prop = alt(seq(atom('.'), map(identifier, name => o.property = {name})), seq(group('[', map(expr(), expr => o.property = {expr})))); return seq(atom('field'), bind(o, 'target', expr(seq(prop, alt(atom('='), statementBoundary)))), prop, option(seq(atom('='), bind(o, 'init', expr()))), statementBoundary); }); export interface AssertionEndpointStatement extends FacetAction { isDynamic: boolean, template: Expr, test?: Expr, } // Principal: Facet export const assertionEndpointStatement: Pattern = facetAction(o => { o.isDynamic = true; return seq(atom('assert'), option(map(atom(':snapshot'), _ => o.isDynamic = false)), bind(o, 'template', expr(seq(atom('when'), group('(', discard)))), option(seq(atom('when'), group('(', bind(o, 'test', expr())))), statementBoundary); }); export interface StatementFacetAction extends FacetAction { body: Statement; } export function blockFacetAction(kw: Pattern): Pattern { return facetAction(o => { o.body = []; return seq(kw, block(o.body)); }); } // Principal: Facet export const dataflowStatement = blockFacetAction(atom('dataflow')); export interface GenericEventEndpointStatement extends StatementFacetAction { terminal: boolean; isDynamic: boolean; } 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; export function mandatoryIfNotTerminal(o: GenericEventEndpointStatement, p: Pattern): Pattern { return i => { return (o.terminal) ? option(p)(i) : p(i); }; } // Principal: Facet export const eventHandlerEndpointStatement: Pattern = facetAction(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', expr())), _ => o.triggerType = 'dataflow'), mandatoryIfNotTerminal(o, statement(o.body))), mapm(seq(bind(o, 'triggerType', alt(atomString('start'), atomString('stop'))), option(statement(o.body))), v => o.terminal ? fail : succeed(v)), seq(bind(o, 'triggerType', alt(atomString('asserted'), atomString('retracted'), atomString('message'))), option(map(atom(':snapshot'), _ => o.isDynamic = false)), bind(o as AssertionEventEndpointStatement, 'pattern', valuePattern(atom('=>'))), mandatoryIfNotTerminal(o, seq(atom('=>'), statement(o.body)))))); }); export interface TypeDefinitionStatement { expectedUse: 'message' | 'assertion'; label: Identifier; fields: Identifier[]; wireName?: Expr; } // Principal: none export const typeDefinitionStatement: Pattern = scope(o => seq(bind(o, 'expectedUse', alt(atomString('message'), atomString('assertion'))), atom('type'), bind(o, 'label', identifier), group('(', bind(o, 'fields', repeat(identifier, { separator: atom(',') }))), option(seq(atom('='), bind(o, 'wireName', withoutSpace(upTo(statementBoundary))))), statementBoundary)); export interface MessageSendStatement extends FacetAction { expr: Expr; } // Principal: Facet export const messageSendStatement: Pattern = facetAction(o => seq(atom('send'), atom('message'), not(statementBoundary), bind(o, 'expr', withoutSpace(upTo(statementBoundary))), statementBoundary)); export interface DuringStatement extends FacetAction { pattern: ValuePattern; body: Statement; } // Principal: Facet export const duringStatement: Pattern = facetAction(o => { o.body = []; return seq(atom('during'), bind(o, 'pattern', valuePattern(atom('=>'))), seq(atom('=>'), statement(o.body))); }); // Principal: Facet export const reactStatement = blockFacetAction(atom('react')); // Principal: none export const bootStatement: Pattern = value(o => { o.value = []; return seq(atom('boot'), block(o.value)); }); // Principal: Facet export const stopStatement = blockFacetAction(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 = mapm(identifier, i => i.text.startsWith('$') ? succeed({ ... i, text: i.text.slice(1) }) : fail); const pDiscard: Pattern = mapm(identifier, i => i.text === '_' ? succeed(void 0) : fail); function hasCapturesOrDiscards(e: Expr): boolean { return foldItems(e, t => match(alt(pCaptureId, pDiscard), [t], null) !== null, (_g, 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[]): Pattern { 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[]): Pattern { return alt( scope(o => { o.type = 'PCapture'; o.inner = { type: 'PDiscard' }; return bind(o, 'binder', pCaptureId); }), scope(o => map(pDiscard, _ => o.type = 'PDiscard')), mapm( 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] { 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 = { 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 = { 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 { if (skel === null) { return template`null`; } else { return template`({shape:${skel.shape}, members: [${commaJoin(skel.members.map(renderSkeleton))}]})`; } }