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 exprBoundary = alt<any>(atom(';'), atom(','), group('{', discard), Matcher.end);
readonly identifier: Pattern<Identifier> = atom(); 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> { expr(... extraStops: Pattern<any>[]): Pattern<Expr> {
return withoutSpace(upTo(alt(this.exprBoundary, ... extraStops))); return withoutSpace(upTo(alt(this.exprBoundary, ... extraStops)));
@ -248,7 +249,7 @@ export class SyndicateParser {
map(scope( map(scope(
(l: { b: Binder, init: Expr }) => (l: { b: Binder, init: Expr }) =>
seq(kw('let'), seq(kw('let'),
bind(l, 'b', this.binder), bind(l, 'b', this.defaultBinder),
atom('='), atom('='),
bind(l, 'init', this.headerExpr))), bind(l, 'init', this.headerExpr))),
l => { l => {
@ -262,7 +263,7 @@ export class SyndicateParser {
readonly fieldDeclarationStatement: Pattern<FieldDeclarationStatement> = readonly fieldDeclarationStatement: Pattern<FieldDeclarationStatement> =
this.turnAction(o => { this.turnAction(o => {
return seq(atom('field'), return seq(atom('field'),
bind(o, 'field', this.binder), bind(o, 'field', this.defaultBinder),
option(seq(atom('='), bind(o, 'init', this.expr()))), option(seq(atom('='), bind(o, 'init', this.expr()))),
this.statementBoundary); this.statementBoundary);
}); });
@ -333,7 +334,7 @@ export class SyndicateParser {
scope(o => seq(bind(o, 'expectedUse', alt(atomString('message'), atomString('assertion'))), scope(o => seq(bind(o, 'expectedUse', alt(atomString('message'), atomString('assertion'))),
atom('type'), atom('type'),
bind(o, 'label', this.identifier), 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('='), option(seq(atom('='),
bind(o, 'wireName', withoutSpace(upTo(this.statementBoundary))))), bind(o, 'wireName', withoutSpace(upTo(this.statementBoundary))))),
this.statementBoundary)); this.statementBoundary));
@ -407,12 +408,13 @@ export class SyndicateParser {
// {expr: expr, ...} - constant // {expr: expr, ...} - constant
// other - constant // other - constant
readonly pCaptureBinder: Pattern<Binder> = pCaptureBinder = (b: Pattern<Binder>): Pattern<Binder> =>
mapm(this.binder, i => { mapm(b, i => {
return i.id.text.startsWith('$') return i.id.text.startsWith('$')
? succeed({ id: { ... i.id, text: i.id.text.slice(1) }, type: i.type }) ? succeed({ id: { ... i.id, text: i.id.text.slice(1) }, type: i.type })
: fail; : fail;
}); });
readonly pCaptureDefaultBinder = this.pCaptureBinder(this.defaultBinder);
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);
@ -440,7 +442,7 @@ export class SyndicateParser {
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.pCaptureDefaultBinder, this.pDiscard), [t], null, '(') !== null,
(_g, b, _k) => b, (_g, b, _k) => b,
bs => bs.some(b => b)); bs => bs.some(b => b));
} }
@ -503,7 +505,7 @@ export class SyndicateParser {
// }); // });
// } else // } else
if (this.hasCapturesOrDiscards(o.ctor)) { 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) { if (r !== null && o.arguments.length === 1) {
return succeed({ return succeed({
type: 'PCapture', type: 'PCapture',
@ -520,7 +522,7 @@ export class SyndicateParser {
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(this.binder(... extraStops)));
}), }),
map(this.expr(... extraStops), e => ({ type: 'PConstant', value: e })) map(this.expr(... extraStops), e => ({ type: 'PConstant', value: e }))
)); ));
@ -528,10 +530,10 @@ export class SyndicateParser {
} }
export class SyndicateTypedParser extends 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), return scope(o => seq(bind(o, 'id', this.identifier),
option(seq(atom(':'), 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 }; return { code: result.text, errors };
} }
function translateNoErrors(source: string): string { function translateNoErrors(source: string, options?: Partial<CompileOptions>): string {
const o = translate(source); const o = translate(source, options);
expect(o.errors.length).toBe(0); expect(o.errors.length).toBe(0);
return o.code; return o.code;
} }
function expectCodeEqual(input: string, output: string) { function expectCodeEqual(input: string, output: string, options?: Partial<CompileOptions>) {
expect(format(translateNoErrors(input))).toBe(format(output)); expect(format(translateNoErrors(input, options))).toBe(format(output));
} }
describe('react', () => { 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', () => { describe('once', () => {