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(() => ({
|
||||
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}),
|
||||
}))`;
|
||||
|
||||
|
|
|
@ -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}))`;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 };
|
||||
|
|
Loading…
Reference in New Issue