From f81cf11ebd3b18127dbd9d791d0331d33034fa91 Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Thu, 9 Dec 2021 18:55:18 +0100 Subject: [PATCH] Pattern (quasi)quotation --- packages/compiler/src/compiler/codegen.ts | 4 +- packages/compiler/src/compiler/grammar.ts | 175 +++++++++++++--------- packages/compiler/src/syntax/matcher.ts | 28 ++++ packages/compiler/src/syntax/scanner.ts | 2 +- 4 files changed, 136 insertions(+), 73 deletions(-) diff --git a/packages/compiler/src/compiler/codegen.ts b/packages/compiler/src/compiler/codegen.ts index 6545ea9..eefabec 100644 --- a/packages/compiler/src/compiler/codegen.ts +++ b/packages/compiler/src/compiler/codegen.ts @@ -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}), }))`; diff --git a/packages/compiler/src/compiler/grammar.ts b/packages/compiler/src/compiler/grammar.ts index 858c7c2..f3bb9cb 100644 --- a/packages/compiler/src/compiler/grammar.ts +++ b/packages/compiler/src/compiler/grammar.ts @@ -2,13 +2,13 @@ /// SPDX-FileCopyrightText: Copyright © 2016-2021 Tony Garnock-Jones 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 = mapm(this.identifier, i => i.text === '_' ? succeed(void 0) : fail); + pUnquote(level: number, extraStops: Pattern[]): Pattern { + if (level == 1) { + return scope(o => { + o.type = 'POuterUnquote'; + return seq(atom('\\'), bind(o, 'outer', this.expr(... extraStops))); + }); + } else { + return scope(o => { + o.type = 'PUnquote'; + return seq(atom('\\'), bind(o, 'unquoted', this.valuePattern(level - 1, ... extraStops))); + }); + } + } + + pQuote(level: number, extraStops: Pattern[]): Pattern { + 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(this.pCaptureBinder, this.pDiscard), [t], null) !== null, @@ -360,86 +395,85 @@ export class SyndicateParser { bs => bs.some(b => b)); } - pArray: Pattern = - scope(o => { + pArray(level: number): Pattern { + 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 = - scope(o => { + pDict(level: number): Pattern { + 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[]): Pattern { - 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[]): Pattern { + 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[]): Pattern { - return alt( + valuePattern(level: number, ... extraStops: Pattern[]): Pattern { + return withoutSpace(alt( scope(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( - this.pRawCall(... extraStops), + this.pArray(level), + this.pDict(level), + this.pQuote(level, extraStops), + this.pUnquote(level, extraStops), + mapm( + 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}))`; } } diff --git a/packages/compiler/src/syntax/matcher.ts b/packages/compiler/src/syntax/matcher.ts index d060261..2562f60 100644 --- a/packages/compiler/src/syntax/matcher.ts +++ b/packages/compiler/src/syntax/matcher.ts @@ -223,6 +223,34 @@ export function separatedBy(itemPattern: Pattern, separator: Pattern) }; } +export function separatedOrTerminatedBy( + itemPattern: Pattern, + separator: Pattern, +): Pattern { + 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; diff --git a/packages/compiler/src/syntax/scanner.ts b/packages/compiler/src/syntax/scanner.ts index 19c0b9d..13a9f8e 100644 --- a/packages/compiler/src/syntax/scanner.ts +++ b/packages/compiler/src/syntax/scanner.ts @@ -9,7 +9,7 @@ export abstract class Scanner implements IterableIterator { 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 };