Support toplevel typed binders in patterns

This commit is contained in:
Tony Garnock-Jones 2024-03-21 16:04:30 +01:00
parent 73b7759816
commit 1e29d528d7
2 changed files with 36 additions and 15 deletions

View File

@ -193,7 +193,8 @@ export class SyndicateParser {
readonly exprBoundary = alt<any>(atom(';'), atom(','), group('{', discard), Matcher.end);
readonly identifier: Pattern<Identifier> = atom();
get binder(): Pattern<Binder> { return scope(o => bind(o, 'id', this.identifier)); }
binder(... _extraStops: Pattern<any>[]): Pattern<Binder> { return scope(o => bind(o, 'id', this.identifier)); }
readonly defaultBinder = this.binder();
expr(... extraStops: Pattern<any>[]): Pattern<Expr> {
return withoutSpace(upTo(alt(this.exprBoundary, ... extraStops)));
@ -248,7 +249,7 @@ export class SyndicateParser {
map(scope(
(l: { b: Binder, init: Expr }) =>
seq(kw('let'),
bind(l, 'b', this.binder),
bind(l, 'b', this.defaultBinder),
atom('='),
bind(l, 'init', this.headerExpr))),
l => {
@ -262,7 +263,7 @@ export class SyndicateParser {
readonly fieldDeclarationStatement: Pattern<FieldDeclarationStatement> =
this.turnAction(o => {
return seq(atom('field'),
bind(o, 'field', this.binder),
bind(o, 'field', this.defaultBinder),
option(seq(atom('='), bind(o, 'init', this.expr()))),
this.statementBoundary);
});
@ -333,7 +334,7 @@ export class SyndicateParser {
scope(o => seq(bind(o, 'expectedUse', alt(atomString('message'), atomString('assertion'))),
atom('type'),
bind(o, 'label', this.identifier),
group('(', bind(o, 'fields', repeat(this.binder, { separator: atom(',') }))),
group('(', bind(o, 'fields', repeat(this.defaultBinder, { separator: atom(',') }))),
option(seq(atom('='),
bind(o, 'wireName', withoutSpace(upTo(this.statementBoundary))))),
this.statementBoundary));
@ -407,12 +408,13 @@ export class SyndicateParser {
// {expr: expr, ...} - constant
// other - constant
readonly pCaptureBinder: Pattern<Binder> =
mapm(this.binder, i => {
pCaptureBinder = (b: Pattern<Binder>): Pattern<Binder> =>
mapm(b, i => {
return i.id.text.startsWith('$')
? succeed({ id: { ... i.id, text: i.id.text.slice(1) }, type: i.type })
: fail;
});
readonly pCaptureDefaultBinder = this.pCaptureBinder(this.defaultBinder);
readonly pDiscard: Pattern<void> =
mapm(this.identifier, i => i.text === '_' ? succeed(void 0) : fail);
@ -440,7 +442,7 @@ export class SyndicateParser {
hasCapturesOrDiscards(e: Expr): boolean {
return foldItems(e,
t => match(alt<any>(this.pCaptureBinder, this.pDiscard), [t], null, '(') !== null,
t => match(alt<any>(this.pCaptureDefaultBinder, this.pDiscard), [t], null, '(') !== null,
(_g, b, _k) => b,
bs => bs.some(b => b));
}
@ -503,7 +505,7 @@ export class SyndicateParser {
// });
// } else
if (this.hasCapturesOrDiscards(o.ctor)) {
const r = match(this.pCaptureBinder, o.ctor, null, '(');
const r = match(this.pCaptureDefaultBinder, o.ctor, null, '(');
if (r !== null && o.arguments.length === 1) {
return succeed({
type: 'PCapture',
@ -520,7 +522,7 @@ export class SyndicateParser {
scope<PCapture>(o => {
o.type = 'PCapture';
o.inner = { type: 'PDiscard' };
return bind(o, 'binder', this.pCaptureBinder);
return bind(o, 'binder', this.pCaptureBinder(this.binder(... extraStops)));
}),
map(this.expr(... extraStops), e => ({ type: 'PConstant', value: e }))
));
@ -528,10 +530,10 @@ export class SyndicateParser {
}
export class SyndicateTypedParser extends SyndicateParser {
get binder(): Pattern<Binder> {
binder(... extraStops: Pattern<any>[]): Pattern<Binder> {
return scope(o => seq(bind(o, 'id', this.identifier),
option(seq(atom(':'),
bind(o, 'type', this.type(atom('=')))))));
bind(o, 'type', this.type(atom('='), ... extraStops))))));
}
}

View File

@ -19,14 +19,14 @@ function translate(source: string, options: Partial<CompileOptions> = {}): { cod
return { code: result.text, errors };
}
function translateNoErrors(source: string): string {
const o = translate(source);
function translateNoErrors(source: string, options?: Partial<CompileOptions>): string {
const o = translate(source, options);
expect(o.errors.length).toBe(0);
return o.code;
}
function expectCodeEqual(input: string, output: string) {
expect(format(translateNoErrors(input))).toBe(format(output));
function expectCodeEqual(input: string, output: string, options?: Partial<CompileOptions>) {
expect(format(translateNoErrors(input, options))).toBe(format(output));
}
describe('react', () => {
@ -150,6 +150,25 @@ __SYNDICATE__.Turn.active.assertDataflow(() => ({
})
}));`));
it('capture with type at top', () => expectCodeEqual(`during $v: T => { ok() }`, `
__SYNDICATE__.Turn.active.assertDataflow(() => ({
target: currentSyndicateTarget,
assertion: __SYNDICATE__.Observe({
pattern: __SYNDICATE__.QuasiValue.finish((__SYNDICATE__.QuasiValue.bind((__SYNDICATE__.QuasiValue._)))),
observer: __SYNDICATE__.Turn.ref(__SYNDICATE__.assertionFacetObserver(
(__vs: __SYNDICATE__.AnyValue) => {
if (Array.isArray(__vs)) {
const __v_0 = T.__from_preserve__(__vs[0]);
if (__v_0 === void 0) return;
const v = __v_0;
ok()
}
}
))
})
}));
`, { typescript: true }));
});
describe('once', () => {