Pattern (quasi)quotation

This commit is contained in:
Tony Garnock-Jones 2021-12-09 18:55:18 +01:00
parent d1c79973c5
commit f81cf11ebd
4 changed files with 136 additions and 73 deletions

View File

@ -138,7 +138,7 @@ export function expand(tree: Items, ctx: ExpansionContext): Items {
return t`assertDataflow(() => ({ return t`assertDataflow(() => ({
target: currentSyndicateTarget, target: currentSyndicateTarget,
assertion: __SYNDICATE__.fromObserve(__SYNDICATE__.Observe({ assertion: __SYNDICATE__.fromObserve(__SYNDICATE__.Observe({
pattern: ${sa.skeleton}, pattern: __SYNDICATE__.QuasiValue.finish(${sa.skeleton}),
observer: __SYNDICATE__.Turn.ref(__SYNDICATE__.assertionFacetObserver( observer: __SYNDICATE__.Turn.ref(__SYNDICATE__.assertionFacetObserver(
(${ctx.argDecl(t, '__vs', '__SYNDICATE__.AnyValue')}) => { (${ctx.argDecl(t, '__vs', '__SYNDICATE__.AnyValue')}) => {
if (Array.isArray(__vs)) { if (Array.isArray(__vs)) {
@ -236,7 +236,7 @@ ${joinItems(sa.captureBinders.map(binderTypeGuard(ctx, t)), '\n')}
} }
const assertion = t`__SYNDICATE__.fromObserve(__SYNDICATE__.Observe({ const assertion = t`__SYNDICATE__.fromObserve(__SYNDICATE__.Observe({
pattern: ${sa.skeleton}, pattern: __SYNDICATE__.QuasiValue.finish(${sa.skeleton}),
observer: __SYNDICATE__.Turn.ref(${entity}), observer: __SYNDICATE__.Turn.ref(${entity}),
}))`; }))`;

View File

@ -2,13 +2,13 @@
/// SPDX-FileCopyrightText: Copyright © 2016-2021 Tony Garnock-Jones <tonyg@leastfixedpoint.com> /// SPDX-FileCopyrightText: Copyright © 2016-2021 Tony Garnock-Jones <tonyg@leastfixedpoint.com>
import { import {
Token, Items, Token, Item, Items,
Pattern, Pattern,
foldItems, match, anonymousTemplate as template, commaJoin, foldItems, match, anonymousTemplate as template, commaJoin,
scope, bind, seq, alt, upTo, atom, atomString, group, scope, bind, seq, alt, upTo, atom, atomString, group,
repeat, option, withoutSpace, map, mapm, rest, discard, repeat, option, withoutSpace, map, mapm, rest, discard,
value, succeed, fail, separatedBy, anything, not value, succeed, fail, separatedOrTerminatedBy, anything, not
} from '../syntax/index.js'; } from '../syntax/index.js';
import * as Matcher from '../syntax/matcher.js'; import * as Matcher from '../syntax/matcher.js';
@ -117,8 +117,8 @@ export interface PConstant {
value: Expr, value: Expr,
} }
export interface PRecord { export interface PConstructor {
type: 'PRecord', type: 'PConstructor',
ctor: Expr, ctor: Expr,
arguments: ValuePattern[], arguments: ValuePattern[],
} }
@ -133,14 +133,28 @@ export interface PDict {
elements: [Expr, ValuePattern][], elements: [Expr, ValuePattern][],
} }
export type ValuePattern = PCapture | PDiscard | PRecord | PConstant | PArray | PDict; export interface PUnquote {
type: 'PUnquote',
interface RawCall { unquoted: ValuePattern,
items: Items;
callee: Expr;
arguments: Expr[];
} }
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 { export interface StaticAnalysis {
skeleton: Expr; // constructs a P.Pattern skeleton: Expr; // constructs a P.Pattern
captureBinders: Binder[]; captureBinders: Binder[];
@ -272,7 +286,7 @@ export class SyndicateParser {
atomString('message'))), atomString('message'))),
option(map(kw('snapshot'), _ => o.isDynamic = false)), option(map(kw('snapshot'), _ => o.isDynamic = false)),
bind(o as AssertionEventEndpointStatement, 'pattern', bind(o as AssertionEventEndpointStatement, 'pattern',
this.valuePattern(atom('=>'))), this.valuePattern(1, atom('=>'))),
this.mandatoryIfNotTerminal( this.mandatoryIfNotTerminal(
o, seq(atom('=>'), this.statement(o.body)))))); o, seq(atom('=>'), this.statement(o.body))))));
}); });
@ -300,7 +314,7 @@ export class SyndicateParser {
this.turnAction(o => { this.turnAction(o => {
o.body = []; o.body = [];
return seq(atom('during'), return seq(atom('during'),
bind(o, 'pattern', this.valuePattern(atom('=>'))), bind(o, 'pattern', this.valuePattern(1, atom('=>'))),
seq(atom('=>'), this.statement(o.body))); seq(atom('=>'), this.statement(o.body)));
}); });
@ -353,6 +367,27 @@ export class SyndicateParser {
readonly pDiscard: Pattern<void> = readonly pDiscard: Pattern<void> =
mapm(this.identifier, i => i.text === '_' ? succeed(void 0) : fail); 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 { hasCapturesOrDiscards(e: Expr): boolean {
return foldItems(e, return foldItems(e,
t => match(alt<any>(this.pCaptureBinder, this.pDiscard), [t], null) !== null, t => match(alt<any>(this.pCaptureBinder, this.pDiscard), [t], null) !== null,
@ -360,86 +395,85 @@ export class SyndicateParser {
bs => bs.some(b => b)); bs => bs.some(b => b));
} }
pArray: Pattern<PArray> = pArray(level: number): Pattern<PArray> {
scope(o => { return scope(o => {
o.type = 'PArray'; o.type = 'PArray';
return group( return group(
'[', mapm(bind(o, 'elements', separatedBy(this.valuePattern(), atom(','))), '[', mapm(bind(o, 'elements',
separatedOrTerminatedBy(this.valuePattern(level), atom(','))),
v => (o.elements.every(p => p.type === 'PConstant') ? fail : succeed(v)))); v => (o.elements.every(p => p.type === 'PConstant') ? fail : succeed(v))));
}); });
}
pDict: Pattern<PDict> = pDict(level: number): Pattern<PDict> {
scope(o => { return scope(o => {
o.type = 'PDict'; o.type = 'PDict';
return group( return group(
'{', mapm(bind(o, '{', mapm(bind(o,
'elements', 'elements',
separatedBy( separatedOrTerminatedBy(
scope<[Expr, ValuePattern]>(e => value<[Expr, ValuePattern]>(e => {
seq(bind(e, '0', this.expr(atom(':'))), e.value = [] as any;
return seq(
bind(e.value, '0', this.expr(atom(':'))),
atom(':'), atom(':'),
bind(e, '1', this.valuePattern()))), bind(e.value, '1', this.valuePattern(level)));
}),
atom(','))), atom(','))),
v => (o.elements.every(e => e[1].type === 'PConstant') ? fail : succeed(v)))); 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 { pConstructor(level: number, extraStops: Pattern<any>[]): Pattern<PConstructor> {
return (!(this.hasCapturesOrDiscards(o.callee) || return scope(o => {
o.arguments.some(a => this.hasCapturesOrDiscards(a)))); 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(... extraStops: Pattern<any>[]): Pattern<ValuePattern> { valuePattern(level: number, ... extraStops: Pattern<any>[]): Pattern<ValuePattern> {
return alt<ValuePattern>( return withoutSpace(alt<ValuePattern>(
scope<PCapture>(o => { scope<PCapture>(o => {
o.type = 'PCapture'; o.type = 'PCapture';
o.inner = { type: 'PDiscard' }; o.inner = { type: 'PDiscard' };
return bind(o, 'binder', this.pCaptureBinder); return bind(o, 'binder', this.pCaptureBinder);
}), }),
scope(o => map(this.pDiscard, _ => o.type = 'PDiscard')), scope(o => map(this.pDiscard, _ => o.type = 'PDiscard')),
this.pArray, this.pArray(level),
this.pDict, this.pDict(level),
mapm<RawCall, ValuePattern>( this.pQuote(level, extraStops),
this.pRawCall(... extraStops), this.pUnquote(level, extraStops),
mapm<PConstructor, PConstant | PCapture | PConstructor>(
this.pConstructor(level, extraStops),
o => { o => {
if (this.isConstant(o)) { // if (o.arguments.every(a => a.type === 'PConstant')) {
return succeed({ type: 'PConstant', value: o.items }); // return succeed({
} else if (this.hasCapturesOrDiscards(o.callee)) { // type: 'PConstant',
const r = match(this.pCaptureBinder, o.callee, null); // value: [... o.ctor, o.argumentGroup],
if (r !== null && o.arguments.length === 1) // });
{ // } else
const argPat = match(this.valuePattern(), o.arguments[0], null); if (this.hasCapturesOrDiscards(o.ctor)) {
if (argPat === null) return fail; const r = match(this.pCaptureBinder, o.ctor, null);
if (r !== null && o.arguments.length === 1) {
return succeed({ return succeed({
type: 'PCapture', type: 'PCapture',
inner: argPat, inner: o.arguments[0],
binder: r binder: r
}); });
} else { } else {
return fail; return fail;
} }
} else { } else {
const argPats = o.arguments.map(a => match(this.valuePattern(), a, null)); return succeed(o);
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 })) map(this.expr(... extraStops), e => ({ type: 'PConstant', value: e }))
); ));
} }
} }
@ -454,36 +488,37 @@ export class SyndicateTypedParser extends SyndicateParser {
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
// Value pattern utilities // 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 { export function compilePattern(pattern: ValuePattern): StaticAnalysis {
const captureBinders: Binder[] = []; const captureBinders: Binder[] = [];
function walk(pattern: ValuePattern): Expr { function walk(pattern: ValuePattern): Expr {
switch (pattern.type) { switch (pattern.type) {
case 'PDiscard': case 'PDiscard':
return eDiscard; return template`(__SYNDICATE__.QuasiValue._)`;
case 'PCapture': { case 'PCapture': {
captureBinders.push(pattern.binder); captureBinders.push(pattern.binder);
return eBind(walk(pattern.inner)); return template`(__SYNDICATE__.QuasiValue.bind(${walk(pattern.inner)}))`;
} }
case 'PConstant': case 'PConstant':
return eLit(pattern.value); return template`(__SYNDICATE__.QuasiValue.lit(${pattern.value}))`;
case 'PRecord': { case 'PConstructor': {
const pieces = [template`(${pattern.ctor}).constructorInfo.label`, const pieces = [template`${pattern.ctor}`, ... pattern.arguments.map(walk)];
... pattern.arguments.map(walk)]; return template`(__SYNDICATE__.QuasiValue.ctor(${commaJoin(pieces)}))`;
return template`(__SYNDICATE__.Pattern.rec(${commaJoin(pieces)}))`;
} }
case 'PArray': { case 'PArray': {
const pieces = pattern.elements.map(walk); const pieces = pattern.elements.map(walk);
return template`(__SYNDICATE__.Pattern.arr(${commaJoin(pieces)}))`; return template`(__SYNDICATE__.QuasiValue.arr(${commaJoin(pieces)}))`;
} }
case 'PDict': { case 'PDict': {
const pieces = pattern.elements.map(([k, v]) => template`[${k}, ${walk(v)}]`); const pieces = pattern.elements.map(([k, v]) => template`[${k}, ${walk(v)}]`);
return template`(__SYNDICATE__.Pattern.dict(${commaJoin(pieces)}))`; 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}))`;
} }
} }

View File

@ -223,6 +223,34 @@ export function separatedBy<T>(itemPattern: Pattern<T>, separator: Pattern<any>)
}; };
} }
export function separatedOrTerminatedBy<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;
} else {
i = r[1];
if (end(i) !== null) return [acc, noItems];
}
}
}
};
}
export interface RepeatOptions { export interface RepeatOptions {
min?: number; min?: number;
max?: number; max?: number;

View File

@ -9,7 +9,7 @@ export abstract class Scanner implements IterableIterator<Token> {
readonly synthetic: boolean | undefined; readonly synthetic: boolean | undefined;
charBuffer: string | null = null; charBuffer: string | null = null;
tokenBuffer: Token | null = null; tokenBuffer: Token | null = null;
delimiters = ' \t\n\r\'"`.,;()[]{}/'; delimiters = ' \t\n\r\'"`.,;()[]{}/\\';
constructor(pos: Pos, synthetic?: boolean) { constructor(pos: Pos, synthetic?: boolean) {
this.pos = { ... pos }; this.pos = { ... pos };