diff --git a/packages/compiler/src/compiler/grammar.ts b/packages/compiler/src/compiler/grammar.ts index 699eb49..c5f951a 100644 --- a/packages/compiler/src/compiler/grammar.ts +++ b/packages/compiler/src/compiler/grammar.ts @@ -193,7 +193,8 @@ export class SyndicateParser { readonly exprBoundary = alt(atom(';'), atom(','), group('{', discard), Matcher.end); readonly identifier: Pattern = atom(); - get binder(): Pattern { return scope(o => bind(o, 'id', this.identifier)); } + binder(... _extraStops: Pattern[]): Pattern { return scope(o => bind(o, 'id', this.identifier)); } + readonly defaultBinder = this.binder(); expr(... extraStops: Pattern[]): Pattern { 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 = 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 = - mapm(this.binder, i => { + pCaptureBinder = (b: Pattern): Pattern => + 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 = 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(this.pCaptureBinder, this.pDiscard), [t], null, '(') !== null, + t => match(alt(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(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(... extraStops: Pattern[]): Pattern { 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)))))); } } diff --git a/packages/compiler/test/compiler.test.ts b/packages/compiler/test/compiler.test.ts index b74f0b3..c924f0a 100644 --- a/packages/compiler/test/compiler.test.ts +++ b/packages/compiler/test/compiler.test.ts @@ -19,14 +19,14 @@ function translate(source: string, options: Partial = {}): { cod return { code: result.text, errors }; } -function translateNoErrors(source: string): string { - const o = translate(source); +function translateNoErrors(source: string, options?: Partial): 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) { + 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', () => {