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(() => ({
target: currentSyndicateTarget,
assertion: __SYNDICATE__.fromObserve(__SYNDICATE__.Observe({
pattern: ${sa.skeleton},
pattern: __SYNDICATE__.QuasiValue.finish(${sa.skeleton}),
observer: __SYNDICATE__.Turn.ref(__SYNDICATE__.assertionFacetObserver(
(${ctx.argDecl(t, '__vs', '__SYNDICATE__.AnyValue')}) => {
if (Array.isArray(__vs)) {
@ -236,7 +236,7 @@ ${joinItems(sa.captureBinders.map(binderTypeGuard(ctx, t)), '\n')}
}
const assertion = t`__SYNDICATE__.fromObserve(__SYNDICATE__.Observe({
pattern: ${sa.skeleton},
pattern: __SYNDICATE__.QuasiValue.finish(${sa.skeleton}),
observer: __SYNDICATE__.Turn.ref(${entity}),
}))`;

View File

@ -2,13 +2,13 @@
/// SPDX-FileCopyrightText: Copyright © 2016-2021 Tony Garnock-Jones <tonyg@leastfixedpoint.com>
import {
Token, Items,
Token, Item, 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
value, succeed, fail, separatedOrTerminatedBy, anything, not
} from '../syntax/index.js';
import * as Matcher from '../syntax/matcher.js';
@ -117,8 +117,8 @@ export interface PConstant {
value: Expr,
}
export interface PRecord {
type: 'PRecord',
export interface PConstructor {
type: 'PConstructor',
ctor: Expr,
arguments: ValuePattern[],
}
@ -133,14 +133,28 @@ export interface PDict {
elements: [Expr, ValuePattern][],
}
export type ValuePattern = PCapture | PDiscard | PRecord | PConstant | PArray | PDict;
interface RawCall {
items: Items;
callee: Expr;
arguments: Expr[];
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[];
@ -272,7 +286,7 @@ export class SyndicateParser {
atomString('message'))),
option(map(kw('snapshot'), _ => o.isDynamic = false)),
bind(o as AssertionEventEndpointStatement, 'pattern',
this.valuePattern(atom('=>'))),
this.valuePattern(1, atom('=>'))),
this.mandatoryIfNotTerminal(
o, seq(atom('=>'), this.statement(o.body))))));
});
@ -300,7 +314,7 @@ export class SyndicateParser {
this.turnAction(o => {
o.body = [];
return seq(atom('during'),
bind(o, 'pattern', this.valuePattern(atom('=>'))),
bind(o, 'pattern', this.valuePattern(1, atom('=>'))),
seq(atom('=>'), this.statement(o.body)));
});
@ -353,6 +367,27 @@ export class SyndicateParser {
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,
@ -360,86 +395,85 @@ export class SyndicateParser {
bs => bs.some(b => b));
}
pArray: Pattern<PArray> =
scope(o => {
pArray(level: number): Pattern<PArray> {
return scope(o => {
o.type = 'PArray';
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))));
});
}
pDict: Pattern<PDict> =
scope(o => {
pDict(level: number): Pattern<PDict> {
return scope(o => {
o.type = 'PDict';
return group(
'{', mapm(bind(o,
'elements',
separatedBy(
scope<[Expr, ValuePattern]>(e =>
seq(bind(e, '0', this.expr(atom(':'))),
separatedOrTerminatedBy(
value<[Expr, ValuePattern]>(e => {
e.value = [] as any;
return seq(
bind(e.value, '0', this.expr(atom(':'))),
atom(':'),
bind(e, '1', this.valuePattern()))),
bind(e.value, '1', this.valuePattern(level)));
}),
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))));
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(... extraStops: Pattern<any>[]): Pattern<ValuePattern> {
return alt<ValuePattern>(
valuePattern(level: number, ... extraStops: Pattern<any>[]): Pattern<ValuePattern> {
return withoutSpace(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),
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 (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;
// 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: argPat,
inner: o.arguments[0],
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[]
});
return succeed(o);
}
}),
map(this.expr(... extraStops), e => ({ type: 'PConstant', value: e }))
);
));
}
}
@ -454,36 +488,37 @@ export class SyndicateTypedParser extends SyndicateParser {
//---------------------------------------------------------------------------
// 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;
return template`(__SYNDICATE__.QuasiValue._)`;
case 'PCapture': {
captureBinders.push(pattern.binder);
return eBind(walk(pattern.inner));
return template`(__SYNDICATE__.QuasiValue.bind(${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)}))`;
return template`(__SYNDICATE__.QuasiValue.lit(${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__.Pattern.arr(${commaJoin(pieces)}))`;
return template`(__SYNDICATE__.QuasiValue.arr(${commaJoin(pieces)}))`;
}
case 'PDict': {
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 {
min?: number;
max?: number;

View File

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