Pattern (quasi)quotation
This commit is contained in:
parent
d1c79973c5
commit
f81cf11ebd
|
@ -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}),
|
||||||
}))`;
|
}))`;
|
||||||
|
|
||||||
|
|
|
@ -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}))`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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 };
|
||||||
|
|
Loading…
Reference in New Issue