From ae20476c747047f6f1d1b9b25f442f5776e8161d Mon Sep 17 00:00:00 2001 From: "Hongyang Du (hond)" Date: Thu, 14 Nov 2019 09:46:12 +0800 Subject: [PATCH 1/6] add Activityfactory (#1401) * add activityfactory * retrigger CI * fix comments * remove unused code * modify text * add some comments * fix param type --- libraries/botbuilder-lg/package.json | 3 +- .../botbuilder-lg/src/activityFactory.ts | 357 +++++++++++++++ libraries/botbuilder-lg/src/index.ts | 1 + .../tests/ActivityFactoryTests.js | 423 ++++++++++++++++++ .../testData/examples/NormalStructuredLG.lg | 288 ++++++++++++ 5 files changed, 1071 insertions(+), 1 deletion(-) create mode 100644 libraries/botbuilder-lg/src/activityFactory.ts create mode 100644 libraries/botbuilder-lg/tests/ActivityFactoryTests.js create mode 100644 libraries/botbuilder-lg/tests/testData/examples/NormalStructuredLG.lg diff --git a/libraries/botbuilder-lg/package.json b/libraries/botbuilder-lg/package.json index a523a06b08..db0a7bd6e9 100644 --- a/libraries/botbuilder-lg/package.json +++ b/libraries/botbuilder-lg/package.json @@ -22,7 +22,8 @@ "antlr4ts": "0.5.0-alpha.1", "botframework-expressions": "4.1.6", "lodash": "^4.17.11", - "uuid": "^3.3.3" + "uuid": "^3.3.3", + "botbuilder-core": "4.1.6" }, "devDependencies": { "@types/mocha": "^5.2.5", diff --git a/libraries/botbuilder-lg/src/activityFactory.ts b/libraries/botbuilder-lg/src/activityFactory.ts new file mode 100644 index 0000000000..4e678f7528 --- /dev/null +++ b/libraries/botbuilder-lg/src/activityFactory.ts @@ -0,0 +1,357 @@ +/** + * @module botbuilder-lg + */ +/** + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. + */ + +import { Activity, SuggestedActions, Attachment, ActivityTypes, ActionTypes, CardAction } from 'botframework-schema'; +import { MessageFactory, CardFactory } from 'botbuilder-core'; + +/** + * The ActivityFactory + * to generate text and then uses simple markdown semantics like chatdown to create Activity. + */ +export class ActivityFactory { + private static genericCardTypeMapping: Map = new Map + ([ + [ 'herocard', CardFactory.contentTypes.heroCard ], + [ 'thumbnailcard', CardFactory.contentTypes.thumbnailCard ], + [ 'audiocard', CardFactory.contentTypes.audioCard ], + [ 'videocard', CardFactory.contentTypes.videoCard ], + [ 'animationcard', CardFactory.contentTypes.animationCard ], + [ 'signincard', CardFactory.contentTypes.signinCard ], + [ 'oauthcard', CardFactory.contentTypes.oauthCard ], + ]); + + /** + * Generate the activity. + * @param lgResult string result from languageGenerator. + */ + public static CreateActivity(lgResult: any): Partial { + if (typeof lgResult === 'string') { + return this.buildActivityFromText(lgResult.trim()); + } + + return this.buildActivityFromLGStructuredResult(lgResult); + } + + /** + * Given a lg result, create a text activity. This method will create a MessageActivity from text. + * @param text lg text output. + */ + private static buildActivityFromText(text: string): Partial { + return MessageFactory.text(text, text); + } + + /** + * Given a structured lg result, create an activity. This method will create an MessageActivity from object + * @param lgValue lg output. + */ + private static buildActivityFromLGStructuredResult(lgValue: any): Partial { + let activity: Partial = {}; + + const type: string = this.getStructureType(lgValue); + if (ActivityFactory.genericCardTypeMapping.has(type)) { + const attachment: Attachment = this.getAttachment(lgValue); + if (attachment !== undefined) { + activity = MessageFactory.attachment(attachment); + } else { + throw new Error(`'${ lgValue }' is not an attachment format.`); + } + } else if (type === 'activity') { + activity = this.buildActivityFromObject(lgValue); + } else { + throw new Error(`type ${ type } is not support currently.`); + } + + return activity; + } + + private static buildActivityFromObject(activityValue: any): Partial { + let activity: Partial = {}; + + if ('type' in activityValue && activityValue.type === 'event') { + activity = this.buildEventActivity(activityValue); + } else { + activity = this.buildMessageActivity(activityValue); + } + + return activity; + } + + private static buildEventActivity(eventValue: any): Partial { + let activity: Partial = { type: ActivityTypes.Event }; + for (const item of Object.keys(eventValue)) { + const property: string = item.trim(); + const value: any = eventValue[item]; + switch (property.toLowerCase()) { + case 'name': + activity.name = value.toString(); + break; + case 'value': + activity.value = value.toString(); + break; + default: + activity[property] = value; + break; + } + } + + return activity; + } + + private static buildMessageActivity(messageValue: any): Partial { + let activity: Partial = { type: ActivityTypes.Message }; + for (const key of Object.keys(messageValue)) { + const property: string = key.trim(); + const value: any = messageValue[key]; + + switch (property.toLowerCase()) { + case 'text': + activity.text = value.toString(); + break; + case 'speak': + activity.speak = value.toString(); + break; + case 'inputhint': + activity.inputHint = value.toString(); + break; + case 'attachments': + activity.attachments = this.getAttachments(value); + break; + case 'suggestedactions': + activity.suggestedActions = this.getSuggestions(value); + break; + case 'attachmentlayout': + activity.attachmentLayout = value.toString(); + default: + activity[property] = value; + break; + } + } + + return activity; + } + + private static getSuggestions(suggestionsValue: any): SuggestedActions { + let actions: any[] = this.normalizedToList(suggestionsValue); + + let suggestedActions: SuggestedActions = { + actions : this.getCardActions(actions), + to: [] + }; + + return suggestedActions; + } + + private static getButtons(buttonsValue: any): CardAction[] { + let actions: any[] = this.normalizedToList(buttonsValue); + return this.getCardActions(actions); + } + + private static getCardActions(actions: any[]): CardAction[] { + let cardActions: (string|CardAction)[] = []; + for (const action of actions) { + if (typeof action === 'string') { + cardActions.push(action.trim()); + } else { + const cardAction: CardAction = this.getCardAction(action); + if (cardAction !== undefined) { + cardActions.push(cardAction); + } + } + } + + return CardFactory.actions(cardActions); + } + + private static getCardAction(cardActionValue: any): CardAction { + const type: string = this.getStructureType(cardActionValue); + let cardAction: CardAction = { + type: ActionTypes.ImBack, + title: '', + value: '' + }; + + if(type !== 'cardaction') { + return undefined; + } + + for (const key of Object.keys(cardActionValue)) { + const property: string = key.trim(); + const value: any = cardActionValue[key]; + + switch (property.toLowerCase()) { + case 'type': + cardAction.type = value.toString(); + break; + case 'title': + cardAction.title = value.toString(); + break; + case 'value': + cardAction.value = value.toString(); + break; + case 'displaytext': + cardAction.displayText = value.toString(); + break; + case 'text': + cardAction.text = value.toString(); + break; + case 'image': + cardAction.image = value.toString(); + break; + default: + cardAction[property] = value; + break; + } + } + + return cardAction; + } + + private static getStructureType(input: any): string { + let result = ''; + + if (input !== undefined) { + if ('$type' in input) { + result = input['$type'].toString(); + } else if ('type' in input) { + result = input['type'].toString(); + } + } + + return result.trim().toLowerCase(); + } + + private static getAttachments(input: any): Attachment[] { + let attachments: Attachment[] = []; + let attachmentsJsonList: any[] = this.normalizedToList(input); + + for (const attachmentsJson of attachmentsJsonList) { + if (typeof attachmentsJson === 'object') { + const attachment = this.getAttachment(attachmentsJson); + if (attachment !== undefined) { + attachments.push(attachment); + } else { + throw new Error(`'${ attachmentsJson }' is not an attachment format.`); + } + } else { + throw new Error(`'${ attachmentsJson }' is not an attachment format.`); + } + } + + return attachments; + } + + private static getAttachment(input: any): Attachment { + let attachment: Attachment = { + contentType: '' + }; + const type: string = this.getStructureType(input); + if (ActivityFactory.genericCardTypeMapping.has(type)) { + attachment = this.getCardAttachment(ActivityFactory.genericCardTypeMapping.get(type), input); + } else if(type === 'adaptivecard') { + attachment = CardFactory.adaptiveCard(input); + } else { + attachment = undefined; + } + + return attachment; + } + + private static getCardAttachment(type: string, input: any): Attachment { + let card: any = {}; + + for (const key in input) { + const property: string = key.trim().toLowerCase(); + const value: any = input[key]; + + switch (property) { + case 'title': + case 'subtitle': + case 'text': + case 'aspect': + case 'value': + card[property] = value; + break; + case 'connectionname': + card['connectionName'] = value; + break; + + case 'image': + case 'images': + if (type === CardFactory.contentTypes.heroCard || type === CardFactory.contentTypes.thumbnailCard) { + if (!('images' in card)) { + card['images'] = []; + } + + let imageList: string[] = this.normalizedToList(value).map((u): string => u.toString()); + imageList.forEach( (u): any => card['images'].push({url : u})); + } else { + card['image'] = {url: value.toString()}; + } + break; + case 'media': + if (!('media' in card)) { + card['media'] = []; + } + + let mediaList: string[] = this.normalizedToList(value).map((u): string => u.toString()); + mediaList.forEach( (u): any => card['media'].push({url : u})); + break; + case 'buttons': + if (!('buttons' in card)) { + card['buttons'] = []; + } + + let buttons: any[] = this.getButtons(value); + buttons.forEach( (u): any => card[property].push(u)); + break; + case 'autostart': + case 'shareable': + case 'autoloop': + const boolValue: boolean = this.getValidBooleanValue(value.toString()); + if (boolValue !== undefined) { + card[property] = boolValue; + } + break; + default: + card[property] = value; + break; + } + } + + const attachment: Attachment = { + contentType: type, + content: card + }; + + return attachment; + } + + private static getValidBooleanValue(boolValue: string): boolean{ + if (boolValue.toLowerCase() == 'true') + { + return true; + } + else if (boolValue.toLowerCase() == 'false') + { + return false; + } + + return undefined; + } + + private static normalizedToList(item: any): any[] { + if (item === undefined) { + return []; + } else if (Array.isArray(item)){ + return item; + } else { + return [item]; + } + } + +} \ No newline at end of file diff --git a/libraries/botbuilder-lg/src/index.ts b/libraries/botbuilder-lg/src/index.ts index 11b5b6fe22..0652e1f8a4 100644 --- a/libraries/botbuilder-lg/src/index.ts +++ b/libraries/botbuilder-lg/src/index.ts @@ -22,3 +22,4 @@ export * from './lgImport'; export * from './range'; export * from './position'; export * from './evaluationTarget'; +export * from './activityFactory'; diff --git a/libraries/botbuilder-lg/tests/ActivityFactoryTests.js b/libraries/botbuilder-lg/tests/ActivityFactoryTests.js new file mode 100644 index 0000000000..7979021a5b --- /dev/null +++ b/libraries/botbuilder-lg/tests/ActivityFactoryTests.js @@ -0,0 +1,423 @@ +const { TemplateEngine, ActivityFactory } = require('../'); +const assert = require('assert'); + +function getTemplateEngine(){ + const filePath = `${ __dirname }/testData/Examples/NormalStructuredLG.lg`; + return new TemplateEngine().addFile(filePath); +} + +function getActivity(templateName, data){ + const engine = getTemplateEngine(); + const lgResult = engine.evaluateTemplate(templateName, data); + return ActivityFactory.CreateActivity(lgResult); +} + + +describe('ActivityFactoryTest', function() { + it('HerocardWithCardAction', function() { + let data = { + title: 'titleContent', + text: 'textContent' + }; + let result = getActivity('HerocardWithCardAction', data); + assertCardActionActivity(result); + }); + + it('adaptivecardActivity', function() { + let data = { + adaptiveCardTitle: 'test' + }; + let result = getActivity('adaptivecardActivity', data); + assertAdaptiveCardActivity(result); + }); + + it('eventActivity', function() { + let data = { + text: 'textContent', + adaptiveCardTitle: 'test' + }; + let result = getActivity('eventActivity', data); + assertEventActivity(result); + }); + + it('activityWithHeroCardAttachment', function() { + let data = { + title: 'titleContent', + text: 'textContent' + }; + let result = getActivity('activityWithHeroCardAttachment', data); + assertActivityWithHeroCardAttachment(result); + }); + + it('activityWithMultiAttachments', function() { + let data = { + title: 'titleContent', + text: 'textContent' + }; + let result = getActivity('activityWithMultiAttachments', data); + assertActivityWithMultiAttachments(result); + }); + + it('activityWithSuggestionActions', function() { + let data = { + title: 'titleContent', + text: 'textContent' + }; + let result = getActivity('activityWithSuggestionActions', data); + assertActivityWithSuggestionActions(result); + }); + + it('messageActivityAll', function() { + let data = { + title: 'titleContent', + text: 'textContent' + }; + let result = getActivity('messageActivityAll', data); + assertMessageActivityAll(result); + }); + + it('activityWithMultiStructuredSuggestionActions', function() { + let data = { + title: 'titleContent', + text: 'textContent' + }; + let result = getActivity('activityWithMultiStructuredSuggestionActions', data); + assertActivityWithMultiStructuredSuggestionActions(result); + }); + + it('activityWithMultiStringSuggestionActions', function() { + let data = { + title: 'titleContent', + text: 'textContent' + }; + let result = getActivity('activityWithMultiStringSuggestionActions', data); + assertActivityWithMultiStringSuggestionActions(result); + }); + + it('HeroCardTemplate', function() { + let data = { + title: 'titleContent', + text: 'textContent', + type: 'herocard' + }; + let result = getActivity('HeroCardTemplate', data); + assertHeroCardActivity(result); + }); + + it('ThumbnailCardTemplate', function() { + let data = { + title: 'titleContent', + text: 'textContent', + type: 'thumbnailcard' + }; + let result = getActivity('ThumbnailCardTemplate', data); + assertThumbnailCardActivity(result); + }); + + it('AudioCardTemplate', function() { + let data = { + title: 'titleContent', + text: 'textContent', + type: 'audiocard' + }; + let result = getActivity('AudioCardTemplate', data); + assertAudioCardActivity(result); + }); + + it('VideoCardTemplate', function() { + let data = { + title: 'titleContent', + text: 'textContent', + type: 'videocard' + }; + let result = getActivity('VideoCardTemplate', data); + assertVideoCardActivity(result); + }); + + it('SigninCardTemplate', function() { + let data = { + title: 'titleContent', + text: 'textContent', + signinlabel: 'Sign in', + url: 'https://login.microsoftonline.com/' + }; + let result = getActivity('SigninCardTemplate', data); + assertSigninCardActivity(result); + }); + + it('OAuthCardTemplate', function() { + let data = { + title: 'titleContent', + text: 'textContent', + connectionName: 'MyConnection', + signinlabel: 'Sign in', + url: 'https://login.microsoftonline.com/' + }; + let result = getActivity('OAuthCardTemplate', data); + assertOAuthCardActivity(result); + }); + + it('SuggestedActionsReference', function() { + let data = { + title: 'titleContent', + text: 'textContent' + }; + let result = getActivity('SuggestedActionsReference', data); + assertSuggestedActionsReferenceActivity(result); + }); +}); + +function assertSuggestedActionsReferenceActivity(activity) { + assert.strictEqual(activity.type, 'message'); + assert.strictEqual(activity.text, 'textContent'); + assert.strictEqual(activity.suggestedActions.actions.length, 5); + assert.strictEqual(activity.suggestedActions.actions[0].value, 'Add todo'); + assert.strictEqual(activity.suggestedActions.actions[1].value, 'View Todo'); + assert.strictEqual(activity.suggestedActions.actions[2].value, 'Remove Todo'); + assert.strictEqual(activity.suggestedActions.actions[3].value, 'Cancel'); + assert.strictEqual(activity.suggestedActions.actions[4].value, 'Help'); +} + +function assertOAuthCardActivity(activity) { + assert.strictEqual(activity.type, 'message'); + assert(activity.text === undefined); + assert(activity.speak === undefined); + assert.strictEqual(activity.attachments.length, 1); + assert.strictEqual(activity.attachments[0].contentType, 'application/vnd.microsoft.card.oauth'); + const card = activity.attachments[0].content; + assert(card !== undefined); + assert.strictEqual(card.connectionName, 'MyConnection'); + assert.strictEqual(card.buttons.length, 1); + assert.strictEqual(card.buttons[0].title, 'Sign in'); + assert.strictEqual(card.buttons[0].type, 'signin'); + assert.strictEqual(card.buttons[0].value, 'https://login.microsoftonline.com/'); +} + +function assertSigninCardActivity(activity) { + assert.strictEqual(activity.type, 'message'); + assert(activity.text === undefined); + assert(activity.speak === undefined); + assert.strictEqual(activity.attachments.length, 1); + assert.strictEqual(activity.attachments[0].contentType, 'application/vnd.microsoft.card.signin'); + const card = activity.attachments[0].content; + assert(card !== undefined); + assert.strictEqual(card.buttons.length, 1); + assert.strictEqual(card.buttons[0].title, 'Sign in'); + assert.strictEqual(card.buttons[0].type, 'signin'); + assert.strictEqual(card.buttons[0].value, 'https://login.microsoftonline.com/'); +} + +function assertVideoCardActivity(activity) { + assert.strictEqual(activity.type, 'message'); + assert(activity.text === undefined); + assert(activity.speak === undefined); + assert.strictEqual(activity.attachments.length, 1); + assert.strictEqual(activity.attachments[0].contentType, 'application/vnd.microsoft.card.video'); + const card = activity.attachments[0].content; + assert(card !== undefined); + assert.strictEqual(card.title, 'Cheese gromit!'); + assert.strictEqual(card.subtitle, 'videocard'); + assert.strictEqual(card.text, 'This is some text describing the card, it\'s cool because it\'s cool'); + assert.strictEqual(card.image.url, 'https://memegenerator.net/img/instances/500x/73055378/cheese-gromit.jpg'); + assert.strictEqual(card.media[0].url, 'https://youtu.be/530FEFogfBQ'); + assert.strictEqual(card.shareable, false); + assert.strictEqual(card.autoloop, true); + assert.strictEqual(card.autostart, true); + assert.strictEqual(card.aspect, '16:9'); + assert.strictEqual(card.buttons.length, 3); + + for (let i = 0; i < card.buttons.length; i++) { + assert.strictEqual(card.buttons[i].title, `Option ${ i + 1 }`); + } +} + +function assertAudioCardActivity(activity) { + assert.strictEqual(activity.type, 'message'); + assert(activity.text === undefined); + assert(activity.speak === undefined); + assert.strictEqual(activity.attachments.length, 1); + assert.strictEqual(activity.attachments[0].contentType, 'application/vnd.microsoft.card.audio'); + const card = activity.attachments[0].content; + assert(card !== undefined); + assert.strictEqual(card.title, 'Cheese gromit!'); + assert.strictEqual(card.subtitle, 'audiocard'); + assert.strictEqual(card.text, 'This is some text describing the card, it\'s cool because it\'s cool'); + assert.strictEqual(card.image.url, 'https://memegenerator.net/img/instances/500x/73055378/cheese-gromit.jpg'); + assert.strictEqual(card.media[0].url, 'https://contoso.com/media/AllegrofromDuetinCMajor.mp3'); + assert.strictEqual(card.shareable, false); + assert.strictEqual(card.autoloop, true); + assert.strictEqual(card.autostart, true); + assert.strictEqual(card.aspect, '16:9'); + assert.strictEqual(card.buttons.length, 3); + + for (let i = 0; i < card.buttons.length; i++) { + assert.strictEqual(card.buttons[i].title, `Option ${ i + 1 }`); + } +} + + +function assertThumbnailCardActivity(activity) { + assert.strictEqual(activity.type, 'message'); + assert(activity.text === undefined); + assert(activity.speak === undefined); + assert.strictEqual(activity.attachments.length, 1); + assert.strictEqual(activity.attachments[0].contentType, 'application/vnd.microsoft.card.thumbnail'); + const card = activity.attachments[0].content; + assert(card !== undefined); + assert.strictEqual(card.title, 'Cheese gromit!'); + assert.strictEqual(card.subtitle, 'thumbnailcard'); + assert.strictEqual(card.text, 'This is some text describing the card, it\'s cool because it\'s cool'); + assert.strictEqual(card.images[0].url, 'https://memegenerator.net/img/instances/500x/73055378/cheese-gromit.jpg'); + assert.strictEqual(card.images[1].url, 'https://memegenerator.net/img/instances/500x/73055378/cheese-gromit.jpg'); + assert.strictEqual(card.buttons.length, 3); + + for (let i = 0; i < card.buttons.length; i++) { + assert.strictEqual(card.buttons[i].title, `Option ${ i + 1 }`); + } +} + +function assertHeroCardActivity(activity) { + assert.strictEqual(activity.type, 'message'); + assert(activity.text === undefined); + assert(activity.speak === undefined); + assert.strictEqual(activity.attachments.length, 1); + assert.strictEqual(activity.attachments[0].contentType, 'application/vnd.microsoft.card.hero'); + const card = activity.attachments[0].content; + assert(card !== undefined); + assert.strictEqual(card.title, 'Cheese gromit!'); + assert.strictEqual(card.subtitle, 'herocard'); + assert.strictEqual(card.text, 'This is some text describing the card, it\'s cool because it\'s cool'); + assert.strictEqual(card.images[0].url, 'https://memegenerator.net/img/instances/500x/73055378/cheese-gromit.jpg'); + assert.strictEqual(card.images[1].url, 'https://memegenerator.net/img/instances/500x/73055378/cheese-gromit.jpg'); + assert.strictEqual(card.buttons.length, 3); + + for (let i = 0; i < card.buttons.length; i++) { + assert.strictEqual(card.buttons[i].title, `Option ${ i + 1 }`); + } +} + +function assertActivityWithMultiStringSuggestionActions(activity) { + assert.strictEqual(activity.type, 'message'); + assert.strictEqual(activity.text, 'textContent'); + assert.strictEqual(activity.suggestedActions.actions.length, 3); + assert.strictEqual(activity.suggestedActions.actions[0].title, 'first suggestion'); + assert.strictEqual(activity.suggestedActions.actions[0].value, 'first suggestion'); + assert.strictEqual(activity.suggestedActions.actions[1].title, 'second suggestion'); + assert.strictEqual(activity.suggestedActions.actions[1].value, 'second suggestion'); + assert.strictEqual(activity.suggestedActions.actions[2].title, 'third suggestion'); + assert.strictEqual(activity.suggestedActions.actions[2].value, 'third suggestion'); +} + +function assertActivityWithMultiStructuredSuggestionActions(activity) { + assert.strictEqual(activity.type, 'message'); + assert.strictEqual(activity.text, 'textContent'); + assert.strictEqual(activity.suggestedActions.actions.length, 3); + assert.strictEqual(activity.suggestedActions.actions[0].title, 'first suggestion'); + assert.strictEqual(activity.suggestedActions.actions[0].value, 'first suggestion'); + assert.strictEqual(activity.suggestedActions.actions[1].title, 'second suggestion'); + assert.strictEqual(activity.suggestedActions.actions[1].value, 'second suggestion'); + assert.strictEqual(activity.suggestedActions.actions[2].title, 'third suggestion'); + assert.strictEqual(activity.suggestedActions.actions[2].value, 'third suggestion'); +} + +function assertMessageActivityAll(activity) { + assert.strictEqual(activity.type, 'message'); + assert.strictEqual(activity.text, 'textContent'); + assert.strictEqual(activity.speak, 'textContent'); + assert.strictEqual(activity.inputHint, 'accepting'); + assert.strictEqual(activity.attachments.length, 1); + assert.strictEqual(activity.attachments[0].contentType, 'application/vnd.microsoft.card.hero'); + const card = activity.attachments[0].content; + assert(card !== undefined); + assert.strictEqual(card.title, 'titleContent'); + assert.strictEqual(card.text, 'textContent'); + assert.strictEqual(card.buttons.length, 1, 'should have one button'); + const button = card.buttons[0]; + assert.strictEqual(button.type, 'imBack'); + assert.strictEqual(button.title, 'titleContent'); + assert.strictEqual(button.value, 'textContent'); + assert.strictEqual(activity.suggestedActions.actions.length, 2); + assert.strictEqual(activity.suggestedActions.actions[0].title, 'firstItem'); + assert.strictEqual(activity.suggestedActions.actions[0].value, 'firstItem'); + assert.strictEqual(activity.suggestedActions.actions[1].title, 'titleContent'); + assert.strictEqual(activity.suggestedActions.actions[1].value, 'textContent'); +} + +function assertActivityWithSuggestionActions(activity) { + assert.strictEqual(activity.type, 'message'); + assert.strictEqual(activity.text, 'textContent'); + assert.strictEqual(activity.suggestedActions.actions.length, 2); + assert.strictEqual(activity.suggestedActions.actions[0].title, 'firstItem'); + assert.strictEqual(activity.suggestedActions.actions[0].value, 'firstItem'); + assert.strictEqual(activity.suggestedActions.actions[1].title, 'titleContent'); + assert.strictEqual(activity.suggestedActions.actions[1].value, 'textContent'); +} + +function assertActivityWithMultiAttachments(activity) { + assert.strictEqual(activity.type, 'message'); + assert(activity.text === undefined); + assert(activity.speak === undefined); + assert.strictEqual(activity.attachments.length, 2); + assert.strictEqual(activity.attachments[1].contentType, 'application/vnd.microsoft.card.thumbnail'); + const card = activity.attachments[1].content; + assert(card !== undefined); + assert.strictEqual(card.title, 'Cheese gromit!'); + assert.strictEqual(card.subtitle, 'type'); + assert.strictEqual(card.text, 'This is some text describing the card, it\'s cool because it\'s cool'); + assert.strictEqual(card.images[0].url, 'https://memegenerator.net/img/instances/500x/73055378/cheese-gromit.jpg'); + assert.strictEqual(card.images[1].url, 'https://memegenerator.net/img/instances/500x/73055378/cheese-gromit.jpg'); + assert.strictEqual(card.buttons.length, 3); + + for (let i = 0; i < card.buttons.length; i++) { + assert.strictEqual(card.buttons[i].title, `Option ${ i + 1 }`); + } +} + +function assertActivityWithHeroCardAttachment(activity) { + assert.strictEqual(activity.type, 'message'); + assert(activity.text === undefined); + assert(activity.speak === undefined); + assert.strictEqual(activity.attachments.length, 1); + assert.strictEqual(activity.attachments[0].contentType, 'application/vnd.microsoft.card.hero'); + const card = activity.attachments[0].content; + assert(card !== undefined); + assert.strictEqual(card.title, 'titleContent'); + assert.strictEqual(card.text, 'textContent'); + assert.strictEqual(card.buttons.length, 1, 'should have one button'); + const button = card.buttons[0]; + assert.strictEqual(button.type, 'imBack'); + assert.strictEqual(button.title, 'titleContent'); + assert.strictEqual(button.value, 'textContent'); +} + +function assertCardActionActivity(activity) { + assert.strictEqual(activity.type, 'message'); + assert(activity.text === undefined); + assert(activity.speak === undefined); + assert.strictEqual(activity.attachments.length, 1); + assert.strictEqual(activity.attachments[0].contentType, 'application/vnd.microsoft.card.hero'); + const card = activity.attachments[0].content; + assert(card !== undefined); + assert.strictEqual(card.title, 'titleContent'); + assert.strictEqual(card.text, 'textContent'); + assert.strictEqual(card.buttons.length, 1, 'should have one button'); + const button = card.buttons[0]; + assert.strictEqual(button.type, 'imBack'); + assert.strictEqual(button.title, 'titleContent'); + assert.strictEqual(button.value, 'textContent'); +} + +function assertAdaptiveCardActivity(activity) { + assert.strictEqual(activity.type, 'message'); + assert(activity.text === undefined); + assert(activity.speak === undefined); + assert.strictEqual(activity.attachments.length, 1, 'should have one attachment'); + assert.strictEqual(activity.attachments[0].contentType, 'application/vnd.microsoft.card.adaptive', 'attachment type should be adaptivecard'); + assert.strictEqual(activity.attachments[0].content.body[0].text, 'test', 'text of first body should have value'); +} + +function assertEventActivity(activity) { + assert.strictEqual(activity.type, 'event'); + assert.strictEqual(activity.name, 'textContent'); + assert.strictEqual(activity.value, 'textContent'); +} \ No newline at end of file diff --git a/libraries/botbuilder-lg/tests/testData/examples/NormalStructuredLG.lg b/libraries/botbuilder-lg/tests/testData/examples/NormalStructuredLG.lg new file mode 100644 index 0000000000..d0ec65aed1 --- /dev/null +++ b/libraries/botbuilder-lg/tests/testData/examples/NormalStructuredLG.lg @@ -0,0 +1,288 @@ +# HeroCardTemplate(type) +[HeroCard + title=Cheese gromit! + subtitle=@{type} + text=This is some text describing the card, it's cool because it's cool + image=https://memegenerator.net/img/instances/500x/73055378/cheese-gromit.jpg + images=https://memegenerator.net/img/instances/500x/73055378/cheese-gromit.jpg + buttons=Option 1| Option 2| Option 3 +] + +# ThumbnailCardTemplate(type) +[ThumbnailCard + title=Cheese gromit! + subtitle=@{type} + text=This is some text describing the card, it's cool because it's cool + image=https://memegenerator.net/img/instances/500x/73055378/cheese-gromit.jpg + images=https://memegenerator.net/img/instances/500x/73055378/cheese-gromit.jpg + buttons=Option 1| Option 2| Option 3 +] + +# AudioCardTemplate(type) +[Audiocard + title=Cheese gromit! + subtitle=@{type} + text=This is some text describing the card, it's cool because it's cool + image=https://memegenerator.net/img/instances/500x/73055378/cheese-gromit.jpg + buttons=Option 1| Option 2| Option 3 + Media=https://contoso.com/media/AllegrofromDuetinCMajor.mp3 + Shareable=false + Autoloop=true + Autostart=true + Aspect=16:9 +] + +# VideoCardTemplate(type) +[VideoCard + title=Cheese gromit! + subtitle=@{type} + text=This is some text describing the card, it's cool because it's cool + image=https://memegenerator.net/img/instances/500x/73055378/cheese-gromit.jpg + buttons=Option 1| Option 2| Option 3 + Media=https://youtu.be/530FEFogfBQ + Shareable=false + Autoloop=true + Autostart=true + Aspect=16:9 +] + +# SigninCardTemplate(signinlabel, url) +[SigninCard + text=This is some text describing the card, it's cool because it's cool + buttons=@{signinButton(signinlabel, url)} +] + +# signinButton(signinlabel, url) +[CardAction + Title = @{signinlabel} + Value = @{url} + Type = signin +] + +# OAuthCardTemplate(signinlabel, url, connectionName) +[OAuthCard + text=This is some text describing the card, it's cool because it's cool + buttons=@{cardActionTemplate('signin', signinlabel, url)} + ConnectionName=@{connectionName} +] + + +# AnimationCardTemplate +[AnimationCard + Title=Animation Card + Subtitle=look at it animate + autostart=true + autoloop=true + Image=https://docs.microsoft.com/en-us/bot-framework/media/how-it-works/architecture-resize.png + Image=https://docs.microsoft.com/en-us/bot-framework/media/how-it-works/architecture-resize.png + Image=https://docs.microsoft.com/en-us/bot-framework/media/how-it-works/architecture-resize.png + Media=http://oi42.tinypic.com/1rchlx.jpg +] + +# HerocardWithCardAction(title, text) +[herocard + Title = @{title} + Text = @{text} + Buttons = @{cardActionTemplate(null, title, text)} +] + +# cardActionTemplate(type, title, value) +[CardAction + Type = @{if(type == null, 'imBack', type)} + Title = @{title} + Value = @{value} + Text = @{title} +] + +# eventActivity(text) +[Activity + Name = @{text} + Value = @{text} + Type = event +] + +# activityWithHeroCardAttachment(title, text) +[Activity + Attachments = @{HerocardWithCardAction(title, text)} +] + +# activityWithMultiAttachments(title, text) +[activity + Attachments = @{HerocardWithCardAction(title, text)} | @{ThumbnailCardTemplate('type')} +] + +# activityWithSuggestionActions(title, text) +[Activity + Text = @{text} + SuggestedActions = firstItem | @{cardActionTemplate(null, title, text)} +] + +# activityWithMultiStringSuggestionActions(title, text) +[Activity + Text = @{text} + SuggestedActions = @{getSuggestions()} +] + +# getSuggestions +- @{foreach(split(stringSuggestions(), '$'), x, trim(x))} + +# stringSuggestions +- first suggestion $ second suggestion $ third suggestion + +# activityWithMultiStructuredSuggestionActions(text) +[Activity + Text = @{text} + SuggestedActions = @{foreach(getSuggestions(), x, cardActionTemplate(null, x, x))} +] + +# adaptivecardActivity +[Activity + Attachments = @{json(adaptivecardjson())} +] + +# messageActivityAll(title, text) +[Activity + Text = @{text} + Speak = @{text} + InputHint = accepting + Attachments = @{HerocardWithCardAction(title, text)} + SuggestedActions = firstItem | @{cardActionTemplate(null, title, text)} + AttachmentLayout = list +] + +# notSupport +[Acti + key = value +] + +# SuggestedActionsReference(text) +[ Activity + Text = @{text} + @{WelcomeActions()} +] + +# WelcomeActions +[Activity + SuggestedActions = Add todo | View Todo | Remove Todo | Cancel | Help +] + +# adaptivecardjson +- ``` +{ + "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", + "type": "AdaptiveCard", + "version": "1.0", + "body": [ + { + "type": "TextBlock", + "text": "@{adaptiveCardTitle}", + "weight": "bolder", + "size": "medium" + }, + { + "type": "ColumnSet", + "columns": [ + { + "type": "Column", + "width": "auto", + "items": [ + { + "type": "Image", + "url": "https://pbs.twimg.com/profile_images/3647943215/d7f12830b3c17a5a9e4afcc370e3a37e_400x400.jpeg", + "size": "small", + "style": "person" + } + ] + }, + { + "type": "Column", + "width": "stretch", + "items": [ + { + "type": "TextBlock", + "text": "Matt Hidinger", + "weight": "bolder", + "wrap": true + }, + { + "type": "TextBlock", + "spacing": "none", + "text": "Created aa", + "isSubtle": true, + "wrap": true + } + ] + } + ] + }, + { + "type": "TextBlock", + "text": "Now that we have defined the main rules and features of the format, we need to produce a schema and publish it to GitHub. The schema will be the starting point of our reference documentation.", + "wrap": true + }, + { + "type": "FactSet", + "facts": [ + { + "title": "Board:", + "value": "Adaptive Card" + }, + { + "title": "List:", + "value": "Backlog" + }, + { + "title": "Assigned to:", + "value": "Matt Hidinger" + }, + { + "title": "Due date:", + "value": "Not set" + } + ] + } + ], + "actions": [ + { + "type": "Action.ShowCard", + "title": "Set due date", + "card": { + "type": "AdaptiveCard", + "body": [ + { + "type": "Input.Date", + "id": "dueDate" + } + ], + "actions": [ + { + "type": "Action.Submit", + "title": "OK" + } + ] + } + }, + { + "type": "Action.ShowCard", + "title": "Comment", + "card": { + "type": "AdaptiveCard", + "body": [ + { + "type": "Input.Text", + "id": "comment", + "isMultiline": true, + "placeholder": "Enter your comment" + } + ], + "actions": [ + { + "type": "Action.Submit", + "title": "OK" + } + ] + } + } + ] +} +``` \ No newline at end of file From dd3dbce460c609e063a57867f3a4531802a8ec23 Mon Sep 17 00:00:00 2001 From: "Hongyang Du (hond)" Date: Fri, 15 Nov 2019 16:22:40 +0800 Subject: [PATCH 2/6] fix template additional newline bug (#1419) --- libraries/botbuilder-lg/src/lgResource.ts | 56 +++++++++++++++++++---- 1 file changed, 48 insertions(+), 8 deletions(-) diff --git a/libraries/botbuilder-lg/src/lgResource.ts b/libraries/botbuilder-lg/src/lgResource.ts index 5c46255e65..59b7e6b06b 100644 --- a/libraries/botbuilder-lg/src/lgResource.ts +++ b/libraries/botbuilder-lg/src/lgResource.ts @@ -55,7 +55,7 @@ export class LGResource { const templateNameLine: string = this.buildTemplateNameLine(newTemplateName, parameters); const newTemplateBody: string = this.convertTemplateBody(templateBody); - const content = `${ templateNameLine }\r\n${ newTemplateBody }`; + const content = `${ templateNameLine }\r\n${ newTemplateBody }\r\n`; const startLine: number = template.parseTree.start.line - 1; const stopLine: number = template.parseTree.stop.line - 1; @@ -79,7 +79,7 @@ export class LGResource { const templateNameLine: string = this.buildTemplateNameLine(templateName, parameters); const newTemplateBody: string = this.convertTemplateBody(templateBody); - const newContent = `${ this.content }\r\n${ templateNameLine }\r\n${ newTemplateBody }`; + const newContent = `${ this.content.trimRight() }\r\n\r\n${ templateNameLine }\r\n${ newTemplateBody }\r\n`; return LGParser.parse(newContent, this.id); } @@ -115,15 +115,55 @@ export class LGResource { throw new Error(`index out of range.`); } - destList.push(...originList.slice(0, startLine)); + destList.push(...this.trimList(originList.slice(0, startLine))); - if (replaceString !== undefined && replaceString.length > 0) { - destList.push(replaceString); + if (stopLine < originList.length - 1) { + // insert at the middle of the content + destList.push('\r\n'); + if (replaceString !== undefined && replaceString.length > 0){ + destList.push(replaceString); + destList.push('\r\n'); + } + + destList.push(...this.trimList(originList.slice(stopLine + 1))); + } else { + // insert at the tail of the content + if (replaceString !== undefined && replaceString.length > 0){ + destList.push('\r\n'); + destList.push(replaceString); + } } - destList.push(...originList.slice(stopLine + 1)); + return this.buildNewLGContent(this.trimList(destList)); + } + + /** + * trim the newlines at the beginning or at the tail of the array + * @param input input array + */ + private trimList(input: string[]): string[] { + if (input === undefined) { + return undefined; + } + + let startIndex = 0; + let endIndex = input.length; + + for(let i = 0; i< input.length; i++) { + if (input[i].trim() !== '') { + startIndex = i; + break; + } + } + + for(let i = input.length - 1; i >= 0; i--) { + if (input[i].trim() !== '') { + endIndex = i + 1; + break; + } + } - return this.buildNewLGContent(destList); + return input.slice(startIndex, endIndex); } private buildNewLGContent(destList: string[]): string { @@ -133,7 +173,7 @@ export class LGResource { result = result.concat(currentItem); if (currentItem.endsWith('\r')) { result = result.concat('\n'); - } else if (i < destList.length - 1) { + } else if (i < destList.length - 1 && !currentItem.endsWith('\r\n')) { result = result.concat('\r\n'); } } From 7115b621ad2746440151b59b6a169bb75c5011e1 Mon Sep 17 00:00:00 2001 From: "Hongyang Du (hond)" Date: Mon, 2 Dec 2019 21:01:40 +0800 Subject: [PATCH 3/6] fix conflict --- libraries/botbuilder-lg/src/LGFileLexer.g4 | 47 +- libraries/botbuilder-lg/src/LGFileParser.g4 | 13 +- .../botbuilder-lg/src/activityChecker.ts | 312 ++++++++ .../botbuilder-lg/src/activityFactory.ts | 303 ++++---- libraries/botbuilder-lg/src/analyzer.ts | 56 +- .../botbuilder-lg/src/customizedMemory.ts | 46 ++ .../src/customizedMemoryScope.ts | 40 - libraries/botbuilder-lg/src/errorListener.ts | 1 + .../botbuilder-lg/src/evaluationTarget.ts | 23 +- libraries/botbuilder-lg/src/evaluator.ts | 208 +++-- libraries/botbuilder-lg/src/expander.ts | 153 +--- .../src/generated/LGFileLexer.ts | 606 +++++++-------- .../src/generated/LGFileParser.ts | 196 +++-- libraries/botbuilder-lg/src/index.ts | 1 + libraries/botbuilder-lg/src/lgParser.ts | 10 +- libraries/botbuilder-lg/src/lgResource.ts | 8 +- libraries/botbuilder-lg/src/lgTemplate.ts | 8 +- libraries/botbuilder-lg/src/mslgTool.ts | 10 +- libraries/botbuilder-lg/src/staticChecker.ts | 338 ++++---- libraries/botbuilder-lg/src/templateEngine.ts | 13 +- .../tests/ActivityChecker.test.js | 60 ++ ...actoryTests.js => ActivityFactory.test.js} | 189 ++++- libraries/botbuilder-lg/tests/lg.test.js | 725 +++++++++++------- .../botbuilder-lg/tests/mslgtool.test.js | 109 +-- .../templateEngineThrowException.test.js | 83 +- .../tests/testData/adaptiveCard.json | 117 +++ .../tests/testData/examples/3.lg | 4 +- .../tests/testData/examples/4.lg | 8 +- .../tests/testData/examples/5.lg | 10 +- .../tests/testData/examples/6.lg | 34 +- .../tests/testData/examples/8.lg | 14 +- .../tests/testData/examples/Analyzer.lg | 42 +- .../tests/testData/examples/BasicActivity.lg | 12 +- .../tests/testData/examples/BasicList.lg | 10 +- .../testData/examples/CaseInsensitive.lg | 10 +- .../testData/examples/ConditionExpression.lg | 8 +- .../examples/DiagnosticStructuredLG.lg | 33 + .../testData/examples/EscapeCharacter.lg | 19 +- .../tests/testData/examples/EvalExpression.lg | 12 +- .../tests/testData/examples/EvaluateOnce.lg | 4 +- .../tests/testData/examples/ExceptionCatch.lg | 2 +- .../tests/testData/examples/Expand.lg | 78 ++ .../testData/examples/ExpressionExtract.lg | 68 ++ .../tests/testData/examples/LoopScope.lg | 5 + .../tests/testData/examples/MemoryScope.lg | 10 +- .../testData/examples/MultiFile-Part1.lg | 2 +- .../testData/examples/MultiFile-Part2.lg | 2 +- .../examples/MultilineTextForAdaptiveCard.lg | 2 +- .../testData/examples/NormalStructuredLG.lg | 188 ++++- .../tests/testData/examples/Regex.lg | 2 +- .../testData/examples/StructuredTemplate.lg | 40 +- .../testData/examples/TemplateAsFunction.lg | 25 +- .../testData/examples/TemplateNameWithDot.lg | 2 +- .../tests/testData/examples/TemplateRef.lg | 12 +- .../tests/testData/examples/herocard.json | 19 + .../testData/examples/importExamples/1.lg | 14 +- .../examples/importExamples/import.lg | 2 +- .../examples/importExamples/import/import3.lg | 2 +- .../tests/testData/examples/lgTemplate.lg | 8 +- .../tests/testData/examples/switchcase.lg | 14 +- .../exceptionExamples/ConditionFormatError.lg | 14 +- .../exceptionExamples/DuplicatedTemplates.lg | 6 +- .../exceptionExamples/ErrorExpression.lg | 2 +- .../ErrorStructuredTemplate.lg | 6 +- .../LgTemplateFunctionError.lg | 10 +- .../exceptionExamples/LoopDetected.lg | 4 +- .../testData/exceptionExamples/NoMatchRule.lg | 2 +- .../exceptionExamples/NoNormalTemplateBody.lg | 4 +- .../exceptionExamples/NoTemplateRef.lg | 8 +- .../SwitchCaseFormatError.lg | 48 +- .../exceptionExamples/SwitchCaseWarning.lg | 8 +- .../TemplateParamsNotMatchArgsNum.lg | 6 +- .../tests/testData/mslgTool/CollateFile1.lg | 2 +- .../tests/testData/mslgTool/CollateFile2.lg | 4 +- .../tests/testData/mslgTool/CollateFile3.lg | 16 +- .../testData/mslgTool/StaticCheckerErrors.lg | 6 +- .../tests/testData/mslgTool/StructuredLG.lg | 24 +- .../tests/testData/mslgTool/ValidFile.lg | 32 +- .../src/builtInFunction.ts | 459 +++++------ .../src/commonRegex.ts | 40 +- .../botframework-expressions/src/constant.ts | 3 +- .../src/expression.ts | 7 +- .../src/expressionEvaluator.ts | 7 +- .../src/expressionParser.ts | 1 - .../src/expressionType.ts | 13 +- .../src/extensions.ts | 29 +- .../botframework-expressions/src/index.ts | 2 +- .../src/memory/composedMemory.ts | 35 + .../src/memory/index.ts | 11 + .../src/memory/memoryInterface.ts | 36 + .../src/memory/simpleObjectMemory.ts | 143 ++++ .../src/parser/Expression.g4 | 20 +- .../src/parser/expressionEngine.ts | 52 -- .../src/parser/generated/ExpressionLexer.ts | 197 +++-- .../parser/generated/ExpressionListener.ts | 28 - .../src/parser/generated/ExpressionParser.ts | 264 ++----- .../src/parser/generated/ExpressionVisitor.ts | 18 - .../src/parser/parseErrorListener.ts | 1 + .../tests/badExpression.test.js | 11 +- .../tests/expression.test.js | 62 +- 100 files changed, 3465 insertions(+), 2568 deletions(-) create mode 100644 libraries/botbuilder-lg/src/activityChecker.ts create mode 100644 libraries/botbuilder-lg/src/customizedMemory.ts delete mode 100644 libraries/botbuilder-lg/src/customizedMemoryScope.ts create mode 100644 libraries/botbuilder-lg/tests/ActivityChecker.test.js rename libraries/botbuilder-lg/tests/{ActivityFactoryTests.js => ActivityFactory.test.js} (72%) create mode 100644 libraries/botbuilder-lg/tests/testData/adaptiveCard.json create mode 100644 libraries/botbuilder-lg/tests/testData/examples/DiagnosticStructuredLG.lg create mode 100644 libraries/botbuilder-lg/tests/testData/examples/Expand.lg create mode 100644 libraries/botbuilder-lg/tests/testData/examples/ExpressionExtract.lg create mode 100644 libraries/botbuilder-lg/tests/testData/examples/LoopScope.lg create mode 100644 libraries/botbuilder-lg/tests/testData/examples/herocard.json create mode 100644 libraries/botframework-expressions/src/memory/composedMemory.ts create mode 100644 libraries/botframework-expressions/src/memory/index.ts create mode 100644 libraries/botframework-expressions/src/memory/memoryInterface.ts create mode 100644 libraries/botframework-expressions/src/memory/simpleObjectMemory.ts diff --git a/libraries/botbuilder-lg/src/LGFileLexer.g4 b/libraries/botbuilder-lg/src/LGFileLexer.g4 index 4debde999b..b2fcfa8105 100644 --- a/libraries/botbuilder-lg/src/LGFileLexer.g4 +++ b/libraries/botbuilder-lg/src/LGFileLexer.g4 @@ -36,6 +36,8 @@ fragment U: 'u' | 'U'; fragment W: 'w' | 'W'; fragment STRING_LITERAL : ('\'' (~['\r\n])* '\'') | ('"' (~["\r\n])* '"'); +fragment EXPRESSION_FRAGMENT : '@' '{' (STRING_LITERAL| ~[\r\n{}'"] )*? '}'; +fragment ESCAPE_CHARACTER_FRAGMENT : '\\' ~[\r\n]?; COMMENTS : ('>'|'$') ~('\r'|'\n')+ NEWLINE? -> skip @@ -60,6 +62,7 @@ DASH LEFT_SQUARE_BRACKET : '[' -> pushMode(STRUCTURED_TEMPLATE_BODY_MODE) ; + RIGHT_SQUARE_BRACKET : ']' ; @@ -121,6 +124,10 @@ WS_IN_BODY : WHITESPACE+ -> type(WS) ; +MULTILINE_PREFIX + : '```' -> pushMode(MULTILINE) + ; + NEWLINE_IN_BODY : '\r'? '\n' {this.ignoreWS = true;} -> type(NEWLINE), popMode ; @@ -146,31 +153,19 @@ CASE ; DEFAULT - : D E F A U L T WHITESPACE* ':' {this.expectKeywords}? {this.ignoreWS = true;} - ; - -MULTI_LINE_TEXT - : '```' .*? '```' { this.ignoreWS = false; this.expectKeywords = false;} + : D E F A U L T WHITESPACE* ':' {this.expectKeywords}? { this.ignoreWS = true;} ; ESCAPE_CHARACTER - : '\\{' | '\\[' | '\\\\' | '\\'[rtn\]}] { this.ignoreWS = false; this.expectKeywords = false;} + : ESCAPE_CHARACTER_FRAGMENT { this.ignoreWS = false; this.expectKeywords = false;} ; EXPRESSION - : '@'? '{' (~[\r\n{}'"] | STRING_LITERAL)*? '}' { this.ignoreWS = false; this.expectKeywords = false;} - ; - -TEMPLATE_REF - : '[' (~[\r\n[\]] | TEMPLATE_REF)* ']' { this.ignoreWS = false; this.expectKeywords = false;} - ; - -TEXT_SEPARATOR - : [\t\r\n{}[\]()] { this.ignoreWS = false; this.expectKeywords = false;} + : EXPRESSION_FRAGMENT { this.ignoreWS = false; this.expectKeywords = false;} ; TEXT - : ~[\t\r\n{}[\]()]+? { this.ignoreWS = false; this.expectKeywords = false;} + : ~[\r\n]+? { this.ignoreWS = false; this.expectKeywords = false;} ; mode STRUCTURED_TEMPLATE_BODY_MODE; @@ -190,7 +185,25 @@ STRUCTURED_NEWLINE STRUCTURED_TEMPLATE_BODY_END : WS_IN_STRUCTURED? RIGHT_SQUARE_BRACKET WS_IN_STRUCTURED? -> popMode ; - + STRUCTURED_CONTENT : ~[\r\n]+ + ; + +mode MULTILINE; + +MULTILINE_SUFFIX + : '```' -> popMode + ; + +MULTILINE_ESCAPE_CHARACTER + : ESCAPE_CHARACTER_FRAGMENT -> type(ESCAPE_CHARACTER) + ; + +MULTILINE_EXPRESSION + : EXPRESSION_FRAGMENT -> type(EXPRESSION) + ; + +MULTILINE_TEXT + : (('\r'? '\n') | ~[\r\n])+? -> type(TEXT) ; \ No newline at end of file diff --git a/libraries/botbuilder-lg/src/LGFileParser.g4 b/libraries/botbuilder-lg/src/LGFileParser.g4 index d08a5bc9b4..5720040559 100644 --- a/libraries/botbuilder-lg/src/LGFileParser.g4 +++ b/libraries/botbuilder-lg/src/LGFileParser.g4 @@ -1,5 +1,4 @@ parser grammar LGFileParser; - options { tokenVocab=LGFileLexer; } file @@ -41,11 +40,11 @@ parameters ; templateBody - : normalTemplateBody #normalBody - | ifElseTemplateBody #ifElseBody + : normalTemplateBody #normalBody + | ifElseTemplateBody #ifElseBody | switchCaseTemplateBody #switchCaseBody | structuredTemplateBody #structuredBody - ; + ; structuredTemplateBody : structuredBodyNameLine structuredBodyContentLine? structuredBodyEndLine @@ -73,12 +72,12 @@ templateString ; normalTemplateString - : DASH (WS|TEXT|EXPRESSION|TEMPLATE_REF|TEXT_SEPARATOR|MULTI_LINE_TEXT|ESCAPE_CHARACTER)* + : DASH (WS|TEXT|EXPRESSION|ESCAPE_CHARACTER|MULTILINE_SUFFIX|MULTILINE_PREFIX)* ; errorTemplateString - : INVALID_TOKEN_DEFAULT_MODE+ - ; + : INVALID_TOKEN_DEFAULT_MODE+ + ; ifElseTemplateBody : ifConditionRule+ diff --git a/libraries/botbuilder-lg/src/activityChecker.ts b/libraries/botbuilder-lg/src/activityChecker.ts new file mode 100644 index 0000000000..fd8c8e4d9a --- /dev/null +++ b/libraries/botbuilder-lg/src/activityChecker.ts @@ -0,0 +1,312 @@ +/** + * @module botbuilder-lg + */ +/** + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. + */ + +import { ActivityTypes, ActionTypes } from 'botbuilder-core'; +import { CardFactory } from 'botbuilder-core'; +import { Diagnostic, DiagnosticSeverity } from './diagnostic'; +import { Range } from './range'; +import { Position } from './position'; +import { Evaluator } from './evaluator'; + +export class ActivityChecker { + public static readonly genericCardTypeMapping: Map = new Map + ([ + [ 'herocard', CardFactory.contentTypes.heroCard ], + [ 'thumbnailcard', CardFactory.contentTypes.thumbnailCard ], + [ 'audiocard', CardFactory.contentTypes.audioCard ], + [ 'videocard', CardFactory.contentTypes.videoCard ], + [ 'animationcard', CardFactory.contentTypes.animationCard ], + [ 'signincard', CardFactory.contentTypes.signinCard ], + [ 'oauthcard', CardFactory.contentTypes.oauthCard ], + [ 'receiptcard', CardFactory.contentTypes.receiptCard ], + ]); + + public static readonly activityProperties: string[] = ['type','id','timestamp','localTimestamp','localTimezone','callerId', + 'serviceUrl','channelId','from','conversation','recipient','textFormat','attachmentLayout','membersAdded', + 'membersRemoved','reactionsAdded','reactionsRemoved','topicName','historyDisclosed','locale','text','speak', + 'inputHint','summary','suggestedActions','attachments','entities','channelData','action','replyToId','label', + 'valueType','value','name','typrelatesToe','code','expiration','importance','deliveryMode','listenFor', + 'textHighlights','semanticAction']; + + public static readonly cardActionProperties: string[] = ['type','title','image','text','displayText','value','channelData']; + + public static check(lgResult: any): Diagnostic[] { + if (lgResult === undefined) { + return [this.buildDiagnostic('LG output is empty', false)]; + } + + if (typeof lgResult === 'string') { + if (!lgResult.startsWith('{') || !lgResult.endsWith('}')) { + return [this.buildDiagnostic('LG output is not a json object, and will fallback to string format.', false)]; + } + + let lgStructuredResult: any = undefined; + + try { + lgStructuredResult = JSON.parse(lgResult); + } catch (error) { + return [this.buildDiagnostic('LG output is not a json object, and will fallback to string format.', false)]; + } + + return this.checkStructuredResult(lgStructuredResult); + } else { + return this.checkStructuredResult(lgResult); + } + } + + public static checkStructuredResult(input: any): Diagnostic[] { + const result: Diagnostic[] = []; + const type: string = this.getStructureType(input); + if (ActivityChecker.genericCardTypeMapping.has(type) || type === 'attachment') { + result.push(...this.checkAttachment(input)); + } else if (type === 'activity') { + result.push(...this.checkActivity(input)); + } else { + const diagnosticMessage: string = (type === undefined || type === '') ? + `'${ Evaluator.LGType }' does not exist in lg output json object.` + : `Type '${ type }' is not supported currently.`; + result.push(this.buildDiagnostic(diagnosticMessage)); + } + + return result; + } + + private static checkActivity(input: any): Diagnostic[] { + const result: Diagnostic[] = []; + let activityType: string = undefined; + if ('type' in input) { + activityType = input['type'].toString().trim(); + } + + result.push(...this.checkActivityType(activityType)); + result.push(...this.checkActivityPropertyName(input)); + result.push(...this.checkActivityProperties(input)); + + return result; + } + + private static checkActivityType(activityType: string): Diagnostic[] { + if (activityType !== undefined) { + if (!Object.values(ActivityTypes).map((u: string): string => u.toLowerCase()).includes(activityType.toLowerCase())) { + return [this.buildDiagnostic(`'${ activityType }' is not a valid activity type.`)]; + } + } + return []; + } + + private static checkActivityPropertyName(input: any): Diagnostic[] { + const invalidProperties: string[] = []; + for (const property of Object.keys(input)) { + if (property === Evaluator.LGType) { + continue; + } + if (!ActivityChecker.activityProperties.map((u: string): string => u.toLowerCase()).includes(property.toLowerCase())) { + invalidProperties.push(property); + } + } + if (invalidProperties.length > 0) { + return [this.buildDiagnostic(`'${ invalidProperties.join(',') }' not support in Activity.`, false)]; + } + + return []; + } + + private static checkActivityProperties(input: any): Diagnostic[] { + const result: Diagnostic[] = []; + for (const key of Object.keys(input)) { + const property: string = key.trim(); + const value: any = input[key]; + + switch (property.toLowerCase()) { + case 'attachments': + result.push(...this.checkAttachments(value)); + break; + case 'suggestedactions': + result.push(...this.checkSuggestions(value)); + break; + default: + break; + } + } + + return result; + } + + private static checkSuggestions(value: any): Diagnostic[] { + const actions: any[] = this.normalizedToList(value); + return this.checkCardActions(actions); + } + + private static checkButtons(value: any): Diagnostic[] { + const actions: any[] = this.normalizedToList(value); + return this.checkCardActions(actions); + } + + private static checkCardActions(actions: any[]): Diagnostic[] { + const result: Diagnostic[] = []; + actions.forEach((u: any): void => { result.push(...this.checkCardAction(u)); }); + return result; + } + + private static checkCardAction(value: any): Diagnostic[] { + const result: Diagnostic[] = []; + if (typeof value === 'string') { + return result; + } + + if (typeof value === 'object') { + const type: string = this.getStructureType(value); + if (type !== 'cardaction'){ + result.push(this.buildDiagnostic(`'${ type }' is not card action type.`, false)); + } else { + result.push(...this.checkCardActionPropertyName(value)); + if ('type' in value) { + result.push(...this.checkCardActionType(value['type'])); + } + } + } else { + result.push(this.buildDiagnostic(`'${ value }' is not a valid card action format.`, false)); + } + + return result; + } + + + private static checkCardActionPropertyName(input: any): Diagnostic[] { + const invalidProperties: string[] = []; + for (const property of Object.keys(input)) { + if (property === Evaluator.LGType) { + continue; + } + if (!ActivityChecker.cardActionProperties.map((u: string): string => u.toLowerCase()).includes(property.toLowerCase())) { + invalidProperties.push(property); + } + } + if (invalidProperties.length > 0) { + return [this.buildDiagnostic(`'${ invalidProperties.join(',') }' not support in card action.`, false)]; + } + + return []; + } + + private static checkCardActionType(cardActionType: string): Diagnostic[] { + const result: Diagnostic[] = []; + if (!cardActionType) { + return result; + } + + if (!Object.values(ActionTypes).map((u: string): string => u.toLowerCase()).includes(cardActionType.toLowerCase())) { + return [this.buildDiagnostic(`'${ cardActionType }' is not a valid card action type.`)]; + } + + return result; + } + + private static checkAttachments(value: any): Diagnostic[] { + const result: Diagnostic[] = []; + const attachmentsJsonList: any[] = this.normalizedToList(value); + + for (const attachmentsJson of attachmentsJsonList) { + if (typeof attachmentsJson === 'object') { + result.push(...this.checkAttachment(attachmentsJson)); + } + } + + return result; + } + + private static checkAttachment(value: any): Diagnostic[] { + const result: Diagnostic[] = []; + const type: string = this.getStructureType(value); + if (ActivityChecker.genericCardTypeMapping.has(type)) { + result.push(...this.checkCardAttachment(value)); + } else if (type === 'adaptivecard') { + // TODO + // check adaptivecard format + } else if(type === 'attachment') { + // TODO + // Check attachment format + } else { + result.push(this.buildDiagnostic(`'${ type }' is not an attachment type.`, false)); + } + + return result; + } + + private static checkCardAttachment(input: any): Diagnostic[] { + const result: Diagnostic[] = []; + for (const key of Object.keys(input)) { + const property: string = key.trim().toLowerCase(); + const value: any = input[key]; + + switch (property) { + case 'buttons': + result.push(...this.checkButtons(value)); + break; + case 'autostart': + case 'shareable': + case 'autoloop': + const boolValue: boolean = this.getValidBooleanValue(value.toString()); + if (boolValue === undefined) { + result.push(this.buildDiagnostic(`'${ value.toString() }' is not a boolean value.`)); + } + break; + default: + break; + } + } + + return result; + } + + private static getStructureType(input: any): string { + let result = ''; + + if (input !== undefined) { + if (Evaluator.LGType in input) { + result = input[Evaluator.LGType].toString(); + } else if ('type' in input) { + // Adaptive card type + result = input['type'].toString(); + } + } + + return result.trim().toLowerCase(); + } + + + private static getValidBooleanValue(boolValue: string): boolean{ + if (boolValue.toLowerCase() === 'true') + { + return true; + } + else if (boolValue.toLowerCase() === 'false') + { + return false; + } + + return undefined; + } + + private static buildDiagnostic(message: string, isError: boolean = true): Diagnostic { + message = message === undefined ? '' : message; + const emptyRange: Range = new Range(new Position(0, 0), new Position(0, 0)); + return isError ? new Diagnostic(emptyRange, message, DiagnosticSeverity.Error) + : new Diagnostic(emptyRange, message, DiagnosticSeverity.Warning); + } + + private static normalizedToList(item: any): any[] { + if (item === undefined) { + return []; + } else if (Array.isArray(item)){ + return item; + } else { + return [item]; + } + } +} \ No newline at end of file diff --git a/libraries/botbuilder-lg/src/activityFactory.ts b/libraries/botbuilder-lg/src/activityFactory.ts index 4e678f7528..0dd88eb18b 100644 --- a/libraries/botbuilder-lg/src/activityFactory.ts +++ b/libraries/botbuilder-lg/src/activityFactory.ts @@ -8,30 +8,33 @@ import { Activity, SuggestedActions, Attachment, ActivityTypes, ActionTypes, CardAction } from 'botframework-schema'; import { MessageFactory, CardFactory } from 'botbuilder-core'; +import { Diagnostic, DiagnosticSeverity } from './diagnostic'; +import { ActivityChecker } from './activityChecker'; +import { Evaluator } from './evaluator'; /** * The ActivityFactory * to generate text and then uses simple markdown semantics like chatdown to create Activity. */ export class ActivityFactory { - private static genericCardTypeMapping: Map = new Map - ([ - [ 'herocard', CardFactory.contentTypes.heroCard ], - [ 'thumbnailcard', CardFactory.contentTypes.thumbnailCard ], - [ 'audiocard', CardFactory.contentTypes.audioCard ], - [ 'videocard', CardFactory.contentTypes.videoCard ], - [ 'animationcard', CardFactory.contentTypes.animationCard ], - [ 'signincard', CardFactory.contentTypes.signinCard ], - [ 'oauthcard', CardFactory.contentTypes.oauthCard ], - ]); + private static adaptiveCardType: string = CardFactory.contentTypes.adaptiveCard; /** * Generate the activity. * @param lgResult string result from languageGenerator. */ - public static CreateActivity(lgResult: any): Partial { + public static createActivity(lgResult: any): Partial { + const diagnostics: Diagnostic[] = ActivityChecker.check(lgResult); + const errors: Diagnostic[] = diagnostics.filter((u: Diagnostic): boolean => u.severity === DiagnosticSeverity.Error); + if (errors !== undefined && errors.length > 0) { + throw new Error(`${ errors.join('\n') }`); + } + if (typeof lgResult === 'string') { - return this.buildActivityFromText(lgResult.trim()); + const structuredLGResult: any = this.parseStructuredLGResult(lgResult.trim()); + return structuredLGResult === undefined ? + this.buildActivityFromText(lgResult.trim()) + :this.buildActivityFromLGStructuredResult(lgResult); } return this.buildActivityFromLGStructuredResult(lgResult); @@ -53,81 +56,41 @@ export class ActivityFactory { let activity: Partial = {}; const type: string = this.getStructureType(lgValue); - if (ActivityFactory.genericCardTypeMapping.has(type)) { - const attachment: Attachment = this.getAttachment(lgValue); - if (attachment !== undefined) { - activity = MessageFactory.attachment(attachment); - } else { - throw new Error(`'${ lgValue }' is not an attachment format.`); - } + if (ActivityChecker.genericCardTypeMapping.has(type) || type === 'attachment') { + activity = MessageFactory.attachment(this.getAttachment(lgValue)); } else if (type === 'activity') { - activity = this.buildActivityFromObject(lgValue); - } else { - throw new Error(`type ${ type } is not support currently.`); - } - - return activity; - } - - private static buildActivityFromObject(activityValue: any): Partial { - let activity: Partial = {}; - - if ('type' in activityValue && activityValue.type === 'event') { - activity = this.buildEventActivity(activityValue); - } else { - activity = this.buildMessageActivity(activityValue); - } - - return activity; - } - - private static buildEventActivity(eventValue: any): Partial { - let activity: Partial = { type: ActivityTypes.Event }; - for (const item of Object.keys(eventValue)) { - const property: string = item.trim(); - const value: any = eventValue[item]; - switch (property.toLowerCase()) { - case 'name': - activity.name = value.toString(); - break; - case 'value': - activity.value = value.toString(); - break; - default: - activity[property] = value; - break; - } + activity = this.buildActivity(lgValue); } return activity; } - private static buildMessageActivity(messageValue: any): Partial { + private static buildActivity(messageValue: any): Partial { let activity: Partial = { type: ActivityTypes.Message }; for (const key of Object.keys(messageValue)) { const property: string = key.trim(); + if (property === Evaluator.LGType) { + continue; + } + const value: any = messageValue[key]; switch (property.toLowerCase()) { - case 'text': - activity.text = value.toString(); - break; - case 'speak': - activity.speak = value.toString(); - break; - case 'inputhint': - activity.inputHint = value.toString(); - break; case 'attachments': activity.attachments = this.getAttachments(value); break; case 'suggestedactions': activity.suggestedActions = this.getSuggestions(value); break; - case 'attachmentlayout': - activity.attachmentLayout = value.toString(); default: - activity[property] = value; + var properties = ActivityChecker.activityProperties.map((u: string): string => u.toLowerCase()); + if (properties.includes(property.toLowerCase())) + { + var realPropertyName = ActivityChecker.activityProperties[properties.indexOf(property.toLowerCase())]; + activity[realPropertyName] = value; + } else { + activity[property.toLowerCase()] = value; + } break; } } @@ -136,9 +99,9 @@ export class ActivityFactory { } private static getSuggestions(suggestionsValue: any): SuggestedActions { - let actions: any[] = this.normalizedToList(suggestionsValue); + const actions: any[] = this.normalizedToList(suggestionsValue); - let suggestedActions: SuggestedActions = { + const suggestedActions: SuggestedActions = { actions : this.getCardActions(actions), to: [] }; @@ -147,77 +110,63 @@ export class ActivityFactory { } private static getButtons(buttonsValue: any): CardAction[] { - let actions: any[] = this.normalizedToList(buttonsValue); + const actions: any[] = this.normalizedToList(buttonsValue); return this.getCardActions(actions); } private static getCardActions(actions: any[]): CardAction[] { - let cardActions: (string|CardAction)[] = []; - for (const action of actions) { - if (typeof action === 'string') { - cardActions.push(action.trim()); - } else { - const cardAction: CardAction = this.getCardAction(action); - if (cardAction !== undefined) { - cardActions.push(cardAction); - } - } - } - - return CardFactory.actions(cardActions); + return actions.map((u: any): CardAction => this.getCardAction(u)); } - private static getCardAction(cardActionValue: any): CardAction { - const type: string = this.getStructureType(cardActionValue); - let cardAction: CardAction = { - type: ActionTypes.ImBack, - title: '', - value: '' - }; - - if(type !== 'cardaction') { - return undefined; - } - - for (const key of Object.keys(cardActionValue)) { - const property: string = key.trim(); - const value: any = cardActionValue[key]; + private static getCardAction(action: any): CardAction + { + let cardAction: CardAction; + if (typeof action === 'string') { + cardAction = { type: ActionTypes.ImBack, value: action, title: action, channelData: undefined }; + } else { + const type: string = this.getStructureType(action); + cardAction = { + type: ActionTypes.ImBack, + title: '', + value: '' + }; + + if(type === 'cardaction') { + for (const key of Object.keys(action)) { + const property: string = key.trim(); + if (property === Evaluator.LGType) { + continue; + } - switch (property.toLowerCase()) { - case 'type': - cardAction.type = value.toString(); - break; - case 'title': - cardAction.title = value.toString(); - break; - case 'value': - cardAction.value = value.toString(); - break; - case 'displaytext': - cardAction.displayText = value.toString(); - break; - case 'text': - cardAction.text = value.toString(); - break; - case 'image': - cardAction.image = value.toString(); - break; - default: - cardAction[property] = value; - break; + const value: any = action[key]; + + switch (property.toLowerCase()) { + case 'displaytext': + cardAction.displayText = value; + break; + case 'channeldata': + cardAction.channelData = value; + break; + default: + cardAction[property.toLowerCase()] = value; + break; + } + } } } return cardAction; } + private static getStructureType(input: any): string { let result = ''; - if (input !== undefined) { - if ('$type' in input) { - result = input['$type'].toString(); + if (input && typeof input === 'object') { + if (Evaluator.LGType in input) { + result = input[Evaluator.LGType].toString(); } else if ('type' in input) { + // Adaptive card type result = input['type'].toString(); } } @@ -226,19 +175,12 @@ export class ActivityFactory { } private static getAttachments(input: any): Attachment[] { - let attachments: Attachment[] = []; - let attachmentsJsonList: any[] = this.normalizedToList(input); + const attachments: Attachment[] = []; + const attachmentsJsonList: any[] = this.normalizedToList(input); for (const attachmentsJson of attachmentsJsonList) { if (typeof attachmentsJson === 'object') { - const attachment = this.getAttachment(attachmentsJson); - if (attachment !== undefined) { - attachments.push(attachment); - } else { - throw new Error(`'${ attachmentsJson }' is not an attachment format.`); - } - } else { - throw new Error(`'${ attachmentsJson }' is not an attachment format.`); + attachments.push(this.getAttachment(attachmentsJson)); } } @@ -250,36 +192,63 @@ export class ActivityFactory { contentType: '' }; const type: string = this.getStructureType(input); - if (ActivityFactory.genericCardTypeMapping.has(type)) { - attachment = this.getCardAttachment(ActivityFactory.genericCardTypeMapping.get(type), input); + if (ActivityChecker.genericCardTypeMapping.has(type)) { + attachment = this.getCardAttachment(ActivityChecker.genericCardTypeMapping.get(type), input); } else if(type === 'adaptivecard') { attachment = CardFactory.adaptiveCard(input); + } else if(type === 'attachment') { + attachment = this.getNormalAttachment(input); } else { - attachment = undefined; + attachment = {contentType: type, content: input}; + } + + return attachment; + } + + private static getNormalAttachment(input: any): Attachment { + const attachment: Attachment = {contentType:''}; + + for (const key of Object.keys(input)) { + const property: string = key.trim(); + const value: any = input[key]; + + switch (property.toLowerCase()) { + case 'contenttype': + const type: string = value.toString().toLowerCase(); + if (ActivityChecker.genericCardTypeMapping.has(type)) { + attachment.contentType = ActivityChecker.genericCardTypeMapping.get(type); + } else if (type === 'adaptivecard') { + attachment.contentType = this.adaptiveCardType; + } else { + attachment.contentType = type; + } + break; + case 'contenturl': + attachment.contentUrl = value; + break; + case 'thumbnailurl': + attachment.thumbnailUrl = value; + break; + default: + attachment[property.toLowerCase()] = value; + break; + } } return attachment; } private static getCardAttachment(type: string, input: any): Attachment { - let card: any = {}; + const card: any = {}; - for (const key in input) { + for (const key of Object.keys(input)) { const property: string = key.trim().toLowerCase(); const value: any = input[key]; switch (property) { - case 'title': - case 'subtitle': - case 'text': - case 'aspect': - case 'value': - card[property] = value; + case 'tap': + card[property] = this.getCardAction(value); break; - case 'connectionname': - card['connectionName'] = value; - break; - case 'image': case 'images': if (type === CardFactory.contentTypes.heroCard || type === CardFactory.contentTypes.thumbnailCard) { @@ -287,7 +256,7 @@ export class ActivityFactory { card['images'] = []; } - let imageList: string[] = this.normalizedToList(value).map((u): string => u.toString()); + const imageList: string[] = this.normalizedToList(value).map((u): string => u.toString()); imageList.forEach( (u): any => card['images'].push({url : u})); } else { card['image'] = {url: value.toString()}; @@ -298,7 +267,7 @@ export class ActivityFactory { card['media'] = []; } - let mediaList: string[] = this.normalizedToList(value).map((u): string => u.toString()); + const mediaList: string[] = this.normalizedToList(value).map((u): string => u.toString()); mediaList.forEach( (u): any => card['media'].push({url : u})); break; case 'buttons': @@ -306,7 +275,7 @@ export class ActivityFactory { card['buttons'] = []; } - let buttons: any[] = this.getButtons(value); + const buttons: any[] = this.getButtons(value); buttons.forEach( (u): any => card[property].push(u)); break; case 'autostart': @@ -317,8 +286,11 @@ export class ActivityFactory { card[property] = boolValue; } break; + case 'connectionname': + card['connectionName'] = value; + break; default: - card[property] = value; + card[property.toLowerCase()] = value; break; } } @@ -332,11 +304,11 @@ export class ActivityFactory { } private static getValidBooleanValue(boolValue: string): boolean{ - if (boolValue.toLowerCase() == 'true') + if (boolValue.toLowerCase() === 'true') { return true; } - else if (boolValue.toLowerCase() == 'false') + else if (boolValue.toLowerCase() === 'false') { return false; } @@ -354,4 +326,25 @@ export class ActivityFactory { } } + private static parseStructuredLGResult(lgStringResult: string): any + { + let lgStructuredResult: any = undefined; + if (lgStringResult === undefined || lgStringResult === '') { + return undefined; + } + + lgStringResult = lgStringResult.trim(); + + if (lgStringResult === '' || !lgStringResult.startsWith('{') || !lgStringResult.endsWith('}')){ + return undefined; + } + + try { + lgStructuredResult = JSON.parse(lgStringResult); + } catch (error) { + return undefined; + } + + return lgStructuredResult; + } } \ No newline at end of file diff --git a/libraries/botbuilder-lg/src/analyzer.ts b/libraries/botbuilder-lg/src/analyzer.ts index 85d2f97477..6c88eeb637 100644 --- a/libraries/botbuilder-lg/src/analyzer.ts +++ b/libraries/botbuilder-lg/src/analyzer.ts @@ -43,8 +43,6 @@ export class Analyzer extends AbstractParseTreeVisitor implement public readonly templateMap: {[name: string]: LGTemplate}; private readonly evalutationTargetStack: EvaluationTarget[] = []; private readonly _expressionParser: ExpressionParserInterface; - private readonly escapeSeperatorRegex: RegExp = new RegExp(/\|(?!\\)/g); - private readonly expressionRecognizeRegex: RegExp = new RegExp(/\}(?!\\).+?\{(?!\\)@?/g); public constructor(templates: LGTemplate[], expressionEngine: ExpressionEngine) { super(); @@ -118,7 +116,7 @@ export class Analyzer extends AbstractParseTreeVisitor implement // make it insensitive const property: string = line.substr(0, start).trim().toLowerCase(); const originValue: string = line.substr(start + 1).trim(); - const valueArray: string[] = Evaluator.wrappedRegExSplit(originValue, this.escapeSeperatorRegex); + const valueArray: string[] = Evaluator.wrappedRegExSplit(originValue, Evaluator.escapeSeperatorReverseRegex); if (valueArray.length === 1) { result.union(this.analyzeText(originValue)); } else { @@ -162,7 +160,7 @@ export class Analyzer extends AbstractParseTreeVisitor implement if (expressions.length > 0) { result.union(this.analyzeExpression(expressions[0].text)); } - if (iterNode.normalTemplateBody() !== undefined) { + if (iterNode.normalTemplateBody()) { result.union(this.visit(iterNode.normalTemplateBody())); } } @@ -172,26 +170,9 @@ export class Analyzer extends AbstractParseTreeVisitor implement public visitNormalTemplateString(ctx: lp.NormalTemplateStringContext): AnalyzerResult { const result: AnalyzerResult = new AnalyzerResult(); - for (const node of ctx.children) { - const innerNode: TerminalNode = node as TerminalNode; - switch (innerNode.symbol.type) { - case lp.LGFileParser.DASH: break; - case lp.LGFileParser.EXPRESSION: { - result.union(this.analyzeExpression(innerNode.text)); - break; - } - case lp.LGFileParser.TEMPLATE_REF: { - result.union(this.analyzeTemplateRef(innerNode.text)); - break; - } - case lp.LGFileParser.MULTI_LINE_TEXT: { - result.union(this.analyzeMultiLineText(innerNode.text)); - break; - } - default: { - break; - } - } + + for (const expression of ctx.EXPRESSION()) { + result.union(this.analyzeExpression(expression.text)); } return result; @@ -238,7 +219,7 @@ export class Analyzer extends AbstractParseTreeVisitor implement private analyzeTextContainsExpression(exp: string): AnalyzerResult { const result: AnalyzerResult = new AnalyzerResult(); - const reversedExps: RegExpMatchArray = exp.split('').reverse().join('').match(this.expressionRecognizeRegex); + const reversedExps: RegExpMatchArray = exp.split('').reverse().join('').match(Evaluator.expressionRecognizeReverseRegex); const expressionsRaw: string[] = reversedExps.map((e: string): string => e.split('').reverse().join('')).reverse(); const expressions: string[] = expressionsRaw.filter((e: string): boolean => e.length > 0); expressions.forEach((item: string): AnalyzerResult => result.union(this.analyzeExpression(item))); @@ -260,38 +241,19 @@ export class Analyzer extends AbstractParseTreeVisitor implement return result; } - private analyzeTemplateRef(exp: string): AnalyzerResult { - exp = exp.replace(/(^\[*)/g, '') - .replace(/(\]*$)/g, ''); - exp = exp.indexOf('(') < 0 ? exp.concat('()') : exp; - - return this.analyzeExpression(exp); - } - - private analyzeMultiLineText(exp: string): AnalyzerResult { - const result: AnalyzerResult = new AnalyzerResult(); - exp = exp.substr(3, exp.length - 6); - const matches: string[] = exp.split('').reverse().join('').match(this.expressionRecognizeRegex).map((e: string): string => e.split('').reverse().join('')).reverse(); - for (const match of matches) { - result.union(this.analyzeExpression(match)); - } - - return result; - } - private currentTarget(): EvaluationTarget { return this.evalutationTargetStack[this.evalutationTargetStack.length - 1]; } private isPureExpression(exp: string): boolean { - if (exp === undefined || exp.length === 0) { + if (!exp) { return false; } exp = exp.trim(); - const reversedExps: RegExpMatchArray = exp.split('').reverse().join('').match(this.expressionRecognizeRegex); + const reversedExps: RegExpMatchArray = exp.split('').reverse().join('').match(Evaluator.expressionRecognizeReverseRegex); // If there is no match, expressions could be null - if (reversedExps === null || reversedExps === undefined || reversedExps.length !== 1) { + if (!reversedExps || reversedExps.length !== 1) { return false; } else { return reversedExps[0].split('').reverse().join('') === exp; diff --git a/libraries/botbuilder-lg/src/customizedMemory.ts b/libraries/botbuilder-lg/src/customizedMemory.ts new file mode 100644 index 0000000000..5671c55271 --- /dev/null +++ b/libraries/botbuilder-lg/src/customizedMemory.ts @@ -0,0 +1,46 @@ +/** + * @module botframework-expressions + */ +/** + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. + */ + +import { MemoryInterface, SimpleObjectMemory } from 'botframework-expressions'; + +export class CustomizedMemory implements MemoryInterface { + + public globalMemory: MemoryInterface; + public localMemory: MemoryInterface; + + public constructor(scope?: any) { + this.globalMemory = !scope ? undefined : SimpleObjectMemory.wrap(scope); + this.localMemory = undefined; + } + + public getValue(path: string): { value: any; error: string } { + let value: any; + let error = ''; + if (this.localMemory) { + ({value, error} = this.localMemory.getValue(path)); + if (!error && value) { + return {value, error}; + } + } + + if (this.globalMemory) { + return this.globalMemory.getValue(path); + } + + return {value, error}; + } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + public setValue(_path: string, _value: any): { value: any; error: string } { + return {value: undefined, error: `LG memory are readonly`}; + } + + public version(): string { + return '0'; + } +} \ No newline at end of file diff --git a/libraries/botbuilder-lg/src/customizedMemoryScope.ts b/libraries/botbuilder-lg/src/customizedMemoryScope.ts deleted file mode 100644 index dd94720fbd..0000000000 --- a/libraries/botbuilder-lg/src/customizedMemoryScope.ts +++ /dev/null @@ -1,40 +0,0 @@ -/** - * @module botbuilder-lg - */ -/** - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. - */ - -import { Extensions } from 'botframework-expressions'; - -/** - * This customized memory scope is designed for allow sub template evaluation can refer - * to the original evaluation scope passed in by wrap the original one in globalScope field - * and inherit that for each sub evaluation. - */ -export class CustomizedMemoryScope extends Map { - public globalScope: any; - private readonly localScope: any; - public constructor(localScope: any, globalScope: any) { - super(); - this.localScope = localScope; - this.globalScope = globalScope; - } - - public get(key: any): any { - let value: any; - let error: string; - ({ value, error } = Extensions.accessProperty(this.localScope, key)); - if (value !== undefined && error === undefined) { - return value; - } - - ({ value, error } = Extensions.accessProperty(this.globalScope, key)); - if (value !== undefined && error === undefined) { - return value; - } - - return undefined; - } -} diff --git a/libraries/botbuilder-lg/src/errorListener.ts b/libraries/botbuilder-lg/src/errorListener.ts index 738ded9f86..46ec74fe92 100644 --- a/libraries/botbuilder-lg/src/errorListener.ts +++ b/libraries/botbuilder-lg/src/errorListener.ts @@ -24,6 +24,7 @@ export class ErrorListener implements ANTLRErrorListener { line: number, charPositionInLine: number, msg: string, + // eslint-disable-next-line @typescript-eslint/no-unused-vars e: RecognitionException | undefined): void { const startPosition: Position = new Position(line, charPositionInLine); // tslint:disable-next-line: max-line-length diff --git a/libraries/botbuilder-lg/src/evaluationTarget.ts b/libraries/botbuilder-lg/src/evaluationTarget.ts index 8d4248a14a..68b85ebc46 100644 --- a/libraries/botbuilder-lg/src/evaluationTarget.ts +++ b/libraries/botbuilder-lg/src/evaluationTarget.ts @@ -1,3 +1,4 @@ + /** * @module botbuilder-lg */ @@ -5,7 +6,7 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. */ - +import { CustomizedMemory } from './customizedMemory'; /** * Runtime template context store */ @@ -21,10 +22,24 @@ export class EvaluationTarget { } public getId(): string { - if (this.scope !== undefined && this.scope !== null) { - return this.templateName + JSON.stringify(this.scope); + const memory = this.scope as CustomizedMemory; + let result = this.templateName; + if (memory) { + if (memory.globalMemory){ + const version = memory.globalMemory.version(); + if (version) { + result = result.concat(version); + } + } + + if (memory.localMemory){ + const localMemoryString = memory.localMemory.toString(); + if (localMemoryString) { + result = result.concat(localMemoryString); + } + } } - return this.templateName; + return result; } } diff --git a/libraries/botbuilder-lg/src/evaluator.ts b/libraries/botbuilder-lg/src/evaluator.ts index beaf54847e..6105fe36bc 100644 --- a/libraries/botbuilder-lg/src/evaluator.ts +++ b/libraries/botbuilder-lg/src/evaluator.ts @@ -10,11 +10,15 @@ import { AbstractParseTreeVisitor, TerminalNode } from 'antlr4ts/tree'; import { BuiltInFunctions, Constant, EvaluatorLookup, Expression, ExpressionEngine, ExpressionEvaluator, ExpressionType, ReturnType } from 'botframework-expressions'; import { keyBy } from 'lodash'; -import { CustomizedMemoryScope } from './customizedMemoryScope'; +import { CustomizedMemory } from './customizedMemory'; import { EvaluationTarget } from './evaluationTarget'; +import { SimpleObjectMemory } from 'botframework-expressions'; import * as lp from './generated/LGFileParser'; import { LGFileParserVisitor } from './generated/LGFileParserVisitor'; import { LGTemplate } from './lgTemplate'; +import { ImportResolver } from './importResolver'; +import * as path from 'path'; +import * as fs from 'fs'; /** * Evaluation tuntime engine @@ -25,8 +29,17 @@ export class Evaluator extends AbstractParseTreeVisitor implements LGFilePa public readonly expressionEngine: ExpressionEngine; public readonly templateMap: { [name: string]: LGTemplate }; private readonly evalutationTargetStack: EvaluationTarget[] = []; - private readonly expressionRecognizeRegex: RegExp = new RegExp(/\}(?!\\).+?\{(?!\\)@?/g); - private readonly escapeSeperatorRegex: RegExp = new RegExp(/\|(?!\\)/g); + + // to support broswer, use look-ahead replace look-behind + // original:/(? implements LGFilePa this.evalutationTargetStack.push(templateTarget); const result: string = this.visit(this.templateMap[templateName].parseTree); - if (previousEvaluateTarget !== undefined) { + if (previousEvaluateTarget) { previousEvaluateTarget.evaluatedChildren.set(currentEvulateId, result); } @@ -80,7 +93,7 @@ export class Evaluator extends AbstractParseTreeVisitor implements LGFilePa public visitStructuredTemplateBody(ctx: lp.StructuredTemplateBodyContext): any { const result: any = {}; const typeName: string = ctx.structuredBodyNameLine().STRUCTURED_CONTENT().text.trim(); - result.$type = typeName; + result[Evaluator.LGType] = typeName; const bodys: TerminalNode[] = ctx.structuredBodyContentLine().STRUCTURED_CONTENT(); for (const body of bodys) { @@ -94,7 +107,7 @@ export class Evaluator extends AbstractParseTreeVisitor implements LGFilePa const property: string = line.substr(0, start).trim().toLowerCase(); const originValue: string = line.substr(start + 1).trim(); - const valueArray: string[] = Evaluator.wrappedRegExSplit(originValue, this.escapeSeperatorRegex); + const valueArray: string[] = Evaluator.wrappedRegExSplit(originValue, Evaluator.escapeSeperatorReverseRegex); if (valueArray.length === 1) { result[property] = this.evalText(originValue); } else { @@ -105,7 +118,7 @@ export class Evaluator extends AbstractParseTreeVisitor implements LGFilePa result[property] = valueList; } - } else if (this.isPureExpression(line)) { + } else if (Evaluator.isPureExpression(line)) { // [MyStruct // Text = foo // {ST2()} @@ -116,14 +129,13 @@ export class Evaluator extends AbstractParseTreeVisitor implements LGFilePa const propertyObject: any = this.evalExpression(line); // Full reference to another structured template is limited to the structured template with same type - if (typeof propertyObject === 'object' && '$type' in propertyObject && propertyObject.$type.toString() === typeName) { + if (typeof propertyObject === 'object' && Evaluator.LGType in propertyObject && propertyObject[Evaluator.LGType].toString() === typeName) { for (const key of Object.keys(propertyObject)) { if (propertyObject.hasOwnProperty(key) && !(key in result)) { result[key] = propertyObject[key]; } } } - } } @@ -167,22 +179,16 @@ export class Evaluator extends AbstractParseTreeVisitor implements LGFilePa for (const node of ctx.children) { const innerNode: TerminalNode = node as TerminalNode; switch (innerNode.symbol.type) { - case lp.LGFileParser.DASH: break; + case lp.LGFileParser.MULTILINE_SUFFIX: + case lp.LGFileParser.MULTILINE_PREFIX: + case lp.LGFileParser.DASH: + break; case lp.LGFileParser.ESCAPE_CHARACTER: - result.push(this.evalEscapeCharacter(innerNode.text)); + result.push(this.evalEscape(innerNode.text)); break; - case lp.LGFileParser.EXPRESSION: { + case lp.LGFileParser.EXPRESSION: result.push(this.evalExpression(innerNode.text)); break; - } - case lp.LGFileParser.TEMPLATE_REF: { - result.push(this.evalTemplateRef(innerNode.text)); - break; - } - case lp.LGFileParser.MULTI_LINE_TEXT: { - result.push(this.evalMultiLineText(innerNode.text)); - break; - } default: { result.push(innerNode.text); break; @@ -204,7 +210,7 @@ export class Evaluator extends AbstractParseTreeVisitor implements LGFilePa } public constructScope(templateName: string, args: any[]): any { - if (this.templateMap[templateName] === undefined) { + if (!this.templateMap[templateName]) { throw new Error(`No such template ${ templateName }`); } @@ -216,17 +222,22 @@ export class Evaluator extends AbstractParseTreeVisitor implements LGFilePa return currentScope; } - if (parameters !== undefined && (args === undefined || parameters.length !== args.length)) { + if (parameters && (args === undefined || parameters.length !== args.length)) { throw new Error(`The length of required parameters does not match the length of provided parameters.`); } const newScope: any = {}; parameters.map((e: string, i: number): void => newScope[e] = args[i]); - if (currentScope instanceof CustomizedMemoryScope) { - return new CustomizedMemoryScope(newScope, currentScope.globalScope); + if (currentScope instanceof CustomizedMemory) { + //inherit current memory's global scope + const memory = new CustomizedMemory(); + memory.globalMemory = currentScope.globalMemory; + memory.localMemory = new SimpleObjectMemory(newScope); + + return memory; } else { - return new CustomizedMemoryScope(newScope, currentScope); + throw new Error(`Scope is a LG customized memory`); } } @@ -276,19 +287,22 @@ export class Evaluator extends AbstractParseTreeVisitor implements LGFilePa return this.evalutationTargetStack[this.evalutationTargetStack.length - 1]; } - private evalEscapeCharacter(exp: string): any { + private evalEscape(exp: string): string { + const validCharactersDict: any = { '\\r': '\r', '\\n': '\n', '\\t': '\t', '\\\\': '\\', - '\\[': '[', - '\\]': ']', - '\\{': '{', - '\\}': '}' }; - return validCharactersDict[exp]; + return exp.replace(/\\[^\r\n]?/g, (sub: string): string => { + if (sub in validCharactersDict) { + return validCharactersDict[sub]; + } else { + return sub.substr(1); + } + }); } private evalCondition(condition: lp.IfConditionContext): boolean { @@ -343,57 +357,27 @@ export class Evaluator extends AbstractParseTreeVisitor implements LGFilePa return result; } - private evalTemplateRef(exp: string): any { - exp = exp.replace(/(^\[*)/g, '') - .replace(/(\]*$)/g, ''); - - if (exp.indexOf('(') < 0) { - if (exp in this.templateMap) { - exp = exp.concat('(') - .concat(this.templateMap[exp].parameters.join()) - .concat(')'); - } else { - exp = exp.concat('()'); - } - } - - return this.evalExpression(exp); - } - - private evalMultiLineText(exp: string): string { - - exp = exp.substr(3, exp.length - 6); //remove ``` ``` - - return this.evalTextContainsExpression(exp); - } - - private evalTextContainsExpression(exp: string): string { - return this.wrappedEvalTextContainsExpression(exp, this.expressionRecognizeRegex); - } - private evalText(exp: string): any { - if (exp === undefined || exp.length === 0) { + if (!exp) { return exp; } - if (this.isPureExpression(exp)) { + if (Evaluator.isPureExpression(exp)) { return this.evalExpression(exp); } else { - - // unescape \| - return this.evalTextContainsExpression(exp).replace(/\\\|/g, '|'); + return this.evalEscape(this.wrappedEvalTextContainsExpression(exp, Evaluator.expressionRecognizeReverseRegex)); } } - private isPureExpression(exp: string): boolean { - if (exp === undefined || exp.length === 0) { + public static isPureExpression(exp: string): boolean { + if (!exp) { return false; } exp = exp.trim(); - const reversedExps: RegExpMatchArray = exp.split('').reverse().join('').match(this.expressionRecognizeRegex); + const reversedExps: RegExpMatchArray = exp.split('').reverse().join('').match(this.expressionRecognizeReverseRegex); // If there is no match, expressions could be null - if (reversedExps === null || reversedExps === undefined || reversedExps.length !== 1) { + if (!reversedExps || reversedExps.length !== 1) { return false; } else { return reversedExps[0].split('').reverse().join('') === exp; @@ -415,28 +399,98 @@ export class Evaluator extends AbstractParseTreeVisitor implements LGFilePa return baseLookup(name.substring(prebuiltPrefix.length)); } - if (this.templateMap[name] !== undefined) { + if (this.templateMap[name]) { // tslint:disable-next-line: max-line-length return new ExpressionEvaluator(name, BuiltInFunctions.apply(this.templateEvaluator(name)), ReturnType.Object, this.validTemplateReference); } - const lgTemplate = 'lgTemplate'; + if (name === Evaluator.templateFunctionName) { + return new ExpressionEvaluator(Evaluator.templateFunctionName, BuiltInFunctions.apply(this.templateFunction()), ReturnType.Object, this.validateTemplateFunction); + } + + if (name === Evaluator.fromFileFunctionName) { + return new ExpressionEvaluator(Evaluator.fromFileFunctionName, BuiltInFunctions.apply(this.fromFile()), ReturnType.Object, this.validateFromFile); + } - if (name === lgTemplate) { - return new ExpressionEvaluator(lgTemplate, BuiltInFunctions.apply(this.lgTemplate()), ReturnType.Object, this.validateLgTemplate); + if (name === Evaluator.activityAttachmentFunctionName) { + return new ExpressionEvaluator(Evaluator.activityAttachmentFunctionName, BuiltInFunctions.apply(this.activityAttachment()), ReturnType.Object, this.validateActivityAttachment); } return baseLookup(name); } - private readonly lgTemplate = (): any => (args: readonly any[]): any => { + private readonly fromFile = (): any => (args: readonly any[]): any => { + const filePath: string = path.normalize(ImportResolver.normalizePath(args[0].toString())); + const resourcePath: string = this.getResourcePath(filePath); + return this.evalText(fs.readFileSync(resourcePath, 'utf-8')); + } + + private getResourcePath(filePath: string): string { + let resourcePath: string; + if (path.isAbsolute(filePath)) { + resourcePath = filePath; + } else { + // relative path is not support in broswer environment + const inBrowser: boolean = typeof window !== 'undefined'; + if (inBrowser) { + throw new Error('relative path is not support in browser.'); + } + const template: LGTemplate = this.templateMap[this.currentTarget().templateName]; + const sourcePath: string = path.normalize(ImportResolver.normalizePath(template.source)); + let baseFolder: string = __dirname; + if (path.isAbsolute(sourcePath)){ + baseFolder = path.dirname(sourcePath); + } + + resourcePath = path.join(baseFolder, filePath); + } + + return resourcePath; + } + + private readonly validateFromFile = (expression: Expression): void => { + if (expression.children.length !== 1) { + throw new Error(`fromFile should have one parameter`); + } + + const children0: Expression = expression.children[0]; + if (children0.returnType !== ReturnType.Object && children0.returnType !== ReturnType.String) { + throw new Error(`${ children0 } can't be used as a file path, must be a string value`); + } + } + + private readonly activityAttachment = (): any => (args: readonly any[]): any => { + return { + [Evaluator.LGType]: 'attachment', + contenttype: args[1].toString(), + content: args[0] + }; + } + + private readonly validateActivityAttachment = (expression: Expression): void => { + if (expression.children.length !== 2) { + throw new Error(`ActivityAttachment should have two parameters`); + } + + const children0: Expression = expression.children[0]; + if (children0.returnType !== ReturnType.Object) { + throw new Error(`${ children0 } can't be used as a json file`); + } + + const children1: Expression = expression.children[1]; + if (children1.returnType !== ReturnType.Object && children1.returnType !== ReturnType.String) { + throw new Error(`${ children0 } can't be used as an attachment format, must be a string value`); + } + } + + private readonly templateFunction = (): any => (args: readonly any[]): any => { const templateName: string = args[0]; const newScope: any = this.constructScope(templateName, args.slice(1)); return this.evaluateTemplate(templateName, newScope); } - private readonly validateLgTemplate = (expression: Expression): void => { + private readonly validateTemplateFunction = (expression: Expression): void => { if (expression.children.length === 0) { throw new Error(`No template name is provided when calling lgTemplate, expected: lgTemplate(templateName, ...args)`); } @@ -451,7 +505,7 @@ export class Evaluator extends AbstractParseTreeVisitor implements LGFilePa // Validate more if the name is string constant if (children0.type === ExpressionType.Constant) { const templateName: string = (children0 as Constant).value; - if (this.templateMap[templateName] === undefined) { + if (!this.templateMap[templateName]) { throw new Error(`No such template '${ templateName }' to call in ${ expression }`); } @@ -473,7 +527,7 @@ export class Evaluator extends AbstractParseTreeVisitor implements LGFilePa private readonly validTemplateReference = (expression: Expression): void => { const templateName: string = expression.type; - if (this.templateMap[templateName] === undefined) { + if (!this.templateMap[templateName]) { throw new Error(`no such template '${ templateName }' to call in ${ expression }`); } diff --git a/libraries/botbuilder-lg/src/expander.ts b/libraries/botbuilder-lg/src/expander.ts index bece51c339..3c7cfa33b7 100644 --- a/libraries/botbuilder-lg/src/expander.ts +++ b/libraries/botbuilder-lg/src/expander.ts @@ -25,8 +25,6 @@ export class Expander extends AbstractParseTreeVisitor implements LGFi private readonly evalutationTargetStack: EvaluationTarget[] = []; private readonly expanderExpressionEngine: ExpressionEngine; private readonly evaluatorExpressionEngine: ExpressionEngine; - private readonly expressionRecognizeRegex: RegExp = new RegExp(/\}(?!\\).+?\{(?!\\)@?/g); - private readonly escapeSeperatorRegex: RegExp = new RegExp(/\|(?!\\)/g); public constructor(templates: LGTemplate[], expressionEngine: ExpressionEngine) { super(); @@ -43,32 +41,14 @@ export class Expander extends AbstractParseTreeVisitor implements LGFi throw new Error(`No such template: ${ templateName }`); } - if (this.evalutationTargetStack.find((u: EvaluationTarget): boolean => u.templateName === templateName) !== undefined) { + if (this.evalutationTargetStack.find((u: EvaluationTarget): boolean => u.templateName === templateName)) { throw new Error(`Loop deteced: ${ this.evalutationTargetStack.reverse() .map((u: EvaluationTarget): string => u.templateName) .join(' => ') }`); } - - const templateTarget: EvaluationTarget = new EvaluationTarget(templateName, scope); - const currentEvulateId: string = templateTarget.getId(); - - let previousEvaluateTarget: EvaluationTarget; - - if (this.evalutationTargetStack.length !== 0) { - previousEvaluateTarget = this.evalutationTargetStack[this.evalutationTargetStack.length - 1]; - if (previousEvaluateTarget.evaluatedChildren.has(currentEvulateId)) { - return previousEvaluateTarget.evaluatedChildren.get(currentEvulateId); - } - } - // Using a stack to track the evalution trace - this.evalutationTargetStack.push(templateTarget); + this.evalutationTargetStack.push(new EvaluationTarget(templateName, scope)); const result: string[] = this.visit(this.templateMap[templateName].parseTree); - - if (previousEvaluateTarget !== undefined) { - previousEvaluateTarget.evaluatedChildren.set(currentEvulateId, result); - } - this.evalutationTargetStack.pop(); return result; @@ -114,7 +94,7 @@ export class Expander extends AbstractParseTreeVisitor implements LGFi const stb: lp.StructuredTemplateBodyContext = ctx.structuredTemplateBody(); const result: any = {}; const typeName: string = stb.structuredBodyNameLine().STRUCTURED_CONTENT().text.trim(); - result.$type = typeName; + result.lgType = typeName; let expandedResult: any[] = [result]; const bodys: TerminalNode[] = stb.structuredBodyContentLine().STRUCTURED_CONTENT(); for (const body of bodys) { @@ -128,7 +108,7 @@ export class Expander extends AbstractParseTreeVisitor implements LGFi const property: string = line.substr(0, start).trim().toLowerCase(); const originValue: string = line.substr(start + 1).trim(); - const valueArray: string[] = Evaluator.wrappedRegExSplit(originValue, this.escapeSeperatorRegex); + const valueArray: string[] = Evaluator.wrappedRegExSplit(originValue, Evaluator.escapeSeperatorReverseRegex); if (valueArray.length === 1) { const id: string = uuid(); expandedResult.forEach((x: any): any => x[property] = id); @@ -143,7 +123,7 @@ export class Expander extends AbstractParseTreeVisitor implements LGFi expandedResult.forEach((x: any): any => x[property] = valueList); } - } else if (this.isPureExpression(line)) { + } else if (Evaluator.isPureExpression(line)) { // [MyStruct // Text = foo // {ST2()} @@ -158,7 +138,7 @@ export class Expander extends AbstractParseTreeVisitor implements LGFi const tempRes: any = JSON.parse(JSON.stringify(res)); // Full reference to another structured template is limited to the structured template with same type - if (typeof propertyObject === 'object' && '$type' in propertyObject && propertyObject.$type.toString() === typeName) { + if (typeof propertyObject === 'object' && Evaluator.LGType in propertyObject && propertyObject[Evaluator.LGType].toString() === typeName) { for (const key of Object.keys(propertyObject)) { if (propertyObject.hasOwnProperty(key) && !(key in tempRes)) { tempRes[key] = propertyObject[key]; @@ -177,7 +157,7 @@ export class Expander extends AbstractParseTreeVisitor implements LGFi const exps: string[] = expandedResult.map((x: string): string => JSON.stringify(x)); const templateRefValues: Map = new Map(); for (const idToString of idToStringMap) { - if ((idToString[1].startsWith('@') || idToString[1].startsWith('{')) && idToString[1].endsWith('}')) { + if (idToString[1].startsWith('@{') && idToString[1].endsWith('}')) { templateRefValues.set(idToString[0], this.evalExpression(idToString[1])); } else { templateRefValues.set(idToString[0], this.evalText(idToString[1])); @@ -212,9 +192,9 @@ export class Expander extends AbstractParseTreeVisitor implements LGFi continue; //skip the first node which is a switch statement } - if (idx === length - 1 && caseNode.switchCaseStat().DEFAULT() !== undefined) { + if (idx === length - 1 && caseNode.switchCaseStat().DEFAULT()) { const defaultBody: lp.NormalTemplateBodyContext = caseNode.normalTemplateBody(); - if (defaultBody !== undefined) { + if (defaultBody) { return this.visit(defaultBody); } else { return undefined; @@ -239,22 +219,17 @@ export class Expander extends AbstractParseTreeVisitor implements LGFi for (const node of ctx.children) { const innerNode: TerminalNode = node as TerminalNode; switch (innerNode.symbol.type) { - case lp.LGFileParser.DASH: break; + case lp.LGFileParser.MULTILINE_PREFIX: + case lp.LGFileParser.MULTILINE_SUFFIX: + case lp.LGFileParser.DASH: + break; case lp.LGFileParser.ESCAPE_CHARACTER: - result = this.stringArrayConcat(result, [this.evalEscapeCharacter(innerNode.text)]); + result = this.stringArrayConcat(result, [this.evalEscape(innerNode.text)]); break; case lp.LGFileParser.EXPRESSION: { result = this.stringArrayConcat(result, this.evalExpression(innerNode.text)); break; } - case lp.LGFileParser.TEMPLATE_REF: { - result = this.stringArrayConcat(result, this.evalTemplateRef(innerNode.text)); - break; - } - case lp.LGFileParser.MULTI_LINE_TEXT: { - result = this.stringArrayConcat(result, this.evalMultiLineText(innerNode.text)); - break; - } default: { result = this.stringArrayConcat(result, [innerNode.text]); break; @@ -273,7 +248,7 @@ export class Expander extends AbstractParseTreeVisitor implements LGFi return this.currentTarget().scope; } - if (parameters !== undefined && (args === undefined || parameters.length !== args.length)) { + if (parameters && (args === undefined || parameters.length !== args.length)) { throw new Error(`The length of required parameters does not match the length of provided parameters.`); } @@ -291,24 +266,24 @@ export class Expander extends AbstractParseTreeVisitor implements LGFi return this.evalutationTargetStack[this.evalutationTargetStack.length - 1]; } - private evalEscapeCharacter(exp: string): string { + private evalEscape(exp: string): string { const validCharactersDict: any = { '\\r': '\r', '\\n': '\n', '\\t': '\t', '\\\\': '\\', - '\\[': '[', - '\\]': ']', - '\\{': '{', - '\\}': '}' }; - return validCharactersDict[exp]; + if (exp in validCharactersDict) { + return validCharactersDict[exp]; + } else { + return exp.substr(1); + } } private evalCondition(condition: lp.IfConditionContext): boolean { const expressions: TerminalNode[] = condition.EXPRESSION(); - if (expressions === undefined || expressions.length === 0) { + if (!expressions || expressions.length === 0) { return true; // no expression means it's else } @@ -326,7 +301,7 @@ export class Expander extends AbstractParseTreeVisitor implements LGFi .replace(/(}*$)/g, ''); const { value: result, error }: { value: any; error: string } = this.evalByExpressionEngine(exp, this.currentTarget().scope); - if (error !== undefined + if (error || result === undefined || typeof result === 'boolean' && !Boolean(result) || Number.isInteger(result) && Number(result) === 0) { @@ -345,63 +320,20 @@ export class Expander extends AbstractParseTreeVisitor implements LGFi .replace(/(}*$)/g, ''); const { value: result, error }: { value: any; error: string } = this.evalByExpressionEngine(exp, this.currentTarget().scope); - if (error !== undefined) { + if (error) { throw new Error(`Error occurs when evaluating expression ${ exp }: ${ error }`); } if (result === undefined) { throw new Error(`Error occurs when evaluating expression '${ exp }': ${ exp } is evaluated to null`); } - if (result instanceof Array) { + if (Array.isArray(result)) { return result; } else { return [result.toString()]; } } - private evalTemplateRef(exp: string): string[] { - exp = exp.replace(/(^\[*)/g, '') - .replace(/(\]*$)/g, ''); - - if (exp.indexOf('(') < 0) { - if (exp in this.templateMap) { - exp = exp.concat('(') - .concat(this.templateMap[exp].parameters.join()) - .concat(')'); - } else { - exp = exp.concat('()'); - } - } - - return this.evalExpression(exp); - } - - private evalMultiLineText(exp: string): string[] { - - exp = exp.substr(3, exp.length - 6); - - const templateRefValues: Map = new Map(); - const matches: string[] = exp.match(/@\{[^{}]+\}/g); - if (matches !== null && matches !== undefined) { - for (const match of matches) { - templateRefValues.set(match, this.evalExpression(match)); - } - } - - let result: string[] = [exp]; - for (const templateRefValue of templateRefValues) { - const tempRes: string[] = []; - for (const res of result) { - for (const refValue of templateRefValue[1]) { - tempRes.push(res.replace(/@\{[^{}]+\}/, refValue)); - } - } - result = tempRes; - } - - return result; - } - private evalByExpressionEngine(exp: string, scope: any): any { const expanderExpression: Expression = this.expanderExpressionEngine.parse(exp); const evaluatorExpression: Expression = this.evaluatorExpressionEngine.parse(exp); @@ -429,7 +361,7 @@ export class Expander extends AbstractParseTreeVisitor implements LGFi return baseLookup(name.substring(prebuiltPrefix.length)); } - if (this.templateMap[name] !== undefined) { + if (this.templateMap[name]) { if (isExpander) { return new ExpressionEvaluator(name, BuiltInFunctions.apply(this.templateExpander(name)), ReturnType.String, this.validTemplateReference); } else { @@ -459,7 +391,7 @@ export class Expander extends AbstractParseTreeVisitor implements LGFi private readonly validTemplateReference = (expression: Expression): void => { const templateName: string = expression.type; - if (this.templateMap[templateName] === undefined) { + if (!this.templateMap[templateName]) { throw new Error(`no such template '${ templateName }' to call in ${ expression }`); } @@ -472,7 +404,7 @@ export class Expander extends AbstractParseTreeVisitor implements LGFi } private reconstructExpression(expanderExpression: Expression, evaluatorExpression: Expression, foundPrebuiltFunction: boolean): Expression { - if (this.templateMap[expanderExpression.type] !== undefined) { + if (this.templateMap[expanderExpression.type]) { if (foundPrebuiltFunction) { return evaluatorExpression; } @@ -489,8 +421,8 @@ export class Expander extends AbstractParseTreeVisitor implements LGFi private evalTextContainsExpression(exp: string): string[] { const templateRefValues: Map = new Map(); - let matches: any = exp.split('').reverse().join('').match(this.expressionRecognizeRegex); - if (matches !== null && matches !== undefined) { + let matches: any = exp.split('').reverse().join('').match(Evaluator.expressionRecognizeReverseRegex); + if (matches) { matches = matches.map((e: string): string => e.split('').reverse().join('')).reverse(); for (const match of matches) { templateRefValues.set(match, this.evalExpression(match)); @@ -502,7 +434,7 @@ export class Expander extends AbstractParseTreeVisitor implements LGFi const tempRes: string[] = []; for (const res of result) { for (const refValue of templateRefValue[1]) { - tempRes.push(res.replace(/@\{[^{}]+\}/, refValue)); + tempRes.push(res.replace(templateRefValue[0], refValue)); } } result = tempRes; @@ -512,31 +444,14 @@ export class Expander extends AbstractParseTreeVisitor implements LGFi } private evalText(exp: string): string[] { - if (exp === undefined || exp.length === 0) { + if (!exp) { return [exp]; } - if (this.isPureExpression(exp)) { + if (Evaluator.isPureExpression(exp)) { return this.evalExpression(exp); } else { - - // unescape \| - return this.evalTextContainsExpression(exp).map((x: string): string => x.replace(/\\\|/g, '|')); - } - } - - private isPureExpression(exp: string): boolean { - if (exp === undefined || exp.length === 0) { - return false; - } - - exp = exp.trim(); - const reversedExps: RegExpMatchArray = exp.split('').reverse().join('').match(this.expressionRecognizeRegex); - // If there is no match, expressions could be null - if (reversedExps === null || reversedExps === undefined || reversedExps.length !== 1) { - return false; - } else { - return reversedExps[0].split('').reverse().join('') === exp; + return this.evalTextContainsExpression(exp).map((x: string): string => x.replace(/\\(.)/g, '$1')); } } } \ No newline at end of file diff --git a/libraries/botbuilder-lg/src/generated/LGFileLexer.ts b/libraries/botbuilder-lg/src/generated/LGFileLexer.ts index bcf607c3ad..033dffff84 100644 --- a/libraries/botbuilder-lg/src/generated/LGFileLexer.ts +++ b/libraries/botbuilder-lg/src/generated/LGFileLexer.ts @@ -41,42 +41,44 @@ export class LGFileLexer extends Lexer { public static readonly COMMA = 16; public static readonly TEXT_IN_NAME = 17; public static readonly WS_IN_BODY_IGNORED = 18; - public static readonly IF = 19; - public static readonly ELSEIF = 20; - public static readonly ELSE = 21; - public static readonly SWITCH = 22; - public static readonly CASE = 23; - public static readonly DEFAULT = 24; - public static readonly MULTI_LINE_TEXT = 25; + public static readonly MULTILINE_PREFIX = 19; + public static readonly IF = 20; + public static readonly ELSEIF = 21; + public static readonly ELSE = 22; + public static readonly SWITCH = 23; + public static readonly CASE = 24; + public static readonly DEFAULT = 25; public static readonly ESCAPE_CHARACTER = 26; public static readonly EXPRESSION = 27; - public static readonly TEMPLATE_REF = 28; - public static readonly TEXT_SEPARATOR = 29; - public static readonly TEXT = 30; - public static readonly WS_IN_STRUCTURED = 31; - public static readonly STRUCTURED_COMMENTS = 32; - public static readonly STRUCTURED_NEWLINE = 33; - public static readonly STRUCTURED_TEMPLATE_BODY_END = 34; - public static readonly STRUCTURED_CONTENT = 35; + public static readonly TEXT = 28; + public static readonly WS_IN_STRUCTURED = 29; + public static readonly STRUCTURED_COMMENTS = 30; + public static readonly STRUCTURED_NEWLINE = 31; + public static readonly STRUCTURED_TEMPLATE_BODY_END = 32; + public static readonly STRUCTURED_CONTENT = 33; + public static readonly MULTILINE_SUFFIX = 34; public static readonly TEMPLATE_NAME_MODE = 1; public static readonly TEMPLATE_BODY_MODE = 2; public static readonly STRUCTURED_TEMPLATE_BODY_MODE = 3; + public static readonly MULTILINE = 4; // tslint:disable:no-trailing-whitespace public static readonly modeNames: string[] = [ - "DEFAULT_MODE", "TEMPLATE_NAME_MODE", "TEMPLATE_BODY_MODE", "STRUCTURED_TEMPLATE_BODY_MODE", + "DEFAULT_MODE", "TEMPLATE_NAME_MODE", "TEMPLATE_BODY_MODE", "STRUCTURED_TEMPLATE_BODY_MODE", + "MULTILINE", ]; public static readonly ruleNames: string[] = [ "LETTER", "NUMBER", "WHITESPACE", "A", "C", "D", "E", "F", "H", "I", "L", - "S", "T", "U", "W", "STRING_LITERAL", "COMMENTS", "WS", "NEWLINE", "HASH", - "DASH", "LEFT_SQUARE_BRACKET", "RIGHT_SQUARE_BRACKET", "IMPORT_DESC", - "IMPORT_PATH", "INVALID_TOKEN_DEFAULT_MODE", "WS_IN_NAME", "NEWLINE_IN_NAME", - "IDENTIFIER", "DOT", "OPEN_PARENTHESIS", "CLOSE_PARENTHESIS", "COMMA", - "TEXT_IN_NAME", "WS_IN_BODY_IGNORED", "WS_IN_BODY", "NEWLINE_IN_BODY", - "IF", "ELSEIF", "ELSE", "SWITCH", "CASE", "DEFAULT", "MULTI_LINE_TEXT", - "ESCAPE_CHARACTER", "EXPRESSION", "TEMPLATE_REF", "TEXT_SEPARATOR", "TEXT", - "WS_IN_STRUCTURED", "STRUCTURED_COMMENTS", "STRUCTURED_NEWLINE", "STRUCTURED_TEMPLATE_BODY_END", - "STRUCTURED_CONTENT", + "S", "T", "U", "W", "STRING_LITERAL", "EXPRESSION_FRAGMENT", "ESCAPE_CHARACTER_FRAGMENT", + "COMMENTS", "WS", "NEWLINE", "HASH", "DASH", "LEFT_SQUARE_BRACKET", "RIGHT_SQUARE_BRACKET", + "IMPORT_DESC", "IMPORT_PATH", "INVALID_TOKEN_DEFAULT_MODE", "WS_IN_NAME", + "NEWLINE_IN_NAME", "IDENTIFIER", "DOT", "OPEN_PARENTHESIS", "CLOSE_PARENTHESIS", + "COMMA", "TEXT_IN_NAME", "WS_IN_BODY_IGNORED", "WS_IN_BODY", "MULTILINE_PREFIX", + "NEWLINE_IN_BODY", "IF", "ELSEIF", "ELSE", "SWITCH", "CASE", "DEFAULT", + "ESCAPE_CHARACTER", "EXPRESSION", "TEXT", "WS_IN_STRUCTURED", "STRUCTURED_COMMENTS", + "STRUCTURED_NEWLINE", "STRUCTURED_TEMPLATE_BODY_END", "STRUCTURED_CONTENT", + "MULTILINE_SUFFIX", "MULTILINE_ESCAPE_CHARACTER", "MULTILINE_EXPRESSION", + "MULTILINE_TEXT", ]; private static readonly _LITERAL_NAMES: Array = [ @@ -88,10 +90,10 @@ export class LGFileLexer extends Lexer { undefined, "COMMENTS", "WS", "NEWLINE", "HASH", "DASH", "LEFT_SQUARE_BRACKET", "RIGHT_SQUARE_BRACKET", "IMPORT_DESC", "IMPORT_PATH", "INVALID_TOKEN_DEFAULT_MODE", "WS_IN_NAME", "IDENTIFIER", "DOT", "OPEN_PARENTHESIS", "CLOSE_PARENTHESIS", - "COMMA", "TEXT_IN_NAME", "WS_IN_BODY_IGNORED", "IF", "ELSEIF", "ELSE", - "SWITCH", "CASE", "DEFAULT", "MULTI_LINE_TEXT", "ESCAPE_CHARACTER", "EXPRESSION", - "TEMPLATE_REF", "TEXT_SEPARATOR", "TEXT", "WS_IN_STRUCTURED", "STRUCTURED_COMMENTS", - "STRUCTURED_NEWLINE", "STRUCTURED_TEMPLATE_BODY_END", "STRUCTURED_CONTENT", + "COMMA", "TEXT_IN_NAME", "WS_IN_BODY_IGNORED", "MULTILINE_PREFIX", "IF", + "ELSEIF", "ELSE", "SWITCH", "CASE", "DEFAULT", "ESCAPE_CHARACTER", "EXPRESSION", + "TEXT", "WS_IN_STRUCTURED", "STRUCTURED_COMMENTS", "STRUCTURED_NEWLINE", + "STRUCTURED_TEMPLATE_BODY_END", "STRUCTURED_CONTENT", "MULTILINE_SUFFIX", ]; public static readonly VOCABULARY: Vocabulary = new VocabularyImpl(LGFileLexer._LITERAL_NAMES, LGFileLexer._SYMBOLIC_NAMES, []); @@ -127,56 +129,44 @@ export class LGFileLexer extends Lexer { // @Override public action(_localctx: RuleContext, ruleIndex: number, actionIndex: number): void { switch (ruleIndex) { - case 20: + case 22: this.DASH_action(_localctx, actionIndex); break; - case 36: - this.NEWLINE_IN_BODY_action(_localctx, actionIndex); - break; - - case 37: - this.IF_action(_localctx, actionIndex); - break; - - case 38: - this.ELSEIF_action(_localctx, actionIndex); - break; - case 39: - this.ELSE_action(_localctx, actionIndex); + this.NEWLINE_IN_BODY_action(_localctx, actionIndex); break; case 40: - this.SWITCH_action(_localctx, actionIndex); + this.IF_action(_localctx, actionIndex); break; case 41: - this.CASE_action(_localctx, actionIndex); + this.ELSEIF_action(_localctx, actionIndex); break; case 42: - this.DEFAULT_action(_localctx, actionIndex); + this.ELSE_action(_localctx, actionIndex); break; case 43: - this.MULTI_LINE_TEXT_action(_localctx, actionIndex); + this.SWITCH_action(_localctx, actionIndex); break; case 44: - this.ESCAPE_CHARACTER_action(_localctx, actionIndex); + this.CASE_action(_localctx, actionIndex); break; case 45: - this.EXPRESSION_action(_localctx, actionIndex); + this.DEFAULT_action(_localctx, actionIndex); break; case 46: - this.TEMPLATE_REF_action(_localctx, actionIndex); + this.ESCAPE_CHARACTER_action(_localctx, actionIndex); break; case 47: - this.TEXT_SEPARATOR_action(_localctx, actionIndex); + this.EXPRESSION_action(_localctx, actionIndex); break; case 48: @@ -236,48 +226,27 @@ export class LGFileLexer extends Lexer { private DEFAULT_action(_localctx: RuleContext, actionIndex: number): void { switch (actionIndex) { case 7: - this.ignoreWS = true; - break; - } - } - private MULTI_LINE_TEXT_action(_localctx: RuleContext, actionIndex: number): void { - switch (actionIndex) { - case 8: - this.ignoreWS = false; this.expectKeywords = false; + this.ignoreWS = true; break; } } private ESCAPE_CHARACTER_action(_localctx: RuleContext, actionIndex: number): void { switch (actionIndex) { - case 9: + case 8: this.ignoreWS = false; this.expectKeywords = false; break; } } private EXPRESSION_action(_localctx: RuleContext, actionIndex: number): void { switch (actionIndex) { - case 10: - this.ignoreWS = false; this.expectKeywords = false; - break; - } - } - private TEMPLATE_REF_action(_localctx: RuleContext, actionIndex: number): void { - switch (actionIndex) { - case 11: - this.ignoreWS = false; this.expectKeywords = false; - break; - } - } - private TEXT_SEPARATOR_action(_localctx: RuleContext, actionIndex: number): void { - switch (actionIndex) { - case 12: + case 9: this.ignoreWS = false; this.expectKeywords = false; break; } } private TEXT_action(_localctx: RuleContext, actionIndex: number): void { switch (actionIndex) { - case 13: + case 10: this.ignoreWS = false; this.expectKeywords = false; break; } @@ -285,25 +254,25 @@ export class LGFileLexer extends Lexer { // @Override public sempred(_localctx: RuleContext, ruleIndex: number, predIndex: number): boolean { switch (ruleIndex) { - case 34: + case 36: return this.WS_IN_BODY_IGNORED_sempred(_localctx, predIndex); - case 37: + case 40: return this.IF_sempred(_localctx, predIndex); - case 38: + case 41: return this.ELSEIF_sempred(_localctx, predIndex); - case 39: + case 42: return this.ELSE_sempred(_localctx, predIndex); - case 40: + case 43: return this.SWITCH_sempred(_localctx, predIndex); - case 41: + case 44: return this.CASE_sempred(_localctx, predIndex); - case 42: + case 45: return this.DEFAULT_sempred(_localctx, predIndex); } return true; @@ -359,241 +328,242 @@ export class LGFileLexer extends Lexer { } public static readonly _serializedATN: string = - "\x03\uAF6F\u8320\u479D\uB75C\u4880\u1605\u191C\uAB37\x02%\u01E1\b\x01" + - "\b\x01\b\x01\b\x01\x04\x02\t\x02\x04\x03\t\x03\x04\x04\t\x04\x04\x05\t" + - "\x05\x04\x06\t\x06\x04\x07\t\x07\x04\b\t\b\x04\t\t\t\x04\n\t\n\x04\v\t" + - "\v\x04\f\t\f\x04\r\t\r\x04\x0E\t\x0E\x04\x0F\t\x0F\x04\x10\t\x10\x04\x11" + - "\t\x11\x04\x12\t\x12\x04\x13\t\x13\x04\x14\t\x14\x04\x15\t\x15\x04\x16" + - "\t\x16\x04\x17\t\x17\x04\x18\t\x18\x04\x19\t\x19\x04\x1A\t\x1A\x04\x1B" + - "\t\x1B\x04\x1C\t\x1C\x04\x1D\t\x1D\x04\x1E\t\x1E\x04\x1F\t\x1F\x04 \t" + - " \x04!\t!\x04\"\t\"\x04#\t#\x04$\t$\x04%\t%\x04&\t&\x04\'\t\'\x04(\t(" + - "\x04)\t)\x04*\t*\x04+\t+\x04,\t,\x04-\t-\x04.\t.\x04/\t/\x040\t0\x041" + - "\t1\x042\t2\x043\t3\x044\t4\x045\t5\x046\t6\x047\t7\x03\x02\x03\x02\x03" + - "\x03\x03\x03\x03\x04\x03\x04\x03\x05\x03\x05\x03\x06\x03\x06\x03\x07\x03" + - "\x07\x03\b\x03\b\x03\t\x03\t\x03\n\x03\n\x03\v\x03\v\x03\f\x03\f\x03\r" + - "\x03\r\x03\x0E\x03\x0E\x03\x0F\x03\x0F\x03\x10\x03\x10\x03\x11\x03\x11" + - "\x07\x11\x93\n\x11\f\x11\x0E\x11\x96\v\x11\x03\x11\x03\x11\x03\x11\x07" + - "\x11\x9B\n\x11\f\x11\x0E\x11\x9E\v\x11\x03\x11\x05\x11\xA1\n\x11\x03\x12" + - "\x03\x12\x06\x12\xA5\n\x12\r\x12\x0E\x12\xA6\x03\x12\x05\x12\xAA\n\x12" + - "\x03\x12\x03\x12\x03\x13\x06\x13\xAF\n\x13\r\x13\x0E\x13\xB0\x03\x13\x03" + - "\x13\x03\x14\x05\x14\xB6\n\x14\x03\x14\x03\x14\x03\x15\x03\x15\x03\x15" + - "\x03\x15\x03\x16\x03\x16\x03\x16\x03\x16\x03\x16\x03\x17\x03\x17\x03\x17" + - "\x03\x17\x03\x18\x03\x18\x03\x19\x03\x19\x07\x19\xCB\n\x19\f\x19\x0E\x19" + - "\xCE\v\x19\x03\x19\x03\x19\x03\x1A\x03\x1A\x07\x1A\xD4\n\x1A\f\x1A\x0E" + - "\x1A\xD7\v\x1A\x03\x1A\x03\x1A\x03\x1B\x03\x1B\x03\x1C\x06\x1C\xDE\n\x1C" + - "\r\x1C\x0E\x1C\xDF\x03\x1C\x03\x1C\x03\x1D\x05\x1D\xE5\n\x1D\x03\x1D\x03" + - "\x1D\x03\x1D\x03\x1D\x03\x1D\x03\x1E\x03\x1E\x03\x1E\x05\x1E\xEF\n\x1E" + - "\x03\x1E\x03\x1E\x03\x1E\x07\x1E\xF4\n\x1E\f\x1E\x0E\x1E\xF7\v\x1E\x03" + - "\x1F\x03\x1F\x03 \x03 \x03!\x03!\x03\"\x03\"\x03#\x06#\u0102\n#\r#\x0E" + - "#\u0103\x03$\x06$\u0107\n$\r$\x0E$\u0108\x03$\x03$\x03$\x03$\x03%\x06" + - "%\u0110\n%\r%\x0E%\u0111\x03%\x03%\x03&\x05&\u0117\n&\x03&\x03&\x03&\x03" + - "&\x03&\x03&\x03\'\x03\'\x03\'\x07\'\u0122\n\'\f\'\x0E\'\u0125\v\'\x03" + - "\'\x03\'\x03\'\x03\'\x03(\x03(\x03(\x03(\x03(\x07(\u0130\n(\f(\x0E(\u0133" + - "\v(\x03(\x03(\x03(\x07(\u0138\n(\f(\x0E(\u013B\v(\x03(\x03(\x03(\x03(" + - "\x03)\x03)\x03)\x03)\x03)\x07)\u0146\n)\f)\x0E)\u0149\v)\x03)\x03)\x03" + - ")\x03)\x03*\x03*\x03*\x03*\x03*\x03*\x03*\x07*\u0156\n*\f*\x0E*\u0159" + - "\v*\x03*\x03*\x03*\x03*\x03+\x03+\x03+\x03+\x03+\x07+\u0164\n+\f+\x0E" + - "+\u0167\v+\x03+\x03+\x03+\x03+\x03,\x03,\x03,\x03,\x03,\x03,\x03,\x03" + - ",\x07,\u0175\n,\f,\x0E,\u0178\v,\x03,\x03,\x03,\x03,\x03-\x03-\x03-\x03" + - "-\x03-\x07-\u0183\n-\f-\x0E-\u0186\v-\x03-\x03-\x03-\x03-\x03-\x03-\x03" + - ".\x03.\x03.\x03.\x03.\x03.\x03.\x03.\x03.\x05.\u0197\n.\x03/\x05/\u019A" + - "\n/\x03/\x03/\x03/\x07/\u019F\n/\f/\x0E/\u01A2\v/\x03/\x03/\x03/\x030" + - "\x030\x030\x070\u01AA\n0\f0\x0E0\u01AD\v0\x030\x030\x030\x031\x031\x03" + - "1\x032\x062\u01B6\n2\r2\x0E2\u01B7\x032\x032\x033\x063\u01BD\n3\r3\x0E" + - "3\u01BE\x034\x034\x074\u01C3\n4\f4\x0E4\u01C6\v4\x034\x054\u01C9\n4\x03" + - "4\x034\x034\x034\x035\x055\u01D0\n5\x035\x035\x036\x056\u01D5\n6\x036" + - "\x036\x056\u01D9\n6\x036\x036\x037\x067\u01DE\n7\r7\x0E7\u01DF\b\xCC\xD5" + - "\u0103\u0184\u01A0\u01B7\x02\x028\x06\x02\x02\b\x02\x02\n\x02\x02\f\x02" + - "\x02\x0E\x02\x02\x10\x02\x02\x12\x02\x02\x14\x02\x02\x16\x02\x02\x18\x02" + - "\x02\x1A\x02\x02\x1C\x02\x02\x1E\x02\x02 \x02\x02\"\x02\x02$\x02\x02&" + - "\x02\x03(\x02\x04*\x02\x05,\x02\x06.\x02\x070\x02\b2\x02\t4\x02\n6\x02" + - "\v8\x02\f:\x02\r<\x02\x02>\x02\x0E@\x02\x0FB\x02\x10D\x02\x11F\x02\x12" + - "H\x02\x13J\x02\x14L\x02\x02N\x02\x02P\x02\x15R\x02\x16T\x02\x17V\x02\x18" + - "X\x02\x19Z\x02\x1A\\\x02\x1B^\x02\x1C`\x02\x1Db\x02\x1Ed\x02\x1Ff\x02" + - " h\x02!j\x02\"l\x02#n\x02$p\x02%\x06\x02\x03\x04\x05\x19\x04\x02C\\c|" + - "\x06\x02\v\v\"\"\xA2\xA2\uFF01\uFF01\x04\x02CCcc\x04\x02EEee\x04\x02F" + - "Fff\x04\x02GGgg\x04\x02HHhh\x04\x02JJjj\x04\x02KKkk\x04\x02NNnn\x04\x02" + - "UUuu\x04\x02VVvv\x04\x02WWww\x04\x02YYyy\x05\x02\f\f\x0F\x0F))\x05\x02" + - "\f\f\x0F\x0F$$\x04\x02&&@@\x04\x02\f\f\x0F\x0F\x04\x02//aa\x07\x02__p" + - "pttvv\x7F\x7F\b\x02\f\f\x0F\x0F$$))}}\x7F\x7F\x06\x02\f\f\x0F\x0F]]__" + - "\t\x02\v\f\x0F\x0F*+]]__}}\x7F\x7F\u01F9\x02&\x03\x02\x02\x02\x02(\x03" + - "\x02\x02\x02\x02*\x03\x02\x02\x02\x02,\x03\x02\x02\x02\x02.\x03\x02\x02" + - "\x02\x020\x03\x02\x02\x02\x022\x03\x02\x02\x02\x024\x03\x02\x02\x02\x02" + - "6\x03\x02\x02\x02\x028\x03\x02\x02\x02\x03:\x03\x02\x02\x02\x03<\x03\x02" + - "\x02\x02\x03>\x03\x02\x02\x02\x03@\x03\x02\x02\x02\x03B\x03\x02\x02\x02" + - "\x03D\x03\x02\x02\x02\x03F\x03\x02\x02\x02\x03H\x03\x02\x02\x02\x04J\x03" + - "\x02\x02\x02\x04L\x03\x02\x02\x02\x04N\x03\x02\x02\x02\x04P\x03\x02\x02" + - "\x02\x04R\x03\x02\x02\x02\x04T\x03\x02\x02\x02\x04V\x03\x02\x02\x02\x04" + - "X\x03\x02\x02\x02\x04Z\x03\x02\x02\x02\x04\\\x03\x02\x02\x02\x04^\x03" + - "\x02\x02\x02\x04`\x03\x02\x02\x02\x04b\x03\x02\x02\x02\x04d\x03\x02\x02" + - "\x02\x04f\x03\x02\x02\x02\x05h\x03\x02\x02\x02\x05j\x03\x02\x02\x02\x05" + - "l\x03\x02\x02\x02\x05n\x03\x02\x02\x02\x05p\x03\x02\x02\x02\x06r\x03\x02" + - "\x02\x02\bt\x03\x02\x02\x02\nv\x03\x02\x02\x02\fx\x03\x02\x02\x02\x0E" + - "z\x03\x02\x02\x02\x10|\x03\x02\x02\x02\x12~\x03\x02\x02\x02\x14\x80\x03" + - "\x02\x02\x02\x16\x82\x03\x02\x02\x02\x18\x84\x03\x02\x02\x02\x1A\x86\x03" + - "\x02\x02\x02\x1C\x88\x03\x02\x02\x02\x1E\x8A\x03\x02\x02\x02 \x8C\x03" + - "\x02\x02\x02\"\x8E\x03\x02\x02\x02$\xA0\x03\x02\x02\x02&\xA2\x03\x02\x02" + - "\x02(\xAE\x03\x02\x02\x02*\xB5\x03\x02\x02\x02,\xB9\x03\x02\x02\x02.\xBD" + - "\x03\x02\x02\x020\xC2\x03\x02\x02\x022\xC6\x03\x02\x02\x024\xC8\x03\x02" + - "\x02\x026\xD1\x03\x02\x02\x028\xDA\x03\x02\x02\x02:\xDD\x03\x02\x02\x02" + - "<\xE4\x03\x02\x02\x02>\xEE\x03\x02\x02\x02@\xF8\x03\x02\x02\x02B\xFA\x03" + - "\x02\x02\x02D\xFC\x03\x02\x02\x02F\xFE\x03\x02\x02\x02H\u0101\x03\x02" + - "\x02\x02J\u0106\x03\x02\x02\x02L\u010F\x03\x02\x02\x02N\u0116\x03\x02" + - "\x02\x02P\u011E\x03\x02\x02\x02R\u012A\x03\x02\x02\x02T\u0140\x03\x02" + - "\x02\x02V\u014E\x03\x02\x02\x02X\u015E\x03\x02\x02\x02Z\u016C\x03\x02" + - "\x02\x02\\\u017D\x03\x02\x02\x02^\u0196\x03\x02\x02\x02`\u0199\x03\x02" + - "\x02\x02b\u01A6\x03\x02\x02\x02d\u01B1\x03\x02\x02\x02f\u01B5\x03\x02" + - "\x02\x02h\u01BC\x03\x02\x02\x02j\u01C0\x03\x02\x02\x02l\u01CF\x03\x02" + - "\x02\x02n\u01D4\x03\x02\x02\x02p\u01DD\x03\x02\x02\x02rs\t\x02\x02\x02" + - "s\x07\x03\x02\x02\x02tu\x042;\x02u\t\x03\x02\x02\x02vw\t\x03\x02\x02w" + - "\v\x03\x02\x02\x02xy\t\x04\x02\x02y\r\x03\x02\x02\x02z{\t\x05\x02\x02" + - "{\x0F\x03\x02\x02\x02|}\t\x06\x02\x02}\x11\x03\x02\x02\x02~\x7F\t\x07" + - "\x02\x02\x7F\x13\x03\x02\x02\x02\x80\x81\t\b\x02\x02\x81\x15\x03\x02\x02" + - "\x02\x82\x83\t\t\x02\x02\x83\x17\x03\x02\x02\x02\x84\x85\t\n\x02\x02\x85" + - "\x19\x03\x02\x02\x02\x86\x87\t\v\x02\x02\x87\x1B\x03\x02\x02\x02\x88\x89" + - "\t\f\x02\x02\x89\x1D\x03\x02\x02\x02\x8A\x8B\t\r\x02\x02\x8B\x1F\x03\x02" + - "\x02\x02\x8C\x8D\t\x0E\x02\x02\x8D!\x03\x02\x02\x02\x8E\x8F\t\x0F\x02" + - "\x02\x8F#\x03\x02\x02\x02\x90\x94\x07)\x02\x02\x91\x93\n\x10\x02\x02\x92" + - "\x91\x03\x02\x02\x02\x93\x96\x03\x02\x02\x02\x94\x92\x03\x02\x02\x02\x94" + - "\x95\x03\x02\x02\x02\x95\x97\x03\x02\x02\x02\x96\x94\x03\x02\x02\x02\x97" + - "\xA1\x07)\x02\x02\x98\x9C\x07$\x02\x02\x99\x9B\n\x11\x02\x02\x9A\x99\x03" + - "\x02\x02\x02\x9B\x9E\x03\x02\x02\x02\x9C\x9A\x03\x02\x02\x02\x9C\x9D\x03" + - "\x02\x02\x02\x9D\x9F\x03\x02\x02\x02\x9E\x9C\x03\x02\x02\x02\x9F\xA1\x07" + - "$\x02\x02\xA0\x90\x03\x02\x02\x02\xA0\x98\x03\x02\x02\x02\xA1%\x03\x02" + - "\x02\x02\xA2\xA4\t\x12\x02\x02\xA3\xA5\n\x13\x02\x02\xA4\xA3\x03\x02\x02" + - "\x02\xA5\xA6\x03\x02\x02\x02\xA6\xA4\x03\x02\x02\x02\xA6\xA7\x03\x02\x02" + - "\x02\xA7\xA9\x03\x02\x02\x02\xA8\xAA\x05*\x14\x02\xA9\xA8\x03\x02\x02" + - "\x02\xA9\xAA\x03\x02\x02\x02\xAA\xAB\x03\x02\x02\x02\xAB\xAC\b\x12\x02" + - "\x02\xAC\'\x03\x02\x02\x02\xAD\xAF\x05\n\x04\x02\xAE\xAD\x03\x02\x02\x02" + - "\xAF\xB0\x03\x02\x02\x02\xB0\xAE\x03\x02\x02\x02\xB0\xB1\x03\x02\x02\x02" + - "\xB1\xB2\x03\x02\x02\x02\xB2\xB3\b\x13\x02\x02\xB3)\x03\x02\x02\x02\xB4" + - "\xB6\x07\x0F\x02\x02\xB5\xB4\x03\x02\x02\x02\xB5\xB6\x03\x02\x02\x02\xB6" + - "\xB7\x03\x02\x02\x02\xB7\xB8\x07\f\x02\x02\xB8+\x03\x02\x02\x02\xB9\xBA" + - "\x07%\x02\x02\xBA\xBB\x03\x02\x02\x02\xBB\xBC\b\x15\x03\x02\xBC-\x03\x02" + - "\x02\x02\xBD\xBE\x07/\x02\x02\xBE\xBF\b\x16\x04\x02\xBF\xC0\x03\x02\x02" + - "\x02\xC0\xC1\b\x16\x05\x02\xC1/\x03\x02\x02\x02\xC2\xC3\x07]\x02\x02\xC3" + - "\xC4\x03\x02\x02\x02\xC4\xC5\b\x17\x06\x02\xC51\x03\x02\x02\x02\xC6\xC7" + - "\x07_\x02\x02\xC73\x03\x02\x02\x02\xC8\xCC\x07]\x02\x02\xC9\xCB\n\x13" + - "\x02\x02\xCA\xC9\x03\x02\x02\x02\xCB\xCE\x03\x02\x02\x02\xCC\xCD\x03\x02" + - "\x02\x02\xCC\xCA\x03\x02\x02\x02\xCD\xCF\x03\x02\x02\x02\xCE\xCC\x03\x02" + - "\x02\x02\xCF\xD0\x07_\x02\x02\xD05\x03\x02\x02\x02\xD1\xD5\x07*\x02\x02" + - "\xD2\xD4\n\x13\x02\x02\xD3\xD2\x03\x02\x02\x02\xD4\xD7\x03\x02\x02\x02" + - "\xD5\xD6\x03\x02\x02\x02\xD5\xD3\x03\x02\x02\x02\xD6\xD8\x03\x02\x02\x02" + - "\xD7\xD5\x03\x02\x02\x02\xD8\xD9\x07+\x02\x02\xD97\x03\x02\x02\x02\xDA" + - "\xDB\v\x02\x02\x02\xDB9\x03\x02\x02\x02\xDC\xDE\x05\n\x04\x02\xDD\xDC" + - "\x03\x02\x02\x02\xDE\xDF\x03\x02\x02\x02\xDF\xDD\x03\x02\x02\x02\xDF\xE0" + - "\x03\x02\x02\x02\xE0\xE1\x03\x02\x02\x02\xE1\xE2\b\x1C\x02\x02\xE2;\x03" + - "\x02\x02\x02\xE3\xE5\x07\x0F\x02\x02\xE4\xE3\x03\x02\x02\x02\xE4\xE5\x03" + - "\x02\x02\x02\xE5\xE6\x03\x02\x02\x02\xE6\xE7\x07\f\x02\x02\xE7\xE8\x03" + - "\x02\x02\x02\xE8\xE9\b\x1D\x07\x02\xE9\xEA\b\x1D\b\x02\xEA=\x03\x02\x02" + - "\x02\xEB\xEF\x05\x06\x02\x02\xEC\xEF\x05\b\x03\x02\xED\xEF\x07a\x02\x02" + - "\xEE\xEB\x03\x02\x02\x02\xEE\xEC\x03\x02\x02\x02\xEE\xED\x03\x02\x02\x02" + - "\xEF\xF5\x03\x02\x02\x02\xF0\xF4\x05\x06\x02\x02\xF1\xF4\x05\b\x03\x02" + - "\xF2\xF4\t\x14\x02\x02\xF3\xF0\x03\x02\x02\x02\xF3\xF1\x03\x02\x02\x02" + - "\xF3\xF2\x03\x02\x02\x02\xF4\xF7\x03\x02\x02\x02\xF5\xF3\x03\x02\x02\x02" + - "\xF5\xF6\x03\x02\x02\x02\xF6?\x03\x02\x02\x02\xF7\xF5\x03\x02\x02\x02" + - "\xF8\xF9\x070\x02\x02\xF9A\x03\x02\x02\x02\xFA\xFB\x07*\x02\x02\xFBC\x03" + - "\x02\x02\x02\xFC\xFD\x07+\x02\x02\xFDE\x03\x02\x02\x02\xFE\xFF\x07.\x02" + - "\x02\xFFG\x03\x02\x02\x02\u0100\u0102\n\x13\x02\x02\u0101\u0100\x03\x02" + - "\x02\x02\u0102\u0103\x03\x02\x02\x02\u0103\u0104\x03\x02\x02\x02\u0103" + - "\u0101\x03\x02\x02\x02\u0104I\x03\x02\x02\x02\u0105\u0107\x05\n\x04\x02" + - "\u0106\u0105\x03\x02\x02\x02\u0107\u0108\x03\x02\x02\x02\u0108\u0106\x03" + - "\x02\x02\x02\u0108\u0109\x03\x02\x02\x02\u0109\u010A\x03\x02\x02\x02\u010A" + - "\u010B\x06$\x02\x02\u010B\u010C\x03\x02\x02\x02\u010C\u010D\b$\x02\x02" + - "\u010DK\x03\x02\x02\x02\u010E\u0110\x05\n\x04\x02\u010F\u010E\x03\x02" + - "\x02\x02\u0110\u0111\x03\x02\x02\x02\u0111\u010F\x03\x02\x02\x02\u0111" + - "\u0112\x03\x02\x02\x02\u0112\u0113\x03\x02\x02\x02\u0113\u0114\b%\t\x02" + - "\u0114M\x03\x02\x02\x02\u0115\u0117\x07\x0F\x02\x02\u0116\u0115\x03\x02" + - "\x02\x02\u0116\u0117\x03\x02\x02\x02\u0117\u0118\x03\x02\x02\x02\u0118" + - "\u0119\x07\f\x02\x02\u0119\u011A\b&\n\x02\u011A\u011B\x03\x02\x02\x02" + - "\u011B\u011C\b&\x07\x02\u011C\u011D\b&\b\x02\u011DO\x03\x02\x02\x02\u011E" + - "\u011F\x05\x18\v\x02\u011F\u0123\x05\x14\t\x02\u0120\u0122\x05\n\x04\x02" + - "\u0121\u0120\x03\x02\x02\x02\u0122\u0125\x03\x02\x02\x02\u0123\u0121\x03" + - "\x02\x02\x02\u0123\u0124\x03\x02\x02\x02\u0124\u0126\x03\x02\x02\x02\u0125" + - "\u0123\x03\x02\x02\x02\u0126\u0127\x07<\x02\x02\u0127\u0128\x06\'\x03" + - "\x02\u0128\u0129\b\'\v\x02\u0129Q\x03\x02\x02\x02\u012A\u012B\x05\x12" + - "\b\x02\u012B\u012C\x05\x1A\f\x02\u012C\u012D\x05\x1C\r\x02\u012D\u0131" + - "\x05\x12\b\x02\u012E\u0130\x05\n\x04\x02\u012F\u012E\x03\x02\x02\x02\u0130" + - "\u0133\x03\x02\x02\x02\u0131\u012F\x03\x02\x02\x02\u0131\u0132\x03\x02" + - "\x02\x02\u0132\u0134\x03\x02\x02\x02\u0133\u0131\x03\x02\x02\x02\u0134" + - "\u0135\x05\x18\v\x02\u0135\u0139\x05\x14\t\x02\u0136\u0138\x05\n\x04\x02" + - "\u0137\u0136\x03\x02\x02\x02\u0138\u013B\x03\x02\x02\x02\u0139\u0137\x03" + - "\x02\x02\x02\u0139\u013A\x03\x02\x02\x02\u013A\u013C\x03\x02\x02\x02\u013B" + - "\u0139\x03\x02\x02\x02\u013C\u013D\x07<\x02\x02\u013D\u013E\x06(\x04\x02" + - "\u013E\u013F\b(\f\x02\u013FS\x03\x02\x02\x02\u0140\u0141\x05\x12\b\x02" + - "\u0141\u0142\x05\x1A\f\x02\u0142\u0143\x05\x1C\r\x02\u0143\u0147\x05\x12" + - "\b\x02\u0144\u0146\x05\n\x04\x02\u0145\u0144\x03\x02\x02\x02\u0146\u0149" + - "\x03\x02\x02\x02\u0147\u0145\x03\x02\x02\x02\u0147\u0148\x03\x02\x02\x02" + - "\u0148\u014A\x03\x02\x02\x02\u0149\u0147\x03\x02\x02\x02\u014A\u014B\x07" + - "<\x02\x02\u014B\u014C\x06)\x05\x02\u014C\u014D\b)\r\x02\u014DU\x03\x02" + - "\x02\x02\u014E\u014F\x05\x1C\r\x02\u014F\u0150\x05\"\x10\x02\u0150\u0151" + - "\x05\x18\v\x02\u0151\u0152\x05\x1E\x0E\x02\u0152\u0153\x05\x0E\x06\x02" + - "\u0153\u0157\x05\x16\n\x02\u0154\u0156\x05\n\x04\x02\u0155\u0154\x03\x02" + - "\x02\x02\u0156\u0159\x03\x02\x02\x02\u0157\u0155\x03\x02\x02\x02\u0157" + - "\u0158\x03\x02\x02\x02\u0158\u015A\x03\x02\x02\x02\u0159\u0157\x03\x02" + - "\x02\x02\u015A\u015B\x07<\x02\x02\u015B\u015C\x06*\x06\x02\u015C\u015D" + - "\b*\x0E\x02\u015DW\x03\x02\x02\x02\u015E\u015F\x05\x0E\x06\x02\u015F\u0160" + - "\x05\f\x05\x02\u0160\u0161\x05\x1C\r\x02\u0161\u0165\x05\x12\b\x02\u0162" + - "\u0164\x05\n\x04\x02\u0163\u0162\x03\x02\x02\x02\u0164\u0167\x03\x02\x02" + - "\x02\u0165\u0163\x03\x02\x02\x02\u0165\u0166\x03\x02\x02\x02\u0166\u0168" + - "\x03\x02\x02\x02\u0167\u0165\x03\x02\x02\x02\u0168\u0169\x07<\x02\x02" + - "\u0169\u016A\x06+\x07\x02\u016A\u016B\b+\x0F\x02\u016BY\x03\x02\x02\x02" + - "\u016C\u016D\x05\x10\x07\x02\u016D\u016E\x05\x12\b\x02\u016E\u016F\x05" + - "\x14\t\x02\u016F\u0170\x05\f\x05\x02\u0170\u0171\x05 \x0F\x02\u0171\u0172" + - "\x05\x1A\f\x02\u0172\u0176\x05\x1E\x0E\x02\u0173\u0175\x05\n\x04\x02\u0174" + - "\u0173\x03\x02\x02\x02\u0175\u0178\x03\x02\x02\x02\u0176\u0174\x03\x02" + - "\x02\x02\u0176\u0177\x03\x02\x02\x02\u0177\u0179\x03\x02\x02\x02\u0178" + - "\u0176\x03\x02\x02\x02\u0179\u017A\x07<\x02\x02\u017A\u017B\x06,\b\x02" + - "\u017B\u017C\b,\x10\x02\u017C[\x03\x02\x02\x02\u017D\u017E\x07b\x02\x02" + - "\u017E\u017F\x07b\x02\x02\u017F\u0180\x07b\x02\x02\u0180\u0184\x03\x02" + - "\x02\x02\u0181\u0183\v\x02\x02\x02\u0182\u0181\x03\x02\x02\x02\u0183\u0186" + - "\x03\x02\x02\x02\u0184\u0185\x03\x02\x02\x02\u0184\u0182\x03\x02\x02\x02" + - "\u0185\u0187\x03\x02\x02\x02\u0186\u0184\x03\x02\x02\x02\u0187\u0188\x07" + - "b\x02\x02\u0188\u0189\x07b\x02\x02\u0189\u018A\x07b\x02\x02\u018A\u018B" + - "\x03\x02\x02\x02\u018B\u018C\b-\x11\x02\u018C]\x03\x02\x02\x02\u018D\u018E" + - "\x07^\x02\x02\u018E\u0197\x07}\x02\x02\u018F\u0190\x07^\x02\x02\u0190" + - "\u0197\x07]\x02\x02\u0191\u0192\x07^\x02\x02\u0192\u0197\x07^\x02\x02" + - "\u0193\u0194\x07^\x02\x02\u0194\u0195\t\x15\x02\x02\u0195\u0197\b.\x12" + - "\x02\u0196\u018D\x03\x02\x02\x02\u0196\u018F\x03\x02\x02\x02\u0196\u0191" + - "\x03\x02\x02\x02\u0196\u0193\x03\x02\x02\x02\u0197_\x03\x02\x02\x02\u0198" + - "\u019A\x07B\x02\x02\u0199\u0198\x03\x02\x02\x02\u0199\u019A\x03\x02\x02" + - "\x02\u019A\u019B\x03\x02\x02\x02\u019B\u01A0\x07}\x02\x02\u019C\u019F" + - "\n\x16\x02\x02\u019D\u019F\x05$\x11\x02\u019E\u019C\x03\x02\x02\x02\u019E" + - "\u019D\x03\x02\x02\x02\u019F\u01A2\x03\x02\x02\x02\u01A0\u01A1\x03\x02" + - "\x02\x02\u01A0\u019E\x03\x02\x02\x02\u01A1\u01A3\x03\x02\x02\x02\u01A2" + - "\u01A0\x03\x02\x02\x02\u01A3\u01A4\x07\x7F\x02\x02\u01A4\u01A5\b/\x13" + - "\x02\u01A5a\x03\x02\x02\x02\u01A6\u01AB\x07]\x02\x02\u01A7\u01AA\n\x17" + - "\x02\x02\u01A8\u01AA\x05b0\x02\u01A9\u01A7\x03\x02\x02\x02\u01A9\u01A8" + - "\x03\x02\x02\x02\u01AA\u01AD\x03\x02\x02\x02\u01AB\u01A9\x03\x02\x02\x02" + - "\u01AB\u01AC\x03\x02\x02\x02\u01AC\u01AE\x03\x02\x02\x02\u01AD\u01AB\x03" + - "\x02\x02\x02\u01AE\u01AF\x07_\x02\x02\u01AF\u01B0\b0\x14\x02\u01B0c\x03" + - "\x02\x02\x02\u01B1\u01B2\t\x18\x02\x02\u01B2\u01B3\b1\x15\x02\u01B3e\x03" + - "\x02\x02\x02\u01B4\u01B6\n\x18\x02\x02\u01B5\u01B4\x03\x02\x02\x02\u01B6" + - "\u01B7\x03\x02\x02\x02\u01B7\u01B8\x03\x02\x02\x02\u01B7\u01B5\x03\x02" + - "\x02\x02\u01B8\u01B9\x03\x02\x02\x02\u01B9\u01BA\b2\x16\x02\u01BAg\x03" + - "\x02\x02\x02\u01BB\u01BD\x05\n\x04\x02\u01BC\u01BB\x03\x02\x02\x02\u01BD" + - "\u01BE\x03\x02\x02\x02\u01BE\u01BC\x03\x02\x02\x02\u01BE\u01BF\x03\x02" + - "\x02\x02\u01BFi\x03\x02\x02\x02\u01C0\u01C4\t\x12\x02\x02\u01C1\u01C3" + - "\n\x13\x02\x02\u01C2\u01C1\x03\x02\x02\x02\u01C3\u01C6\x03\x02\x02\x02" + - "\u01C4\u01C2\x03\x02\x02\x02\u01C4\u01C5\x03\x02\x02\x02\u01C5\u01C8\x03" + - "\x02\x02\x02\u01C6\u01C4\x03\x02\x02\x02\u01C7\u01C9\x07\x0F\x02\x02\u01C8" + - "\u01C7\x03\x02\x02\x02\u01C8\u01C9\x03\x02\x02\x02\u01C9\u01CA\x03\x02" + - "\x02\x02\u01CA\u01CB\x07\f\x02\x02\u01CB\u01CC\x03\x02\x02\x02\u01CC\u01CD" + - "\b4\x02\x02\u01CDk\x03\x02\x02\x02\u01CE\u01D0\x07\x0F\x02\x02\u01CF\u01CE" + - "\x03\x02\x02\x02\u01CF\u01D0\x03\x02\x02\x02\u01D0\u01D1\x03\x02\x02\x02" + - "\u01D1\u01D2\x07\f\x02\x02\u01D2m\x03\x02\x02\x02\u01D3\u01D5\x05h3\x02" + - "\u01D4\u01D3\x03\x02\x02\x02\u01D4\u01D5\x03\x02\x02\x02\u01D5\u01D6\x03" + - "\x02\x02\x02\u01D6\u01D8\x052\x18\x02\u01D7\u01D9\x05h3\x02\u01D8\u01D7" + - "\x03\x02\x02\x02\u01D8\u01D9\x03\x02\x02\x02\u01D9\u01DA\x03\x02\x02\x02" + - "\u01DA\u01DB\b6\b\x02\u01DBo\x03\x02\x02\x02\u01DC\u01DE\n\x13\x02\x02" + - "\u01DD\u01DC\x03\x02\x02\x02\u01DE\u01DF\x03\x02\x02\x02\u01DF\u01DD\x03" + - "\x02\x02\x02\u01DF\u01E0\x03\x02\x02\x02\u01E0q\x03\x02\x02\x02.\x02\x03" + - "\x04\x05\x94\x9C\xA0\xA6\xA9\xB0\xB5\xCC\xD5\xDF\xE4\xEE\xF3\xF5\u0103" + - "\u0108\u0111\u0116\u0123\u0131\u0139\u0147\u0157\u0165\u0176\u0184\u0196" + - "\u0199\u019E\u01A0\u01A9\u01AB\u01B7\u01BE\u01C4\u01C8\u01CF\u01D4\u01D8" + - "\u01DF\x17\b\x02\x02\x07\x03\x02\x03\x16\x02\x07\x04\x02\x07\x05\x02\t" + - "\x05\x02\x06\x02\x02\t\x04\x02\x03&\x03\x03\'\x04\x03(\x05\x03)\x06\x03" + - "*\x07\x03+\b\x03,\t\x03-\n\x03.\v\x03/\f\x030\r\x031\x0E\x032\x0F"; + "\x03\uAF6F\u8320\u479D\uB75C\u4880\u1605\u191C\uAB37\x02$\u01E7\b\x01" + + "\b\x01\b\x01\b\x01\b\x01\x04\x02\t\x02\x04\x03\t\x03\x04\x04\t\x04\x04" + + "\x05\t\x05\x04\x06\t\x06\x04\x07\t\x07\x04\b\t\b\x04\t\t\t\x04\n\t\n\x04" + + "\v\t\v\x04\f\t\f\x04\r\t\r\x04\x0E\t\x0E\x04\x0F\t\x0F\x04\x10\t\x10\x04" + + "\x11\t\x11\x04\x12\t\x12\x04\x13\t\x13\x04\x14\t\x14\x04\x15\t\x15\x04" + + "\x16\t\x16\x04\x17\t\x17\x04\x18\t\x18\x04\x19\t\x19\x04\x1A\t\x1A\x04" + + "\x1B\t\x1B\x04\x1C\t\x1C\x04\x1D\t\x1D\x04\x1E\t\x1E\x04\x1F\t\x1F\x04" + + " \t \x04!\t!\x04\"\t\"\x04#\t#\x04$\t$\x04%\t%\x04&\t&\x04\'\t\'\x04(" + + "\t(\x04)\t)\x04*\t*\x04+\t+\x04,\t,\x04-\t-\x04.\t.\x04/\t/\x040\t0\x04" + + "1\t1\x042\t2\x043\t3\x044\t4\x045\t5\x046\t6\x047\t7\x048\t8\x049\t9\x04" + + ":\t:\x04;\t;\x03\x02\x03\x02\x03\x03\x03\x03\x03\x04\x03\x04\x03\x05\x03" + + "\x05\x03\x06\x03\x06\x03\x07\x03\x07\x03\b\x03\b\x03\t\x03\t\x03\n\x03" + + "\n\x03\v\x03\v\x03\f\x03\f\x03\r\x03\r\x03\x0E\x03\x0E\x03\x0F\x03\x0F" + + "\x03\x10\x03\x10\x03\x11\x03\x11\x07\x11\x9C\n\x11\f\x11\x0E\x11\x9F\v" + + "\x11\x03\x11\x03\x11\x03\x11\x07\x11\xA4\n\x11\f\x11\x0E\x11\xA7\v\x11" + + "\x03\x11\x05\x11\xAA\n\x11\x03\x12\x03\x12\x03\x12\x03\x12\x07\x12\xB0" + + "\n\x12\f\x12\x0E\x12\xB3\v\x12\x03\x12\x03\x12\x03\x13\x03\x13\x05\x13" + + "\xB9\n\x13\x03\x14\x03\x14\x06\x14\xBD\n\x14\r\x14\x0E\x14\xBE\x03\x14" + + "\x05\x14\xC2\n\x14\x03\x14\x03\x14\x03\x15\x06\x15\xC7\n\x15\r\x15\x0E" + + "\x15\xC8\x03\x15\x03\x15\x03\x16\x05\x16\xCE\n\x16\x03\x16\x03\x16\x03" + + "\x17\x03\x17\x03\x17\x03\x17\x03\x18\x03\x18\x03\x18\x03\x18\x03\x18\x03" + + "\x19\x03\x19\x03\x19\x03\x19\x03\x1A\x03\x1A\x03\x1B\x03\x1B\x07\x1B\xE3" + + "\n\x1B\f\x1B\x0E\x1B\xE6\v\x1B\x03\x1B\x03\x1B\x03\x1C\x03\x1C\x07\x1C" + + "\xEC\n\x1C\f\x1C\x0E\x1C\xEF\v\x1C\x03\x1C\x03\x1C\x03\x1D\x03\x1D\x03" + + "\x1E\x06\x1E\xF6\n\x1E\r\x1E\x0E\x1E\xF7\x03\x1E\x03\x1E\x03\x1F\x05\x1F" + + "\xFD\n\x1F\x03\x1F\x03\x1F\x03\x1F\x03\x1F\x03\x1F\x03 \x03 \x03 \x05" + + " \u0107\n \x03 \x03 \x03 \x07 \u010C\n \f \x0E \u010F\v \x03!\x03!\x03" + + "\"\x03\"\x03#\x03#\x03$\x03$\x03%\x06%\u011A\n%\r%\x0E%\u011B\x03&\x06" + + "&\u011F\n&\r&\x0E&\u0120\x03&\x03&\x03&\x03&\x03\'\x06\'\u0128\n\'\r\'" + + "\x0E\'\u0129\x03\'\x03\'\x03(\x03(\x03(\x03(\x03(\x03(\x03)\x05)\u0135" + + "\n)\x03)\x03)\x03)\x03)\x03)\x03)\x03*\x03*\x03*\x07*\u0140\n*\f*\x0E" + + "*\u0143\v*\x03*\x03*\x03*\x03*\x03+\x03+\x03+\x03+\x03+\x07+\u014E\n+" + + "\f+\x0E+\u0151\v+\x03+\x03+\x03+\x07+\u0156\n+\f+\x0E+\u0159\v+\x03+\x03" + + "+\x03+\x03+\x03,\x03,\x03,\x03,\x03,\x07,\u0164\n,\f,\x0E,\u0167\v,\x03" + + ",\x03,\x03,\x03,\x03-\x03-\x03-\x03-\x03-\x03-\x03-\x07-\u0174\n-\f-\x0E" + + "-\u0177\v-\x03-\x03-\x03-\x03-\x03.\x03.\x03.\x03.\x03.\x07.\u0182\n." + + "\f.\x0E.\u0185\v.\x03.\x03.\x03.\x03.\x03/\x03/\x03/\x03/\x03/\x03/\x03" + + "/\x03/\x07/\u0193\n/\f/\x0E/\u0196\v/\x03/\x03/\x03/\x03/\x030\x030\x03" + + "0\x031\x031\x031\x032\x062\u01A3\n2\r2\x0E2\u01A4\x032\x032\x033\x063" + + "\u01AA\n3\r3\x0E3\u01AB\x034\x034\x074\u01B0\n4\f4\x0E4\u01B3\v4\x034" + + "\x054\u01B6\n4\x034\x034\x034\x034\x035\x055\u01BD\n5\x035\x035\x036\x05" + + "6\u01C2\n6\x036\x036\x056\u01C6\n6\x036\x036\x037\x067\u01CB\n7\r7\x0E" + + "7\u01CC\x038\x038\x038\x038\x038\x038\x039\x039\x039\x039\x03:\x03:\x03" + + ":\x03:\x03;\x05;\u01DE\n;\x03;\x03;\x06;\u01E2\n;\r;\x0E;\u01E3\x03;\x03" + + ";\b\xB1\xE4\xED\u011B\u01A4\u01E3\x02\x02<\x07\x02\x02\t\x02\x02\v\x02" + + "\x02\r\x02\x02\x0F\x02\x02\x11\x02\x02\x13\x02\x02\x15\x02\x02\x17\x02" + + "\x02\x19\x02\x02\x1B\x02\x02\x1D\x02\x02\x1F\x02\x02!\x02\x02#\x02\x02" + + "%\x02\x02\'\x02\x02)\x02\x02+\x02\x03-\x02\x04/\x02\x051\x02\x063\x02" + + "\x075\x02\b7\x02\t9\x02\n;\x02\v=\x02\f?\x02\rA\x02\x02C\x02\x0EE\x02" + + "\x0FG\x02\x10I\x02\x11K\x02\x12M\x02\x13O\x02\x14Q\x02\x02S\x02\x15U\x02" + + "\x02W\x02\x16Y\x02\x17[\x02\x18]\x02\x19_\x02\x1Aa\x02\x1Bc\x02\x1Ce\x02" + + "\x1Dg\x02\x1Ei\x02\x1Fk\x02 m\x02!o\x02\"q\x02#s\x02$u\x02\x02w\x02\x02" + + "y\x02\x02\x07\x02\x03\x04\x05\x06\x16\x04\x02C\\c|\x06\x02\v\v\"\"\xA2" + + "\xA2\uFF01\uFF01\x04\x02CCcc\x04\x02EEee\x04\x02FFff\x04\x02GGgg\x04\x02" + + "HHhh\x04\x02JJjj\x04\x02KKkk\x04\x02NNnn\x04\x02UUuu\x04\x02VVvv\x04\x02" + + "WWww\x04\x02YYyy\x05\x02\f\f\x0F\x0F))\x05\x02\f\f\x0F\x0F$$\b\x02\f\f" + + "\x0F\x0F$$))}}\x7F\x7F\x04\x02\f\f\x0F\x0F\x04\x02&&@@\x04\x02//aa\u01F9" + + "\x02+\x03\x02\x02\x02\x02-\x03\x02\x02\x02\x02/\x03\x02\x02\x02\x021\x03" + + "\x02\x02\x02\x023\x03\x02\x02\x02\x025\x03\x02\x02\x02\x027\x03\x02\x02" + + "\x02\x029\x03\x02\x02\x02\x02;\x03\x02\x02\x02\x02=\x03\x02\x02\x02\x03" + + "?\x03\x02\x02\x02\x03A\x03\x02\x02\x02\x03C\x03\x02\x02\x02\x03E\x03\x02" + + "\x02\x02\x03G\x03\x02\x02\x02\x03I\x03\x02\x02\x02\x03K\x03\x02\x02\x02" + + "\x03M\x03\x02\x02\x02\x04O\x03\x02\x02\x02\x04Q\x03\x02\x02\x02\x04S\x03" + + "\x02\x02\x02\x04U\x03\x02\x02\x02\x04W\x03\x02\x02\x02\x04Y\x03\x02\x02" + + "\x02\x04[\x03\x02\x02\x02\x04]\x03\x02\x02\x02\x04_\x03\x02\x02\x02\x04" + + "a\x03\x02\x02\x02\x04c\x03\x02\x02\x02\x04e\x03\x02\x02\x02\x04g\x03\x02" + + "\x02\x02\x05i\x03\x02\x02\x02\x05k\x03\x02\x02\x02\x05m\x03\x02\x02\x02" + + "\x05o\x03\x02\x02\x02\x05q\x03\x02\x02\x02\x06s\x03\x02\x02\x02\x06u\x03" + + "\x02\x02\x02\x06w\x03\x02\x02\x02\x06y\x03\x02\x02\x02\x07{\x03\x02\x02" + + "\x02\t}\x03\x02\x02\x02\v\x7F\x03\x02\x02\x02\r\x81\x03\x02\x02\x02\x0F" + + "\x83\x03\x02\x02\x02\x11\x85\x03\x02\x02\x02\x13\x87\x03\x02\x02\x02\x15" + + "\x89\x03\x02\x02\x02\x17\x8B\x03\x02\x02\x02\x19\x8D\x03\x02\x02\x02\x1B" + + "\x8F\x03\x02\x02\x02\x1D\x91\x03\x02\x02\x02\x1F\x93\x03\x02\x02\x02!" + + "\x95\x03\x02\x02\x02#\x97\x03\x02\x02\x02%\xA9\x03\x02\x02\x02\'\xAB\x03" + + "\x02\x02\x02)\xB6\x03\x02\x02\x02+\xBA\x03\x02\x02\x02-\xC6\x03\x02\x02" + + "\x02/\xCD\x03\x02\x02\x021\xD1\x03\x02\x02\x023\xD5\x03\x02\x02\x025\xDA" + + "\x03\x02\x02\x027\xDE\x03\x02\x02\x029\xE0\x03\x02\x02\x02;\xE9\x03\x02" + + "\x02\x02=\xF2\x03\x02\x02\x02?\xF5\x03\x02\x02\x02A\xFC\x03\x02\x02\x02" + + "C\u0106\x03\x02\x02\x02E\u0110\x03\x02\x02\x02G\u0112\x03\x02\x02\x02" + + "I\u0114\x03\x02\x02\x02K\u0116\x03\x02\x02\x02M\u0119\x03\x02\x02\x02" + + "O\u011E\x03\x02\x02\x02Q\u0127\x03\x02\x02\x02S\u012D\x03\x02\x02\x02" + + "U\u0134\x03\x02\x02\x02W\u013C\x03\x02\x02\x02Y\u0148\x03\x02\x02\x02" + + "[\u015E\x03\x02\x02\x02]\u016C\x03\x02\x02\x02_\u017C\x03\x02\x02\x02" + + "a\u018A\x03\x02\x02\x02c\u019B\x03\x02\x02\x02e\u019E\x03\x02\x02\x02" + + "g\u01A2\x03\x02\x02\x02i\u01A9\x03\x02\x02\x02k\u01AD\x03\x02\x02\x02" + + "m\u01BC\x03\x02\x02\x02o\u01C1\x03\x02\x02\x02q\u01CA\x03\x02\x02\x02" + + "s\u01CE\x03\x02\x02\x02u\u01D4\x03\x02\x02\x02w\u01D8\x03\x02\x02\x02" + + "y\u01E1\x03\x02\x02\x02{|\t\x02\x02\x02|\b\x03\x02\x02\x02}~\x042;\x02" + + "~\n\x03\x02\x02\x02\x7F\x80\t\x03\x02\x02\x80\f\x03\x02\x02\x02\x81\x82" + + "\t\x04\x02\x02\x82\x0E\x03\x02\x02\x02\x83\x84\t\x05\x02\x02\x84\x10\x03" + + "\x02\x02\x02\x85\x86\t\x06\x02\x02\x86\x12\x03\x02\x02\x02\x87\x88\t\x07" + + "\x02\x02\x88\x14\x03\x02\x02\x02\x89\x8A\t\b\x02\x02\x8A\x16\x03\x02\x02" + + "\x02\x8B\x8C\t\t\x02\x02\x8C\x18\x03\x02\x02\x02\x8D\x8E\t\n\x02\x02\x8E" + + "\x1A\x03\x02\x02\x02\x8F\x90\t\v\x02\x02\x90\x1C\x03\x02\x02\x02\x91\x92" + + "\t\f\x02\x02\x92\x1E\x03\x02\x02\x02\x93\x94\t\r\x02\x02\x94 \x03\x02" + + "\x02\x02\x95\x96\t\x0E\x02\x02\x96\"\x03\x02\x02\x02\x97\x98\t\x0F\x02" + + "\x02\x98$\x03\x02\x02\x02\x99\x9D\x07)\x02\x02\x9A\x9C\n\x10\x02\x02\x9B" + + "\x9A\x03\x02\x02\x02\x9C\x9F\x03\x02\x02\x02\x9D\x9B\x03\x02\x02\x02\x9D" + + "\x9E\x03\x02\x02\x02\x9E\xA0\x03\x02\x02\x02\x9F\x9D\x03\x02\x02\x02\xA0" + + "\xAA\x07)\x02\x02\xA1\xA5\x07$\x02\x02\xA2\xA4\n\x11\x02\x02\xA3\xA2\x03" + + "\x02\x02\x02\xA4\xA7\x03\x02\x02\x02\xA5\xA3\x03\x02\x02\x02\xA5\xA6\x03" + + "\x02\x02\x02\xA6\xA8\x03\x02\x02\x02\xA7\xA5\x03\x02\x02\x02\xA8\xAA\x07" + + "$\x02\x02\xA9\x99\x03\x02\x02\x02\xA9\xA1\x03\x02\x02\x02\xAA&\x03\x02" + + "\x02\x02\xAB\xAC\x07B\x02\x02\xAC\xB1\x07}\x02\x02\xAD\xB0\x05%\x11\x02" + + "\xAE\xB0\n\x12\x02\x02\xAF\xAD\x03\x02\x02\x02\xAF\xAE\x03\x02\x02\x02" + + "\xB0\xB3\x03\x02\x02\x02\xB1\xB2\x03\x02\x02\x02\xB1\xAF\x03\x02\x02\x02" + + "\xB2\xB4\x03\x02\x02\x02\xB3\xB1\x03\x02\x02\x02\xB4\xB5\x07\x7F\x02\x02" + + "\xB5(\x03\x02\x02\x02\xB6\xB8\x07^\x02\x02\xB7\xB9\n\x13\x02\x02\xB8\xB7" + + "\x03\x02\x02\x02\xB8\xB9\x03\x02\x02\x02\xB9*\x03\x02\x02\x02\xBA\xBC" + + "\t\x14\x02\x02\xBB\xBD\n\x13\x02\x02\xBC\xBB\x03\x02\x02\x02\xBD\xBE\x03" + + "\x02\x02\x02\xBE\xBC\x03\x02\x02\x02\xBE\xBF\x03\x02\x02\x02\xBF\xC1\x03" + + "\x02\x02\x02\xC0\xC2\x05/\x16\x02\xC1\xC0\x03\x02\x02\x02\xC1\xC2\x03" + + "\x02\x02\x02\xC2\xC3\x03\x02\x02\x02\xC3\xC4\b\x14\x02\x02\xC4,\x03\x02" + + "\x02\x02\xC5\xC7\x05\v\x04\x02\xC6\xC5\x03\x02\x02\x02\xC7\xC8\x03\x02" + + "\x02\x02\xC8\xC6\x03\x02\x02\x02\xC8\xC9\x03\x02\x02\x02\xC9\xCA\x03\x02" + + "\x02\x02\xCA\xCB\b\x15\x02\x02\xCB.\x03\x02\x02\x02\xCC\xCE\x07\x0F\x02" + + "\x02\xCD\xCC\x03\x02\x02\x02\xCD\xCE\x03\x02\x02\x02\xCE\xCF\x03\x02\x02" + + "\x02\xCF\xD0\x07\f\x02\x02\xD00\x03\x02\x02\x02\xD1\xD2\x07%\x02\x02\xD2" + + "\xD3\x03\x02\x02\x02\xD3\xD4\b\x17\x03\x02\xD42\x03\x02\x02\x02\xD5\xD6" + + "\x07/\x02\x02\xD6\xD7\b\x18\x04\x02\xD7\xD8\x03\x02\x02\x02\xD8\xD9\b" + + "\x18\x05\x02\xD94\x03\x02\x02\x02\xDA\xDB\x07]\x02\x02\xDB\xDC\x03\x02" + + "\x02\x02\xDC\xDD\b\x19\x06\x02\xDD6\x03\x02\x02\x02\xDE\xDF\x07_\x02\x02" + + "\xDF8\x03\x02\x02\x02\xE0\xE4\x07]\x02\x02\xE1\xE3\n\x13\x02\x02\xE2\xE1" + + "\x03\x02\x02\x02\xE3\xE6\x03\x02\x02\x02\xE4\xE5\x03\x02\x02\x02\xE4\xE2" + + "\x03\x02\x02\x02\xE5\xE7\x03\x02\x02\x02\xE6\xE4\x03\x02\x02\x02\xE7\xE8" + + "\x07_\x02\x02\xE8:\x03\x02\x02\x02\xE9\xED\x07*\x02\x02\xEA\xEC\n\x13" + + "\x02\x02\xEB\xEA\x03\x02\x02\x02\xEC\xEF\x03\x02\x02\x02\xED\xEE\x03\x02" + + "\x02\x02\xED\xEB\x03\x02\x02\x02\xEE\xF0\x03\x02\x02\x02\xEF\xED\x03\x02" + + "\x02\x02\xF0\xF1\x07+\x02\x02\xF1<\x03\x02\x02\x02\xF2\xF3\v\x02\x02\x02" + + "\xF3>\x03\x02\x02\x02\xF4\xF6\x05\v\x04\x02\xF5\xF4\x03\x02\x02\x02\xF6" + + "\xF7\x03\x02\x02\x02\xF7\xF5\x03\x02\x02\x02\xF7\xF8\x03\x02\x02\x02\xF8" + + "\xF9\x03\x02\x02\x02\xF9\xFA\b\x1E\x02\x02\xFA@\x03\x02\x02\x02\xFB\xFD" + + "\x07\x0F\x02\x02\xFC\xFB\x03\x02\x02\x02\xFC\xFD\x03\x02\x02\x02\xFD\xFE" + + "\x03\x02\x02\x02\xFE\xFF\x07\f\x02\x02\xFF\u0100\x03\x02\x02\x02\u0100" + + "\u0101\b\x1F\x07\x02\u0101\u0102\b\x1F\b\x02\u0102B\x03\x02\x02\x02\u0103" + + "\u0107\x05\x07\x02\x02\u0104\u0107\x05\t\x03\x02\u0105\u0107\x07a\x02" + + "\x02\u0106\u0103\x03\x02\x02\x02\u0106\u0104\x03\x02\x02\x02\u0106\u0105" + + "\x03\x02\x02\x02\u0107\u010D\x03\x02\x02\x02\u0108\u010C\x05\x07\x02\x02" + + "\u0109\u010C\x05\t\x03\x02\u010A\u010C\t\x15\x02\x02\u010B\u0108\x03\x02" + + "\x02\x02\u010B\u0109\x03\x02\x02\x02\u010B\u010A\x03\x02\x02\x02\u010C" + + "\u010F\x03\x02\x02\x02\u010D\u010B\x03\x02\x02\x02\u010D\u010E\x03\x02" + + "\x02\x02\u010ED\x03\x02\x02\x02\u010F\u010D\x03\x02\x02\x02\u0110\u0111" + + "\x070\x02\x02\u0111F\x03\x02\x02\x02\u0112\u0113\x07*\x02\x02\u0113H\x03" + + "\x02\x02\x02\u0114\u0115\x07+\x02\x02\u0115J\x03\x02\x02\x02\u0116\u0117" + + "\x07.\x02\x02\u0117L\x03\x02\x02\x02\u0118\u011A\n\x13\x02\x02\u0119\u0118" + + "\x03\x02\x02\x02\u011A\u011B\x03\x02\x02\x02\u011B\u011C\x03\x02\x02\x02" + + "\u011B\u0119\x03\x02\x02\x02\u011CN\x03\x02\x02\x02\u011D\u011F\x05\v" + + "\x04\x02\u011E\u011D\x03\x02\x02\x02\u011F\u0120\x03\x02\x02\x02\u0120" + + "\u011E\x03\x02\x02\x02\u0120\u0121\x03\x02\x02\x02\u0121\u0122\x03\x02" + + "\x02\x02\u0122\u0123\x06&\x02\x02\u0123\u0124\x03\x02\x02\x02\u0124\u0125" + + "\b&\x02\x02\u0125P\x03\x02\x02\x02\u0126\u0128\x05\v\x04\x02\u0127\u0126" + + "\x03\x02\x02\x02\u0128\u0129\x03\x02\x02\x02\u0129\u0127\x03\x02\x02\x02" + + "\u0129\u012A\x03\x02\x02\x02\u012A\u012B\x03\x02\x02\x02\u012B\u012C\b" + + "\'\t\x02\u012CR\x03\x02\x02\x02\u012D\u012E\x07b\x02\x02\u012E\u012F\x07" + + "b\x02\x02\u012F\u0130\x07b\x02\x02\u0130\u0131\x03\x02\x02\x02\u0131\u0132" + + "\b(\n\x02\u0132T\x03\x02\x02\x02\u0133\u0135\x07\x0F\x02\x02\u0134\u0133" + + "\x03\x02\x02\x02\u0134\u0135\x03\x02\x02\x02\u0135\u0136\x03\x02\x02\x02" + + "\u0136\u0137\x07\f\x02\x02\u0137\u0138\b)\v\x02\u0138\u0139\x03\x02\x02" + + "\x02\u0139\u013A\b)\x07\x02\u013A\u013B\b)\b\x02\u013BV\x03\x02\x02\x02" + + "\u013C\u013D\x05\x19\v\x02\u013D\u0141\x05\x15\t\x02\u013E\u0140\x05\v" + + "\x04\x02\u013F\u013E\x03\x02\x02\x02\u0140\u0143\x03\x02\x02\x02\u0141" + + "\u013F\x03\x02\x02\x02\u0141\u0142\x03\x02\x02\x02\u0142\u0144\x03\x02" + + "\x02\x02\u0143\u0141\x03\x02\x02\x02\u0144\u0145\x07<\x02\x02\u0145\u0146" + + "\x06*\x03\x02\u0146\u0147\b*\f\x02\u0147X\x03\x02\x02\x02\u0148\u0149" + + "\x05\x13\b\x02\u0149\u014A\x05\x1B\f\x02\u014A\u014B\x05\x1D\r\x02\u014B" + + "\u014F\x05\x13\b\x02\u014C\u014E\x05\v\x04\x02\u014D\u014C\x03\x02\x02" + + "\x02\u014E\u0151\x03\x02\x02\x02\u014F\u014D\x03\x02\x02\x02\u014F\u0150" + + "\x03\x02\x02\x02\u0150\u0152\x03\x02\x02\x02\u0151\u014F\x03\x02\x02\x02" + + "\u0152\u0153\x05\x19\v\x02\u0153\u0157\x05\x15\t\x02\u0154\u0156\x05\v" + + "\x04\x02\u0155\u0154\x03\x02\x02\x02\u0156\u0159\x03\x02\x02\x02\u0157" + + "\u0155\x03\x02\x02\x02\u0157\u0158\x03\x02\x02\x02\u0158\u015A\x03\x02" + + "\x02\x02\u0159\u0157\x03\x02\x02\x02\u015A\u015B\x07<\x02\x02\u015B\u015C" + + "\x06+\x04\x02\u015C\u015D\b+\r\x02\u015DZ\x03\x02\x02\x02\u015E\u015F" + + "\x05\x13\b\x02\u015F\u0160\x05\x1B\f\x02\u0160\u0161\x05\x1D\r\x02\u0161" + + "\u0165\x05\x13\b\x02\u0162\u0164\x05\v\x04\x02\u0163\u0162\x03\x02\x02" + + "\x02\u0164\u0167\x03\x02\x02\x02\u0165\u0163\x03\x02\x02\x02\u0165\u0166" + + "\x03\x02\x02\x02\u0166\u0168\x03\x02\x02\x02\u0167\u0165\x03\x02\x02\x02" + + "\u0168\u0169\x07<\x02\x02\u0169\u016A\x06,\x05\x02\u016A\u016B\b,\x0E" + + "\x02\u016B\\\x03\x02\x02\x02\u016C\u016D\x05\x1D\r\x02\u016D\u016E\x05" + + "#\x10\x02\u016E\u016F\x05\x19\v\x02\u016F\u0170\x05\x1F\x0E\x02\u0170" + + "\u0171\x05\x0F\x06\x02\u0171\u0175\x05\x17\n\x02\u0172\u0174\x05\v\x04" + + "\x02\u0173\u0172\x03\x02\x02\x02\u0174\u0177\x03\x02\x02\x02\u0175\u0173" + + "\x03\x02\x02\x02\u0175\u0176\x03\x02\x02\x02\u0176\u0178\x03\x02\x02\x02" + + "\u0177\u0175\x03\x02\x02\x02\u0178\u0179\x07<\x02\x02\u0179\u017A\x06" + + "-\x06\x02\u017A\u017B\b-\x0F\x02\u017B^\x03\x02\x02\x02\u017C\u017D\x05" + + "\x0F\x06\x02\u017D\u017E\x05\r\x05\x02\u017E\u017F\x05\x1D\r\x02\u017F" + + "\u0183\x05\x13\b\x02\u0180\u0182\x05\v\x04\x02\u0181\u0180\x03\x02\x02" + + "\x02\u0182\u0185\x03\x02\x02\x02\u0183\u0181\x03\x02\x02\x02\u0183\u0184" + + "\x03\x02\x02\x02\u0184\u0186\x03\x02\x02\x02\u0185\u0183\x03\x02\x02\x02" + + "\u0186\u0187\x07<\x02\x02\u0187\u0188\x06.\x07\x02\u0188\u0189\b.\x10" + + "\x02\u0189`\x03\x02\x02\x02\u018A\u018B\x05\x11\x07\x02\u018B\u018C\x05" + + "\x13\b\x02\u018C\u018D\x05\x15\t\x02\u018D\u018E\x05\r\x05\x02\u018E\u018F" + + "\x05!\x0F\x02\u018F\u0190\x05\x1B\f\x02\u0190\u0194\x05\x1F\x0E\x02\u0191" + + "\u0193\x05\v\x04\x02\u0192\u0191\x03\x02\x02\x02\u0193\u0196\x03\x02\x02" + + "\x02\u0194\u0192\x03\x02\x02\x02\u0194\u0195\x03\x02\x02\x02\u0195\u0197" + + "\x03\x02\x02\x02\u0196\u0194\x03\x02\x02\x02\u0197\u0198\x07<\x02\x02" + + "\u0198\u0199\x06/\b\x02\u0199\u019A\b/\x11\x02\u019Ab\x03\x02\x02\x02" + + "\u019B\u019C\x05)\x13\x02\u019C\u019D\b0\x12\x02\u019Dd\x03\x02\x02\x02" + + "\u019E\u019F\x05\'\x12\x02\u019F\u01A0\b1\x13\x02\u01A0f\x03\x02\x02\x02" + + "\u01A1\u01A3\n\x13\x02\x02\u01A2\u01A1\x03\x02\x02\x02\u01A3\u01A4\x03" + + "\x02\x02\x02\u01A4\u01A5\x03\x02\x02\x02\u01A4\u01A2\x03\x02\x02\x02\u01A5" + + "\u01A6\x03\x02\x02\x02\u01A6\u01A7\b2\x14\x02\u01A7h\x03\x02\x02\x02\u01A8" + + "\u01AA\x05\v\x04\x02\u01A9\u01A8\x03\x02\x02\x02\u01AA\u01AB\x03\x02\x02" + + "\x02\u01AB\u01A9\x03\x02\x02\x02\u01AB\u01AC\x03\x02\x02\x02\u01ACj\x03" + + "\x02\x02\x02\u01AD\u01B1\t\x14\x02\x02\u01AE\u01B0\n\x13\x02\x02\u01AF" + + "\u01AE\x03\x02\x02\x02\u01B0\u01B3\x03\x02\x02\x02\u01B1\u01AF\x03\x02" + + "\x02\x02\u01B1\u01B2\x03\x02\x02\x02\u01B2\u01B5\x03\x02\x02\x02\u01B3" + + "\u01B1\x03\x02\x02\x02\u01B4\u01B6\x07\x0F\x02\x02\u01B5\u01B4\x03\x02" + + "\x02\x02\u01B5\u01B6\x03\x02\x02\x02\u01B6\u01B7\x03\x02\x02\x02\u01B7" + + "\u01B8\x07\f\x02\x02\u01B8\u01B9\x03\x02\x02\x02\u01B9\u01BA\b4\x02\x02" + + "\u01BAl\x03\x02\x02\x02\u01BB\u01BD\x07\x0F\x02\x02\u01BC\u01BB\x03\x02" + + "\x02\x02\u01BC\u01BD\x03\x02\x02\x02\u01BD\u01BE\x03\x02\x02\x02\u01BE" + + "\u01BF\x07\f\x02\x02\u01BFn\x03\x02\x02\x02\u01C0\u01C2\x05i3\x02\u01C1" + + "\u01C0\x03\x02\x02\x02\u01C1\u01C2\x03\x02\x02\x02\u01C2\u01C3\x03\x02" + + "\x02\x02\u01C3\u01C5\x057\x1A\x02\u01C4\u01C6\x05i3\x02\u01C5\u01C4\x03" + + "\x02\x02\x02\u01C5\u01C6\x03\x02\x02\x02\u01C6\u01C7\x03\x02\x02\x02\u01C7" + + "\u01C8\b6\b\x02\u01C8p\x03\x02\x02\x02\u01C9\u01CB\n\x13\x02\x02\u01CA" + + "\u01C9\x03\x02\x02\x02\u01CB\u01CC\x03\x02\x02\x02\u01CC\u01CA\x03\x02" + + "\x02\x02\u01CC\u01CD\x03\x02\x02\x02\u01CDr\x03\x02\x02\x02\u01CE\u01CF" + + "\x07b\x02\x02\u01CF\u01D0\x07b\x02\x02\u01D0\u01D1\x07b\x02\x02\u01D1" + + "\u01D2\x03\x02\x02\x02\u01D2\u01D3\b8\b\x02\u01D3t\x03\x02\x02\x02\u01D4" + + "\u01D5\x05)\x13\x02\u01D5\u01D6\x03\x02\x02\x02\u01D6\u01D7\b9\x15\x02" + + "\u01D7v\x03\x02\x02\x02\u01D8\u01D9\x05\'\x12\x02\u01D9\u01DA\x03\x02" + + "\x02\x02\u01DA\u01DB\b:\x16\x02\u01DBx\x03\x02\x02\x02\u01DC\u01DE\x07" + + "\x0F\x02\x02\u01DD\u01DC\x03\x02\x02\x02\u01DD\u01DE\x03\x02\x02\x02\u01DE" + + "\u01DF\x03\x02\x02\x02\u01DF\u01E2\x07\f\x02\x02\u01E0\u01E2\n\x13\x02" + + "\x02\u01E1\u01DD\x03\x02\x02\x02\u01E1\u01E0\x03\x02\x02\x02\u01E2\u01E3" + + "\x03\x02\x02\x02\u01E3\u01E4\x03\x02\x02\x02\u01E3\u01E1\x03\x02\x02\x02" + + "\u01E4\u01E5\x03\x02\x02\x02\u01E5\u01E6\b;\x17\x02\u01E6z\x03\x02\x02" + + "\x02.\x02\x03\x04\x05\x06\x9D\xA5\xA9\xAF\xB1\xB8\xBE\xC1\xC8\xCD\xE4" + + "\xED\xF7\xFC\u0106\u010B\u010D\u011B\u0120\u0129\u0134\u0141\u014F\u0157" + + "\u0165\u0175\u0183\u0194\u01A4\u01AB\u01B1\u01B5\u01BC\u01C1\u01C5\u01CC" + + "\u01DD\u01E1\u01E3\x18\b\x02\x02\x07\x03\x02\x03\x18\x02\x07\x04\x02\x07" + + "\x05\x02\t\x05\x02\x06\x02\x02\t\x04\x02\x07\x06\x02\x03)\x03\x03*\x04" + + "\x03+\x05\x03,\x06\x03-\x07\x03.\b\x03/\t\x030\n\x031\v\x032\f\t\x1C\x02" + + "\t\x1D\x02\t\x1E\x02"; public static __ATN: ATN; public static get _ATN(): ATN { if (!LGFileLexer.__ATN) { diff --git a/libraries/botbuilder-lg/src/generated/LGFileParser.ts b/libraries/botbuilder-lg/src/generated/LGFileParser.ts index c243fa5425..c4e994920d 100644 --- a/libraries/botbuilder-lg/src/generated/LGFileParser.ts +++ b/libraries/botbuilder-lg/src/generated/LGFileParser.ts @@ -53,23 +53,22 @@ export class LGFileParser extends Parser { public static readonly COMMA = 16; public static readonly TEXT_IN_NAME = 17; public static readonly WS_IN_BODY_IGNORED = 18; - public static readonly IF = 19; - public static readonly ELSEIF = 20; - public static readonly ELSE = 21; - public static readonly SWITCH = 22; - public static readonly CASE = 23; - public static readonly DEFAULT = 24; - public static readonly MULTI_LINE_TEXT = 25; + public static readonly MULTILINE_PREFIX = 19; + public static readonly IF = 20; + public static readonly ELSEIF = 21; + public static readonly ELSE = 22; + public static readonly SWITCH = 23; + public static readonly CASE = 24; + public static readonly DEFAULT = 25; public static readonly ESCAPE_CHARACTER = 26; public static readonly EXPRESSION = 27; - public static readonly TEMPLATE_REF = 28; - public static readonly TEXT_SEPARATOR = 29; - public static readonly TEXT = 30; - public static readonly WS_IN_STRUCTURED = 31; - public static readonly STRUCTURED_COMMENTS = 32; - public static readonly STRUCTURED_NEWLINE = 33; - public static readonly STRUCTURED_TEMPLATE_BODY_END = 34; - public static readonly STRUCTURED_CONTENT = 35; + public static readonly TEXT = 28; + public static readonly WS_IN_STRUCTURED = 29; + public static readonly STRUCTURED_COMMENTS = 30; + public static readonly STRUCTURED_NEWLINE = 31; + public static readonly STRUCTURED_TEMPLATE_BODY_END = 32; + public static readonly STRUCTURED_CONTENT = 33; + public static readonly MULTILINE_SUFFIX = 34; public static readonly RULE_file = 0; public static readonly RULE_paragraph = 1; public static readonly RULE_newline = 2; @@ -113,10 +112,10 @@ export class LGFileParser extends Parser { undefined, "COMMENTS", "WS", "NEWLINE", "HASH", "DASH", "LEFT_SQUARE_BRACKET", "RIGHT_SQUARE_BRACKET", "IMPORT_DESC", "IMPORT_PATH", "INVALID_TOKEN_DEFAULT_MODE", "WS_IN_NAME", "IDENTIFIER", "DOT", "OPEN_PARENTHESIS", "CLOSE_PARENTHESIS", - "COMMA", "TEXT_IN_NAME", "WS_IN_BODY_IGNORED", "IF", "ELSEIF", "ELSE", - "SWITCH", "CASE", "DEFAULT", "MULTI_LINE_TEXT", "ESCAPE_CHARACTER", "EXPRESSION", - "TEMPLATE_REF", "TEXT_SEPARATOR", "TEXT", "WS_IN_STRUCTURED", "STRUCTURED_COMMENTS", - "STRUCTURED_NEWLINE", "STRUCTURED_TEMPLATE_BODY_END", "STRUCTURED_CONTENT", + "COMMA", "TEXT_IN_NAME", "WS_IN_BODY_IGNORED", "MULTILINE_PREFIX", "IF", + "ELSEIF", "ELSE", "SWITCH", "CASE", "DEFAULT", "ESCAPE_CHARACTER", "EXPRESSION", + "TEXT", "WS_IN_STRUCTURED", "STRUCTURED_COMMENTS", "STRUCTURED_NEWLINE", + "STRUCTURED_TEMPLATE_BODY_END", "STRUCTURED_CONTENT", "MULTILINE_SUFFIX", ]; public static readonly VOCABULARY: Vocabulary = new VocabularyImpl(LGFileParser._LITERAL_NAMES, LGFileParser._SYMBOLIC_NAMES, []); @@ -797,12 +796,12 @@ export class LGFileParser extends Parser { this.state = 141; this._errHandler.sync(this); _la = this._input.LA(1); - while ((((_la) & ~0x1F) === 0 && ((1 << _la) & ((1 << LGFileParser.WS) | (1 << LGFileParser.MULTI_LINE_TEXT) | (1 << LGFileParser.ESCAPE_CHARACTER) | (1 << LGFileParser.EXPRESSION) | (1 << LGFileParser.TEMPLATE_REF) | (1 << LGFileParser.TEXT_SEPARATOR) | (1 << LGFileParser.TEXT))) !== 0)) { + while ((((_la) & ~0x1F) === 0 && ((1 << _la) & ((1 << LGFileParser.WS) | (1 << LGFileParser.MULTILINE_PREFIX) | (1 << LGFileParser.ESCAPE_CHARACTER) | (1 << LGFileParser.EXPRESSION) | (1 << LGFileParser.TEXT))) !== 0) || _la === LGFileParser.MULTILINE_SUFFIX) { { { this.state = 138; _la = this._input.LA(1); - if (!((((_la) & ~0x1F) === 0 && ((1 << _la) & ((1 << LGFileParser.WS) | (1 << LGFileParser.MULTI_LINE_TEXT) | (1 << LGFileParser.ESCAPE_CHARACTER) | (1 << LGFileParser.EXPRESSION) | (1 << LGFileParser.TEMPLATE_REF) | (1 << LGFileParser.TEXT_SEPARATOR) | (1 << LGFileParser.TEXT))) !== 0))) { + if (!((((_la) & ~0x1F) === 0 && ((1 << _la) & ((1 << LGFileParser.WS) | (1 << LGFileParser.MULTILINE_PREFIX) | (1 << LGFileParser.ESCAPE_CHARACTER) | (1 << LGFileParser.EXPRESSION) | (1 << LGFileParser.TEXT))) !== 0) || _la === LGFileParser.MULTILINE_SUFFIX)) { this._errHandler.recoverInline(this); } else { if (this._input.LA(1) === Token.EOF) { @@ -1175,7 +1174,7 @@ export class LGFileParser extends Parser { } public static readonly _serializedATN: string = - "\x03\uAF6F\u8320\u479D\uB75C\u4880\u1605\u191C\uAB37\x03%\xBF\x04\x02" + + "\x03\uAF6F\u8320\u479D\uB75C\u4880\u1605\u191C\uAB37\x03$\xBF\x04\x02" + "\t\x02\x04\x03\t\x03\x04\x04\t\x04\x04\x05\t\x05\x04\x06\t\x06\x04\x07" + "\t\x07\x04\b\t\b\x04\t\t\t\x04\n\t\n\x04\v\t\v\x04\f\t\f\x04\r\t\r\x04" + "\x0E\t\x0E\x04\x0F\t\x0F\x04\x10\t\x10\x04\x11\t\x11\x04\x12\t\x12\x04" + @@ -1198,63 +1197,63 @@ export class LGFileParser extends Parser { "\x03\x19\x03\x19\x035\x02\x02\x1A\x02\x02\x04\x02\x06\x02\b\x02\n\x02" + "\f\x02\x0E\x02\x10\x02\x12\x02\x14\x02\x16\x02\x18\x02\x1A\x02\x1C\x02" + "\x1E\x02 \x02\"\x02$\x02&\x02(\x02*\x02,\x02.\x020\x02\x02\b\x03\x03\x05" + - "\x05\x03\x02\x0E\x13\x04\x02\x04\x04\x1B \x03\x02\x15\x17\x05\x02\x04" + - "\x04\x1D\x1D \x03\x02\x18\x1A\xBF\x023\x03\x02\x02\x02\x04<\x03\x02\x02" + - "\x02\x06>\x03\x02\x02\x02\b@\x03\x02\x02\x02\nE\x03\x02\x02\x02\fP\x03" + - "\x02\x02\x02\x0ES\x03\x02\x02\x02\x10[\x03\x02\x02\x02\x12l\x03\x02\x02" + - "\x02\x14n\x03\x02\x02\x02\x16t\x03\x02\x02\x02\x18z\x03\x02\x02\x02\x1A" + - "~\x03\x02\x02\x02\x1C\x83\x03\x02\x02\x02\x1E\x89\x03\x02\x02\x02 \x8B" + - "\x03\x02\x02\x02\"\x93\x03\x02\x02\x02$\x98\x03\x02\x02\x02&\x9C\x03\x02" + - "\x02\x02(\xA1\x03\x02\x02\x02*\xAA\x03\x02\x02\x02,\xAE\x03\x02\x02\x02" + - ".\xB3\x03\x02\x02\x020\xBB\x03\x02\x02\x0224\x05\x04\x03\x0232\x03\x02" + - "\x02\x0245\x03\x02\x02\x0256\x03\x02\x02\x0253\x03\x02\x02\x0267\x03\x02" + - "\x02\x0278\x07\x02\x02\x038\x03\x03\x02\x02\x029=\x05\x06\x04\x02:=\x05" + - "\b\x05\x02;=\x050\x19\x02<9\x03\x02\x02\x02<:\x03\x02\x02\x02<;\x03\x02" + - "\x02\x02=\x05\x03\x02\x02\x02>?\t\x02\x02\x02?\x07\x03\x02\x02\x02@A\x05" + - "\n\x06\x02AC\x05\x06\x04\x02BD\x05\x12\n\x02CB\x03\x02\x02\x02CD\x03\x02" + - "\x02\x02D\t\x03\x02\x02\x02EK\x07\x06\x02\x02FH\x05\x0E\b\x02GI\x05\x10" + - "\t\x02HG\x03\x02\x02\x02HI\x03\x02\x02\x02IL\x03\x02\x02\x02JL\x05\f\x07" + - "\x02KF\x03\x02\x02\x02KJ\x03\x02\x02\x02L\v\x03\x02\x02\x02MO\t\x03\x02" + - "\x02NM\x03\x02\x02\x02OR\x03\x02\x02\x02PN\x03\x02\x02\x02PQ\x03\x02\x02" + - "\x02Q\r\x03\x02\x02\x02RP\x03\x02\x02\x02SX\x07\x0E\x02\x02TU\x07\x0F" + - "\x02\x02UW\x07\x0E\x02\x02VT\x03\x02\x02\x02WZ\x03\x02\x02\x02XV\x03\x02" + - "\x02\x02XY\x03\x02\x02\x02Y\x0F\x03\x02\x02\x02ZX\x03\x02\x02\x02[d\x07" + - "\x10\x02\x02\\a\x07\x0E\x02\x02]^\x07\x12\x02\x02^`\x07\x0E\x02\x02_]" + - "\x03\x02\x02\x02`c\x03\x02\x02\x02a_\x03\x02\x02\x02ab\x03\x02\x02\x02" + - "be\x03\x02\x02\x02ca\x03\x02\x02\x02d\\\x03\x02\x02\x02de\x03\x02\x02" + - "\x02ef\x03\x02\x02\x02fg\x07\x11\x02\x02g\x11\x03\x02\x02\x02hm\x05\x1C" + - "\x0F\x02im\x05$\x13\x02jm\x05*\x16\x02km\x05\x14\v\x02lh\x03\x02\x02\x02" + - "li\x03\x02\x02\x02lj\x03\x02\x02\x02lk\x03\x02\x02\x02m\x13\x03\x02\x02" + - "\x02np\x05\x16\f\x02oq\x05\x18\r\x02po\x03\x02\x02\x02pq\x03\x02\x02\x02" + - "qr\x03\x02\x02\x02rs\x05\x1A\x0E\x02s\x15\x03\x02\x02\x02tu\x07\b\x02" + - "\x02uv\x07%\x02\x02vw\x07#\x02\x02w\x17\x03\x02\x02\x02xy\x07%\x02\x02" + - "y{\x07#\x02\x02zx\x03\x02\x02\x02{|\x03\x02\x02\x02|z\x03\x02\x02\x02" + - "|}\x03\x02\x02\x02}\x19\x03\x02\x02\x02~\x7F\x07$\x02\x02\x7F\x1B\x03" + - "\x02\x02\x02\x80\x81\x05\x1E\x10\x02\x81\x82\x05\x06\x04\x02\x82\x84\x03" + - "\x02\x02\x02\x83\x80\x03\x02\x02\x02\x84\x85\x03\x02\x02\x02\x85\x83\x03" + - "\x02\x02\x02\x85\x86\x03\x02\x02\x02\x86\x1D\x03\x02\x02\x02\x87\x8A\x05" + - " \x11\x02\x88\x8A\x05\"\x12\x02\x89\x87\x03\x02\x02\x02\x89\x88\x03\x02" + - "\x02\x02\x8A\x1F\x03\x02\x02\x02\x8B\x8F\x07\x07\x02\x02\x8C\x8E\t\x04" + - "\x02\x02\x8D\x8C\x03\x02\x02\x02\x8E\x91\x03\x02\x02\x02\x8F\x8D\x03\x02" + - "\x02\x02\x8F\x90\x03\x02\x02\x02\x90!\x03\x02\x02\x02\x91\x8F\x03\x02" + - "\x02\x02\x92\x94\x07\f\x02\x02\x93\x92\x03\x02\x02\x02\x94\x95\x03\x02" + - "\x02\x02\x95\x93\x03\x02\x02\x02\x95\x96\x03\x02\x02\x02\x96#\x03\x02" + - "\x02\x02\x97\x99\x05&\x14\x02\x98\x97\x03\x02\x02\x02\x99\x9A\x03\x02" + - "\x02\x02\x9A\x98\x03\x02\x02\x02\x9A\x9B\x03\x02\x02\x02\x9B%\x03\x02" + - "\x02\x02\x9C\x9D\x05(\x15\x02\x9D\x9F\x05\x06\x04\x02\x9E\xA0\x05\x1C" + - "\x0F\x02\x9F\x9E\x03\x02\x02\x02\x9F\xA0\x03\x02\x02\x02\xA0\'\x03\x02" + - "\x02\x02\xA1\xA2\x07\x07\x02\x02\xA2\xA6\t\x05\x02\x02\xA3\xA5\t\x06\x02" + - "\x02\xA4\xA3\x03\x02\x02\x02\xA5\xA8\x03\x02\x02\x02\xA6\xA4\x03\x02\x02" + - "\x02\xA6\xA7\x03\x02\x02\x02\xA7)\x03\x02\x02\x02\xA8\xA6\x03\x02\x02" + - "\x02\xA9\xAB\x05,\x17\x02\xAA\xA9\x03\x02\x02\x02\xAB\xAC\x03\x02\x02" + - "\x02\xAC\xAA\x03\x02\x02\x02\xAC\xAD\x03\x02\x02\x02\xAD+\x03\x02\x02" + - "\x02\xAE\xAF\x05.\x18\x02\xAF\xB1\x05\x06\x04\x02\xB0\xB2\x05\x1C\x0F" + - "\x02\xB1\xB0\x03\x02\x02\x02\xB1\xB2\x03\x02\x02\x02\xB2-\x03\x02\x02" + - "\x02\xB3\xB4\x07\x07\x02\x02\xB4\xB8\t\x07\x02\x02\xB5\xB7\t\x06\x02\x02" + - "\xB6\xB5\x03\x02\x02\x02\xB7\xBA\x03\x02\x02\x02\xB8\xB6\x03\x02\x02\x02" + - "\xB8\xB9\x03\x02\x02\x02\xB9/\x03\x02\x02\x02\xBA\xB8\x03\x02\x02\x02" + - "\xBB\xBC\x07\n\x02\x02\xBC\xBD\x07\v\x02\x02\xBD1\x03\x02\x02\x02\x18" + - "5\x03\x02\x02\x02\b@\x03\x02\x02\x02\nE\x03\x02\x02" + + "\x02\fP\x03\x02\x02\x02\x0ES\x03\x02\x02\x02\x10[\x03\x02\x02\x02\x12" + + "l\x03\x02\x02\x02\x14n\x03\x02\x02\x02\x16t\x03\x02\x02\x02\x18z\x03\x02" + + "\x02\x02\x1A~\x03\x02\x02\x02\x1C\x83\x03\x02\x02\x02\x1E\x89\x03\x02" + + "\x02\x02 \x8B\x03\x02\x02\x02\"\x93\x03\x02\x02\x02$\x98\x03\x02\x02\x02" + + "&\x9C\x03\x02\x02\x02(\xA1\x03\x02\x02\x02*\xAA\x03\x02\x02\x02,\xAE\x03" + + "\x02\x02\x02.\xB3\x03\x02\x02\x020\xBB\x03\x02\x02\x0224\x05\x04\x03\x02" + + "32\x03\x02\x02\x0245\x03\x02\x02\x0256\x03\x02\x02\x0253\x03\x02\x02\x02" + + "67\x03\x02\x02\x0278\x07\x02\x02\x038\x03\x03\x02\x02\x029=\x05\x06\x04" + + "\x02:=\x05\b\x05\x02;=\x050\x19\x02<9\x03\x02\x02\x02<:\x03\x02\x02\x02" + + "<;\x03\x02\x02\x02=\x05\x03\x02\x02\x02>?\t\x02\x02\x02?\x07\x03\x02\x02" + + "\x02@A\x05\n\x06\x02AC\x05\x06\x04\x02BD\x05\x12\n\x02CB\x03\x02\x02\x02" + + "CD\x03\x02\x02\x02D\t\x03\x02\x02\x02EK\x07\x06\x02\x02FH\x05\x0E\b\x02" + + "GI\x05\x10\t\x02HG\x03\x02\x02\x02HI\x03\x02\x02\x02IL\x03\x02\x02\x02" + + "JL\x05\f\x07\x02KF\x03\x02\x02\x02KJ\x03\x02\x02\x02L\v\x03\x02\x02\x02" + + "MO\t\x03\x02\x02NM\x03\x02\x02\x02OR\x03\x02\x02\x02PN\x03\x02\x02\x02" + + "PQ\x03\x02\x02\x02Q\r\x03\x02\x02\x02RP\x03\x02\x02\x02SX\x07\x0E\x02" + + "\x02TU\x07\x0F\x02\x02UW\x07\x0E\x02\x02VT\x03\x02\x02\x02WZ\x03\x02\x02" + + "\x02XV\x03\x02\x02\x02XY\x03\x02\x02\x02Y\x0F\x03\x02\x02\x02ZX\x03\x02" + + "\x02\x02[d\x07\x10\x02\x02\\a\x07\x0E\x02\x02]^\x07\x12\x02\x02^`\x07" + + "\x0E\x02\x02_]\x03\x02\x02\x02`c\x03\x02\x02\x02a_\x03\x02\x02\x02ab\x03" + + "\x02\x02\x02be\x03\x02\x02\x02ca\x03\x02\x02\x02d\\\x03\x02\x02\x02de" + + "\x03\x02\x02\x02ef\x03\x02\x02\x02fg\x07\x11\x02\x02g\x11\x03\x02\x02" + + "\x02hm\x05\x1C\x0F\x02im\x05$\x13\x02jm\x05*\x16\x02km\x05\x14\v\x02l" + + "h\x03\x02\x02\x02li\x03\x02\x02\x02lj\x03\x02\x02\x02lk\x03\x02\x02\x02" + + "m\x13\x03\x02\x02\x02np\x05\x16\f\x02oq\x05\x18\r\x02po\x03\x02\x02\x02" + + "pq\x03\x02\x02\x02qr\x03\x02\x02\x02rs\x05\x1A\x0E\x02s\x15\x03\x02\x02" + + "\x02tu\x07\b\x02\x02uv\x07#\x02\x02vw\x07!\x02\x02w\x17\x03\x02\x02\x02" + + "xy\x07#\x02\x02y{\x07!\x02\x02zx\x03\x02\x02\x02{|\x03\x02\x02\x02|z\x03" + + "\x02\x02\x02|}\x03\x02\x02\x02}\x19\x03\x02\x02\x02~\x7F\x07\"\x02\x02" + + "\x7F\x1B\x03\x02\x02\x02\x80\x81\x05\x1E\x10\x02\x81\x82\x05\x06\x04\x02" + + "\x82\x84\x03\x02\x02\x02\x83\x80\x03\x02\x02\x02\x84\x85\x03\x02\x02\x02" + + "\x85\x83\x03\x02\x02\x02\x85\x86\x03\x02\x02\x02\x86\x1D\x03\x02\x02\x02" + + "\x87\x8A\x05 \x11\x02\x88\x8A\x05\"\x12\x02\x89\x87\x03\x02\x02\x02\x89" + + "\x88\x03\x02\x02\x02\x8A\x1F\x03\x02\x02\x02\x8B\x8F\x07\x07\x02\x02\x8C" + + "\x8E\t\x04\x02\x02\x8D\x8C\x03\x02\x02\x02\x8E\x91\x03\x02\x02\x02\x8F" + + "\x8D\x03\x02\x02\x02\x8F\x90\x03\x02\x02\x02\x90!\x03\x02\x02\x02\x91" + + "\x8F\x03\x02\x02\x02\x92\x94\x07\f\x02\x02\x93\x92\x03\x02\x02\x02\x94" + + "\x95\x03\x02\x02\x02\x95\x93\x03\x02\x02\x02\x95\x96\x03\x02\x02\x02\x96" + + "#\x03\x02\x02\x02\x97\x99\x05&\x14\x02\x98\x97\x03\x02\x02\x02\x99\x9A" + + "\x03\x02\x02\x02\x9A\x98\x03\x02\x02\x02\x9A\x9B\x03\x02\x02\x02\x9B%" + + "\x03\x02\x02\x02\x9C\x9D\x05(\x15\x02\x9D\x9F\x05\x06\x04\x02\x9E\xA0" + + "\x05\x1C\x0F\x02\x9F\x9E\x03\x02\x02\x02\x9F\xA0\x03\x02\x02\x02\xA0\'" + + "\x03\x02\x02\x02\xA1\xA2\x07\x07\x02\x02\xA2\xA6\t\x05\x02\x02\xA3\xA5" + + "\t\x06\x02\x02\xA4\xA3\x03\x02\x02\x02\xA5\xA8\x03\x02\x02\x02\xA6\xA4" + + "\x03\x02\x02\x02\xA6\xA7\x03\x02\x02\x02\xA7)\x03\x02\x02\x02\xA8\xA6" + + "\x03\x02\x02\x02\xA9\xAB\x05,\x17\x02\xAA\xA9\x03\x02\x02\x02\xAB\xAC" + + "\x03\x02\x02\x02\xAC\xAA\x03\x02\x02\x02\xAC\xAD\x03\x02\x02\x02\xAD+" + + "\x03\x02\x02\x02\xAE\xAF\x05.\x18\x02\xAF\xB1\x05\x06\x04\x02\xB0\xB2" + + "\x05\x1C\x0F\x02\xB1\xB0\x03\x02\x02\x02\xB1\xB2\x03\x02\x02\x02\xB2-" + + "\x03\x02\x02\x02\xB3\xB4\x07\x07\x02\x02\xB4\xB8\t\x07\x02\x02\xB5\xB7" + + "\t\x06\x02\x02\xB6\xB5\x03\x02\x02\x02\xB7\xBA\x03\x02\x02\x02\xB8\xB6" + + "\x03\x02\x02\x02\xB8\xB9\x03\x02\x02\x02\xB9/\x03\x02\x02\x02\xBA\xB8" + + "\x03\x02\x02\x02\xBB\xBC\x07\n\x02\x02\xBC\xBD\x07\v\x02\x02\xBD1\x03" + + "\x02\x02\x02\x185 0){ + if (replaceString){ destList.push(replaceString); destList.push('\r\n'); } @@ -128,7 +128,7 @@ export class LGResource { destList.push(...this.trimList(originList.slice(stopLine + 1))); } else { // insert at the tail of the content - if (replaceString !== undefined && replaceString.length > 0){ + if (replaceString){ destList.push('\r\n'); destList.push(replaceString); } @@ -182,7 +182,7 @@ export class LGResource { } private convertTemplateBody(templateBody: string): string { - if (templateBody === undefined || templateBody.length === 0) { + if (!templateBody) { return ''; } diff --git a/libraries/botbuilder-lg/src/lgTemplate.ts b/libraries/botbuilder-lg/src/lgTemplate.ts index 0abcb8f0dd..8767762ee4 100644 --- a/libraries/botbuilder-lg/src/lgTemplate.ts +++ b/libraries/botbuilder-lg/src/lgTemplate.ts @@ -47,7 +47,7 @@ export class LGTemplate { private readonly extractName = (parseTree: TemplateDefinitionContext): string => { // tslint:disable-next-line: newline-per-chained-call const nameContext: TemplateNameContext = parseTree.templateNameLine().templateName(); - if (nameContext === undefined || nameContext.text === undefined) { + if (!nameContext || !nameContext.text) { return ''; } @@ -67,7 +67,7 @@ export class LGTemplate { private readonly extractBody = (parseTree: TemplateDefinitionContext, lgfileContent: string): string => { const templateBody: TemplateBodyContext = parseTree.templateBody(); - if (templateBody === undefined) { + if (!templateBody) { return ''; } @@ -92,4 +92,8 @@ export class LGTemplate { return result; } + + public toString(): string { + return `[${ this.name }(${ this.parameters.join(', ') })]"${ this.body }"`; + } } diff --git a/libraries/botbuilder-lg/src/mslgTool.ts b/libraries/botbuilder-lg/src/mslgTool.ts index cef9dc8547..471c861241 100644 --- a/libraries/botbuilder-lg/src/mslgTool.ts +++ b/libraries/botbuilder-lg/src/mslgTool.ts @@ -1,5 +1,5 @@ /** - * @module bbotbuilder-lg + * @module botbuilder-lg */ /** * Copyright (c) Microsoft Corporation. All rights reserved. @@ -38,7 +38,7 @@ export class MSLGTool { // extract templates this.templates = LGParser.parse(lgFileContent).templates; - if (this.templates !== undefined && this.templates.length > 0) { + if (this.templates && this.templates.length > 0) { this.runTemplateExtractor(this.templates); } @@ -59,10 +59,10 @@ export class MSLGTool { public collateTemplates(): string { let result = ''; - if (this.collationMessages === undefined || this.collationMessages.length === 0) { + if (!this.collationMessages || this.collationMessages.length === 0) { for (const template of this.collatedTemplates) { result += '# ' + template[0] + '\n'; - if (template[1] instanceof Array) { + if (Array.isArray(template[1])) { const templateStrs: string[] = template[1] as string[]; for (const templateStr of templateStrs) { if (templateStr.startsWith('-')) { @@ -109,7 +109,7 @@ export class MSLGTool { this.collatedTemplates.set(template[0], this.collatedTemplates.get(template[0]).set(condition[0], condition[1])); } } - } else if (this.collatedTemplates.get(template[0]) instanceof Array && template[1] instanceof Array) { + } else if (Array.isArray(this.collatedTemplates.get(template[0])) && Array.isArray(template[1])) { // tslint:disable-next-line: max-line-length this.collatedTemplates.set(template[0], Array.from(new Set(this.collatedTemplates.get(template[0]).concat(template[1])))); } else { diff --git a/libraries/botbuilder-lg/src/staticChecker.ts b/libraries/botbuilder-lg/src/staticChecker.ts index 6e1c5775b8..88642bb012 100644 --- a/libraries/botbuilder-lg/src/staticChecker.ts +++ b/libraries/botbuilder-lg/src/staticChecker.ts @@ -24,127 +24,25 @@ import { LGTemplate } from './lgTemplate'; import { Position } from './position'; import { Range } from './range'; -/** - * Static checker tool - */ -export class StaticChecker { - private readonly expressionEngine: ExpressionEngine; - - public constructor(expressionEngine?: ExpressionEngine) { - this.expressionEngine = expressionEngine !== undefined ? expressionEngine : new ExpressionEngine(); - } - - public checkFiles(filePaths: string[], importResolver?: ImportResolverDelegate): Diagnostic[] { - let result: Diagnostic[] = []; - let templates: LGTemplate[] = []; - let isParseSuccess = true; - - try { - let totalLGResources: LGResource[] = []; - filePaths.forEach((filePath: string): void => { - importResolver = importResolver !== undefined ? importResolver : ImportResolver.fileResolver; - - filePath = path.normalize(ImportResolver.normalizePath(filePath)); - const rootResource: LGResource = LGParser.parse(fs.readFileSync(filePath, 'utf-8'), filePath); - const lgResources: LGResource[] = rootResource.discoverLGResources(importResolver); - totalLGResources = totalLGResources.concat(lgResources); - }); - - // Remove duplicated lg files by id - // tslint:disable-next-line: max-line-length - const deduplicatedLGResources: LGResource[] = totalLGResources.filter((resource: LGResource, index: number, self: LGResource[]): boolean => - index === self.findIndex((t: LGResource): boolean => ( - t.id === resource.id - )) - ); - - templates = deduplicatedLGResources.reduce((acc: LGTemplate[], x: LGResource): any => - acc = acc.concat(x.templates), [] - ); - } catch (e) { - isParseSuccess = false; - if (e instanceof LGException) { - result = result.concat(e.getDiagnostic()); - } else { - const diagnostic: Diagnostic = new Diagnostic(new Range(new Position(0, 0), new Position(0, 0)), e.message); - result.push(diagnostic); - } - } - - if (isParseSuccess) { - result = result.concat(this.checkTemplates(templates)); - } - - return result; - } - - public checkFile(filePath: string, importResolver?: ImportResolverDelegate): Diagnostic[] { - return this.checkFiles([filePath], importResolver); - } - - public checkText(content: string, id?: string, importResolver?: ImportResolverDelegate): Diagnostic[] { - if (importResolver === undefined) { - const importPath: string = ImportResolver.normalizePath(id); - if (!path.isAbsolute(importPath)) { - throw new Error('[Error] id must be full path when importResolver is empty'); - } - } - - let result: Diagnostic[] = []; - let templates: LGTemplate[] = []; - let isParseSuccess = true; - - try { - const rootResource: LGResource = LGParser.parse(content, id); - const resources: LGResource[] = rootResource.discoverLGResources(importResolver); - - templates = resources.reduce((acc: LGTemplate[], x: LGResource): any => - // tslint:disable-next-line: align - acc = acc.concat(x.templates), [] - ); - } catch (e) { - isParseSuccess = false; - if (e instanceof LGException) { - result = result.concat(e.getDiagnostic()); - } else { - const diagnostic: Diagnostic = new Diagnostic(new Range(new Position(0, 0), new Position(0, 0)), e.message); - result.push(diagnostic); - } - } - - if (isParseSuccess) { - result = result.concat(this.checkTemplates(templates)); - } - - return result; - } - - public checkTemplates(templates: LGTemplate[]): Diagnostic[] { - // tslint:disable-next-line: no-use-before-declare - return new StaticCheckerInner(templates, this.expressionEngine).check(); - } -} // tslint:disable-next-line: completed-docs class StaticCheckerInner extends AbstractParseTreeVisitor implements LGFileParserVisitor { - public readonly Templates: LGTemplate[]; - public TemplateMap: {[name: string]: LGTemplate}; + public readonly templates: LGTemplate[]; + public templateMap: {[name: string]: LGTemplate}; private currentSource: string = ''; private readonly baseExpressionEngine: ExpressionEngine; private _expressionParser: ExpressionParserInterface; - private readonly expressionRecognizeRegex: RegExp = new RegExp(/\}(?!\\).+?\{(?!\\)@?/g); - private readonly escapeSeperatorRegex: RegExp = new RegExp(/\|(?!\\)/g); private readonly structuredNameRegex: RegExp = new RegExp(/^[a-z0-9_][a-z0-9_\-\.]*$/i); public constructor(templates: LGTemplate[], expressionEngine: ExpressionEngine) { super(); - this.Templates = templates; + this.templates = templates; this.baseExpressionEngine = expressionEngine; } private get expressionParser(): ExpressionParserInterface { if (this._expressionParser === undefined) { - const evaluator: Evaluator = new Evaluator(this.Templates, this.baseExpressionEngine); + const evaluator: Evaluator = new Evaluator(this.templates, this.baseExpressionEngine); this._expressionParser = evaluator.expressionEngine; } @@ -154,9 +52,9 @@ class StaticCheckerInner extends AbstractParseTreeVisitor implemen public check(): Diagnostic[] { let result: Diagnostic[] = []; - // check dup, before we build up TemplateMap + // check dup first const grouped: {[name: string]: LGTemplate[]} = {}; - this.Templates.forEach((t: LGTemplate): void => { + this.templates.forEach((t: LGTemplate): void => { if (!(t.name in grouped)) { grouped[t.name] = []; } @@ -177,16 +75,16 @@ class StaticCheckerInner extends AbstractParseTreeVisitor implemen } // we can safely convert now, because we know there is no dup - this.TemplateMap = keyBy(this.Templates, (t: LGTemplate): string => t.name); + this.templateMap = keyBy(this.templates, (t: LGTemplate): string => t.name); - if (this.Templates.length <= 0) { + if (this.templates.length <= 0) { result.push(this.buildLGDiagnostic({ message: `File must have at least one template definition`, severity: DiagnosticSeverity.Warning })); } - this.Templates.forEach((template: LGTemplate): void => { + this.templates.forEach((template: LGTemplate): void => { this.currentSource = template.source; result = result.concat(this.visit(template.parseTree)); }); @@ -198,14 +96,14 @@ class StaticCheckerInner extends AbstractParseTreeVisitor implemen let result: Diagnostic[] = []; const templateNameLine: lp.TemplateNameLineContext = context.templateNameLine(); const errorTemplateName: lp.ErrorTemplateNameContext = templateNameLine.errorTemplateName(); - if (errorTemplateName !== undefined) { + if (errorTemplateName) { result.push(this.buildLGDiagnostic({ message: `Not a valid template name line`, context: errorTemplateName })); } else { const templateName: string = context.templateNameLine().templateName().text; - if (context.templateBody() === undefined) { + if (!context.templateBody()) { result.push(this.buildLGDiagnostic({ message: `There is no template body in template ${ templateName }`, context: context.templateNameLine(), @@ -224,13 +122,12 @@ class StaticCheckerInner extends AbstractParseTreeVisitor implemen for (const templateStr of context.templateString()) { const errorTemplateStr: lp.ErrorTemplateStringContext = templateStr.errorTemplateString(); - if (errorTemplateStr !== undefined) { + if (errorTemplateStr) { result.push(this.buildLGDiagnostic({ message: `Invalid template body line, did you miss '-' at line begin`, context: errorTemplateStr})); } else { - const item: Diagnostic[] = this.visit(templateStr); - result = result.concat(item); + result = result.concat(this.visit(templateStr)); } } @@ -249,7 +146,7 @@ class StaticCheckerInner extends AbstractParseTreeVisitor implemen const content: lp.StructuredBodyContentLineContext = context.structuredBodyContentLine(); let bodys: TerminalNode[] = []; - if (content !== undefined) { + if (content) { bodys = content.STRUCTURED_CONTENT(); } @@ -263,13 +160,13 @@ class StaticCheckerInner extends AbstractParseTreeVisitor implemen if (line !== '') { const start: number = line.indexOf('='); - if (start < 0 && !this.isPureExpression(line)) { + if (start < 0 && !Evaluator.isPureExpression(line)) { result.push(this.buildLGDiagnostic({ message: `Structured content does not support`, context: content})); } else if (start > 0) { const originValue: string = line.substr(start + 1).trim(); - const valueArray: string[] = Evaluator.wrappedRegExSplit(originValue, this.escapeSeperatorRegex); + const valueArray: string[] = Evaluator.wrappedRegExSplit(originValue, Evaluator.escapeSeperatorReverseRegex); if (valueArray.length === 1) { result = result.concat(this.checkText(originValue, context)); } else { @@ -277,7 +174,7 @@ class StaticCheckerInner extends AbstractParseTreeVisitor implemen result = result.concat(this.checkText(item.trim(), context)); } } - } else if (this.isPureExpression(line)) { + } else if (Evaluator.isPureExpression(line)) { result = result.concat(this.checkExpression(line, context)); } } @@ -451,7 +348,7 @@ class StaticCheckerInner extends AbstractParseTreeVisitor implemen } } if (caseExpr || defaultExpr) { - if (iterNode.normalTemplateBody() !== undefined) { + if (iterNode.normalTemplateBody()) { result = result.concat(this.visit(iterNode.normalTemplateBody())); } else { result.push(this.buildLGDiagnostic({ @@ -468,100 +365,40 @@ class StaticCheckerInner extends AbstractParseTreeVisitor implemen public visitNormalTemplateString(context: lp.NormalTemplateStringContext): Diagnostic[] { let result: Diagnostic[] = []; - for (const child of context.children) { - const node: TerminalNode = child as TerminalNode; - switch (node.symbol.type) { - case lp.LGFileParser.TEMPLATE_REF: { - result = result.concat(this.checkTemplateRef(node.text, context)); - break; - } - case lp.LGFileParser.EXPRESSION: { - result = result.concat(this.checkExpression(node.text, context)); - break; - } - case lp.LGFileParser.MULTI_LINE_TEXT: { - result = result.concat(this.checkMultiLineText(node.text, context)); - break; - } - case lp.LGFileParser.TEXT: { - result = result.concat(this.checkErrorMultiLineText(node.text, context)); - break; - } - default: - break; - } - } - - return result; - } - - protected defaultResult(): Diagnostic[] { - return []; - } - private checkTemplateRef(exp: string, context: ParserRuleContext): Diagnostic[] { - const result: Diagnostic[] = []; - exp = exp.replace(/(^\[*)/g, '') - .replace(/(\]*$)/g, '') - .trim(); - - let expression: string = exp; - if (exp.indexOf('(') < 0) { - if (exp in this.TemplateMap) { - expression = exp.concat('(') - .concat(this.TemplateMap[exp].parameters.join()) - .concat(')'); - } else { - expression = exp.concat('()'); - } + for (const expression of context.EXPRESSION()) { + result = result.concat(this.checkExpression(expression.text, context)); } - try { - this.expressionParser.parse(expression); - } catch (e) { + const multiLinePrefixNum: number = context.MULTILINE_PREFIX().length; + const multiLineSuffixNum: number = context.MULTILINE_SUFFIX().length; + + if (multiLinePrefixNum > 0 && multiLinePrefixNum > multiLineSuffixNum) { result.push(this.buildLGDiagnostic({ - message: e.message.concat(` in template reference '${ exp }'`), + message: 'Close ``` is missing.', context: context })); - - return result; } - return result; } - private checkMultiLineText(exp: string, context: ParserRuleContext): Diagnostic[] { - exp = exp.substr(3, exp.length - 6); - - return this.checkText(exp, context, true); + protected defaultResult(): Diagnostic[] { + return []; } - private checkText(exp: string, context: ParserRuleContext, isMultiLineText: boolean = false): Diagnostic[] { + private checkText(exp: string, context: ParserRuleContext): Diagnostic[] { let result: Diagnostic[] = []; - const reg: RegExp = isMultiLineText ? /@\{[^{}]+\}/g : /@?\{[^{}]+\}/g; - const matches: string[] = exp.match(reg); - if (matches !== null && matches !== undefined) { + const matches: string[] = exp.split('').reverse().join('').match(Evaluator.expressionRecognizeReverseRegex); + + if (matches) { for (const match of matches) { - result = result.concat(this.checkExpression(match, context)); + result = result.concat(this.checkExpression(match.split('').reverse().join(''), context)); } } return result; } - private checkErrorMultiLineText(exp: string, context: ParserRuleContext): Diagnostic[] { - const result: Diagnostic[] = []; - - if (exp.startsWith('```')) { - result.push(this.buildLGDiagnostic({ - message: 'Multi line variation must be enclosed in ```', - context: context - })); - } - - return result; - } - private checkExpression(exp: string, context: ParserRuleContext): Diagnostic[] { const result: Diagnostic[] = []; exp = exp.replace(/(^@*)/g, '') @@ -588,9 +425,9 @@ class StaticCheckerInner extends AbstractParseTreeVisitor implemen const severity: DiagnosticSeverity = parameters.severity === undefined ? DiagnosticSeverity.Error : parameters.severity; const context: ParserRuleContext = parameters.context; // tslint:disable-next-line: max-line-length - const startPosition: Position = context === undefined ? new Position(0, 0) : new Position(context.start.line, context.start.charPositionInLine); + const startPosition: Position = !context ? new Position(0, 0) : new Position(context.start.line, context.start.charPositionInLine); // tslint:disable-next-line: max-line-length - const stopPosition: Position = context === undefined ? new Position(0, 0) : new Position(context.stop.line, context.stop.charPositionInLine + context.stop.text.length); + const stopPosition: Position = !context ? new Position(0, 0) : new Position(context.stop.line, context.stop.charPositionInLine + context.stop.text.length); const range: Range = new Range(startPosition, stopPosition); message = `error message: ${ message }`; if (this.currentSource !== undefined && this.currentSource !== '') { @@ -599,20 +436,105 @@ class StaticCheckerInner extends AbstractParseTreeVisitor implemen return new Diagnostic(range, message, severity); } +} + +/** + * Static checker tool + */ +export class StaticChecker { + private readonly expressionEngine: ExpressionEngine; + + public constructor(expressionEngine?: ExpressionEngine) { + this.expressionEngine = expressionEngine ? expressionEngine : new ExpressionEngine(); + } + + public checkFiles(filePaths: string[], importResolver?: ImportResolverDelegate): Diagnostic[] { + let result: Diagnostic[] = []; + let templates: LGTemplate[] = []; + let isParseSuccess = true; + + try { + let totalLGResources: LGResource[] = []; + filePaths.forEach((filePath: string): void => { + importResolver = importResolver ? importResolver : ImportResolver.fileResolver; + + filePath = path.normalize(ImportResolver.normalizePath(filePath)); + const rootResource: LGResource = LGParser.parse(fs.readFileSync(filePath, 'utf-8'), filePath); + const lgResources: LGResource[] = rootResource.discoverLGResources(importResolver); + totalLGResources = totalLGResources.concat(lgResources); + }); + + // Remove duplicated lg files by id + // tslint:disable-next-line: max-line-length + const deduplicatedLGResources: LGResource[] = totalLGResources.filter((resource: LGResource, index: number, self: LGResource[]): boolean => + index === self.findIndex((t: LGResource): boolean => ( + t.id === resource.id + )) + ); + + templates = deduplicatedLGResources.reduce((acc: LGTemplate[], x: LGResource): any => + acc = acc.concat(x.templates), [] + ); + } catch (e) { + isParseSuccess = false; + if (e instanceof LGException) { + result = result.concat(e.getDiagnostic()); + } else { + const diagnostic: Diagnostic = new Diagnostic(new Range(new Position(0, 0), new Position(0, 0)), e.message); + result.push(diagnostic); + } + } + + if (isParseSuccess) { + result = result.concat(this.checkTemplates(templates)); + } + + return result; + } - private isPureExpression(exp: string): boolean { - if (exp === undefined || exp.length === 0) { - return false; + public checkFile(filePath: string, importResolver?: ImportResolverDelegate): Diagnostic[] { + return this.checkFiles([filePath], importResolver); + } + + public checkText(content: string, id?: string, importResolver?: ImportResolverDelegate): Diagnostic[] { + if (!importResolver) { + const importPath: string = ImportResolver.normalizePath(id); + if (!path.isAbsolute(importPath)) { + throw new Error('[Error] id must be full path when importResolver is empty'); + } } - exp = exp.trim(); - const reversedExps: RegExpMatchArray = exp.split('').reverse().join('').match(this.expressionRecognizeRegex); - // If there is no match, expressions could be null - if (reversedExps === null || reversedExps === undefined || reversedExps.length !== 1) { - return false; - } else { - return reversedExps[0].split('').reverse().join('') === exp; + let result: Diagnostic[] = []; + let templates: LGTemplate[] = []; + let isParseSuccess = true; + + try { + const rootResource: LGResource = LGParser.parse(content, id); + const resources: LGResource[] = rootResource.discoverLGResources(importResolver); + + templates = resources.reduce((acc: LGTemplate[], x: LGResource): any => + // tslint:disable-next-line: align + acc = acc.concat(x.templates), [] + ); + } catch (e) { + isParseSuccess = false; + if (e instanceof LGException) { + result = result.concat(e.getDiagnostic()); + } else { + const diagnostic: Diagnostic = new Diagnostic(new Range(new Position(0, 0), new Position(0, 0)), e.message); + result.push(diagnostic); + } + } + + if (isParseSuccess) { + result = result.concat(this.checkTemplates(templates)); } + + return result; } -} \ No newline at end of file + public checkTemplates(templates: LGTemplate[]): Diagnostic[] { + // tslint:disable-next-line: no-use-before-declare + return new StaticCheckerInner(templates, this.expressionEngine).check(); + } +} diff --git a/libraries/botbuilder-lg/src/templateEngine.ts b/libraries/botbuilder-lg/src/templateEngine.ts index fa9a78ee28..c8bf0b9a63 100644 --- a/libraries/botbuilder-lg/src/templateEngine.ts +++ b/libraries/botbuilder-lg/src/templateEngine.ts @@ -5,7 +5,7 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. */ -import { ExpressionEngine } from 'botframework-expressions'; +import { ExpressionEngine, MemoryInterface} from 'botframework-expressions'; import * as fs from 'fs'; import * as path from 'path'; import { Analyzer, AnalyzerResult } from './analyzer'; @@ -17,6 +17,7 @@ import { LGParser } from './lgParser'; import { LGResource } from './lgResource'; import { LGTemplate } from './lgTemplate'; import { StaticChecker } from './staticChecker'; +import { CustomizedMemory } from './customizedMemory'; /** * LG parser and evaluation engine @@ -97,16 +98,16 @@ export class TemplateEngine { return this; } - public evaluateTemplate(templateName: string, scope?: any): any { + public evaluateTemplate(templateName: string, scope?: MemoryInterface | any): any { + scope = new CustomizedMemory(scope); const evalutor: Evaluator = new Evaluator(this.templates, this.expressionEngine); - return evalutor.evaluateTemplate(templateName, scope); } public expandTemplate(templateName: string, scope?: any): string[] { const expander: Expander = new Expander(this.templates, this.expressionEngine); - return expander.expandTemplate(templateName, scope); + return expander.expandTemplate(templateName, new CustomizedMemory(scope)); } public analyzeTemplate(templateName: string): AnalyzerResult { @@ -126,11 +127,11 @@ export class TemplateEngine { this.runStaticCheck(mergedTemplates); const evalutor: Evaluator = new Evaluator(mergedTemplates, this.expressionEngine); - return evalutor.evaluateTemplate(fakeTemplateId, scope); + return evalutor.evaluateTemplate(fakeTemplateId, new CustomizedMemory(scope)); } private readonly runStaticCheck = (templates: LGTemplate[]): void => { - const templatesToCheck: LGTemplate[] = templates === undefined ? this.templates : templates; + const templatesToCheck: LGTemplate[] = !templates ? this.templates : templates; const diagnostics: Diagnostic[] = new StaticChecker(this.expressionEngine).checkTemplates(templatesToCheck); const errors: Diagnostic[] = diagnostics.filter((u: Diagnostic): boolean => u.severity === DiagnosticSeverity.Error); diff --git a/libraries/botbuilder-lg/tests/ActivityChecker.test.js b/libraries/botbuilder-lg/tests/ActivityChecker.test.js new file mode 100644 index 0000000000..ae21ba1e1d --- /dev/null +++ b/libraries/botbuilder-lg/tests/ActivityChecker.test.js @@ -0,0 +1,60 @@ +const { TemplateEngine, ActivityChecker } = require('../lib'); +const assert = require('assert'); + +function getTemplateEngine(){ + const filePath = `${ __dirname }/testData/Examples/DiagnosticStructuredLG.lg`; + return new TemplateEngine().addFile(filePath); +} + +function getDiagnostics(templateName, data){ + const engine = getTemplateEngine(); + const lgResult = engine.evaluateTemplate(templateName, data); + return ActivityChecker.check(lgResult); +} + +describe('ActivityCheckerTest', function() { + it('inlineActivityChecker', function() { + const diagnostics = ActivityChecker.check('Not a valid json'); + assert(diagnostics.length === 1); + assert.strictEqual(diagnostics[0].severity, 1); + assert.strictEqual(diagnostics[0].message, 'LG output is not a json object, and will fallback to string format.'); + }); + + it('emptyActivityChecker', function() { + const diagnostics = ActivityChecker.check('{}'); + assert(diagnostics.length === 1); + assert.strictEqual(diagnostics[0].severity, 0); + assert.strictEqual(diagnostics[0].message, `'lgType' does not exist in lg output json object.`); + }); + + it('ErrorStructuredType', function() { + const diagnostics = getDiagnostics('ErrorStructuredType', undefined); + assert(diagnostics.length === 1); + assert.strictEqual(diagnostics[0].severity, 0); + assert.strictEqual(diagnostics[0].message, `Type 'mystruct' is not supported currently.`); + }); + + it('ErrorActivityType', function() { + const diagnostics = getDiagnostics('ErrorActivityType', undefined); + assert(diagnostics.length === 2); + assert.strictEqual(diagnostics[0].severity, 0); + assert.strictEqual(diagnostics[0].message, `'xxx' is not a valid activity type.`); + assert.strictEqual(diagnostics[1].severity, 1); + assert.strictEqual(diagnostics[1].message, `'invalidproperty' not support in Activity.`); + }); + + it('ErrorMessage', function() { + const diagnostics = getDiagnostics('ErrorMessage', undefined); + assert(diagnostics.length === 5); + assert.strictEqual(diagnostics[0].severity, 1); + assert.strictEqual(diagnostics[0].message, `'attachment,suggestedaction' not support in Activity.`); + assert.strictEqual(diagnostics[1].severity, 1); + assert.strictEqual(diagnostics[1].message, `'mystruct' is not card action type.`); + assert.strictEqual(diagnostics[2].severity, 0); + assert.strictEqual(diagnostics[2].message, `'yyy' is not a valid card action type.`); + assert.strictEqual(diagnostics[3].severity, 0); + assert.strictEqual(diagnostics[3].message, `'notsure' is not a boolean value.`); + assert.strictEqual(diagnostics[4].severity, 1); + assert.strictEqual(diagnostics[4].message, `'mystruct' is not an attachment type.`); + }); +}); \ No newline at end of file diff --git a/libraries/botbuilder-lg/tests/ActivityFactoryTests.js b/libraries/botbuilder-lg/tests/ActivityFactory.test.js similarity index 72% rename from libraries/botbuilder-lg/tests/ActivityFactoryTests.js rename to libraries/botbuilder-lg/tests/ActivityFactory.test.js index 7979021a5b..379d5f4f7d 100644 --- a/libraries/botbuilder-lg/tests/ActivityFactoryTests.js +++ b/libraries/botbuilder-lg/tests/ActivityFactory.test.js @@ -1,4 +1,4 @@ -const { TemplateEngine, ActivityFactory } = require('../'); +const { TemplateEngine, ActivityFactory } = require('../lib'); const assert = require('assert'); function getTemplateEngine(){ @@ -9,11 +9,21 @@ function getTemplateEngine(){ function getActivity(templateName, data){ const engine = getTemplateEngine(); const lgResult = engine.evaluateTemplate(templateName, data); - return ActivityFactory.CreateActivity(lgResult); + return ActivityFactory.createActivity(lgResult); } describe('ActivityFactoryTest', function() { + it('inlineActivityFactory', function() { + let result = ActivityFactory.createActivity('text'); + assert(result.text === 'text'); + assert(result.speak === 'text'); + }); + + it('NotSupportStructuredType', function() { + assert.throws(() => {getActivity('notSupport', undefined); }, Error); + }); + it('HerocardWithCardAction', function() { let data = { title: 'titleContent', @@ -31,15 +41,54 @@ describe('ActivityFactoryTest', function() { assertAdaptiveCardActivity(result); }); - it('eventActivity', function() { + it('externalAdaptiveCardActivity', function() { let data = { - text: 'textContent', adaptiveCardTitle: 'test' }; + let result = getActivity('externalAdaptiveCardActivity', data); + assertAdaptiveCardActivity(result); + }); + + it('multiExternalAdaptiveCardActivity', function() { + let data = {titles: ['test0', 'test1', 'test2']}; + let result = getActivity('multiExternalAdaptiveCardActivity', data); + assertMultiAdaptiveCardActivity(result); + }); + + it('adaptivecardActivityWithAttachmentStructure', function() { + let data = { + adaptiveCardTitle: 'test' + }; + let result = getActivity('adaptivecardActivityWithAttachmentStructure', data); + assertAdaptiveCardActivity(result); + }); + + it('externalHeroCardActivity', function() { + let data = { + type: 'imBack', + title: 'taptitle', + value: 'tapvalue' + }; + let result = getActivity('externalHeroCardActivity', data); + assertActivityWithHeroCardAttachment(result); + }); + + it('eventActivity', function() { + let data = { + text: 'textContent' + }; let result = getActivity('eventActivity', data); assertEventActivity(result); }); + it('handoffActivity', function() { + let data = { + text: 'textContent' + }; + let result = getActivity('handoffActivity', data); + assertHandoffActivity(result); + }); + it('activityWithHeroCardAttachment', function() { let data = { title: 'titleContent', @@ -49,6 +98,16 @@ describe('ActivityFactoryTest', function() { assertActivityWithHeroCardAttachment(result); }); + it('herocardAttachment', function() { + let data = { + type: 'imBack', + title: 'taptitle', + value: 'tapvalue' + }; + let result = getActivity('herocardAttachment', data); + assertActivityWithHeroCardAttachment(result); + }); + it('activityWithMultiAttachments', function() { let data = { title: 'titleContent', @@ -78,7 +137,6 @@ describe('ActivityFactoryTest', function() { it('activityWithMultiStructuredSuggestionActions', function() { let data = { - title: 'titleContent', text: 'textContent' }; let result = getActivity('activityWithMultiStructuredSuggestionActions', data); @@ -87,7 +145,6 @@ describe('ActivityFactoryTest', function() { it('activityWithMultiStringSuggestionActions', function() { let data = { - title: 'titleContent', text: 'textContent' }; let result = getActivity('activityWithMultiStringSuggestionActions', data); @@ -96,8 +153,6 @@ describe('ActivityFactoryTest', function() { it('HeroCardTemplate', function() { let data = { - title: 'titleContent', - text: 'textContent', type: 'herocard' }; let result = getActivity('HeroCardTemplate', data); @@ -106,8 +161,6 @@ describe('ActivityFactoryTest', function() { it('ThumbnailCardTemplate', function() { let data = { - title: 'titleContent', - text: 'textContent', type: 'thumbnailcard' }; let result = getActivity('ThumbnailCardTemplate', data); @@ -116,8 +169,6 @@ describe('ActivityFactoryTest', function() { it('AudioCardTemplate', function() { let data = { - title: 'titleContent', - text: 'textContent', type: 'audiocard' }; let result = getActivity('AudioCardTemplate', data); @@ -126,8 +177,6 @@ describe('ActivityFactoryTest', function() { it('VideoCardTemplate', function() { let data = { - title: 'titleContent', - text: 'textContent', type: 'videocard' }; let result = getActivity('VideoCardTemplate', data); @@ -136,8 +185,6 @@ describe('ActivityFactoryTest', function() { it('SigninCardTemplate', function() { let data = { - title: 'titleContent', - text: 'textContent', signinlabel: 'Sign in', url: 'https://login.microsoftonline.com/' }; @@ -147,8 +194,6 @@ describe('ActivityFactoryTest', function() { it('OAuthCardTemplate', function() { let data = { - title: 'titleContent', - text: 'textContent', connectionName: 'MyConnection', signinlabel: 'Sign in', url: 'https://login.microsoftonline.com/' @@ -157,9 +202,34 @@ describe('ActivityFactoryTest', function() { assertOAuthCardActivity(result); }); + it('ReceiptCardTemplate', function() { + let data = { + type: 'ReceiptCard', + receiptItems: [ + { + title: 'Data Transfer', + price: '$ 38.45', + quantity: '368', + image: { + url: 'https://github.com/amido/azure-vector-icons/raw/master/renders/traffic-manager.png' + } + }, + { + title: 'App Service', + price: '$ 45.00', + quantity: '720', + image: { + url: 'https://github.com/amido/azure-vector-icons/raw/master/renders/cloud-service.png' + } + } + ] + }; + let result = getActivity('ReceiptCardTemplate', data); + assertReceiptCardActivity(result); + }); + it('SuggestedActionsReference', function() { let data = { - title: 'titleContent', text: 'textContent' }; let result = getActivity('SuggestedActionsReference', data); @@ -186,6 +256,7 @@ function assertOAuthCardActivity(activity) { assert.strictEqual(activity.attachments[0].contentType, 'application/vnd.microsoft.card.oauth'); const card = activity.attachments[0].content; assert(card !== undefined); + assert.strictEqual(card.text, 'This is some text describing the card, it\'s cool because it\'s cool'); assert.strictEqual(card.connectionName, 'MyConnection'); assert.strictEqual(card.buttons.length, 1); assert.strictEqual(card.buttons[0].title, 'Sign in'); @@ -314,10 +385,13 @@ function assertActivityWithMultiStructuredSuggestionActions(activity) { assert.strictEqual(activity.suggestedActions.actions.length, 3); assert.strictEqual(activity.suggestedActions.actions[0].title, 'first suggestion'); assert.strictEqual(activity.suggestedActions.actions[0].value, 'first suggestion'); + assert.strictEqual(activity.suggestedActions.actions[0].text, 'first suggestion'); assert.strictEqual(activity.suggestedActions.actions[1].title, 'second suggestion'); assert.strictEqual(activity.suggestedActions.actions[1].value, 'second suggestion'); + assert.strictEqual(activity.suggestedActions.actions[1].text, 'second suggestion'); assert.strictEqual(activity.suggestedActions.actions[2].title, 'third suggestion'); assert.strictEqual(activity.suggestedActions.actions[2].value, 'third suggestion'); + assert.strictEqual(activity.suggestedActions.actions[2].text, 'third suggestion'); } function assertMessageActivityAll(activity) { @@ -325,11 +399,24 @@ function assertMessageActivityAll(activity) { assert.strictEqual(activity.text, 'textContent'); assert.strictEqual(activity.speak, 'textContent'); assert.strictEqual(activity.inputHint, 'accepting'); + assert.strictEqual(activity.attachmentLayout, 'list'); + + const semanticAction = activity.semanticAction; + assert.strictEqual(semanticAction.id, 'actionId'); + assert.strictEqual(Object.keys(semanticAction.entities).length, 1); + assert.strictEqual('key1' in semanticAction.entities, true); + assert.strictEqual(semanticAction.entities['key1'].type, 'entityType'); + assert.strictEqual(activity.attachments.length, 1); assert.strictEqual(activity.attachments[0].contentType, 'application/vnd.microsoft.card.hero'); const card = activity.attachments[0].content; assert(card !== undefined); - assert.strictEqual(card.title, 'titleContent'); + + const tap = card.tap; + assert.strictEqual(tap.title, 'taptitle'); + assert.strictEqual(tap.value, 'tapvalue'); + assert.strictEqual(tap.type, 'imBack'); + assert.strictEqual(card.text, 'textContent'); assert.strictEqual(card.buttons.length, 1, 'should have one button'); const button = card.buttons[0]; @@ -381,6 +468,12 @@ function assertActivityWithHeroCardAttachment(activity) { assert.strictEqual(activity.attachments[0].contentType, 'application/vnd.microsoft.card.hero'); const card = activity.attachments[0].content; assert(card !== undefined); + + const tap = card.tap; + assert.strictEqual(tap.title, 'taptitle'); + assert.strictEqual(tap.value, 'tapvalue'); + assert.strictEqual(tap.type, 'imBack'); + assert.strictEqual(card.title, 'titleContent'); assert.strictEqual(card.text, 'textContent'); assert.strictEqual(card.buttons.length, 1, 'should have one button'); @@ -390,6 +483,12 @@ function assertActivityWithHeroCardAttachment(activity) { assert.strictEqual(button.value, 'textContent'); } +function assertHandoffActivity(activity) { + assert.strictEqual(activity.type, 'handoff'); + assert.strictEqual(activity.name, 'textContent'); + assert.strictEqual(activity.value, 'textContent'); +} + function assertCardActionActivity(activity) { assert.strictEqual(activity.type, 'message'); assert(activity.text === undefined); @@ -416,8 +515,58 @@ function assertAdaptiveCardActivity(activity) { assert.strictEqual(activity.attachments[0].content.body[0].text, 'test', 'text of first body should have value'); } +function assertMultiAdaptiveCardActivity(activity) { + assert.strictEqual(activity.type, 'message'); + assert(activity.text === undefined); + assert(activity.speak === undefined); + assert.strictEqual(activity.attachments.length, 3); + for (let i = 0; i < activity.attachments.length; i++) { + assert.strictEqual(activity.attachments[i].contentType, `application/vnd.microsoft.card.adaptive`); + assert.strictEqual(activity.attachments[i].content.body[0].text, `test${ i }`); + } +} + function assertEventActivity(activity) { assert.strictEqual(activity.type, 'event'); assert.strictEqual(activity.name, 'textContent'); assert.strictEqual(activity.value, 'textContent'); +} + +function assertReceiptCardActivity(activity) { + assert.strictEqual(activity.type, 'message'); + assert.strictEqual(activity.attachments.length, 1); + assert.strictEqual(activity.attachments[0].contentType, 'application/vnd.microsoft.card.receipt'); + const card = activity.attachments[0].content; + assert(card !== undefined); + + assert.strictEqual(card.title, 'John Doe'); + assert.strictEqual(card.tax, '$ 7.50'); + assert.strictEqual(card.total, '$ 90.95'); + + const buttons = card.buttons; + assert.strictEqual(buttons.length, 1); + const button = buttons[0]; + assert.strictEqual(button.title, 'More information'); + assert.strictEqual(button.type, 'openUrl'); + assert.strictEqual(button.value, 'https://azure.microsoft.com/en-us/pricing/'); + assert.strictEqual(button.image, 'https://account.windowsazure.com/content/6.10.1.38-.8225.160809-1618/aux-pre/images/offer-icon-freetrial.png'); + + const facts = card.facts; + assert.strictEqual(facts.length, 2); + assert.strictEqual(facts[0].key, 'Order Number'); + assert.strictEqual(facts[0].value, '1234'); + assert.strictEqual(facts[1].key, 'Payment Method'); + assert.strictEqual(facts[1].value, 'VISA 5555-****'); + + const items = card.items; + assert.strictEqual(items.length, 2); + assert.strictEqual(items[0].title, 'Data Transfer'); + assert.strictEqual(items[0].image.url, 'https://github.com/amido/azure-vector-icons/raw/master/renders/traffic-manager.png'); + assert.strictEqual(items[0].price, '$ 38.45'); + assert.strictEqual(items[0].quantity, '368'); + assert.strictEqual(items[1].title, 'App Service'); + assert.strictEqual(items[1].image.url, 'https://github.com/amido/azure-vector-icons/raw/master/renders/cloud-service.png'); + assert.strictEqual(items[1].price, '$ 45.00'); + assert.strictEqual(items[1].quantity, '720'); + } \ No newline at end of file diff --git a/libraries/botbuilder-lg/tests/lg.test.js b/libraries/botbuilder-lg/tests/lg.test.js index daf3c3a6d3..7999f26a2c 100644 --- a/libraries/botbuilder-lg/tests/lg.test.js +++ b/libraries/botbuilder-lg/tests/lg.test.js @@ -1,270 +1,262 @@ const { TemplateEngine, LGParser } = require('../'); +const { SimpleObjectMemory } = require('botframework-expressions'); const assert = require('assert'); const fs = require('fs'); function GetExampleFilePath(fileName) { - return `${__dirname}/testData/examples/` + fileName; + return `${ __dirname }/testData/examples/` + fileName; } -describe('LG', function () { - it('TestBasic', function () { +describe('LG', function() { + it('TestBasic', function() { let engine = new TemplateEngine().addFile(GetExampleFilePath('2.lg')); - let evaled = engine.evaluateTemplate("wPhrase"); + let evaled = engine.evaluateTemplate('wPhrase'); const options = ['Hi', 'Hello', 'Hiya']; - assert.strictEqual(options.includes(evaled), true, `The result ${evaled} is not in those options [${options.join(",")}]`); + assert.strictEqual(options.includes(evaled), true, `The result ${ evaled } is not in those options [${ options.join(',') }]`); }); - it('TestBasicTemplateReference', function () { + it('TestBasicTemplateReference', function() { let engine = new TemplateEngine().addFile(GetExampleFilePath('3.lg')); - let evaled = engine.evaluateTemplate("welcome-user", undefined); - const options = ["Hi", "Hello", "Hiya", "Hi :)", "Hello :)", "Hiya :)"]; - assert.strictEqual(options.includes(evaled), true, `The result ${evaled} is not in those options [${options.join(",")}]`); + let evaled = engine.evaluateTemplate('welcome-user', undefined); + const options = ['Hi', 'Hello', 'Hiya', 'Hi :)', 'Hello :)', 'Hiya :)']; + assert.strictEqual(options.includes(evaled), true, `The result ${ evaled } is not in those options [${ options.join(',') }]`); }); - it('TestBasicTemplateRefAndEntityRef', function () { + it('TestBasicTemplateRefAndEntityRef', function() { let engine = new TemplateEngine().addFile(GetExampleFilePath('4.lg')); let userName = 'DL'; - let evaled = engine.evaluateTemplate("welcome-user", { userName: userName }); - const options = ["Hi", "Hello", "Hiya ", "Hi :)", "Hello :)", "Hiya :)"]; - assert.strictEqual(evaled.includes(userName), true, `The result ${evaled} does not contiain ${userName}`); + let evaled = engine.evaluateTemplate('welcome-user', { userName: userName }); + const options = ['Hi', 'Hello', 'Hiya ', 'Hi :)', 'Hello :)', 'Hiya :)']; + assert.strictEqual(evaled.includes(userName), true, `The result ${ evaled } does not contiain ${ userName }`); }); - it('TestBaicConditionalTemplate', function () { + it('TestBaicConditionalTemplate', function() { let engine = new TemplateEngine().addFile(GetExampleFilePath('5.lg')); - let evaled = engine.evaluateTemplate("time-of-day-readout", { timeOfDay: "morning" }); - assert.strictEqual(evaled === "Good morning" || evaled === "Morning! ", true, `Evaled is ${evaled}`); + let evaled = engine.evaluateTemplate('time-of-day-readout', { timeOfDay: 'morning' }); + assert.strictEqual(evaled === 'Good morning' || evaled === 'Morning! ', true, `Evaled is ${ evaled }`); - evaled = engine.evaluateTemplate("time-of-day-readout", { timeOfDay: "evening" }); - assert.strictEqual(evaled === "Good evening" || evaled === "Evening! ", true, `Evaled is ${evaled}`); + evaled = engine.evaluateTemplate('time-of-day-readout', { timeOfDay: 'evening' }); + assert.strictEqual(evaled === 'Good evening' || evaled === 'Evening! ', true, `Evaled is ${ evaled }`); }); - it('TestBasicConditionalTemplateWithoutDefault', function () { + it('TestBasicConditionalTemplateWithoutDefault', function() { let engine = new TemplateEngine().addFile(GetExampleFilePath('5.lg')); - let evaled = engine.evaluateTemplate("time-of-day-readout-without-default", { timeOfDay: "morning" }); - assert.strictEqual(evaled === "Good morning" || evaled === "Morning! ", true, `Evaled is ${evaled}`); + let evaled = engine.evaluateTemplate('time-of-day-readout-without-default', { timeOfDay: 'morning' }); + assert.strictEqual(evaled === 'Good morning' || evaled === 'Morning! ', true, `Evaled is ${ evaled }`); - evaled = engine.evaluateTemplate("time-of-day-readout-without-default2", { timeOfDay: "morning" }); - assert.strictEqual(evaled === "Good morning" || evaled === "Morning! ", true, `Evaled is ${evaled}`); + evaled = engine.evaluateTemplate('time-of-day-readout-without-default2', { timeOfDay: 'morning' }); + assert.strictEqual(evaled === 'Good morning' || evaled === 'Morning! ', true, `Evaled is ${ evaled }`); - evaled = engine.evaluateTemplate("time-of-day-readout-without-default2", { timeOfDay: "evening" }); - assert.strictEqual(evaled, undefined, `Evaled is ${evaled} which should be undefined.`); + evaled = engine.evaluateTemplate('time-of-day-readout-without-default2', { timeOfDay: 'evening' }); + assert.strictEqual(evaled, undefined, `Evaled is ${ evaled } which should be undefined.`); }); - it('TestBasicTemplateRefWithParameters', function () { + it('TestBasicTemplateRefWithParameters', function() { let engine = new TemplateEngine().addFile(GetExampleFilePath('6.lg')); - let evaled = engine.evaluateTemplate("welcome", undefined); - const options1 = ["Hi DongLei :)", "Hey DongLei :)", "Hello DongLei :)"] - assert.strictEqual(options1.includes(evaled), true, `Evaled is ${evaled}`); + let evaled = engine.evaluateTemplate('welcome', undefined); + const options1 = ['Hi DongLei :)', 'Hey DongLei :)', 'Hello DongLei :)']; + assert.strictEqual(options1.includes(evaled), true, `Evaled is ${ evaled }`); - const options2 = ["Hi DL :)", "Hey DL :)", "Hello DL :)"] - evaled = engine.evaluateTemplate("welcome", { userName: "DL" }); - assert.strictEqual(options2.includes(evaled), true, `Evaled is ${evaled}`); + const options2 = ['Hi DL :)', 'Hey DL :)', 'Hello DL :)']; + evaled = engine.evaluateTemplate('welcome', { userName: 'DL' }); + assert.strictEqual(options2.includes(evaled), true, `Evaled is ${ evaled }`); }); - it('TestBasicSwitchCaseTemplate', function () { + it('TestBasicSwitchCaseTemplate', function() { let engine = new TemplateEngine().addFile(GetExampleFilePath('switchcase.lg')); - let evaled1 = engine.evaluateTemplate('greetInAWeek', { day: "Saturday" }); - assert.strictEqual(evaled1 === "Happy Saturday!", true, `Evaled is ${evaled1}`); + let evaled1 = engine.evaluateTemplate('greetInAWeek', { day: 'Saturday' }); + assert.strictEqual(evaled1 === 'Happy Saturday!', true, `Evaled is ${ evaled1 }`); - let evaled2 = engine.evaluateTemplate('greetInAWeek', { day: "Sunday" }); - assert.strictEqual(evaled2 === "Happy Sunday!", true, `Evaled is ${evaled2}`); - - let evaled3 = engine.evaluateTemplate('greetInAWeek', { day: "Monday" }); - assert.strictEqual(evaled3 === "Work Hard!", true, `Evaled is ${evaled3}`); + let evaled3 = engine.evaluateTemplate('greetInAWeek', { day: 'Monday' }); + assert.strictEqual(evaled3 === 'Work Hard!', true, `Evaled is ${ evaled3 }`); }); - it('TestBasicListSupport', function () { + it('TestBasicListSupport', function() { let engine = new TemplateEngine().addFile(GetExampleFilePath('BasicList.lg')); - let evaled = engine.evaluateTemplate("BasicJoin", { items: ["1"] }); - assert.strictEqual(evaled, "1", `Evaled is ${evaled}`); + let evaled = engine.evaluateTemplate('BasicJoin', { items: ['1'] }); + assert.strictEqual(evaled, '1', `Evaled is ${ evaled }`); - evaled = engine.evaluateTemplate("BasicJoin", { items: ["1", "2"] }); - assert.strictEqual(evaled, "1, 2", `Evaled is ${evaled}`); + evaled = engine.evaluateTemplate('BasicJoin', { items: ['1', '2'] }); + assert.strictEqual(evaled, '1, 2', `Evaled is ${ evaled }`); - evaled = engine.evaluateTemplate("BasicJoin", { items: ["1", "2", "3"] }); - assert.strictEqual(evaled, "1, 2 and 3", `Evaled is ${evaled}`); + evaled = engine.evaluateTemplate('BasicJoin', { items: ['1', '2', '3'] }); + assert.strictEqual(evaled, '1, 2 and 3', `Evaled is ${ evaled }`); }); - it('TestBasicExtendedFunctions', function () { + it('TestBasicExtendedFunctions', function() { let engine = new TemplateEngine().addFile(GetExampleFilePath('6.lg')); const alarms = [ { - time: "7 am", - date: "tomorrow" + time: '7 am', + date: 'tomorrow' }, { - time: "8 pm", - date: "tomorrow" + time: '8 pm', + date: 'tomorrow' } ]; let evaled = engine.evaluateTemplate('ShowAlarmsWithForeach', { alarms: alarms }); - assert.strictEqual(evaled === "You have 2 alarms, 7 am at tomorrow and 8 pm at tomorrow", true, `Evaled is ${evaled}`); + assert.strictEqual(evaled === 'You have 2 alarms, 7 am at tomorrow and 8 pm at tomorrow', true, `Evaled is ${ evaled }`); - evaled = engine.evaluateTemplate("ShowAlarmsWithLgTemplate", { alarms: alarms }); - assert.strictEqual(evaled === "You have 2 alarms, 7 am at tomorrow and 8 pm at tomorrow", true, `Evaled is ${evaled}`); + evaled = engine.evaluateTemplate('ShowAlarmsWithLgTemplate', { alarms: alarms }); + assert.strictEqual(evaled === 'You have 2 alarms, 7 am at tomorrow and 8 pm at tomorrow', true, `Evaled is ${ evaled }`); - evaled = engine.evaluateTemplate("ShowAlarmsWithDynamicLgTemplate", { alarms: alarms, templateName: "ShowAlarm" }); - assert.strictEqual(evaled === "You have 2 alarms, 7 am at tomorrow and 8 pm at tomorrow", true, `Evaled is ${evaled}`); + evaled = engine.evaluateTemplate('ShowAlarmsWithDynamicLgTemplate', { alarms: alarms, templateName: 'ShowAlarm' }); + assert.strictEqual(evaled === 'You have 2 alarms, 7 am at tomorrow and 8 pm at tomorrow', true, `Evaled is ${ evaled }`); }); - it('TestCaseInsensitive', function () { + it('TestCaseInsensitive', function() { let engine = new TemplateEngine().addFile(GetExampleFilePath('CaseInsensitive.lg')); const alarms = [ { - time: "7 am", - date: "tomorrow" + time: '7 am', + date: 'tomorrow' }, { - time: "8 pm", - date: "tomorrow" + time: '8 pm', + date: 'tomorrow' } ]; let evaled = engine.evaluateTemplate('ShowAlarms', { alarms: alarms }); - assert.strictEqual(evaled === "You have two alarms", true, `Evaled is ${evaled}`); + assert.strictEqual(evaled === 'You have two alarms', true, `Evaled is ${ evaled }`); - let evaled1 = engine.evaluateTemplate('greetInAWeek', { day: "Saturday" }); - assert.strictEqual(evaled1 === "Happy Saturday!", true, `Evaled is ${evaled1}`); + let evaled1 = engine.evaluateTemplate('greetInAWeek', { day: 'Saturday' }); + assert.strictEqual(evaled1 === 'Happy Saturday!', true, `Evaled is ${ evaled1 }`); }); - it('TestListWithOnlyOneElement', function () { - var engine = new TemplateEngine().addFile(GetExampleFilePath("8.lg")); - var evaled = engine.evaluateTemplate("ShowTasks", { recentTasks: ['Task1'] }); - assert.strictEqual(evaled === "Your most recent task is Task1. You can let me know if you want to add or complete a task.", true, `Evaled is ${evaled}`); + it('TestListWithOnlyOneElement', function() { + var engine = new TemplateEngine().addFile(GetExampleFilePath('8.lg')); + var evaled = engine.evaluateTemplate('ShowTasks', { recentTasks: ['Task1'] }); + assert.strictEqual(evaled === 'Your most recent task is Task1. You can let me know if you want to add or complete a task.', true, `Evaled is ${ evaled }`); }); - it('TestTemplateNameWithDotIn', function () { - var engine = new TemplateEngine().addFile(GetExampleFilePath("TemplateNameWithDot.lg")); - var evaled1 = engine.evaluateTemplate("Hello.World", ""); - assert.strictEqual(evaled1 === "Hello World", true, `Evaled is ${evaled1}`); + it('TestTemplateNameWithDotIn', function() { + var engine = new TemplateEngine().addFile(GetExampleFilePath('TemplateNameWithDot.lg')); + var evaled1 = engine.evaluateTemplate('Hello.World', ''); + assert.strictEqual(evaled1 === 'Hello World', true, `Evaled is ${ evaled1 }`); - var evaled2 = engine.evaluateTemplate("Hello", ""); - assert.strictEqual(evaled2 === "Hello World", true, `Evaled is ${evaled2}`); + var evaled2 = engine.evaluateTemplate('Hello', ''); + assert.strictEqual(evaled2 === 'Hello World', true, `Evaled is ${ evaled2 }`); }); - it('TestBasicInlineTemplate', function () { + it('TestBasicInlineTemplate', function() { var emptyEngine = new TemplateEngine(); - assert.strictEqual(emptyEngine.evaluate("Hi"), "Hi", emptyEngine.evaluate("Hi")); - assert.strictEqual(emptyEngine.evaluate("Hi", ""), "Hi", emptyEngine.evaluate("Hi", "")); + assert.strictEqual(emptyEngine.evaluate('Hi'), 'Hi', emptyEngine.evaluate('Hi')); + assert.strictEqual(emptyEngine.evaluate('Hi', ''), 'Hi', emptyEngine.evaluate('Hi', '')); - assert.strictEqual(emptyEngine.evaluate("Hi {name}", { name: 'DL' }), "Hi DL"); + assert.strictEqual(emptyEngine.evaluate('Hi @{name}', { name: 'DL' }), 'Hi DL'); - assert.strictEqual(emptyEngine.evaluate("Hi {name.FirstName}{name.LastName}", { name: { FirstName: "D", LastName: "L" } }), "Hi DL"); - assert.strictEqual(emptyEngine.evaluate("Hi \n Hello", ""), "Hi \n Hello"); - assert.strictEqual(emptyEngine.evaluate("Hi \r\n Hello", ""), "Hi \r\n Hello"); - assert.strictEqual(emptyEngine.evaluate("Hi \r\n @{name}", { name: "DL" }), "Hi \r\n DL"); - assert.strictEqual(new TemplateEngine().evaluate("Hi", ""), "Hi"); + assert.strictEqual(emptyEngine.evaluate('Hi @{name.FirstName}@{name.LastName}', { name: { FirstName: 'D', LastName: 'L' } }), 'Hi DL'); + assert.strictEqual(emptyEngine.evaluate('Hi \n Hello', ''), 'Hi \n Hello'); + assert.strictEqual(emptyEngine.evaluate('Hi \r\n Hello', ''), 'Hi \r\n Hello'); + assert.strictEqual(emptyEngine.evaluate('Hi \r\n @{name}', { name: 'DL' }), 'Hi \r\n DL'); + assert.strictEqual(new TemplateEngine().evaluate('Hi', ''), 'Hi'); }); - it('TestInlineTemplateWithTemplateFile', function () { - var emptyEngine = new TemplateEngine().addFile(GetExampleFilePath("8.lg")); - assert.strictEqual(emptyEngine.evaluate("Hi"), "Hi", emptyEngine.evaluate("Hi")); - assert.strictEqual(emptyEngine.evaluate("Hi", ""), "Hi", emptyEngine.evaluate("Hi", "")); + it('TestInlineTemplateWithTemplateFile', function() { + var emptyEngine = new TemplateEngine().addFile(GetExampleFilePath('8.lg')); + assert.strictEqual(emptyEngine.evaluate('Hi'), 'Hi', emptyEngine.evaluate('Hi')); + assert.strictEqual(emptyEngine.evaluate('Hi', ''), 'Hi', emptyEngine.evaluate('Hi', '')); - assert.strictEqual(emptyEngine.evaluate("Hi {name}", { name: 'DL' }), "Hi DL", emptyEngine.evaluate("Hi {name}", { name: 'DL' })); + assert.strictEqual(emptyEngine.evaluate('Hi @{name}', { name: 'DL' }), 'Hi DL', emptyEngine.evaluate('Hi @{name}', { name: 'DL' })); - assert.strictEqual(emptyEngine.evaluate("Hi {name.FirstName}{name.LastName} [RecentTasks]", { name: { FirstName: "D", LastName: "L" } }), "Hi DL You don't have any tasks.", emptyEngine.evaluate("Hi {name.FirstName}{name.LastName} [RecentTasks]", { name: { FirstName: "D", LastName: "L" } })); + assert.strictEqual(emptyEngine.evaluate('Hi @{name.FirstName}@{name.LastName} @{RecentTasks()}', { name: { FirstName: 'D', LastName: 'L' } }), 'Hi DL You don\'t have any tasks.', emptyEngine.evaluate('Hi @{name.FirstName}@{name.LastName} @{RecentTasks()}', { name: { FirstName: 'D', LastName: 'L' } })); - assert.strictEqual(emptyEngine.evaluate("Hi {name.FirstName}{name.LastName} [RecentTasks]", { name: { FirstName: "D", LastName: "L" }, recentTasks: ["task1"] }), "Hi DL Your most recent task is task1. You can let me know if you want to add or complete a task.", emptyEngine.evaluate("Hi {name.FirstName}{name.LastName} [RecentTasks]", { name: { FirstName: "D", LastName: "L" }, recentTasks: ["task1"] })); + assert.strictEqual(emptyEngine.evaluate('Hi @{name.FirstName}@{name.LastName} @{RecentTasks()}', { name: { FirstName: 'D', LastName: 'L' }, recentTasks: ['task1'] }), 'Hi DL Your most recent task is task1. You can let me know if you want to add or complete a task.', emptyEngine.evaluate('Hi @{name.FirstName}@{name.LastName} @{RecentTasks()}', { name: { FirstName: 'D', LastName: 'L' }, recentTasks: ['task1'] })); }); - it('TestMultiLine', function () { - var engine = new TemplateEngine().addFile(GetExampleFilePath("MultilineTextForAdaptiveCard.lg")); - var evaled1 = engine.evaluateTemplate("wPhrase", ""); - var options1 = ["\r\ncardContent\r\n", "hello", "\ncardContent\n"]; - assert.strictEqual(options1.includes(evaled1), true, `1.Evaled is ${evaled1}`); + it('TestMultiLine', function() { + var engine = new TemplateEngine().addFile(GetExampleFilePath('MultilineTextForAdaptiveCard.lg')); + var evaled1 = engine.evaluateTemplate('wPhrase', ''); + var options1 = ['\r\ncardContent\r\n', 'hello', '\ncardContent\n']; + assert.strictEqual(options1.includes(evaled1), true, `1.Evaled is ${ evaled1 }`); - var evaled2 = engine.evaluateTemplate("nameTemplate", { name: "N" }); - var options2 = ["\r\nN\r\n", "N", "\nN\n"]; - assert.strictEqual(options2.includes(evaled2), true, `2.Evaled is ${evaled2}`); + var evaled2 = engine.evaluateTemplate('nameTemplate', { name: 'N' }); + var options2 = ['\r\nN\r\n', 'N', '\nN\n']; + assert.strictEqual(options2.includes(evaled2), true, `2.Evaled is ${ evaled2 }`); - var evaled3 = engine.evaluateTemplate("adaptivecardsTemplate", ""); + var evaled3 = engine.evaluateTemplate('adaptivecardsTemplate', ''); console.log(evaled3); - var evaled4 = engine.evaluateTemplate("refTemplate", ""); - var options4 = ["\r\nhi\r\n", "\nhi\n"]; - assert.strictEqual(options4.includes(evaled4), true, `4.Evaled is ${evaled4}`); + var evaled4 = engine.evaluateTemplate('refTemplate', ''); + var options4 = ['\r\nhi\r\n', '\nhi\n']; + assert.strictEqual(options4.includes(evaled4), true, `4.Evaled is ${ evaled4 }`); }); - it('TestTemplateRef', function () { - var engine = new TemplateEngine().addFile(GetExampleFilePath("TemplateRef.lg")); - var scope = { time: "morning", name: "Dong Lei" }; - var evaled1 = engine.evaluateTemplate("Hello", scope); - assert.strictEqual(evaled1, "Good morning Dong Lei", `Evaled is ${evaled1}`); - - var evaled2 = engine.evaluateTemplate("Hello2", scope); - assert.strictEqual(evaled2, "Good morning Dong Lei", `Evaled is ${evaled2}`); - - var evaled3 = engine.evaluateTemplate("Hello3", scope); - assert.strictEqual(evaled3, "Good morning Dong Lei", `Evaled is ${evaled3}`); + it('TestTemplateRef', function() { + var engine = new TemplateEngine().addFile(GetExampleFilePath('TemplateRef.lg')); + var scope = { time: 'morning', name: 'Dong Lei' }; + var evaled1 = engine.evaluateTemplate('Hello', scope); + assert.strictEqual(evaled1, 'Good morning Dong Lei', `Evaled is ${ evaled1 }`); }); - it('TestEscapeCharacter', function () { - var engine = new TemplateEngine().addFile(GetExampleFilePath("EscapeCharacter.lg")); - var evaled = engine.evaluateTemplate("wPhrase", null); - assert.strictEqual(evaled, "Hi \r\n\t[]{}\\", "Happy path failed."); + it('TestEscapeCharacter', function() { + var engine = new TemplateEngine().addFile(GetExampleFilePath('EscapeCharacter.lg')); + var evaled = engine.evaluateTemplate('wPhrase', undefined); + assert.strictEqual(evaled, 'Hi \r\n\t[]{}\\', 'Happy path failed.'); - evaled = engine.evaluateTemplate("otherEscape", null); - assert.strictEqual(evaled, "Hi \\y \\", "Happy path failed."); + evaled = engine.evaluateTemplate('otherEscape', undefined); + assert.strictEqual(evaled, 'Hi y ', 'Happy path failed.'); - evaled = engine.evaluateTemplate("escapeInExpression", null); - assert.strictEqual(evaled, "Hi hello\\\\"); + evaled = engine.evaluateTemplate('escapeInExpression', undefined); + assert.strictEqual(evaled, 'Hi hello\\\\'); - evaled = engine.evaluateTemplate("escapeInExpression2", null); - assert.strictEqual(evaled, "Hi hello'"); + evaled = engine.evaluateTemplate('escapeInExpression2', undefined); + assert.strictEqual(evaled, 'Hi hello\''); - evaled = engine.evaluateTemplate("escapeInExpression3", null); - assert.strictEqual(evaled, "Hi hello\""); + evaled = engine.evaluateTemplate('escapeInExpression3', undefined); + assert.strictEqual(evaled, 'Hi hello"'); - evaled = engine.evaluateTemplate("escapeInExpression4", null); - assert.strictEqual(evaled, "Hi hello\""); + evaled = engine.evaluateTemplate('escapeInExpression4', undefined); + assert.strictEqual(evaled, 'Hi hello"'); - evaled = engine.evaluateTemplate("escapeInExpression5", null); - assert.strictEqual(evaled, "Hi hello\n"); + evaled = engine.evaluateTemplate('escapeInExpression5', undefined); + assert.strictEqual(evaled, 'Hi hello\n'); - evaled = engine.evaluateTemplate("escapeInExpression6", null); - assert.strictEqual(evaled, "Hi hello\n"); + evaled = engine.evaluateTemplate('escapeInExpression6', undefined); + assert.strictEqual(evaled, 'Hi hello\n'); - evaled = engine.evaluateTemplate("showTodo", { todos: ["A", "B", "C"] }); - assert.strictEqual(evaled.replace(/\r\n/g, '\n'), "\n Your most recent 3 tasks are\n * A\n* B\n* C\n "); + evaled = engine.evaluateTemplate('showTodo', { todos: ['A', 'B', 'C'] }); + assert.strictEqual(evaled.replace(/\r\n/g, '\n'), '\n Your most recent 3 tasks are\n * A\n* B\n* C\n '); - evaled = engine.evaluateTemplate("showTodo", null); - assert.strictEqual(evaled.replace(/\r\n/g, '\n'), "\n You don't have any \"t\\\\odo'\".\n "); + evaled = engine.evaluateTemplate('showTodo', undefined); + assert.strictEqual(evaled.replace(/\r\n/g, '\n'), '\n You don\'t have any "t\\\\odo\'".\n '); }); - it('TestAnalyzer', function () { + it('TestAnalyzer', function() { var testData = [ { name: 'orderReadOut', - variableOptions: ["orderType", "userName", "base", "topping", "bread", "meat"], - templateRefOptions: ["wPhrase", "pizzaOrderConfirmation", "sandwichOrderConfirmation"] + variableOptions: ['orderType', 'userName', 'base', 'topping', 'bread', 'meat'], + templateRefOptions: ['wPhrase', 'pizzaOrderConfirmation', 'sandwichOrderConfirmation'] }, { name: 'sandwichOrderConfirmation', - variableOptions: ["bread", "meat"], + variableOptions: ['bread', 'meat'], templateRefOptions: [] }, { name: 'template1', // TODO: input.property should really be: customer.property but analyzer needs to be - variableOptions: ["alarms", "customer", "tasks[0]", "age", "city"], - templateRefOptions: ["template2", "template3", "template4", "template5", "template6"] + variableOptions: ['alarms', 'customer', 'tasks[0]', 'age', 'city'], + templateRefOptions: ['template2', 'template3', 'template4', 'template5', 'template6'] }, { name: 'coffee-to-go-order', variableOptions: ['coffee', 'userName', 'size', 'price'], - templateRefOptions: ["wPhrase", "LatteOrderConfirmation", "MochaOrderConfirmation", "CuppuccinoOrderConfirmation"] + templateRefOptions: ['wPhrase', 'LatteOrderConfirmation', 'MochaOrderConfirmation', 'CuppuccinoOrderConfirmation'] }, - ] + ]; for (const testItem of testData) { - var engine = new TemplateEngine().addFile(GetExampleFilePath("Analyzer.lg")); + var engine = new TemplateEngine().addFile(GetExampleFilePath('Analyzer.lg')); var evaled1 = engine.analyzeTemplate(testItem.name); var variableEvaled = evaled1.Variables; var variableEvaledOptions = testItem.variableOptions; @@ -277,19 +269,38 @@ describe('LG', function () { } }); - it('TestlgTemplateFunction', function () { - var engine = new TemplateEngine().addFile(GetExampleFilePath("lgTemplate.lg")); + it('TestlgTemplateFunction', function() { + var engine = new TemplateEngine().addFile(GetExampleFilePath('lgTemplate.lg')); var evaled = engine.evaluateTemplate('TemplateC', ''); var options = ['Hi', 'Hello']; assert.strictEqual(options.includes(evaled), true); - evaled = engine.evaluateTemplate('TemplateD', { b: "morning" }); + evaled = engine.evaluateTemplate('TemplateD', { b: 'morning' }); options = ['Hi morning', 'Hello morning']; assert.strictEqual(options.includes(evaled), true); }); - it('TestAnalyzelgTemplateFunction', function () { - var engine = new TemplateEngine().addFile(GetExampleFilePath("lgTemplate.lg")); + it('TestTemplateAsFunction', function() { + var engine = new TemplateEngine().addFile(GetExampleFilePath('TemplateAsFunction.lg')); + + var evaled = engine.evaluateTemplate('Test2'); + assert.strictEqual(evaled, 'hello world', `Evaled is ${ evaled }`); + + evaled = engine.evaluateTemplate('Test3'); + assert.strictEqual(evaled, 'hello world', `Evaled is ${ evaled }`); + + evaled = engine.evaluateTemplate('Test4'); + assert.strictEqual(evaled.trim(), 'hello world', `Evaled is ${ evaled }`); + + evaled = engine.evaluateTemplate('dupNameWithTemplate'); + assert.strictEqual(evaled, 'calculate length of ms by user\'s template', `Evaled is ${ evaled }`); + + evaled = engine.evaluateTemplate('dupNameWithBuiltinFunc'); + assert.strictEqual(evaled, 2, `Evaled is ${ evaled }`); + }); + + it('TestAnalyzelgTemplateFunction', function() { + var engine = new TemplateEngine().addFile(GetExampleFilePath('lgTemplate.lg')); var evaled = engine.analyzeTemplate('TemplateD'); var variableEvaled = evaled.Variables; var options = ['b']; @@ -297,180 +308,264 @@ describe('LG', function () { options.forEach(e => assert.strictEqual(variableEvaled.includes(e), true)); }); - it('TestExceptionCatch', function () { - var engine = new TemplateEngine().addFile(GetExampleFilePath("ExceptionCatch.lg")); + it('TestExceptionCatch', function() { + var engine = new TemplateEngine().addFile(GetExampleFilePath('ExceptionCatch.lg')); try { - engine.evaluateTemplate("NoVariableMatch"); - assert.fail('Should throw exception.') + engine.evaluateTemplate('NoVariableMatch'); + assert.fail('Should throw exception.'); } catch (e) { console.log(e.message); } - }) + }); - it('TestMultipleLgFiles', function () { + it('TestMultipleLgFiles', function() { var file123 = [ - GetExampleFilePath("MultiFile-Part1.lg"), - GetExampleFilePath("MultiFile-Part2.lg"), - GetExampleFilePath("MultiFile-Part3.lg"), + GetExampleFilePath('MultiFile-Part1.lg'), + GetExampleFilePath('MultiFile-Part2.lg'), + GetExampleFilePath('MultiFile-Part3.lg'), ]; - var msg = "hello from t1, ref template2: 'hello from t2, ref template3: hello from t3' and ref template3: 'hello from t3'"; + var msg = 'hello from t1, ref template2: \'hello from t2, ref template3: hello from t3\' and ref template3: \'hello from t3\''; var engine = new TemplateEngine().addFiles([file123[0], file123[1], file123[2]]); - assert.strictEqual(engine.evaluateTemplate("template1"), msg); + assert.strictEqual(engine.evaluateTemplate('template1'), msg); engine = new TemplateEngine().addFiles([file123[1], file123[0], file123[2]]); - assert.strictEqual(engine.evaluateTemplate("template1"), msg); + assert.strictEqual(engine.evaluateTemplate('template1'), msg); engine = new TemplateEngine().addFiles([file123[2], file123[1], file123[0]]); - assert.strictEqual(engine.evaluateTemplate("template1"), msg); - }) + assert.strictEqual(engine.evaluateTemplate('template1'), msg); + }); - it('TestImportLgFiles', function () { - var engine = new TemplateEngine().addFile(GetExampleFilePath("importExamples/import.lg")); + it('TestImportLgFiles', function() { + var engine = new TemplateEngine().addFile(GetExampleFilePath('importExamples/import.lg')); // Assert 6.lg is imported only once when there are several relative paths which point to the same file. // Assert import cycle loop is handled well as expected when a file imports itself. assert.strictEqual(engine.templates.length, 14); - const options1 = ["Hi", "Hello", "Hey"]; - var evaled = engine.evaluateTemplate("basicTemplate"); - assert.strictEqual(options1.includes(evaled), true, `Evaled is ${evaled}`); + const options1 = ['Hi', 'Hello', 'Hey']; + var evaled = engine.evaluateTemplate('basicTemplate'); + assert.strictEqual(options1.includes(evaled), true, `Evaled is ${ evaled }`); - const options2 = ["Hi DongLei :)", "Hey DongLei :)", "Hello DongLei :)"]; - evaled = engine.evaluateTemplate("welcome"); - assert.strictEqual(options2.includes(evaled), true, `Evaled is ${evaled}`); + const options2 = ['Hi DongLei :)', 'Hey DongLei :)', 'Hello DongLei :)']; + evaled = engine.evaluateTemplate('welcome'); + assert.strictEqual(options2.includes(evaled), true, `Evaled is ${ evaled }`); - const options3 = ["Hi DL :)", "Hey DL :)", "Hello DL :)"]; - evaled = engine.evaluateTemplate("welcome", { userName: "DL" }); - assert.strictEqual(options3.includes(evaled), true, `Evaled is ${evaled}`); + const options3 = ['Hi DL :)', 'Hey DL :)', 'Hello DL :)']; + evaled = engine.evaluateTemplate('welcome', { userName: 'DL' }); + assert.strictEqual(options3.includes(evaled), true, `Evaled is ${ evaled }`); - const options4 = ["Hi 2", "Hello 2"]; - evaled = engine.evaluateTemplate("basicTemplate2"); - assert.strictEqual(options4.includes(evaled), true, `Evaled is ${evaled}`); + const options4 = ['Hi 2', 'Hello 2']; + evaled = engine.evaluateTemplate('basicTemplate2'); + assert.strictEqual(options4.includes(evaled), true, `Evaled is ${ evaled }`); - const options5 = ["Hi 2", "Hello 2"]; - evaled = engine.evaluateTemplate("template3"); - assert.strictEqual(options5.includes(evaled), true, `Evaled is ${evaled}`); + const options5 = ['Hi 2', 'Hello 2']; + evaled = engine.evaluateTemplate('template3'); + assert.strictEqual(options5.includes(evaled), true, `Evaled is ${ evaled }`); // Assert 6.lg of relative path is imported from text. - engine = new TemplateEngine().addText(`# basicTemplate\r\n- Hi\r\n- Hello\r\n[import](./6.lg)`, GetExampleFilePath("xx.lg")); + engine = new TemplateEngine().addText(`# basicTemplate\r\n- Hi\r\n- Hello\r\n[import](./6.lg)`, GetExampleFilePath('xx.lg')); assert.strictEqual(engine.templates.length, 8); - evaled = engine.evaluateTemplate("basicTemplate"); - assert.strictEqual(options1.includes(evaled), true, `Evaled is ${evaled}`); + evaled = engine.evaluateTemplate('basicTemplate'); + assert.strictEqual(options1.includes(evaled), true, `Evaled is ${ evaled }`); - evaled = engine.evaluateTemplate("welcome"); - assert.strictEqual(options2.includes(evaled), true, `Evaled is ${evaled}`); + evaled = engine.evaluateTemplate('welcome'); + assert.strictEqual(options2.includes(evaled), true, `Evaled is ${ evaled }`); - evaled = engine.evaluateTemplate("welcome", { userName: "DL" }); - assert.strictEqual(options3.includes(evaled), true, `Evaled is ${evaled}`); + evaled = engine.evaluateTemplate('welcome', { userName: 'DL' }); + assert.strictEqual(options3.includes(evaled), true, `Evaled is ${ evaled }`); }); - it('TestLgFileImportMultipleTimes', function () { - let engine = new TemplateEngine().addFiles([GetExampleFilePath("importExamples/import.lg"), GetExampleFilePath("importExamples/import2.lg")]); + it('TestRegex', function() { + var engine = new TemplateEngine().addFile(GetExampleFilePath('Regex.lg')); + var evaled = engine.evaluateTemplate('wPhrase'); + assert.strictEqual(evaled, 'Hi'); + + var evaled = engine.evaluateTemplate('wPhrase', {name: 'jack'}); + assert.strictEqual(evaled, 'Hi jack'); + + var evaled = engine.evaluateTemplate('wPhrase', {name: 'morethanfive'}); + assert.strictEqual(evaled, 'Hi'); + }); + + it('TestLgFileImportMultipleTimes', function() { + let engine = new TemplateEngine().addFiles([GetExampleFilePath('importExamples/import.lg'), GetExampleFilePath('importExamples/import2.lg')]); // Assert 6.lg is imported only once and no exceptions are thrown when it is imported from multiple files. assert.strictEqual(engine.templates.length, 14); - const options1 = ["Hi", "Hello", "Hey"]; - var evaled = engine.evaluateTemplate("basicTemplate"); - assert.strictEqual(options1.includes(evaled), true, `Evaled is ${evaled}`); + const options1 = ['Hi', 'Hello', 'Hey']; + var evaled = engine.evaluateTemplate('basicTemplate'); + assert.strictEqual(options1.includes(evaled), true, `Evaled is ${ evaled }`); - const options2 = ["Hi DongLei :)", "Hey DongLei :)", "Hello DongLei :)"]; - evaled = engine.evaluateTemplate("welcome"); - assert.strictEqual(options2.includes(evaled), true, `Evaled is ${evaled}`); + const options2 = ['Hi DongLei :)', 'Hey DongLei :)', 'Hello DongLei :)']; + evaled = engine.evaluateTemplate('welcome'); + assert.strictEqual(options2.includes(evaled), true, `Evaled is ${ evaled }`); - const options3 = ["Hi DL :)", "Hey DL :)", "Hello DL :)"]; - evaled = engine.evaluateTemplate("welcome", { userName: "DL" }); - assert.strictEqual(options3.includes(evaled), true, `Evaled is ${evaled}`); + const options3 = ['Hi DL :)', 'Hey DL :)', 'Hello DL :)']; + evaled = engine.evaluateTemplate('welcome', { userName: 'DL' }); + assert.strictEqual(options3.includes(evaled), true, `Evaled is ${ evaled }`); - const options4 = ["Hi 2", "Hello 2"]; - evaled = engine.evaluateTemplate("basicTemplate2"); - assert.strictEqual(options4.includes(evaled), true, `Evaled is ${evaled}`); + const options4 = ['Hi 2', 'Hello 2']; + evaled = engine.evaluateTemplate('basicTemplate2'); + assert.strictEqual(options4.includes(evaled), true, `Evaled is ${ evaled }`); - var evaled = engine.evaluateTemplate("basicTemplate3"); - assert.strictEqual(options1.includes(evaled), true, `Evaled is ${evaled}`); + var evaled = engine.evaluateTemplate('basicTemplate3'); + assert.strictEqual(options1.includes(evaled), true, `Evaled is ${ evaled }`); - evaled = engine.evaluateTemplate("basicTemplate4"); - assert.strictEqual(options4.includes(evaled), true, `Evaled is ${evaled}`); + evaled = engine.evaluateTemplate('basicTemplate4'); + assert.strictEqual(options4.includes(evaled), true, `Evaled is ${ evaled }`); - engine = new TemplateEngine().addFile(GetExampleFilePath("importExamples/import.lg")); + engine = new TemplateEngine().addFile(GetExampleFilePath('importExamples/import.lg')); try { - engine.addFile(GetExampleFilePath("importExamples/import2.lg")); + engine.addFile(GetExampleFilePath('importExamples/import2.lg')); assert.fail('Should throw error.'); } catch (e) { console.log(e.message); - assert.strictEqual(e.message.includes("Dup definitions found for template wPhrase"), true); + assert.strictEqual(e.message.includes('Dup definitions found for template wPhrase'), true); } - engine = new TemplateEngine().addFiles([GetExampleFilePath("importExamples/import.lg")]); + engine = new TemplateEngine().addFiles([GetExampleFilePath('importExamples/import.lg')]); try { - engine.addFiles([GetExampleFilePath("importExamples/import2.lg")]); + engine.addFiles([GetExampleFilePath('importExamples/import2.lg')]); assert.fail('Should throw error.'); } catch (e) { - assert.strictEqual(e.message.includes("Dup definitions found for template wPhrase"), true); + assert.strictEqual(e.message.includes('Dup definitions found for template wPhrase'), true); } }); - - it('TestRegex', function () { - var engine = new TemplateEngine().addFile(GetExampleFilePath("Regex.lg")); - var evaled = engine.evaluateTemplate('wPhrase'); - assert.strictEqual(evaled, 'Hi'); - var evaled = engine.evaluateTemplate('wPhrase', {name: 'jack'}); - assert.strictEqual(evaled, 'Hi jack'); + it('TestExpandTemplate', function() { + var engine = new TemplateEngine().addFile(GetExampleFilePath('Expand.lg')); + + // without scope + var evaled = engine.expandTemplate('FinalGreeting'); + assert.strictEqual(evaled.length, 4, `Evaled is ${ evaled }`); + let expectedResults = ['Hi Morning', 'Hi Evening', 'Hello Morning', 'Hello Evening']; + expectedResults.forEach(x => assert(evaled.includes(x))); + + // with scope + evaled = engine.expandTemplate('TimeOfDayWithCondition', { time: 'evening'}); + assert.strictEqual(evaled.length, 2, `Evaled is ${ evaled }`); + expectedResults = ['Hi Evening', 'Hello Evening']; + expectedResults.forEach(x => assert(evaled.includes(x))); + + // with scope + evaled = engine.expandTemplate('greetInAWeek', {day:'Sunday'}); + assert.strictEqual(evaled.length, 2, `Evaled is ${ evaled }`); + expectedResults = ['Nice Sunday!', 'Happy Sunday!']; + expectedResults.forEach(x => assert(evaled.includes(x))); + }); - var evaled = engine.evaluateTemplate('wPhrase', {name: 'morethanfive'}); - assert.strictEqual(evaled, 'Hi'); + it('TestExpandTemplateWithRef', function() { + var engine = new TemplateEngine().addFile(GetExampleFilePath('Expand.lg')); + + const alarms = [ + { + time: '7 am', + date: 'tomorrow' + }, + { + time: '8 pm', + date: 'tomorrow' + } + ]; + + var evaled = engine.expandTemplate('ShowAlarmsWithLgTemplate', {alarms}); + assert.strictEqual(evaled.length, 2, `Evaled is ${ evaled }`); + assert.strictEqual(evaled[0], 'You have 2 alarms, they are 8 pm at tomorrow', `Evaled is ${ evaled }`); + assert.strictEqual(evaled[1], 'You have 2 alarms, they are 8 pm of tomorrow', `Evaled is ${ evaled }`); }); - it('TestEvalExpression', function () { - var engine = new TemplateEngine().addFile(GetExampleFilePath("EvalExpression.lg")); - const userName = "MS"; - var evaled = engine.evaluateTemplate('template1', {userName}); - assert.strictEqual(evaled, 'Hi MS', `Evaled is ${evaled}`); + it('TestExpandTemplateWithRefInMultiLine', function() { + var engine = new TemplateEngine().addFile(GetExampleFilePath('Expand.lg')); + + const alarms = [ + { + time: '7 am', + date: 'tomorrow' + }, + { + time: '8 pm', + date: 'tomorrow' + } + ]; + + var evaled = engine.expandTemplate('ShowAlarmsWithMultiLine', {alarms}); + assert.strictEqual(evaled.length, 2, `Evaled is ${ evaled }`); + const eval1Options = ['\r\nYou have 2 alarms.\r\nThey are 8 pm at tomorrow\r\n', '\nYou have 2 alarms.\nThey are 8 pm at tomorrow\n']; + const eval2Options = ['\r\nYou have 2 alarms.\r\nThey are 8 pm of tomorrow\r\n', '\nYou have 2 alarms.\nThey are 8 pm of tomorrow\n']; + assert(eval1Options.includes(evaled[0])); + assert(eval2Options.includes(evaled[1])); + }); - evaled = engine.evaluateTemplate('template2', {userName}); - assert.strictEqual(evaled, 'Hi MS', `Evaled is ${evaled}`); + it('TestExpandTemplateWithFunction', function() { + var engine = new TemplateEngine().addFile(GetExampleFilePath('Expand.lg')); + + const alarms = [ + { + time: '7 am', + date: 'tomorrow' + }, + { + time: '8 pm', + date: 'tomorrow' + } + ]; + + var evaled = engine.expandTemplate('ShowAlarmsWithForeach', {alarms}); + assert.strictEqual(evaled.length, 1, `Evaled is ${ evaled }`); + const evalOptions = [ + 'You have 2 alarms, 7 am at tomorrow and 8 pm at tomorrow', + 'You have 2 alarms, 7 am at tomorrow and 8 pm of tomorrow', + 'You have 2 alarms, 7 am of tomorrow and 8 pm at tomorrow', + 'You have 2 alarms, 7 am of tomorrow and 8 pm of tomorrow' + ]; - evaled = engine.evaluateTemplate('template3', {userName}); - assert.strictEqual(evaled, 'HiMS', `Evaled is ${evaled}`); + assert(evalOptions.includes(evaled[0])); - const options1 = ["\r\nHi MS\r\n", "\nHi MS\n"]; - evaled = engine.evaluateTemplate("template4", { userName}); - assert.strictEqual(options1.includes(evaled), true, `Evaled is ${evaled}`); + evaled = engine.expandTemplate('T2'); + assert.strictEqual(evaled.length, 1, `Evaled is ${ evaled }`); + assert(evaled[0] === '3' || evaled[0] === '5'); - const options2 = ["\r\nHiMS\r\n", "\nHiMS\n"]; - evaled = engine.evaluateTemplate("template5", { userName}); - assert.strictEqual(options2.includes(evaled), true, `Evaled is ${evaled}`); + evaled = engine.expandTemplate('T3'); + assert.strictEqual(evaled.length, 1, `Evaled is ${ evaled }`); + assert(evaled[0] === '3' || evaled[0] === '5'); - evaled = engine.evaluateTemplate('template6', {userName}); - assert.strictEqual(evaled, 'goodmorning', `Evaled is ${evaled}`); + evaled = engine.expandTemplate('T4'); + assert.strictEqual(evaled.length, 1, `Evaled is ${ evaled }`); + assert(evaled[0] === 'ey' || evaled[0] === 'el'); }); - it('TestTemplateAsFunction', function () { - var engine = new TemplateEngine().addFile(GetExampleFilePath("TemplateAsFunction.lg")); + it('TestEvalExpression', function() { + var engine = new TemplateEngine().addFile(GetExampleFilePath('EvalExpression.lg')); + const userName = 'MS'; + var evaled = engine.evaluateTemplate('template1', {userName}); + assert.strictEqual(evaled, 'Hi MS', `Evaled is ${ evaled }`); - var evaled = engine.evaluateTemplate('Test2'); - assert.strictEqual(evaled, 'hello world', `Evaled is ${evaled}`); + evaled = engine.evaluateTemplate('template2', {userName}); + assert.strictEqual(evaled, 'Hi MS', `Evaled is ${ evaled }`); - evaled = engine.evaluateTemplate('Test3'); - assert.strictEqual(evaled, 'hello world', `Evaled is ${evaled}`); + evaled = engine.evaluateTemplate('template3', {userName}); + assert.strictEqual(evaled, 'HiMS', `Evaled is ${ evaled }`); - evaled = engine.evaluateTemplate('Test4'); - assert.strictEqual(evaled.trim(), 'hello world', `Evaled is ${evaled}`); + const options1 = ['\r\nHi MS\r\n', '\nHi MS\n']; + evaled = engine.evaluateTemplate('template4', { userName}); + assert.strictEqual(options1.includes(evaled), true, `Evaled is ${ evaled }`); - evaled = engine.evaluateTemplate('dupNameWithTemplate'); - assert.strictEqual(evaled, 'calculate length of ms by user\'s template', `Evaled is ${evaled}`); + const options2 = ['\r\nHiMS\r\n', '\nHiMS\n']; + evaled = engine.evaluateTemplate('template5', { userName}); + assert.strictEqual(options2.includes(evaled), true, `Evaled is ${ evaled }`); - evaled = engine.evaluateTemplate('dupNameWithBuiltinFunc'); - assert.strictEqual(evaled, 2, `Evaled is ${evaled}`); + evaled = engine.evaluateTemplate('template6', {userName}); + assert.strictEqual(evaled, 'goodmorning', `Evaled is ${ evaled }`); }); - it('TestLGResource', function () { - var lgResource = LGParser.parse(fs.readFileSync(GetExampleFilePath("2.lg"), 'utf-8')); + + it('TestLGResource', function() { + var lgResource = LGParser.parse(fs.readFileSync(GetExampleFilePath('2.lg'), 'utf-8')); assert.strictEqual(lgResource.templates.length, 1); assert.strictEqual(lgResource.imports.length, 0); @@ -513,68 +608,64 @@ describe('LG', function () { assert.strictEqual(lgResource.templates.length, 1); }); - it('TestMemoryScope', function () { - var engine = new TemplateEngine().addFile(GetExampleFilePath("MemoryScope.lg")); + it('TestMemoryScope', function() { + var engine = new TemplateEngine().addFile(GetExampleFilePath('MemoryScope.lg')); var evaled = engine.evaluateTemplate('T1', { turn: { name: 'Dong', count: 3 } }); assert.strictEqual(evaled, 'Hi Dong, welcome to Seattle, Seattle is a beautiful place, how many burgers do you want, 3?'); - evaled = engine.evaluateTemplate('AskBread', { + const objscope = { schema: { Bread: { enum: ['A', 'B'] } } - }); - + }; + var scope = new SimpleObjectMemory(objscope); + + evaled = engine.evaluateTemplate('AskBread', scope); assert.strictEqual(evaled, 'Which Bread, A or B do you want?'); - }) + }); - it('TestStructuredTemplate', function () { - var engine = new TemplateEngine().addFile(GetExampleFilePath("StructuredTemplate.lg")); + it('TestStructuredTemplate', function() { + var engine = new TemplateEngine().addFile(GetExampleFilePath('StructuredTemplate.lg')); var evaled = engine.evaluateTemplate('AskForAge.prompt'); assert.equal(evaled.text, evaled.speak); evaled = engine.evaluateTemplate('AskForAge.prompt2'); - if (evaled.text.includes("how old")){ - assert.deepStrictEqual(evaled, JSON.parse("{\"$type\":\"Activity\",\"text\":\"how old are you?\",\"suggestedactions\":[\"10\",\"20\",\"30\"]}")); + if (evaled.text.includes('how old')){ + assert.deepStrictEqual(evaled, JSON.parse('{"lgType":"Activity","text":"how old are you?","suggestedactions":["10","20","30"]}')); } else { - assert.deepStrictEqual(evaled, JSON.parse("{\"$type\":\"Activity\",\"text\":\"what's your age?\",\"suggestedactions\":[\"10\",\"20\",\"30\"]}")); + assert.deepStrictEqual(evaled, JSON.parse('{"lgType":"Activity","text":"what\'s your age?","suggestedactions":["10","20","30"]}')); } evaled = engine.evaluateTemplate('AskForAge.prompt3'); - - if (evaled.text.includes("how old")){ - assert.deepStrictEqual(evaled, JSON.parse("{\"$type\":\"Activity\",\"text\":\"how old are you?\",\"suggestions\":[\"10 | cards\",\"20 | cards\"]}")); - } else { - assert.deepStrictEqual(evaled, JSON.parse("{\"$type\":\"Activity\",\"text\":\"what's your age?\",\"suggestions\":[\"10 | cards\",\"20 | cards\"]}")); - } - + assert.deepStrictEqual(evaled, JSON.parse('{"lgType":"Activity","text":"@{GetAge()}","suggestions":["10 | cards","20 | cards"]}')); evaled = engine.evaluateTemplate('T1'); - assert.deepStrictEqual(evaled, JSON.parse("{\"$type\":\"Activity\",\"text\":\"This is awesome\",\"speak\":\"foo bar I can also speak!\"}")); + assert.deepStrictEqual(evaled, JSON.parse('{"lgType":"Activity","text":"This is awesome","speak":"foo bar I can also speak!"}')); evaled = engine.evaluateTemplate('ST1'); - assert.deepStrictEqual(evaled, JSON.parse("{\"$type\":\"MyStruct\",\"text\":\"foo\",\"speak\":\"bar\"}")); + assert.deepStrictEqual(evaled, JSON.parse('{"lgType":"MyStruct","text":"foo","speak":"bar"}')); evaled = engine.evaluateTemplate('AskForColor'); - assert.deepStrictEqual(evaled, JSON.parse("{\"$type\":\"Activity\",\"suggestedactions\":[{\"$type\":\"MyStruct\",\"speak\":\"bar\",\"text\":\"zoo\"},{\"$type\":\"Activity\",\"speak\":\"I can also speak!\"}]}")); + assert.deepStrictEqual(evaled, JSON.parse('{"lgType":"Activity","suggestedactions":[{"lgType":"MyStruct","speak":"bar","text":"zoo"},{"lgType":"Activity","speak":"I can also speak!"}]}')); evaled = engine.evaluateTemplate('MultiExpression'); - assert.equal(evaled, "{\"$type\":\"Activity\",\"speak\":\"I can also speak!\"} {\"$type\":\"MyStruct\",\"text\":\"hi\"}"); + assert.equal(evaled, '{"lgType":"Activity","speak":"I can also speak!"} {"lgType":"MyStruct","text":"hi"}'); evaled = engine.evaluateTemplate('StructuredTemplateRef'); - assert.deepStrictEqual(evaled, JSON.parse("{\"$type\":\"MyStruct\",\"text\":\"hi\"}")); + assert.deepStrictEqual(evaled, JSON.parse('{"lgType":"MyStruct","text":"hi"}')); evaled = engine.evaluateTemplate('MultiStructuredRef'); - assert.deepStrictEqual(evaled, JSON.parse("{\"$type\":\"MyStruct\",\"list\":[{\"$type\":\"SubStruct\",\"text\":\"hello\"},{\"$type\":\"SubStruct\",\"text\":\"world\"}]}")); + assert.deepStrictEqual(evaled, JSON.parse('{"lgType":"MyStruct","list":[{"lgType":"SubStruct","text":"hello"},{"lgType":"SubStruct","text":"world"}]}')); - evaled = engine.evaluateTemplate('templateWithSquareBrackets', {manufacturer: {Name : "Acme Co"}}); - assert.deepStrictEqual(evaled, JSON.parse("{\"$type\":\"Struct\",\"text\":\"Acme Co\"}")); + evaled = engine.evaluateTemplate('templateWithSquareBrackets', {manufacturer: {Name : 'Acme Co'}}); + assert.deepStrictEqual(evaled, JSON.parse('{"lgType":"Struct","text":"Acme Co"}')); }); - it('TestEvaluateOnce', function () { - var engine = new TemplateEngine().addFile(GetExampleFilePath("EvaluateOnce.lg")); + it('TestEvaluateOnce', function() { + var engine = new TemplateEngine().addFile(GetExampleFilePath('EvaluateOnce.lg')); var evaled = engine.evaluateTemplate('templateWithSameParams', { param: 'ms' }); assert.notEqual(evaled, undefined); @@ -588,19 +679,81 @@ describe('LG', function () { evaled = engine.evaluateTemplate('templateWithDifferentParams', { param1: 'ms', param2: 'newms' }); }); - it('TestConditionExpression', function () { - var engine = new TemplateEngine().addFile(GetExampleFilePath("ConditionExpression.lg")); + it('TestConditionExpression', function() { + var engine = new TemplateEngine().addFile(GetExampleFilePath('ConditionExpression.lg')); var evaled = engine.evaluateTemplate('conditionTemplate', { num: 1 }); - assert.equal(evaled, "Your input is one"); + assert.equal(evaled, 'Your input is one'); evaled = engine.evaluateTemplate('conditionTemplate', { num: 2 }); - assert.equal(evaled, "Your input is two"); + assert.equal(evaled, 'Your input is two'); evaled = engine.evaluateTemplate('conditionTemplate', { num: 3 }); - assert.equal(evaled, "Your input is three"); + assert.equal(evaled, 'Your input is three'); evaled = engine.evaluateTemplate('conditionTemplate', { num: 4 }); - assert.equal(evaled, "Your input is not one, two or three"); + assert.equal(evaled, 'Your input is not one, two or three'); + }); + + it('TestExpandTemplateWithStructuredLG', function() { + var engine = new TemplateEngine().addFile(GetExampleFilePath('StructuredTemplate.lg')); + + var evaled = engine.expandTemplate('AskForAge.prompt'); + assert.strictEqual(evaled.length, 4, `Evaled is ${ evaled }`); + + let expectedResults = [ + '{"lgType":"Activity","text":"how old are you?","speak":"how old are you?"}', + '{"lgType":"Activity","text":"how old are you?","speak":"what\'s your age?"}', + '{"lgType":"Activity","text":"what\'s your age?","speak":"how old are you?"}', + '{"lgType":"Activity","text":"what\'s your age?","speak":"what\'s your age?"}' + ]; + expectedResults.forEach( u => assert(evaled.includes(u))); + + evaled = engine.expandTemplate('ExpanderT1'); + assert.strictEqual(evaled.length, 4, `Evaled is ${ evaled }`); + + expectedResults = [ + '{"lgType":"MyStruct","text":"Hi","speak":"how old are you?"}', + '{"lgType":"MyStruct","text":"Hi","speak":"what\'s your age?"}', + '{"lgType":"MyStruct","text":"Hello","speak":"how old are you?"}', + '{"lgType":"MyStruct","text":"Hello","speak":"what\'s your age?"}' + ]; + expectedResults.forEach( u => assert(evaled.includes(u))); + }); + + it('TestExpressionextract', function() { + var engine = new TemplateEngine().addFile(GetExampleFilePath('ExpressionExtract.lg')); + + var evaled1 = engine.evaluateTemplate('templateWithBrackets'); + var evaled2 = engine.evaluateTemplate('templateWithBrackets2'); + var evaled3 = engine.evaluateTemplate('templateWithBrackets3').toString().trim(); + let espectedResult = 'don\'t mix {} and \'{}\''; + assert.strictEqual(evaled1, espectedResult); + assert.strictEqual(evaled2, espectedResult); + assert.strictEqual(evaled3, espectedResult); + + evaled1 = engine.evaluateTemplate('templateWithQuotationMarks'); + evaled2 = engine.evaluateTemplate('templateWithQuotationMarks2'); + evaled3 = engine.evaluateTemplate('templateWithQuotationMarks3').toString().trim(); + espectedResult = 'don\'t mix {"} and ""\'"'; + assert.strictEqual(evaled1, espectedResult); + assert.strictEqual(evaled2, espectedResult); + assert.strictEqual(evaled3, espectedResult); + + evaled1 = engine.evaluateTemplate('templateWithUnpairedBrackets1'); + evaled2 = engine.evaluateTemplate('templateWithUnpairedBrackets12'); + evaled3 = engine.evaluateTemplate('templateWithUnpairedBrackets13').toString().trim(); + espectedResult = '{prefix 5 sufix'; + assert.strictEqual(evaled1, espectedResult); + assert.strictEqual(evaled2, espectedResult); + assert.strictEqual(evaled3, espectedResult); + + evaled1 = engine.evaluateTemplate('templateWithUnpairedBrackets2'); + evaled2 = engine.evaluateTemplate('templateWithUnpairedBrackets22'); + evaled3 = engine.evaluateTemplate('templateWithUnpairedBrackets23').toString().trim(); + espectedResult = 'prefix 5 sufix}'; + assert.strictEqual(evaled1, espectedResult); + assert.strictEqual(evaled2, espectedResult); + assert.strictEqual(evaled3, espectedResult); }); }); diff --git a/libraries/botbuilder-lg/tests/mslgtool.test.js b/libraries/botbuilder-lg/tests/mslgtool.test.js index 0b68018daf..01a61b0d45 100644 --- a/libraries/botbuilder-lg/tests/mslgtool.test.js +++ b/libraries/botbuilder-lg/tests/mslgtool.test.js @@ -1,3 +1,4 @@ + const { MSLGTool } = require('../'); const assert = require('assert'); const fs = require('fs'); @@ -9,24 +10,24 @@ function GetErrors(mslgtool, fileName){ } -describe('MSLGTool', function () { - it('TestValidateReturnStaticCheckerErrors', function () { +describe('MSLGTool', function() { + it('TestValidateReturnStaticCheckerErrors', function() { let errors = GetErrors(new MSLGTool(),'StaticCheckerErrors.lg'); - assert.strictEqual(errors.length,6) - assert(errors[0].includes("There is no template body in template template")); - assert(errors[1].includes("condition is not end with else")) - assert(errors[2].includes("control flow is not starting with switch")) - assert(errors[3].includes("control flow is not ending with default statement")) - assert(errors[4].includes("control flow should have at least one case statement")) - assert(errors[5].includes("Not a valid template name line")) + assert.strictEqual(errors.length,6); + assert(errors[0].includes('There is no template body in template template')); + assert(errors[1].includes('condition is not end with else')); + assert(errors[2].includes('control flow is not starting with switch')); + assert(errors[3].includes('control flow is not ending with default statement')); + assert(errors[4].includes('control flow should have at least one case statement')); + assert(errors[5].includes('Not a valid template name line')); }); - it('TestValidateReturnNoErrors', function () { + it('TestValidateReturnNoErrors', function() { let errors = GetErrors(new MSLGTool(),'ValidFile.lg'); assert.strictEqual(errors.length, 0); }); - it('TestCollateTemplates', function () { + it('TestCollateTemplates', function() { const mslgTool = new MSLGTool(); let errors = GetErrors(mslgTool, 'CollateFile1.lg'); assert.strictEqual(errors.length, 0); @@ -42,7 +43,7 @@ describe('MSLGTool', function () { assert.strictEqual(mslgTool.collatedTemplates.get('TimeOfDay').length, 3); }); - it('TestCollateTemplatesOfStructuredLG', function () { + it('TestCollateTemplatesOfStructuredLG', function() { const mslgTool = new MSLGTool(); errors = GetErrors(mslgTool, 'CollateFile4.lg'); assert.strictEqual(errors.length, 0); @@ -57,7 +58,7 @@ describe('MSLGTool', function () { assert.strictEqual(result.replace(/\r\n/g, '\n'), '# ST2\n[MyStruct\n Speak = bar\n Text = zoo\n]\n\n'); }); - it('TestExpandTemplate', function () { + it('TestExpandTemplate', function() { const mslgTool = new MSLGTool(); let errors = GetErrors(mslgTool, 'CollateFile1.lg'); assert.strictEqual(errors.length, 0); @@ -67,7 +68,7 @@ describe('MSLGTool', function () { expectedResults.forEach(element => { assert.strictEqual(expandedTemplate.includes(element), true); }); - }) + }); it('TestExpandTemplateWithScope', function() { const mslgTool = new MSLGTool(); @@ -86,7 +87,7 @@ describe('MSLGTool', function () { assert.strictEqual(expandedTemplate2.includes(element), true); }); - }) + }); it('TestExpandTemplateWithRef', function() { const mslgTool = new MSLGTool(); @@ -94,19 +95,19 @@ describe('MSLGTool', function () { assert.strictEqual(errors.length, 0); const alarms = [ { - time: "7 am", - date : "tomorrow" + time: '7 am', + date : 'tomorrow' }, { - time:"8 pm", - date :"tomorrow" + time:'8 pm', + date :'tomorrow' } ]; let expandedTemplate = mslgTool.expandTemplate('ShowAlarmsWithLgTemplate', {alarms: alarms}); assert.strictEqual(expandedTemplate.length, 2); - assert.strictEqual(expandedTemplate[0], "You have 2 alarms, they are 8 pm at tomorrow"); - assert.strictEqual(expandedTemplate[1], "You have 2 alarms, they are 8 pm of tomorrow"); - }) + assert.strictEqual(expandedTemplate[0], 'You have 2 alarms, they are 8 pm at tomorrow'); + assert.strictEqual(expandedTemplate[1], 'You have 2 alarms, they are 8 pm of tomorrow'); + }); it('TestExpandTemplateWithFunction', function() { const mslgTool = new MSLGTool(); @@ -114,20 +115,20 @@ describe('MSLGTool', function () { assert.strictEqual(errors.length, 0); const alarms = [ { - time: "7 am", - date : "tomorrow" + time: '7 am', + date : 'tomorrow' }, { - time:"8 pm", - date :"tomorrow" + time:'8 pm', + date :'tomorrow' } ]; let evaled = mslgTool.expandTemplate('ShowAlarmsWithForeach', {alarms: alarms}); const evalOptions = [ - "You have 2 alarms, 7 am at tomorrow and 8 pm at tomorrow", - "You have 2 alarms, 7 am at tomorrow and 8 pm of tomorrow", - "You have 2 alarms, 7 am of tomorrow and 8 pm at tomorrow", - "You have 2 alarms, 7 am of tomorrow and 8 pm of tomorrow" + 'You have 2 alarms, 7 am at tomorrow and 8 pm at tomorrow', + 'You have 2 alarms, 7 am at tomorrow and 8 pm of tomorrow', + 'You have 2 alarms, 7 am of tomorrow and 8 pm at tomorrow', + 'You have 2 alarms, 7 am of tomorrow and 8 pm of tomorrow' ]; assert.strictEqual(evaled.length, 1); @@ -135,16 +136,16 @@ describe('MSLGTool', function () { evaled = mslgTool.expandTemplate('T2'); assert.strictEqual(evaled.length, 1); - assert.strictEqual(evaled[0] === "3" || evaled[0] === "5", true); + assert.strictEqual(evaled[0] === '3' || evaled[0] === '5', true); evaled = mslgTool.expandTemplate('T3'); assert.strictEqual(evaled.length, 1); - assert.strictEqual(evaled[0] === "3" || evaled[0] === "5", true); + assert.strictEqual(evaled[0] === '3' || evaled[0] === '5', true); evaled = mslgTool.expandTemplate('T4'); assert.strictEqual(evaled.length, 1); - assert.strictEqual(evaled[0] === "ey" || evaled[0] === "el", true); - }) + assert.strictEqual(evaled[0] === 'ey' || evaled[0] === 'el', true); + }); it('TestExpandTemplateWithRefInMultiLine', function() { const mslgTool = new MSLGTool(); @@ -152,21 +153,21 @@ describe('MSLGTool', function () { assert.strictEqual(errors.length, 0); const alarms = [ { - time: "7 am", - date : "tomorrow" + time: '7 am', + date : 'tomorrow' }, { - time:"8 pm", - date :"tomorrow" + time:'8 pm', + date :'tomorrow' } ]; let expandedTemplate = mslgTool.expandTemplate('ShowAlarmsWithMultiLine', {alarms: alarms}); assert.strictEqual(expandedTemplate.length, 2); - const eval1Options = ["\r\nYou have 2 alarms.\r\nThey are 8 pm at tomorrow\r\n", "\nYou have 2 alarms.\nThey are 8 pm at tomorrow\n"]; - const eval2Options = ["\r\nYou have 2 alarms.\r\nThey are 8 pm of tomorrow\r\n", "\nYou have 2 alarms.\nThey are 8 pm of tomorrow\n"] + const eval1Options = ['\r\nYou have 2 alarms.\r\nThey are 8 pm at tomorrow\r\n', '\nYou have 2 alarms.\nThey are 8 pm at tomorrow\n']; + const eval2Options = ['\r\nYou have 2 alarms.\r\nThey are 8 pm of tomorrow\r\n', '\nYou have 2 alarms.\nThey are 8 pm of tomorrow\n']; assert(eval1Options.includes(expandedTemplate[0])); assert(eval2Options.includes(expandedTemplate[1])); - }) + }); it('TestExpandTemplateWithStructuredLG', function() { const mslgTool = new MSLGTool(); @@ -175,23 +176,23 @@ describe('MSLGTool', function () { let expandedTemplates = mslgTool.expandTemplate('AskForAge.prompt'); assert.strictEqual(expandedTemplates.length, 4); let evalOptions = [ - '{"$type":"Activity","text":"how old are you?","speak":"how old are you?"}', - '{"$type":"Activity","text":"how old are you?","speak":"what\'s your age?"}', - '{"$type":"Activity","text":"what\'s your age?","speak":"how old are you?"}', - '{"$type":"Activity","text":"what\'s your age?","speak":"what\'s your age?"}' - ] - + '{"lgType":"Activity","text":"how old are you?","speak":"how old are you?"}', + '{"lgType":"Activity","text":"how old are you?","speak":"what\'s your age?"}', + '{"lgType":"Activity","text":"what\'s your age?","speak":"how old are you?"}', + '{"lgType":"Activity","text":"what\'s your age?","speak":"what\'s your age?"}' + ]; evalOptions.forEach(evalOption => assert(expandedTemplates.includes(evalOption))); expandedTemplates = mslgTool.expandTemplate('ExpanderT1'); assert.strictEqual(expandedTemplates.length, 4); evalOptions = [ - '{"$type":"MyStruct","text":"Hi","speak":"how old are you?"}', - '{"$type":"MyStruct","text":"Hi","speak":"what\'s your age?"}', - '{"$type":"MyStruct","text":"Hello","speak":"how old are you?"}', - '{"$type":"MyStruct","text":"Hello","speak":"what\'s your age?"}' - ] + '{"lgType":"MyStruct","text":"Hi","speak":"how old are you?"}', + '{"lgType":"MyStruct","text":"Hi","speak":"what\'s your age?"}', + '{"lgType":"MyStruct","text":"Hello","speak":"how old are you?"}', + '{"lgType":"MyStruct","text":"Hello","speak":"what\'s your age?"}' + ]; evalOptions.forEach(evalOption => assert(expandedTemplates.includes(evalOption))); - }) -}) \ No newline at end of file + }); +}); + diff --git a/libraries/botbuilder-lg/tests/templateEngineThrowException.test.js b/libraries/botbuilder-lg/tests/templateEngineThrowException.test.js index 4078bcb0e5..bcab667ad6 100644 --- a/libraries/botbuilder-lg/tests/templateEngineThrowException.test.js +++ b/libraries/botbuilder-lg/tests/templateEngineThrowException.test.js @@ -6,48 +6,49 @@ function GetExampleFilePath(fileName){ } const StaticCheckExceptionData = [ - "ErrorTemplateParameters.lg", - "NoNormalTemplateBody.lg", - "ConditionFormatError.lg", - "NoTemplateRef.lg", - "TemplateParamsNotMatchArgsNum.lg", - "ErrorSeperateChar.lg", - "ErrorSeperateChar2.lg", - "MultilineVariation.lg", - "InvalidTemplateName.lg", - "InvalidTemplateName2.lg", - "DuplicatedTemplates.lg", - "LgTemplateFunctionError.lg", - "SwitchCaseFormatError.lg", - "InvalidLGFileImportPath.lg", - "DuplicatedTemplatesInImportFiles.lg", - "ErrorStructuredTemplate.lg" - ]; + 'MultilineVariation.lg', + 'ErrorTemplateParameters.lg', + 'NoNormalTemplateBody.lg', + 'ConditionFormatError.lg', + 'NoTemplateRef.lg', + 'TemplateParamsNotMatchArgsNum.lg', + 'ErrorSeperateChar.lg', + 'ErrorSeperateChar2.lg', + 'MultilineVariation.lg', + 'InvalidTemplateName.lg', + 'InvalidTemplateName2.lg', + 'DuplicatedTemplates.lg', + 'LgTemplateFunctionError.lg', + 'SwitchCaseFormatError.lg', + 'InvalidLGFileImportPath.lg', + 'DuplicatedTemplatesInImportFiles.lg', + 'ErrorStructuredTemplate.lg' +]; const StaticCheckWariningData = [ - "EmptyLGFile.lg", - "OnlyNoMatchRule.lg", - "NoMatchRule.lg", - "SwitchCaseWarning.lg", - "EmptyTemplate.lg", + 'EmptyLGFile.lg', + 'OnlyNoMatchRule.lg', + 'NoMatchRule.lg', + 'SwitchCaseWarning.lg', + 'EmptyTemplate.lg', ]; const AnalyzerExceptionData = [ - ["LoopDetected.lg","NotExistTemplateName"], - ["LoopDetected.lg","wPhrase"], + ['LoopDetected.lg','NotExistTemplateName'], + ['LoopDetected.lg','wPhrase'], ]; const EvaluatorExceptionData = [ - ["ErrorExpression.lg","template1"], - ["LoopDetected.lg","wPhrase"], - ["LoopDetected.lg","NotExistTemplate"], + ['ErrorExpression.lg','template1'], + ['LoopDetected.lg','wPhrase'], + ['LoopDetected.lg','NotExistTemplate'], ]; -describe('LGExceptionTest', function () { +describe('LGExceptionTest', function() { - it('WariningTest', function () { + it('WariningTest', function() { for (const testDateItem of StaticCheckWariningData ) { var engine = new TemplateEngine().addFile(GetExampleFilePath(testDateItem)); var report = new StaticChecker().checkTemplates(engine.templates); @@ -56,7 +57,7 @@ describe('LGExceptionTest', function () { } }); - it('ThrowExceptionTest', function () { + it('ThrowExceptionTest', function() { for (const testDateItem of StaticCheckExceptionData ) { var isFail = false; try { @@ -67,28 +68,28 @@ describe('LGExceptionTest', function () { } if (isFail) { - assert.fail("should throw error."); + assert.fail('should throw error.'); } } }); - it('AnalyzerThrowExceptionTest', function () { + it('AnalyzerThrowExceptionTest', function() { for (const testDateItem of AnalyzerExceptionData ) { var isFail = false; - var errorMessage = ""; + var errorMessage = ''; var engine; try { engine = new TemplateEngine().addFile(GetExampleFilePath(testDateItem[0])); } catch (e) { isFail = true; - errorMessage = "error occurs when parsing file"; + errorMessage = 'error occurs when parsing file'; } if(!isFail) { try { engine.AnalyzeTemplate(testDateItem[1]); isFail = true; - errorMessage = "No exception is thrown."; + errorMessage = 'No exception is thrown.'; } catch (e) { errorMessage = e.message; } @@ -100,23 +101,23 @@ describe('LGExceptionTest', function () { } }); - it('EvaluatorThrowExceptionTest', function () { + it('EvaluatorThrowExceptionTest', function() { for (const testDateItem of EvaluatorExceptionData ) { var isFail = false; - var errorMessage = ""; + var errorMessage = ''; var engine; try { engine = new TemplateEngine().addFile(GetExampleFilePath(testDateItem[0])); } catch (e) { isFail = true; - errorMessage = "error occurs when parsing file"; + errorMessage = 'error occurs when parsing file'; } if(!isFail) { try { engine.EvaluateTemplate(testDateItem[1]); isFail = true; - errorMessage = "No exception is thrown."; + errorMessage = 'No exception is thrown.'; } catch (e) { errorMessage = e.message; } @@ -129,6 +130,6 @@ describe('LGExceptionTest', function () { }); it('AddTextWithWrongId', function() { - assert.throws(() => { new TemplateEngine().addText("# t \n - hi", "a.lg"); }, Error); + assert.throws(() => { new TemplateEngine().addText('# t \n - hi', 'a.lg'); }, Error); }); -}); \ No newline at end of file +}); diff --git a/libraries/botbuilder-lg/tests/testData/adaptiveCard.json b/libraries/botbuilder-lg/tests/testData/adaptiveCard.json new file mode 100644 index 0000000000..b4868a9bd4 --- /dev/null +++ b/libraries/botbuilder-lg/tests/testData/adaptiveCard.json @@ -0,0 +1,117 @@ +{ + "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", + "type": "AdaptiveCard", + "version": "1.0", + "body": [ + { + "type": "TextBlock", + "text": "@{adaptiveCardTitle}", + "weight": "bolder", + "size": "medium" + }, + { + "type": "ColumnSet", + "columns": [ + { + "type": "Column", + "width": "auto", + "items": [ + { + "type": "Image", + "url": "https://pbs.twimg.com/profile_images/3647943215/d7f12830b3c17a5a9e4afcc370e3a37e_400x400.jpeg", + "size": "small", + "style": "person" + } + ] + }, + { + "type": "Column", + "width": "stretch", + "items": [ + { + "type": "TextBlock", + "text": "Matt Hidinger", + "weight": "bolder", + "wrap": true + }, + { + "type": "TextBlock", + "spacing": "none", + "text": "Created aa", + "isSubtle": true, + "wrap": true + } + ] + } + ] + }, + { + "type": "TextBlock", + "text": "Now that we have defined the main rules and features of the format, we need to produce a schema and publish it to GitHub. The schema will be the starting point of our reference documentation.", + "wrap": true + }, + { + "type": "FactSet", + "facts": [ + { + "title": "Board:", + "value": "Adaptive Card" + }, + { + "title": "List:", + "value": "Backlog" + }, + { + "title": "Assigned to:", + "value": "Matt Hidinger" + }, + { + "title": "Due date:", + "value": "Not set" + } + ] + } + ], + "actions": [ + { + "type": "Action.ShowCard", + "title": "Set due date", + "card": { + "type": "AdaptiveCard", + "body": [ + { + "type": "Input.Date", + "id": "dueDate" + } + ], + "actions": [ + { + "type": "Action.Submit", + "title": "OK" + } + ] + } + }, + { + "type": "Action.ShowCard", + "title": "Comment", + "card": { + "type": "AdaptiveCard", + "body": [ + { + "type": "Input.Text", + "id": "comment", + "isMultiline": true, + "placeholder": "Enter your comment" + } + ], + "actions": [ + { + "type": "Action.Submit", + "title": "OK" + } + ] + } + } + ] +} diff --git a/libraries/botbuilder-lg/tests/testData/examples/3.lg b/libraries/botbuilder-lg/tests/testData/examples/3.lg index da75cd2d13..f2e414f70c 100644 --- a/libraries/botbuilder-lg/tests/testData/examples/3.lg +++ b/libraries/botbuilder-lg/tests/testData/examples/3.lg @@ -8,6 +8,6 @@ > Using a template in another template > Sometimes the bot will say 'Hi' and other times it will say 'Hi :)' # welcome-user() -- [wPhrase] -- [wPhrase] :) +- @{wPhrase()} +- @{wPhrase()} :) diff --git a/libraries/botbuilder-lg/tests/testData/examples/4.lg b/libraries/botbuilder-lg/tests/testData/examples/4.lg index d544b2d06b..16dae5e3cd 100644 --- a/libraries/botbuilder-lg/tests/testData/examples/4.lg +++ b/libraries/botbuilder-lg/tests/testData/examples/4.lg @@ -8,10 +8,10 @@ > Using a template in another template > Sometimes the bot will say 'Hi' and other times it will say 'Hi :)' # welcome -- [wPhrase] -- [wPhrase] :) +- @{wPhrase()} +- @{wPhrase()} :) > Using entity references. Unless explicitly specified, entities default to string. Valid types are String, Int, Long, Float, Double, Bool, DateTime # welcome-user -- [wPhrase] @{userName} -- [wPhrase] @{userName} :) +- @{wPhrase()} @{userName} +- @{wPhrase()} @{userName} :) diff --git a/libraries/botbuilder-lg/tests/testData/examples/5.lg b/libraries/botbuilder-lg/tests/testData/examples/5.lg index 61a200823b..769a6617a5 100644 --- a/libraries/botbuilder-lg/tests/testData/examples/5.lg +++ b/libraries/botbuilder-lg/tests/testData/examples/5.lg @@ -8,14 +8,14 @@ > Using a template in another template > Sometimes the bot will say 'Hi' and other times it will say 'Hi :)' # welcome-user -- [wPhrase] -- [wPhrase] :) +- @{wPhrase()} +- @{wPhrase()} :) > Using entity references # welcome-user2 -- [wPhrase] -- [wPhrase] @{userName} -- [wPhrase] @{userName} :) +- @{wPhrase()} +- @{wPhrase()} @{userName} +- @{wPhrase()} @{userName} :) > Conditional response template > Outer list is condition expression; L2 list is one-of collection diff --git a/libraries/botbuilder-lg/tests/testData/examples/6.lg b/libraries/botbuilder-lg/tests/testData/examples/6.lg index d5b4ab93e2..4a2eb5ff1d 100644 --- a/libraries/botbuilder-lg/tests/testData/examples/6.lg +++ b/libraries/botbuilder-lg/tests/testData/examples/6.lg @@ -8,40 +8,40 @@ > Using a template in another template > Sometimes the bot will say 'Hi' and other times it will say 'Hi :)' # welcome-user(userName) -- [wPhrase] {userName} :) +- @{wPhrase()} @{userName} :) > Using entity references # welcome -- IF: {userName} - - [welcome-user(userName)] +- IF: @{userName} + - @{welcome-user(userName)} - ELSE: - - [welcome-user('DongLei')] + - @{welcome-user('DongLei')} # ShowAlarm(alarm) -- {alarm.time} at {alarm.date} +- @{alarm.time} at @{alarm.date} # ShowAlarmsWithForeach -- IF: {count(alarms) == 1} - - You have one alarm [ShowAlarm(alarms[0])] -- ELSEIF: {count(alarms) == 2} - - You have {count(alarms)} alarms, {join(foreach(alarms, x, ShowAlarm(x)), ', ', ' and ')} +- IF: @{count(alarms) == 1} + - You have one alarm @{ShowAlarm(alarms[0])} +- ELSEIF: @{count(alarms) == 2} + - You have @{count(alarms)} alarms, @{join(foreach(alarms, x, ShowAlarm(x)), ', ', ' and ')} - ELSE: - You don't have any alarms # ShowAlarmsWithLgTemplate -- IF: {count(alarms) == 1} - - You have one alarm [ShowAlarm(alarms[0])] -- ELSEIF: {count(alarms) == 2} - - You have {count(alarms)} alarms, {join(foreach(alarms, x, lgTemplate('ShowAlarm', x)), ', ', ' and ')} +- IF: @{count(alarms) == 1} + - You have one alarm @{ShowAlarm(alarms[0])} +- ELSEIF: @{count(alarms) == 2} + - You have @{count(alarms)} alarms, @{join(foreach(alarms, x, template('ShowAlarm', x)), ', ', ' and ')} - ELSE: - You don't have any alarms # ShowAlarmsWithDynamicLgTemplate(templateName) -- IF: {count(alarms) == 1} - - You have one alarm [ShowAlarm(alarms[0])] -- ELSEIF: {count(alarms) == 2} - - You have {count(alarms)} alarms, {join(foreach(alarms, x, lgTemplate(templateName, x)), ', ', ' and ')} +- IF: @{count(alarms) == 1} + - You have one alarm @{ShowAlarm(alarms[0])} +- ELSEIF: @{count(alarms) == 2} + - You have @{count(alarms)} alarms, @{join(foreach(alarms, x, template(templateName, x)), ', ', ' and ')} - ELSE: - You don't have any alarms \ No newline at end of file diff --git a/libraries/botbuilder-lg/tests/testData/examples/8.lg b/libraries/botbuilder-lg/tests/testData/examples/8.lg index b78e3f5870..1924d3d04e 100644 --- a/libraries/botbuilder-lg/tests/testData/examples/8.lg +++ b/libraries/botbuilder-lg/tests/testData/examples/8.lg @@ -1,12 +1,12 @@ # RecentTasks -- IF: {count(recentTasks) == 1} - - Your most recent task is {recentTasks[0]}. You can let me know if you want to add or complete a task. -- ELSEIF: {count(recentTasks) == 2} - - Your most recent tasks are {join(recentTasks, ',', 'and')}. You can let me know if you want to add or complete a task. -- ELSEIF: {count(recentTasks) > 2} - - Your most recent {count(recentTasks)} tasks are {join(recentTasks, ',', 'and')}. You can let me know if you want to add or complete a task. +- IF: @{count(recentTasks) == 1} + - Your most recent task is @{recentTasks[0]}. You can let me know if you want to add or complete a task. +- ELSEIF: @{count(recentTasks) == 2} + - Your most recent tasks are @{join(recentTasks, ',', 'and')}. You can let me know if you want to add or complete a task. +- ELSEIF: @{count(recentTasks) > 2} + - Your most recent @{count(recentTasks)} tasks are @{join(recentTasks, ',', 'and')}. You can let me know if you want to add or complete a task. - ELSE: - You don't have any tasks. # ShowTasks -- {RecentTasks()} \ No newline at end of file +- @{RecentTasks()} \ No newline at end of file diff --git a/libraries/botbuilder-lg/tests/testData/examples/Analyzer.lg b/libraries/botbuilder-lg/tests/testData/examples/Analyzer.lg index c0a76efdff..a5c8916379 100644 --- a/libraries/botbuilder-lg/tests/testData/examples/Analyzer.lg +++ b/libraries/botbuilder-lg/tests/testData/examples/Analyzer.lg @@ -1,26 +1,26 @@ # wPhrase -- Hi {userName} +- Hi @{userName} # pizzaOrderConfirmation -- Your pizza order of {base} with toppings {join(topping, ',', 'and')} is confirmed. +- Your pizza order of @{base} with toppings @{join(topping, ',', 'and')} is confirmed. # sandwichOrderConfirmation -- Your {bread} sandwich with {meat} is on its way. Thanks. +- Your @{bread} sandwich with @{meat} is on its way. Thanks. # orderReadOut -- IF: {orderType == 'pizza'} -- [wPhrase] [pizzaOrderConfirmation] -- ELSEIF: {orderType == 'sandwich'} -- [sandwichOrderConfirmation] +- IF: @{orderType == 'pizza'} +- @{wPhrase()} @{pizzaOrderConfirmation()} +- ELSEIF: @{orderType == 'sandwich'} +- @{sandwichOrderConfirmation()} # template1 -- [template2] {age} [template4] [template6(age)] +- @{template2()} @{age} @{template4()} @{template6(age)} # template2 -- {join(foreach(alarms, x, template3(customer)), ',', 'and')} {tasks[0]} +- @{join(foreach(alarms, x, template3(customer)), ',', 'and')} @{tasks[0]} # template3(input) -- {input.property} +- @{input.property} # template4 - ``` @@ -34,22 +34,22 @@ - hi # LatteOrderConfirmation --Here is your {size} Latte. You need to pay {price} dollars! Thank you! +-Here is your @{size} Latte. You need to pay @{price} dollars! Thank you! # MochaOrderConfirmation --Here is your {size} Mocha. You need to pay {price} dollars! Thank you! +-Here is your @{size} Mocha. You need to pay @{price} dollars! Thank you! # CuppuccinoOrderConfirmation --Here is your {size} Cuppuccino. You need to pay {price} dollars! Thank you! +-Here is your @{size} Cuppuccino. You need to pay @{price} dollars! Thank you! # coffee-to-go-order --SWITCH:{coffee} -- CASE: {'Latte'} - - [wPhrase] [LatteOrderConfirmation] -- CASE: {'Mocha'} - - [wPhrase] [MochaOrderConfirmation] -- CASE: {'CuppuccinoOrderConfirmation'} - - [wPhrase] [CuppuccinoOrderConfirmation] +-SWITCH:@{coffee} +- CASE: @{'Latte'} + - @{wPhrase()} @{LatteOrderConfirmation()} +- CASE: @{'Mocha'} + - @{wPhrase()} @{MochaOrderConfirmation()} +- CASE: @{'CuppuccinoOrderConfirmation'} + - @{wPhrase()} @{CuppuccinoOrderConfirmation()} - DEFAULT: - - [wPhrase], welcome next time! + - @{wPhrase()}, welcome next time! diff --git a/libraries/botbuilder-lg/tests/testData/examples/BasicActivity.lg b/libraries/botbuilder-lg/tests/testData/examples/BasicActivity.lg index 7cecd6b65f..636aa233fe 100644 --- a/libraries/botbuilder-lg/tests/testData/examples/BasicActivity.lg +++ b/libraries/botbuilder-lg/tests/testData/examples/BasicActivity.lg @@ -1,9 +1,9 @@ # RecentTasks -- IF: {count(recentTasks) == 1} - - Your most recent task is {recentTasks[0]}. You can let me know if you want to add or complete a task.|| Your most recent task is {recentTasks[0]}. You can let me know. -- ELSEIF: {count(recentTasks) == 2} - - Your most recent tasks are {join(recentTasks, ',', 'and')}. You can let me know if you want to add or complete a task. || Your most recent tasks are {join(recentTasks, ',', 'and')}. You can let me know. -- ELSEIF: {count(recentTasks) >= 3} - - Your most recent tasks are {join(recentTasks, ',', 'and')}. You can let me know if you want to add or complete a task. && Your most recent tasks are {join(recentTasks, ',', 'and')}. You can let me know. +- IF: @{count(recentTasks) == 1} + - Your most recent task is @{recentTasks[0]}. You can let me know if you want to add or complete a task.|| Your most recent task is @{recentTasks[0]}. You can let me know. +- ELSEIF: @{count(recentTasks) == 2} + - Your most recent tasks are @{join(recentTasks, ',', 'and')}. You can let me know if you want to add or complete a task. || Your most recent tasks are @{join(recentTasks, ',', 'and')}. You can let me know. +- ELSEIF: @{count(recentTasks) >= 3} + - Your most recent tasks are @{join(recentTasks, ',', 'and')}. You can let me know if you want to add or complete a task. && Your most recent tasks are @{join(recentTasks, ',', 'and')}. You can let me know. - ELSE: - You don't have any tasks. \ No newline at end of file diff --git a/libraries/botbuilder-lg/tests/testData/examples/BasicList.lg b/libraries/botbuilder-lg/tests/testData/examples/BasicList.lg index ed62a1b1b4..6e4b808341 100644 --- a/libraries/botbuilder-lg/tests/testData/examples/BasicList.lg +++ b/libraries/botbuilder-lg/tests/testData/examples/BasicList.lg @@ -1,7 +1,7 @@ # BasicJoin -- IF : {count(items) == 2} - - {join(items, ', ')} -- ELSEIF : {count(items) > 2} - - {join(items, ', ', ' and ')} +- IF : @{count(items) == 2} + - @{join(items, ', ')} +- ELSEIF : @{count(items) > 2} + - @{join(items, ', ', ' and ')} - ELSE : - - {items[0]} \ No newline at end of file + - @{items[0]} \ No newline at end of file diff --git a/libraries/botbuilder-lg/tests/testData/examples/CaseInsensitive.lg b/libraries/botbuilder-lg/tests/testData/examples/CaseInsensitive.lg index 2942fae2a5..0703f7c35d 100644 --- a/libraries/botbuilder-lg/tests/testData/examples/CaseInsensitive.lg +++ b/libraries/botbuilder-lg/tests/testData/examples/CaseInsensitive.lg @@ -1,16 +1,16 @@ # ShowAlarms -- iF: {count(alarms) == 1} +- iF: @{count(alarms) == 1} - You have one alarm -- Elseif: {count(alarms) == 2} +- Elseif: @{count(alarms) == 2} - You have two alarms - elSe: - You don't have any alarms # greetInAWeek --sWItCH: {day} - -cAsE: {'Saturday'} +-sWItCH: @{day} + -cAsE: @{'Saturday'} -Happy Saturday! - -CASe: {'Sunday'} + -CASe: @{'Sunday'} -Happy Sunday! -dEFAULT: -Work Hard! \ No newline at end of file diff --git a/libraries/botbuilder-lg/tests/testData/examples/ConditionExpression.lg b/libraries/botbuilder-lg/tests/testData/examples/ConditionExpression.lg index 8e4d629f54..abe04c8f75 100644 --- a/libraries/botbuilder-lg/tests/testData/examples/ConditionExpression.lg +++ b/libraries/botbuilder-lg/tests/testData/examples/ConditionExpression.lg @@ -1,9 +1,9 @@ # conditionTemplate(num) -- IF: {num == 1} +- IF: @{num == 1} - Your input is one -- ELSE if: {num == 2} +- ELSE if: @{num == 2} - Your input is two -- ELSEIF: {num == 3} +- ELSEIF: @{num == 3} - Your input is three - else: -- Your input is not one, two or three \ No newline at end of file +- Your input is not one, two or three diff --git a/libraries/botbuilder-lg/tests/testData/examples/DiagnosticStructuredLG.lg b/libraries/botbuilder-lg/tests/testData/examples/DiagnosticStructuredLG.lg new file mode 100644 index 0000000000..d00b9af22f --- /dev/null +++ b/libraries/botbuilder-lg/tests/testData/examples/DiagnosticStructuredLG.lg @@ -0,0 +1,33 @@ +# ErrorStructuredType +[MyStruct + text = name +] + +# ErrorActivityType +[Activity + type = xxx + invalidProperty = hi +] + +# ErrorMessage +[Activity + attachment = @{HerocardWithCardAction()} + suggestedaction = hello + attachments = @{HerocardWithCardAction()} | @{ErrorStructuredType()} +] + +# HerocardWithCardAction +[herocard + Title = title + Text = text + Buttons = @{ErrorStructuredType()} | @{cardActionTemplate()} + autoloop = notsure +] + +# cardActionTemplate +[CardAction + Type = yyy + Title = title + Value = value + Text = text +] \ No newline at end of file diff --git a/libraries/botbuilder-lg/tests/testData/examples/EscapeCharacter.lg b/libraries/botbuilder-lg/tests/testData/examples/EscapeCharacter.lg index 12e6e73cab..bc78ba00ae 100644 --- a/libraries/botbuilder-lg/tests/testData/examples/EscapeCharacter.lg +++ b/libraries/botbuilder-lg/tests/testData/examples/EscapeCharacter.lg @@ -3,29 +3,32 @@ # wPhrase - Hi \r\n\t\[\]\{\}\\ +# AtEscapeChar +- Hi{1+1}[wPhrase]{wPhrase()}\@{wPhrase()}@{1+1}\@{1+1}\ \ + # otherEscape - Hi \y \ # escapeInExpression -- Hi {replace('hello\\', '\\', '\\\\')} +- Hi @{replace('hello\\', '\\', '\\\\')} # escapeInExpression2 -- Hi {replace('hello\"', '\"', "'")} +- Hi @{replace('hello\"', '\"', "'")} # escapeInExpression3 -- Hi {replace("hello'", "'", '\"')} +- Hi @{replace("hello'", "'", '\"')} # escapeInExpression4 -- Hi {replace("hello\n", "\n", '\"')} +- Hi @{replace("hello\n", "\n", '\"')} # escapeInExpression5 -- Hi {replace('hello\"', '\"', '\n')} +- Hi @{replace('hello\"', '\"', '\n')} # escapeInExpression6 -- Hi {replace("hello'", "'", '\n')} +- Hi @{replace("hello'", "'", '\n')} # showTodo(todos) -- IF: {count(todos) > 0} +- IF: @{count(todos) > 0} - ``` Your most recent @{count(todos)} tasks are @{join(foreach(todos, x, showSingleTodo(x)), '\n')} @@ -36,4 +39,4 @@ ``` # showSingleTodo(x) -- * {x} +- * @{x} diff --git a/libraries/botbuilder-lg/tests/testData/examples/EvalExpression.lg b/libraries/botbuilder-lg/tests/testData/examples/EvalExpression.lg index b78b2e3d36..033901d62c 100644 --- a/libraries/botbuilder-lg/tests/testData/examples/EvalExpression.lg +++ b/libraries/botbuilder-lg/tests/testData/examples/EvalExpression.lg @@ -1,21 +1,21 @@ # template1 -- Hi {userName} +- Hi @{userName} - # template2 +# template2 - Hi @{userName} - # template3 +# template3 - Hi@{userName} - # template4 +# template4 - ``` Hi @{userName} ``` - # template5 +# template5 - ``` Hi@{userName} ``` # template6 -- @{'good'}@{'morning'} +- @{'good'}@{'morning'} \ No newline at end of file diff --git a/libraries/botbuilder-lg/tests/testData/examples/EvaluateOnce.lg b/libraries/botbuilder-lg/tests/testData/examples/EvaluateOnce.lg index a76fd199d6..e1c5c1fd88 100644 --- a/libraries/botbuilder-lg/tests/testData/examples/EvaluateOnce.lg +++ b/libraries/botbuilder-lg/tests/testData/examples/EvaluateOnce.lg @@ -1,8 +1,8 @@ # templateWithSameParams(param) -- {listTemplate1(param)} {listTemplate1(param)} +- @{listTemplate1(param)} @{listTemplate1(param)} # templateWithDifferentParams(param1, param2) -- {listTemplate1(param1)} {listTemplate1(param2)} +- @{listTemplate1(param1)} @{listTemplate1(param2)} # listTemplate1(param) - item1 diff --git a/libraries/botbuilder-lg/tests/testData/examples/ExceptionCatch.lg b/libraries/botbuilder-lg/tests/testData/examples/ExceptionCatch.lg index b9c78ccf3f..4710c9d9ab 100644 --- a/libraries/botbuilder-lg/tests/testData/examples/ExceptionCatch.lg +++ b/libraries/botbuilder-lg/tests/testData/examples/ExceptionCatch.lg @@ -1,3 +1,3 @@ > Should throw exception when Name is undefinied # NoVariableMatch -- {Name} \ No newline at end of file +- @{Name} \ No newline at end of file diff --git a/libraries/botbuilder-lg/tests/testData/examples/Expand.lg b/libraries/botbuilder-lg/tests/testData/examples/Expand.lg new file mode 100644 index 0000000000..db0261a9ef --- /dev/null +++ b/libraries/botbuilder-lg/tests/testData/examples/Expand.lg @@ -0,0 +1,78 @@ +# Greeting +- Hi +- Hello + +#TimeOfDay +- Morning +- Evening + +# FinalGreeting +- @{Greeting()} @{TimeOfDay()} + +# TimeOfDayWithCondition +- IF: @{time == 'morning'} + - @{Greeting()} Morning +- ELSEIF: @{time == 'evening'} + - @{Greeting()} Evening +- ELSE: + - @{Greeting()} Afternoon + +# greetInAWeek +- SWITCH: @{day} + - CASE: @{'Saturday'} + - Happy Saturday! + - Nice Saturday! + - CASE: @{'Sunday'} + - Happy Sunday! + - Nice Sunday! + - DEFAULT: + - Work Hard! + - Weekend soon! + +# ShowAlarm(alarm) +- @{alarm.time} at @{alarm.date} +- @{alarm.time} of @{alarm.date} + +# ShowAlarmsWithForeach +- IF: @{count(alarms) == 1} + - You have one alarm @{ShowAlarm(alarms[0])} +- ELSEIF: @{count(alarms) == 2} + - You have @{count(alarms)} alarms, @{join(foreach(alarms, alarm, ShowAlarm(alarm)), ', ', ' and ')} +- ELSE: + - You don't have any alarms + +# ShowAlarmsWithLgTemplate +- IF: @{count(alarms) == 1} + - You have one alarm @{ShowAlarm(alarms[0])} +- ELSEIF: @{count(alarms) == 2} + - You have @{count(alarms)} alarms, they are @{ShowAlarm(alarms[1])} +- ELSE: + - You don't have any alarms + +# ShowAlarmsWithMultiLine +-``` +You have @{count(alarms)} alarms. +They are @{ShowAlarm(alarms[1])} +``` + +# bookTransportTicket +-SWITCH:@{pass} +- CASE: @{'Flight'} + - Flight ticket booked +- CASE: @{'Train'} + - Train ticket booked +- DEFAULT: + - Shuttle ticket booked + +# T1 +- Hey +- Hello + +# T2 +- @{prebuilt.length(T1())} + +# T3 +- @{count(T1())} + +# T4 +- @{substring(T1(), 1, 2)} \ No newline at end of file diff --git a/libraries/botbuilder-lg/tests/testData/examples/ExpressionExtract.lg b/libraries/botbuilder-lg/tests/testData/examples/ExpressionExtract.lg new file mode 100644 index 0000000000..0695b1fc37 --- /dev/null +++ b/libraries/botbuilder-lg/tests/testData/examples/ExpressionExtract.lg @@ -0,0 +1,68 @@ +> normal template body test +# templateWithBrackets +- don't mix @{'{}'} and '@{concat('{',"}")}' + +# templateWithQuotationMarks +> please use '"',"\"" is not support. The same, please use "'", '\'' is not support +- don't mix @{'{"}'} and "@{concat('"',"'")}" + +# templateWithUnpairedBrackets1 +- {prefix @{length('hello')} sufix + +# templateWithUnpairedBrackets2 +- prefix @{length('hello')} sufix} + +> structured lg test +# templateWithBrackets2 +- @{structuredTemplateWithBrackets().key} + +# structuredTemplateWithBrackets +[MyStruct + key = don't mix @{'{}'} and '@{concat('{',"}")}' +] + +# templateWithQuotationMarks2 +- @{structuredTemplateWithQuotationMarks().key} + +# structuredTemplateWithQuotationMarks +[MyStruct + key = don't mix @{'{"}'} and "@{concat('"',"'")}" +] + +# templateWithUnpairedBrackets12 +- @{structuredTemplateWithUnpairedBrackets1().key} + +# structuredTemplateWithUnpairedBrackets1 +[MyStruct + key = {prefix @{length('hello')} sufix +] + +# templateWithUnpairedBrackets22 +- @{structuredTemplateWithUnpairedBrackets2().key} + + +# structuredTemplateWithUnpairedBrackets2 +[MyStruct + key = prefix @{length('hello')} sufix} +] + +> multiline test +# templateWithBrackets3 +- ``` +don't mix @{'{}'} and '@{concat('{',"}")}' +``` + +# templateWithQuotationMarks3 +- ``` +don't mix @{'{"}'} and "@{concat('"',"'")}" +``` + +# templateWithUnpairedBrackets13 +- ``` +{prefix @{length('hello')} sufix +``` + +# templateWithUnpairedBrackets23 +- ``` +prefix @{length('hello')} sufix} +``` diff --git a/libraries/botbuilder-lg/tests/testData/examples/LoopScope.lg b/libraries/botbuilder-lg/tests/testData/examples/LoopScope.lg new file mode 100644 index 0000000000..09da2f53b4 --- /dev/null +++ b/libraries/botbuilder-lg/tests/testData/examples/LoopScope.lg @@ -0,0 +1,5 @@ +# template1 +- @{call(scope)} + +# call(scope) +- hi \ No newline at end of file diff --git a/libraries/botbuilder-lg/tests/testData/examples/MemoryScope.lg b/libraries/botbuilder-lg/tests/testData/examples/MemoryScope.lg index 7cecd7b874..e233501017 100644 --- a/libraries/botbuilder-lg/tests/testData/examples/MemoryScope.lg +++ b/libraries/botbuilder-lg/tests/testData/examples/MemoryScope.lg @@ -1,14 +1,14 @@ # T1 -- {T2("Seattle")} +- @{T2("Seattle")} # T2(city) -- Hi {turn.name}, welcome to {city}, {T3(city)} +- Hi @{turn.name}, welcome to @{city}, @{T3(city)} # T3(location) -- {location} is a beautiful place, how many burgers do you want, {turn.count}? +- @{location} is a beautiful place, how many burgers do you want, @{turn.count}? # AskBread -- [AskEnum("Bread")] +- @{AskEnum("Bread")} # AskEnum(prop) -- Which {prop}, {join(schema[prop].enum, ' or ')} do you want? \ No newline at end of file +- Which @{prop}, @{join(schema[prop].enum, ' or ')} do you want? \ No newline at end of file diff --git a/libraries/botbuilder-lg/tests/testData/examples/MultiFile-Part1.lg b/libraries/botbuilder-lg/tests/testData/examples/MultiFile-Part1.lg index a822819992..e021d4a7d3 100644 --- a/libraries/botbuilder-lg/tests/testData/examples/MultiFile-Part1.lg +++ b/libraries/botbuilder-lg/tests/testData/examples/MultiFile-Part1.lg @@ -1,4 +1,4 @@ > Part 1 of 3 lg files # template1 -- hello from t1, ref template2: '[template2]' and ref template3: '[template3]' \ No newline at end of file +- hello from t1, ref template2: '@{template2()}' and ref template3: '@{template3()}' \ No newline at end of file diff --git a/libraries/botbuilder-lg/tests/testData/examples/MultiFile-Part2.lg b/libraries/botbuilder-lg/tests/testData/examples/MultiFile-Part2.lg index 2d003ecd1b..10b5bfc731 100644 --- a/libraries/botbuilder-lg/tests/testData/examples/MultiFile-Part2.lg +++ b/libraries/botbuilder-lg/tests/testData/examples/MultiFile-Part2.lg @@ -1,4 +1,4 @@ > Part 2 of 3 lg files # template2 -- hello from t2, ref template3: [template3] +- hello from t2, ref template3: @{template3()} diff --git a/libraries/botbuilder-lg/tests/testData/examples/MultilineTextForAdaptiveCard.lg b/libraries/botbuilder-lg/tests/testData/examples/MultilineTextForAdaptiveCard.lg index bebb66ca3b..9a5d2cecdd 100644 --- a/libraries/botbuilder-lg/tests/testData/examples/MultilineTextForAdaptiveCard.lg +++ b/libraries/botbuilder-lg/tests/testData/examples/MultilineTextForAdaptiveCard.lg @@ -10,7 +10,7 @@ cardContent - ``` @{name} ``` -- {name} +- @{name} # refTemplate - ``` diff --git a/libraries/botbuilder-lg/tests/testData/examples/NormalStructuredLG.lg b/libraries/botbuilder-lg/tests/testData/examples/NormalStructuredLG.lg index d0ec65aed1..29b19ac436 100644 --- a/libraries/botbuilder-lg/tests/testData/examples/NormalStructuredLG.lg +++ b/libraries/botbuilder-lg/tests/testData/examples/NormalStructuredLG.lg @@ -1,4 +1,14 @@ -# HeroCardTemplate(type) +> invalid structured type +# notSupport +[Acti + key = value +] + +> ----------------------------------------------------------------------- +> ---------------------Normal card samples------------------------------- +> ----------------------------------------------------------------------- + +# HeroCardTemplate(type) [HeroCard title=Cheese gromit! subtitle=@{type} @@ -32,6 +42,16 @@ Aspect=16:9 ] +# ReceiptCardTemplate(type, receiptItems) +[ReceiptCard + Title = John Doe + Tax = $ 7.50 + Total = $ 90.95 + buttons = @{ReceiptButton()} + Facts = @{json(ReceiptFacts())} + items = @{receiptItems} +] + # VideoCardTemplate(type) [VideoCard title=Cheese gromit! @@ -52,13 +72,6 @@ buttons=@{signinButton(signinlabel, url)} ] -# signinButton(signinlabel, url) -[CardAction - Title = @{signinlabel} - Value = @{url} - Type = signin -] - # OAuthCardTemplate(signinlabel, url, connectionName) [OAuthCard text=This is some text describing the card, it's cool because it's cool @@ -66,7 +79,6 @@ ConnectionName=@{connectionName} ] - # AnimationCardTemplate [AnimationCard Title=Animation Card @@ -79,20 +91,11 @@ Media=http://oi42.tinypic.com/1rchlx.jpg ] -# HerocardWithCardAction(title, text) -[herocard - Title = @{title} - Text = @{text} - Buttons = @{cardActionTemplate(null, title, text)} -] - -# cardActionTemplate(type, title, value) -[CardAction - Type = @{if(type == null, 'imBack', type)} - Title = @{title} - Value = @{value} - Text = @{title} -] +> ------------------------Activity samples---------------------------- +> Various types of activities. Support all activity types, including: +> message, contactRelationUpdate, conversationUpdate, typing, endOfConversation +> event, invoke, deleteUserData, messageUpdate, messageDelete, installationUpdate +> messageReaction, suggestion, trace and handoff # eventActivity(text) [Activity @@ -101,6 +104,64 @@ Type = event ] +# handoffActivity(text) +[Activity + Name = @{text} + Value = @{text} + Type = handoff +] + +# messageActivityAll(title, text) +[Activity + Text = @{text} + Speak = @{text} + InputHint = accepting + Attachments = @{HerocardWithCardAction(title, text)} + SuggestedActions = firstItem | @{cardActionTemplate(null, title, text)} + AttachmentLayout = list + SemanticAction = @{json(SemanticActionTemplate())} +] + +> --------------------External File reference samples----------------------- +# externalAdaptiveCardActivity +[Activity + attachments = @{ActivityAttachment(json(fromFile('../adaptiveCard.json')), 'adaptiveCard')} +] + +# multiExternalAdaptiveCardActivity +[Activity + attachments = @{foreach(titles, x, ActivityAttachment(externalAdaptiveCard(x), 'adaptiveCard'))} +] + +# externalAdaptiveCard(adaptiveCardTitle) +- @{json(fromFile('../adaptiveCard.json'))} + +# externalHeroCardActivity(type, title, value) +[Activity + attachments = @{ActivityAttachment(json(fromFile('.\\herocard.json')), 'herocard')} +] + +# herocardActivityWithAttachmentStructure +[Activity + attachments = @{herocardAttachment('imBack', 'taptitle', 'tapvalue')} +] + +# herocardAttachment(type, title, value) +[Attachment + contenttype = herocard + content = @{json(fromFile('.\\herocard.json'))} +] + + +> --------------------------Other samples--------------------------- +# HerocardWithCardAction(title, text) +[herocard + Title = @{title} + Text = @{text} + tap = @{cardActionTemplate('imBack', 'taptitle', 'tapvalue')} + Buttons = @{cardActionTemplate(null, title, text)} +] + # activityWithHeroCardAttachment(title, text) [Activity Attachments = @{HerocardWithCardAction(title, text)} @@ -123,12 +184,6 @@ SuggestedActions = @{getSuggestions()} ] -# getSuggestions -- @{foreach(split(stringSuggestions(), '$'), x, trim(x))} - -# stringSuggestions -- first suggestion $ second suggestion $ third suggestion - # activityWithMultiStructuredSuggestionActions(text) [Activity Text = @{text} @@ -140,19 +195,15 @@ Attachments = @{json(adaptivecardjson())} ] -# messageActivityAll(title, text) +# adaptivecardActivityWithAttachmentStructure [Activity - Text = @{text} - Speak = @{text} - InputHint = accepting - Attachments = @{HerocardWithCardAction(title, text)} - SuggestedActions = firstItem | @{cardActionTemplate(null, title, text)} - AttachmentLayout = list + Attachments = @{adaptiveAttachment()} ] -# notSupport -[Acti - key = value +# adaptiveAttachment +[Attachment + contenttype = adaptivecard + content = @{json(adaptivecardjson())} ] # SuggestedActionsReference(text) @@ -166,6 +217,65 @@ SuggestedActions = Add todo | View Todo | Remove Todo | Cancel | Help ] + +> --------------internal cardaction templates---------------------- +# cardActionTemplate(type, title, value) +[CardAction + Type = @{if(type == null, 'imBack', type)} + Title = @{title} + Value = @{value} + Text = @{title} +] + +# signinButton(signinlabel, url) +[CardAction + Title = @{signinlabel} + Value = @{url} + type = signin +] + +# ReceiptButton +[CardAction + Title = More information + Image = https://account.windowsazure.com/content/6.10.1.38-.8225.160809-1618/aux-pre/images/offer-icon-freetrial.png + Value = https://azure.microsoft.com/en-us/pricing/ + type = openUrl +] + +> --------------------Internal string templates------------------------- +# getSuggestions +- @{foreach(split(stringSuggestions(), '$'), x, trim(x))} + +# stringSuggestions +- first suggestion $ second suggestion $ third suggestion + +# SemanticActionTemplate +- ``` +{ + "id": "actionId", + "entities": { + "key1": { + "type": "entityType" + } + }, + "state": null +} +``` + +# ReceiptFacts +- ``` +[ + { + "key": "Order Number", + "value": "1234" + }, + { + "key": "Payment Method", + "value": "VISA 5555-****" + } +] +``` + # adaptivecardjson - ``` { diff --git a/libraries/botbuilder-lg/tests/testData/examples/Regex.lg b/libraries/botbuilder-lg/tests/testData/examples/Regex.lg index 4cdc697464..7c491ce550 100644 --- a/libraries/botbuilder-lg/tests/testData/examples/Regex.lg +++ b/libraries/botbuilder-lg/tests/testData/examples/Regex.lg @@ -1,5 +1,5 @@ # wPhrase - IF: @{name && isMatch(name, '^[a-z]{1,5}$')} - - Hi {name} + - Hi @{name} - ELSE: - Hi \ No newline at end of file diff --git a/libraries/botbuilder-lg/tests/testData/examples/StructuredTemplate.lg b/libraries/botbuilder-lg/tests/testData/examples/StructuredTemplate.lg index 75aa1dd582..87c38b9898 100644 --- a/libraries/botbuilder-lg/tests/testData/examples/StructuredTemplate.lg +++ b/libraries/botbuilder-lg/tests/testData/examples/StructuredTemplate.lg @@ -15,15 +15,16 @@ > With '|' you are making attachments a list. # AskForAge.prompt2 [Activity - Text = {GetAge()} + Text = @{GetAge()} SuggestedActions = 10 | 20 | 30 ] > You can use '\' as an escape character +> \@{GetAge()} would not be evaluated as expression, would be parsed as '@{getAge()}' string # AskForAge.prompt3 [Activity - Text = {GetAge()} + Text = \@{GetAge()} Suggestions = 10 \| cards | 20 \| cards ] @@ -33,8 +34,8 @@ > and whitespace inb front of the end square bracket is allowed # T1 [Activity - Text = {T2()} - Speak = foo bar {T3().speak} + Text = @{T2()} + Speak = foo bar @{T3().speak} ] @@ -53,7 +54,7 @@ # ST1 [MyStruct Text = foo - {ST2()} + @{ST2()} ] @@ -67,7 +68,7 @@ > each item can also be a structure # AskForColor [Activity - SuggestedActions = {ST2()} | {T3()} + SuggestedActions = @{ST2()} | @{T3()} ] @@ -79,7 +80,7 @@ > template can ref to another steuctured template # StructuredTemplateRef -- [T4] +- @{T4()} # T4 @@ -91,16 +92,35 @@ > if you want to re-use the structured, foreach function is a good way # MultiStructuredRef [MyStruct - list = {foreach(createArray('hello','world'), x, T5(x))} + list = @{foreach(createArray('hello','world'), x, T5(x))} ] # T5(text) [SubStruct - Text = {text} + Text = @{text} ] + # templateWithSquareBrackets(manufacturer) [Struct - Text = {manufacturer['Name']} + Text = @{manufacturer['Name']} +] + + +# ExpanderT1 +[MyStruct + Text = @{ExpanderT2()} + @{ExpanderT3()} ] + + +# ExpanderT2 +- Hi +- Hello + +# ExpanderT3 +[MyStruct + Speak = @{GetAge()} + Text = zoo +] \ No newline at end of file diff --git a/libraries/botbuilder-lg/tests/testData/examples/TemplateAsFunction.lg b/libraries/botbuilder-lg/tests/testData/examples/TemplateAsFunction.lg index bc9f809228..a1adc8d3f0 100644 --- a/libraries/botbuilder-lg/tests/testData/examples/TemplateAsFunction.lg +++ b/libraries/botbuilder-lg/tests/testData/examples/TemplateAsFunction.lg @@ -1,22 +1,23 @@ # ShowList(list) -- {join(list, ' ')} +- @{join(list, ' ')} - # Test2 -- [ShowList(createArray('hello', 'world'))] +# Test2 +- @{ShowList(createArray('hello', 'world'))} - # Test3 -- {ShowList(createArray('hello', 'world'))} +# Test3 +- @{ShowList(createArray('hello', 'world'))} - # Test4 +# Test4 - ``` @{ShowList(createArray('hello', 'world'))} ``` - # length(str) -- calculate length of {str} by user's template +# length(str) +- calculate length of @{str} by user's template - # dupNameWithTemplate -- {length('ms')} +# dupNameWithTemplate +- @{length('ms')} + +# dupNameWithBuiltinFunc +- @{prebuilt.length('ms')} - # dupNameWithBuiltinFunc -- {prebuilt.length('ms')} \ No newline at end of file diff --git a/libraries/botbuilder-lg/tests/testData/examples/TemplateNameWithDot.lg b/libraries/botbuilder-lg/tests/testData/examples/TemplateNameWithDot.lg index 7a7f693722..9bd0328d08 100644 --- a/libraries/botbuilder-lg/tests/testData/examples/TemplateNameWithDot.lg +++ b/libraries/botbuilder-lg/tests/testData/examples/TemplateNameWithDot.lg @@ -5,7 +5,7 @@ > Ref such a template should work fine # Hello -- [Hello.World] +- @{Hello.World()} > But > if it's used as a lambda, it might have problems like diff --git a/libraries/botbuilder-lg/tests/testData/examples/TemplateRef.lg b/libraries/botbuilder-lg/tests/testData/examples/TemplateRef.lg index 4c9a2e8e5d..c10984e3c0 100644 --- a/libraries/botbuilder-lg/tests/testData/examples/TemplateRef.lg +++ b/libraries/botbuilder-lg/tests/testData/examples/TemplateRef.lg @@ -1,16 +1,10 @@ # Hello -- [Welcome(time)] {name} - -# Hello2 -- [Welcome] {name} - - # Hello3 -- {Welcome(time)} {name} +- @{Welcome(time)} @{name} # Welcome(time) -- IF: {time == 'morning'} +- IF: @{time == 'morning'} - Good morning -- ELSEIF: {time == 'evening'} +- ELSEIF: @{time == 'evening'} - Good evening - ELSE: - How are you doing, \ No newline at end of file diff --git a/libraries/botbuilder-lg/tests/testData/examples/herocard.json b/libraries/botbuilder-lg/tests/testData/examples/herocard.json new file mode 100644 index 0000000000..2622fe5950 --- /dev/null +++ b/libraries/botbuilder-lg/tests/testData/examples/herocard.json @@ -0,0 +1,19 @@ +{ + "title": "titleContent", + "text": "textContent", + "buttons": [ + { + "type": "imBack", + "title": "titleContent", + "value": "textContent", + "text": "textContent" + } + ], + "tap": { + "type": "@{type}", + "title": "@{title}", + "text": "@{title}", + "value": "@{value}" + } + } + \ No newline at end of file diff --git a/libraries/botbuilder-lg/tests/testData/examples/importExamples/1.lg b/libraries/botbuilder-lg/tests/testData/examples/importExamples/1.lg index b78e3f5870..1924d3d04e 100644 --- a/libraries/botbuilder-lg/tests/testData/examples/importExamples/1.lg +++ b/libraries/botbuilder-lg/tests/testData/examples/importExamples/1.lg @@ -1,12 +1,12 @@ # RecentTasks -- IF: {count(recentTasks) == 1} - - Your most recent task is {recentTasks[0]}. You can let me know if you want to add or complete a task. -- ELSEIF: {count(recentTasks) == 2} - - Your most recent tasks are {join(recentTasks, ',', 'and')}. You can let me know if you want to add or complete a task. -- ELSEIF: {count(recentTasks) > 2} - - Your most recent {count(recentTasks)} tasks are {join(recentTasks, ',', 'and')}. You can let me know if you want to add or complete a task. +- IF: @{count(recentTasks) == 1} + - Your most recent task is @{recentTasks[0]}. You can let me know if you want to add or complete a task. +- ELSEIF: @{count(recentTasks) == 2} + - Your most recent tasks are @{join(recentTasks, ',', 'and')}. You can let me know if you want to add or complete a task. +- ELSEIF: @{count(recentTasks) > 2} + - Your most recent @{count(recentTasks)} tasks are @{join(recentTasks, ',', 'and')}. You can let me know if you want to add or complete a task. - ELSE: - You don't have any tasks. # ShowTasks -- {RecentTasks()} \ No newline at end of file +- @{RecentTasks()} \ No newline at end of file diff --git a/libraries/botbuilder-lg/tests/testData/examples/importExamples/import.lg b/libraries/botbuilder-lg/tests/testData/examples/importExamples/import.lg index 24519b77c9..862801f97c 100644 --- a/libraries/botbuilder-lg/tests/testData/examples/importExamples/import.lg +++ b/libraries/botbuilder-lg/tests/testData/examples/importExamples/import.lg @@ -1,7 +1,7 @@ # basicTemplate - Hi - Hello -- [wPhrase] +- @{wPhrase()} [import](../6.lg) [import](.\..\6.lg) diff --git a/libraries/botbuilder-lg/tests/testData/examples/importExamples/import/import3.lg b/libraries/botbuilder-lg/tests/testData/examples/importExamples/import/import3.lg index ab55db90d8..b8ca3b0607 100644 --- a/libraries/botbuilder-lg/tests/testData/examples/importExamples/import/import3.lg +++ b/libraries/botbuilder-lg/tests/testData/examples/importExamples/import/import3.lg @@ -1,4 +1,4 @@ [import](../import2.lg) # template3 -- [basicTemplate4] \ No newline at end of file +- @{basicTemplate4()} \ No newline at end of file diff --git a/libraries/botbuilder-lg/tests/testData/examples/lgTemplate.lg b/libraries/botbuilder-lg/tests/testData/examples/lgTemplate.lg index 4d456544ac..01ca8c779b 100644 --- a/libraries/botbuilder-lg/tests/testData/examples/lgTemplate.lg +++ b/libraries/botbuilder-lg/tests/testData/examples/lgTemplate.lg @@ -3,12 +3,12 @@ - Hello # TemplateB(a) -- Hi {a} -- Hello {a} +- Hi @{a} +- Hello @{a} # TemplateC -- {TemplateA()} +- @{TemplateA()} #TemplateD -- {TemplateB(b)} \ No newline at end of file +- @{TemplateB(b)} \ No newline at end of file diff --git a/libraries/botbuilder-lg/tests/testData/examples/switchcase.lg b/libraries/botbuilder-lg/tests/testData/examples/switchcase.lg index 6b30b5f4bb..97438a9400 100644 --- a/libraries/botbuilder-lg/tests/testData/examples/switchcase.lg +++ b/libraries/botbuilder-lg/tests/testData/examples/switchcase.lg @@ -1,8 +1,8 @@ # greetInAWeek --SWITCH: {day} --CASE: {'Saturday'} --Happy Saturday! --CASE: {'Sunday'} --Happy Sunday! --DEFAULT: --Work Hard! \ No newline at end of file + -SWITCH: @{day} + -CASE: @{'Saturday'} + -Happy Saturday! + -CASE: @{'Sunday'} + -Happy Sunday! + -DEFAULT: + -Work Hard! \ No newline at end of file diff --git a/libraries/botbuilder-lg/tests/testData/exceptionExamples/ConditionFormatError.lg b/libraries/botbuilder-lg/tests/testData/exceptionExamples/ConditionFormatError.lg index 3caa972d68..68e53f29d1 100644 --- a/libraries/botbuilder-lg/tests/testData/exceptionExamples/ConditionFormatError.lg +++ b/libraries/botbuilder-lg/tests/testData/exceptionExamples/ConditionFormatError.lg @@ -5,30 +5,30 @@ > more than 1 if # templat2 - - IF: {true} + - IF: @{true} - hello -- IF: {false} +- IF: @{false} - hi > else should not follewed by any expressions # templat3 - - IF: {true} + - IF: @{true} - hello -- ELSE: {false} +- ELSE: @{false} - hi > only elseif is allowed in middle of condition # template4 -- IF: {true} +- IF: @{true} - hi -- IF: {true} +- IF: @{true} - hi - ELSE: - hi > at most one space is allowed between if/else/elseif and : # template5 -- IF : {true} +- IF : @{true} - hi - ELSE : - hi \ No newline at end of file diff --git a/libraries/botbuilder-lg/tests/testData/exceptionExamples/DuplicatedTemplates.lg b/libraries/botbuilder-lg/tests/testData/exceptionExamples/DuplicatedTemplates.lg index fe0e1486d2..f860bd3f76 100644 --- a/libraries/botbuilder-lg/tests/testData/exceptionExamples/DuplicatedTemplates.lg +++ b/libraries/botbuilder-lg/tests/testData/exceptionExamples/DuplicatedTemplates.lg @@ -1,8 +1,8 @@ # template1 - hi - # template1(name) +# template1(name) - hi - # template1 - - hi \ No newline at end of file +# template1 + - hi \ No newline at end of file diff --git a/libraries/botbuilder-lg/tests/testData/exceptionExamples/ErrorExpression.lg b/libraries/botbuilder-lg/tests/testData/exceptionExamples/ErrorExpression.lg index 2bb0287154..f219ddb9f2 100644 --- a/libraries/botbuilder-lg/tests/testData/exceptionExamples/ErrorExpression.lg +++ b/libraries/botbuilder-lg/tests/testData/exceptionExamples/ErrorExpression.lg @@ -1,3 +1,3 @@ > Expression Evaluate error # template1 -- {length(first(createArray(1,2)))} \ No newline at end of file +- @{length(first(createArray(1,2)))} \ No newline at end of file diff --git a/libraries/botbuilder-lg/tests/testData/exceptionExamples/ErrorStructuredTemplate.lg b/libraries/botbuilder-lg/tests/testData/exceptionExamples/ErrorStructuredTemplate.lg index 01f4db4a33..e2d9bfebc1 100644 --- a/libraries/botbuilder-lg/tests/testData/exceptionExamples/ErrorStructuredTemplate.lg +++ b/libraries/botbuilder-lg/tests/testData/exceptionExamples/ErrorStructuredTemplate.lg @@ -11,15 +11,15 @@ # errorExpression1 [Activity - {NOTemplate()} + @{NOTemplate()} ] # errorExpression2 [Activity - Text = {NOTemplate()} + Text = @{NOTemplate()} ] # errorStructuredType [Activity% Text = hi -] \ No newline at end of file +] \ No newline at end of file diff --git a/libraries/botbuilder-lg/tests/testData/exceptionExamples/LgTemplateFunctionError.lg b/libraries/botbuilder-lg/tests/testData/exceptionExamples/LgTemplateFunctionError.lg index 0360e69ad9..f798cd05fd 100644 --- a/libraries/botbuilder-lg/tests/testData/exceptionExamples/LgTemplateFunctionError.lg +++ b/libraries/botbuilder-lg/tests/testData/exceptionExamples/LgTemplateFunctionError.lg @@ -1,10 +1,10 @@ > no such template # template3 -- {NotExistTemplate()} +- @{NotExistTemplate()} - > arguments mismatch +> arguments mismatch # template4 -- {template5()} +- @{template5()} - # template5(param1) - - hi \ No newline at end of file +# template5(param1) + - hi \ No newline at end of file diff --git a/libraries/botbuilder-lg/tests/testData/exceptionExamples/LoopDetected.lg b/libraries/botbuilder-lg/tests/testData/exceptionExamples/LoopDetected.lg index 078d3dfb96..b83d506d26 100644 --- a/libraries/botbuilder-lg/tests/testData/exceptionExamples/LoopDetected.lg +++ b/libraries/botbuilder-lg/tests/testData/exceptionExamples/LoopDetected.lg @@ -1,10 +1,10 @@ > Welcome Phrase template > LG runtime will pick a text value from the one-of collection list at random. # wPhrase -- [welcome-user] +- @{welcome-user()} > Using a template in another template > Sometimes the bot will say 'Hi' and other times it will say 'Hi :)' # welcome-user -- [wPhrase] +- @{wPhrase()} diff --git a/libraries/botbuilder-lg/tests/testData/exceptionExamples/NoMatchRule.lg b/libraries/botbuilder-lg/tests/testData/exceptionExamples/NoMatchRule.lg index 35c290d58a..19ca4b133e 100644 --- a/libraries/botbuilder-lg/tests/testData/exceptionExamples/NoMatchRule.lg +++ b/libraries/botbuilder-lg/tests/testData/exceptionExamples/NoMatchRule.lg @@ -1,3 +1,3 @@ # template -- IF: {foo == 'bar'} +- IF: @{foo == 'bar'} - ok diff --git a/libraries/botbuilder-lg/tests/testData/exceptionExamples/NoNormalTemplateBody.lg b/libraries/botbuilder-lg/tests/testData/exceptionExamples/NoNormalTemplateBody.lg index bff85c5207..25920e0c5a 100644 --- a/libraries/botbuilder-lg/tests/testData/exceptionExamples/NoNormalTemplateBody.lg +++ b/libraries/botbuilder-lg/tests/testData/exceptionExamples/NoNormalTemplateBody.lg @@ -1,9 +1,9 @@ > Should throw exception when there is no normal template body in condition block # template1 -- IF: {number == 1} +- IF: @{number == 1} > Should throw exception when there is no normal template body in default block # template2 -- IF: {number == 2} +- IF: @{number == 2} - hello - ELSE: diff --git a/libraries/botbuilder-lg/tests/testData/exceptionExamples/NoTemplateRef.lg b/libraries/botbuilder-lg/tests/testData/exceptionExamples/NoTemplateRef.lg index 475c665c73..02d7e8d12b 100644 --- a/libraries/botbuilder-lg/tests/testData/exceptionExamples/NoTemplateRef.lg +++ b/libraries/botbuilder-lg/tests/testData/exceptionExamples/NoTemplateRef.lg @@ -1,14 +1,16 @@ # template -- [templateRef] +- @{templateRef()} # template1 -- [templateRef(a)] +- @{templateRef(a)} + # multiLineTemplate - ``` -@{[templateRefInMultiLine]} +@{templateRefInMultiLine()} ``` # otherTemplate - hi + diff --git a/libraries/botbuilder-lg/tests/testData/exceptionExamples/SwitchCaseFormatError.lg b/libraries/botbuilder-lg/tests/testData/exceptionExamples/SwitchCaseFormatError.lg index 7e3b643a6b..3b618f2e50 100644 --- a/libraries/botbuilder-lg/tests/testData/exceptionExamples/SwitchCaseFormatError.lg +++ b/libraries/botbuilder-lg/tests/testData/exceptionExamples/SwitchCaseFormatError.lg @@ -1,68 +1,68 @@ #template0 --SWITCH :{case} -- CASE: {'case1'} +-SWITCH :@{case} +- CASE: @{'case1'} - output1 -- CASE: {'case2'} +- CASE: @{'case2'} - output2 - DEFAULT: -output3 #tempalte1 --SWITCH:{case1} --SWITCH:{case2} +-SWITCH:@{case1} +-SWITCH:@{case2} #template2 --CASE:{'case1'} +-CASE:@{'case1'} - Oh No! -DEFAULT: -exit #template3 --SWITCH:{case} -- CASE: {'case1'} +-SWITCH:@{case} +- CASE: @{'case1'} - output1 - DEFAULT: - final output -- CASE: {'case2'} +- CASE: @{'case2'} - output2 #template4 --SWITCH:{case} -- CASE: {'case1'} +-SWITCH:@{case} +- CASE: @{'case1'} - output1 -- CASE: {'case2'} +- CASE: @{'case2'} - output2 -- DEFAULT:{'default'} +- DEFAULT:@{'default'} - final output #template5 --SWITCH:{case} -- CASE: {'case1'} +-SWITCH:@{case} +- CASE: @{'case1'} - DEFAULT: - final output #template6 --SWITCH:{case} -- CASE: {'case1'} +-SWITCH:@{case} +- CASE: @{'case1'} - output1 -- CASE: {'case2'} +- CASE: @{'case2'} - output2 -- DEFAULT:{'default'} +- DEFAULT:@{'default'} #template7 -SWITCH: cases -- CASE: {'case1'} +- CASE: @{'case1'} - output1 -- CASE: {'case2'} +- CASE: @{'case2'} - output2 - DEFAULT: - text #template8 --SWITCH:{case} -- CASE: {'case1'} +-SWITCH:@{case} +- CASE: @{'case1'} - output1 -- CASE: {'case2'} +- CASE: @{'case2'} - output2 - DEFAULT: default2 -output 3 \ No newline at end of file diff --git a/libraries/botbuilder-lg/tests/testData/exceptionExamples/SwitchCaseWarning.lg b/libraries/botbuilder-lg/tests/testData/exceptionExamples/SwitchCaseWarning.lg index 4d72ca43a2..75e5f662c0 100644 --- a/libraries/botbuilder-lg/tests/testData/exceptionExamples/SwitchCaseWarning.lg +++ b/libraries/botbuilder-lg/tests/testData/exceptionExamples/SwitchCaseWarning.lg @@ -1,11 +1,11 @@ #template1 --SWITCH:{'case'} +-SWITCH:@{'case'} - DEFAULT: - final output #template2 --SWITCH:{'case'} -- CASE: {'case1'} +-SWITCH:@{'case'} +- CASE: @{'case1'} - output1 -- CASE: {'case2'} +- CASE: @{'case2'} - output2 \ No newline at end of file diff --git a/libraries/botbuilder-lg/tests/testData/exceptionExamples/TemplateParamsNotMatchArgsNum.lg b/libraries/botbuilder-lg/tests/testData/exceptionExamples/TemplateParamsNotMatchArgsNum.lg index e2ff85ddb4..b3347e15b6 100644 --- a/libraries/botbuilder-lg/tests/testData/exceptionExamples/TemplateParamsNotMatchArgsNum.lg +++ b/libraries/botbuilder-lg/tests/testData/exceptionExamples/TemplateParamsNotMatchArgsNum.lg @@ -2,7 +2,7 @@ - hi # template -- [templateRef('p1','p2','p3')] +- @{templateRef('p1','p2','p3')} # template2 - ``` @@ -10,7 +10,7 @@ ``` # template3 -- {templateRef()} +- @{templateRef()} # template4 -- [templateRef()] \ No newline at end of file +- @{templateRef()} \ No newline at end of file diff --git a/libraries/botbuilder-lg/tests/testData/mslgTool/CollateFile1.lg b/libraries/botbuilder-lg/tests/testData/mslgTool/CollateFile1.lg index dcfd98c82e..5be0e79583 100644 --- a/libraries/botbuilder-lg/tests/testData/mslgTool/CollateFile1.lg +++ b/libraries/botbuilder-lg/tests/testData/mslgTool/CollateFile1.lg @@ -7,4 +7,4 @@ - Evening # FinalGreeting -- [Greeting] [TimeOfDay] \ No newline at end of file +- @{Greeting()} @{TimeOfDay()} \ No newline at end of file diff --git a/libraries/botbuilder-lg/tests/testData/mslgTool/CollateFile2.lg b/libraries/botbuilder-lg/tests/testData/mslgTool/CollateFile2.lg index 0ae73476cd..ec8c822d9d 100644 --- a/libraries/botbuilder-lg/tests/testData/mslgTool/CollateFile2.lg +++ b/libraries/botbuilder-lg/tests/testData/mslgTool/CollateFile2.lg @@ -4,9 +4,9 @@ - Afternoon # TimeOfDayWithCondition -- IF: {time == 'morning'} +- IF: @{time == 'morning'} - Have a good morning -- ELSEIF: {time == 'evening'} +- ELSEIF: @{time == 'evening'} - Have a good evening - ELSE: - Have a good afternoon \ No newline at end of file diff --git a/libraries/botbuilder-lg/tests/testData/mslgTool/CollateFile3.lg b/libraries/botbuilder-lg/tests/testData/mslgTool/CollateFile3.lg index e0cba503ea..328ccf5575 100644 --- a/libraries/botbuilder-lg/tests/testData/mslgTool/CollateFile3.lg +++ b/libraries/botbuilder-lg/tests/testData/mslgTool/CollateFile3.lg @@ -3,19 +3,19 @@ - Hi # TimeOfDayWithCondition -- IF: {time == 'morning'} - - [Greeting] Morning -- ELSEIF: {time == 'evening'} - - [Greeting] Evening +- IF: @{time == 'morning'} + - @{Greeting()} Morning +- ELSEIF: @{time == 'evening'} + - @{Greeting()} Evening - ELSE: - - [Greeting] Afternoon + - @{Greeting()} Afternoon # greetInAWeek -- SWITCH: {day} - - CASE: {'Saturday'} +- SWITCH: @{day} + - CASE: @{'Saturday'} - Happy Saturday! - Nice Saturday! - - CASE: {'Sunday'} + - CASE: @{'Sunday'} - Happy Sunday! - Nice Sunday! - DEFAULT: diff --git a/libraries/botbuilder-lg/tests/testData/mslgTool/StaticCheckerErrors.lg b/libraries/botbuilder-lg/tests/testData/mslgTool/StaticCheckerErrors.lg index f4864e7f9f..21ca0af127 100644 --- a/libraries/botbuilder-lg/tests/testData/mslgTool/StaticCheckerErrors.lg +++ b/libraries/botbuilder-lg/tests/testData/mslgTool/StaticCheckerErrors.lg @@ -3,15 +3,15 @@ > No match rule # template2 -- IF: {foo == 'bar'} +- IF: @{foo == 'bar'} - ok # template3 --CASE: {'bar'} +-CASE: @{'bar'} - bar # template4 -- SWITCH:{foo} +- SWITCH:@{foo} - default: -bar diff --git a/libraries/botbuilder-lg/tests/testData/mslgTool/StructuredLG.lg b/libraries/botbuilder-lg/tests/testData/mslgTool/StructuredLG.lg index 8d217b1114..c7ead2a624 100644 --- a/libraries/botbuilder-lg/tests/testData/mslgTool/StructuredLG.lg +++ b/libraries/botbuilder-lg/tests/testData/mslgTool/StructuredLG.lg @@ -15,7 +15,7 @@ > With '|' you are making attachments a list. # AskForAge.prompt2 [Activity - Text = {GetAge()} + Text = @{GetAge()} SuggestedActions = 10 | 20 | 30 ] @@ -23,7 +23,7 @@ > You can use '\' as an escape character # AskForAge.prompt3 [Activity - Text = {GetAge()} + Text = @{GetAge()} Suggestions = 10 \| cards | 20 \| cards ] @@ -33,8 +33,8 @@ > and whitespace inb front of the end square bracket is allowed # T1 [Activity - Text = {T2()} - Speak = foo bar {T3().speak} + Text = @{T2()} + Speak = foo bar @{T3().speak} ] @@ -53,7 +53,7 @@ # ST1 [MyStruct Text = foo - {ST2()} + @{ST2()} ] @@ -67,7 +67,7 @@ > each item can also be a structure # AskForColor [Activity - SuggestedActions = {ST2()} | {T3()} + SuggestedActions = @{ST2()} | @{T3()} ] @@ -79,7 +79,7 @@ > template can ref to another steuctured template # StructuredTemplateRef -- [T4] +- @{T4()} # T4 @@ -91,19 +91,19 @@ > if you want to re-use the structured, foreach function is a good way # MultiStructuredRef [MyStruct - list = {foreach(createArray('hello','world'), x, T5(x))} + list = @{foreach(createArray('hello','world'), x, T5(x))} ] # T5(text) [SubStruct - Text = {text} + Text = @{text} ] # ExpanderT1 [MyStruct - Text = {ExpanderT2()} - {ExpanderT3()} + Text = @{ExpanderT2()} + @{ExpanderT3()} ] @@ -113,6 +113,6 @@ # ExpanderT3 [MyStruct - Speak = {GetAge()} + Speak = @{GetAge()} Text = zoo ] \ No newline at end of file diff --git a/libraries/botbuilder-lg/tests/testData/mslgTool/ValidFile.lg b/libraries/botbuilder-lg/tests/testData/mslgTool/ValidFile.lg index 91bbb65005..93319a3211 100644 --- a/libraries/botbuilder-lg/tests/testData/mslgTool/ValidFile.lg +++ b/libraries/botbuilder-lg/tests/testData/mslgTool/ValidFile.lg @@ -1,20 +1,20 @@ # ShowAlarm(alarm) -- {alarm.time} at {alarm.date} -- {alarm.time} of {alarm.date} +- @{alarm.time} at @{alarm.date} +- @{alarm.time} of @{alarm.date} # ShowAlarmsWithForeach -- IF: {count(alarms) == 1} - - You have one alarm [ShowAlarm(alarms[0])] -- ELSEIF: {count(alarms) == 2} - - You have {count(alarms)} alarms, {join(foreach(alarms, alarm, ShowAlarm(alarm)), ', ', ' and ')} +- IF: @{count(alarms) == 1} + - You have one alarm @{ShowAlarm(alarms[0])} +- ELSEIF: @{count(alarms) == 2} + - You have @{count(alarms)} alarms, @{join(foreach(alarms, alarm, ShowAlarm(alarm)), ', ', ' and ')} - ELSE: - You don't have any alarms # ShowAlarmsWithLgTemplate -- IF: {count(alarms) == 1} - - You have one alarm [ShowAlarm(alarms[0])] -- ELSEIF: {count(alarms) == 2} - - You have {count(alarms)} alarms, they are {ShowAlarm(alarms[1])} +- IF: @{count(alarms) == 1} + - You have one alarm @{ShowAlarm(alarms[0])} +- ELSEIF: @{count(alarms) == 2} + - You have @{count(alarms)} alarms, they are @{ShowAlarm(alarms[1])} - ELSE: - You don't have any alarms @@ -25,10 +25,10 @@ They are @{ShowAlarm(alarms[1])} ``` # bookTransportTicket --SWITCH:{pass} -- CASE: {'Flight'} +-SWITCH:@{pass} +- CASE: @{'Flight'} - Flight ticket booked -- CASE: {'Train'} +- CASE: @{'Train'} - Train ticket booked - DEFAULT: - Shuttle ticket booked @@ -38,10 +38,10 @@ They are @{ShowAlarm(alarms[1])} - Hello # T2 -- {prebuilt.length(T1())} +- @{prebuilt.length(T1())} # T3 -- {count(T1())} +- @{count(T1())} # T4 -- {substring(T1(), 1, 2)} \ No newline at end of file +- @{substring(T1(), 1, 2)} \ No newline at end of file diff --git a/libraries/botframework-expressions/src/builtInFunction.ts b/libraries/botframework-expressions/src/builtInFunction.ts index ce7b0892b3..5eb2a30b77 100644 --- a/libraries/botframework-expressions/src/builtInFunction.ts +++ b/libraries/botframework-expressions/src/builtInFunction.ts @@ -1,4 +1,4 @@ - +/* eslint-disable @typescript-eslint/no-unused-vars */ /** * @module botframework-expressions */ @@ -18,6 +18,7 @@ import { EvaluateExpressionDelegate, ExpressionEvaluator, ValidateExpressionDele import { ExpressionType } from './expressionType'; import { Extensions } from './extensions'; import { TimeZoneConverter } from './timeZoneConverter'; +import { MemoryInterface, SimpleObjectMemory, ComposedMemory } from './memory'; /** * Verify the result of an expression is of the appropriate type and return a string if not. @@ -229,7 +230,7 @@ export class BuiltInFunctions { */ public static verifyNumericList(value: any, expression: Expression, _: number): string { let error: string; - if (!(value instanceof Array)) { + if (!Array.isArray(value)) { error = `${ expression } is not a list.`; } else { for (const elt of value) { @@ -251,7 +252,7 @@ export class BuiltInFunctions { */ public static verifyContainer(value: any, expression: Expression, _: number): string { let error: string; - if (!(typeof value === 'string') && !(value instanceof Array) && !(value instanceof Map)) { + if (!(typeof value === 'string') && !Array.isArray(value) && !(value instanceof Map)) { error = `${ expression } must be a string or list or map.`; } @@ -281,7 +282,7 @@ export class BuiltInFunctions { */ public static verifyList(value: any, expression: Expression): string { let error: string; - if (!(value instanceof Array)) { + if (!Array.isArray(value)) { error = `${ expression } is not a list or array.`; } @@ -389,20 +390,20 @@ export class BuiltInFunctions { * @param verify Optional function to verify each child's result. * @returns List of child values or error message. */ - public static evaluateChildren(expression: Expression, state: any, verify?: VerifyExpression): { args: any []; error: string } { + public static evaluateChildren(expression: Expression, state: MemoryInterface, verify?: VerifyExpression): { args: any []; error: string } { const args: any[] = []; let value: any; let error: string; let pos = 0; for (const child of expression.children) { ({ value, error } = child.tryEvaluate(state)); - if (error !== undefined) { + if (error) { break; } if (verify !== undefined) { error = verify(value, child, pos); } - if (error !== undefined) { + if (error) { break; } args.push(value); @@ -419,12 +420,12 @@ export class BuiltInFunctions { * @returns Delegate for evaluating an expression. */ public static apply(func: (arg0: any []) => any, verify?: VerifyExpression): EvaluateExpressionDelegate { - return (expression: Expression, state: any): { value: any; error: string } => { + return (expression: Expression, state: MemoryInterface): { value: any; error: string } => { let value: any; let error: string; let args: any []; ({ args, error } = BuiltInFunctions.evaluateChildren(expression, state, verify)); - if (error === undefined) { + if (!error) { try { value = func(args); } catch (e) { @@ -443,12 +444,12 @@ export class BuiltInFunctions { * @returns Delegate for evaluating an expression. */ public static applyWithError(func: (arg0: any []) => any, verify?: VerifyExpression): EvaluateExpressionDelegate { - return (expression: Expression, state: any): { value: any; error: string } => { + return (expression: Expression, state: MemoryInterface): { value: any; error: string } => { let value: any; let error: string; let args: any []; ({ args, error } = BuiltInFunctions.evaluateChildren(expression, state, verify)); - if (error === undefined) { + if (!error) { try { ({ value, error } = func(args)); } catch (e) { @@ -515,21 +516,21 @@ export class BuiltInFunctions { public static comparison(type: string, func: (arg0: any []) => boolean, validator: ValidateExpressionDelegate, verify?: VerifyExpression): ExpressionEvaluator { return new ExpressionEvaluator( type, - (expression: Expression, state: any): { value: any; error: string } => { + (expression: Expression, state: MemoryInterface): { value: any; error: string } => { let result = false; let error: string; let args: any []; ({ args, error } = BuiltInFunctions.evaluateChildren(expression, state, verify)); - if (error === undefined) { - const isNumber: boolean = args !== undefined && args.length > 0 && typeof args[0] === 'number'; + if (!error) { + const isNumber: boolean = args && args.length > 0 && typeof args[0] === 'number'; for (const arg of args) { - if (arg !== undefined && (typeof arg === 'number') !== isNumber) { + if (arg && (typeof arg === 'number') !== isNumber) { error = `Arguments must either all be numbers or strings in ${ expression }`; break; } } - if (error === undefined) { + if (!error) { try { result = func(args); } catch (e) { @@ -566,16 +567,16 @@ export class BuiltInFunctions { public static timeTransform(type: string, func: (timestamp: moment.Moment, numOfTransformation: any) => any): ExpressionEvaluator { return new ExpressionEvaluator( type, - (expression: Expression, state: any): { value: any; error: string } => { + (expression: Expression, state: MemoryInterface): { value: any; error: string } => { let result: any; let error: string; let value: any; let args: any []; ({ args, error } = BuiltInFunctions.evaluateChildren(expression, state)); - if (error === undefined) { + if (!error) { if (typeof args[0] === 'string' && typeof args[1] === 'number') { ({ value, error } = BuiltInFunctions.parseTimestamp(args[0])); - if (error === undefined) { + if (!error) { if (args.length === 3 && typeof args[2] === 'string') { result = func(value, args[1]).format(BuiltInFunctions.timestampFormatter(args[2])); } else { @@ -597,7 +598,7 @@ export class BuiltInFunctions { public static parseTimestamp(timeStamp: string, transform?: (arg0: moment.Moment) => any): { value: any; error: string } { let value: any; const error: string = this.verifyISOTimestamp(timeStamp); - if (error === undefined) { + if (!error) { const parsed: moment.Moment = moment(timeStamp).utc(); value = transform !== undefined ? transform(parsed) : parsed; } @@ -611,7 +612,7 @@ export class BuiltInFunctions { */ public static lookup(type: string): ExpressionEvaluator { const evaluator: ExpressionEvaluator = BuiltInFunctions._functions.get(type); - if (evaluator === undefined) { + if (!evaluator) { throw new Error(`${ type } does not have an evaluator, it's not a built-in function or a customized function`); } @@ -705,25 +706,73 @@ export class BuiltInFunctions { } } - private static accessor(expression: Expression, state: any): { value: any; error: string } { - let value: any; - let error: string; - let instance: any; - const children: Expression[] = expression.children; - if (children.length === 2) { - ({ value: instance, error } = children[1].tryEvaluate(state)); - } else { - instance = state; + /** + * Try to accumulate the path from an Accessor or Element, from right to left + * return the accumulated path and the expression left unable to accumulate + * @param expression + * @param state + */ + private static tryAccumulatePath(expression: Expression, state: MemoryInterface): {path: string; left: Expression; error: string} { + let path = ''; + let left = expression; + while (left !== undefined) { + if (left.type === ExpressionType.Accessor) { + path = (left.children[0] as Constant).value + '.' + path; + left = left.children.length === 2 ? left.children[1] : undefined; + } else if (left.type === ExpressionType.Element) { + let value: any; + let error: string; + ({value, error} = left.children[1].tryEvaluate(state)); + + if (error !== undefined) { + return {path: undefined, left: undefined, error}; + } + + if (isNaN(parseInt(value)) && typeof value !== 'string') { + return {path: undefined, left: undefined, error:`${ left.children[1].toString() } dones't return a int or string`}; + } + + path = `[${ value }].${ path }`; + left = left.children[0]; + } else { + break; + } } - if (error === undefined && children[0] instanceof Constant && (children[0] as Constant).returnType === ReturnType.String) { - ({ value, error } = Extensions.accessProperty(instance, (children[0] as Constant).value.toString())); + // make sure we generated a valid path + path = path.replace(/(\.*$)/g, '').replace(/(\.\[)/g, '['); + if (path === '') { + path = undefined; } - return { value, error }; + return {path, left, error:undefined}; } - private static getProperty(expression: Expression, state: any): { value: any; error: string } { + private static accessor(expression: Expression, state: MemoryInterface): { value: any; error: string } { + let path: string; + let left: Expression; + let error: string; + ({path, left, error} = BuiltInFunctions.tryAccumulatePath(expression, state)); + if (error) { + return {value: undefined, error}; + } + + if (left == undefined) { + // fully converted to path, so we just delegate to memory scope + return state.getValue(path); + } else { + let newScope: any; + let err: string; + ({value: newScope, error: err} = left.tryEvaluate(state)); + if (err) { + return {value: undefined, error: err}; + } + + return new SimpleObjectMemory(newScope).getValue(path); + } + } + + private static getProperty(expression: Expression, state: MemoryInterface): { value: any; error: string } { let value: any; let error: string; let instance: any; @@ -731,11 +780,11 @@ export class BuiltInFunctions { const children: Expression[] = expression.children; ({ value: instance, error } = children[0].tryEvaluate(state)); - if (error === undefined) { + if (!error) { ({ value: property, error } = children[1].tryEvaluate(state)); - if (error === undefined) { - ({ value, error } = Extensions.accessProperty(instance, property.toString())); + if (!error) { + ({ value, error } = new SimpleObjectMemory(instance).getValue(property.toString())); } } @@ -744,7 +793,7 @@ export class BuiltInFunctions { private static coalesce(objetcList: object[]): any { for (const obj of objetcList) { - if (obj !== undefined) { + if (obj) { return obj; } } @@ -769,7 +818,7 @@ export class BuiltInFunctions { error = 'the first parameter should be either an object or a string'; } - if (error === undefined) { + if (!error) { try { evaled = jsPath.apply(path, json); } catch (e) { @@ -782,17 +831,17 @@ export class BuiltInFunctions { return {value: result, error}; } - private static extractElement(expression: Expression, state: any): { value: any; error: string } { + private static extractElement(expression: Expression, state: MemoryInterface): { value: any; error: string } { let value: any; let error: string; const instance: Expression = expression.children[0]; const index: Expression = expression.children[1]; let inst: any; ({ value: inst, error } = instance.tryEvaluate(state)); - if (error === undefined) { + if (!error) { let idxValue: any; ({ value: idxValue, error } = index.tryEvaluate(state)); - if (error === undefined) { + if (!error) { if (Number.isInteger(idxValue)) { ({ value, error } = Extensions.accessIndex(inst, Number(idxValue))); } else if (typeof idxValue === 'string') { @@ -810,7 +859,7 @@ export class BuiltInFunctions { let modifiable = false; if (expected !== undefined) { // Modifiable list - modifiable = value instanceof Array; + modifiable = Array.isArray(value); } else { // Modifiable object modifiable = value instanceof Map; @@ -824,95 +873,40 @@ export class BuiltInFunctions { return modifiable; } - private static trySetPathToValue(path: Expression, value: any, state: any, expected?: number): { instance: any; error: string } { - let result: any; + private static setPathToValue(expression: Expression, state: MemoryInterface): { value: any; error: string } { + let path: string; + let left: Expression; let error: string; - let instance: any; - let index: any; - const children: Expression[] = path.children; - if (path.type === ExpressionType.Accessor || path.type === ExpressionType.Element) { - ({ value: index, error } = children[path.type === ExpressionType.Accessor ? 0 : 1].tryEvaluate(state)); - if (error === undefined) { - const iindex: number = index; - if (children.length === 2) { - ({ instance, error } = this.trySetPathToValue(children[path.type === ExpressionType.Accessor ? 1 : 0], undefined, state, iindex)); - } else { - instance = state; - } - - if (error === undefined) { - if (typeof index === 'string') { - const propName: string = index; - if (value !== undefined) { - result = Extensions.setProperty(instance, propName, value); - } else { - ({ value: result, error } = Extensions.accessProperty(instance, propName)); - if (error !== undefined || result === undefined || !this.canBeModified(result, propName, expected)) { - // Create new value for parents to use - if (expected !== undefined) { - result = Extensions.setProperty(instance, propName, [expected + 1]); - } else { - result = Extensions.setProperty(instance, propName, new Map()); - } - } - } - } else if (iindex !== undefined) { - // Child instance should be a list already because we passed down the iindex. - if (instance instanceof Array) { - const list: any[] = instance; - if (list.length <= iindex) { - while (list.length < iindex) { - // Extend list. - list.push(undefined); - } - } - - // Assign value or expected list size or object - result = value !== undefined ? value : expected !== undefined ? [expected + 1] : new Map(); - list[iindex] = result; - } else { - error = `${ children[0] } is not a list.`; - } - } else { - error = `${ children[0] } is not a valid path.`; - } - } - } - } else { - error = `${ path } is not a path that can be set to a value.`; + ({path, left, error} = BuiltInFunctions.tryAccumulatePath(expression.children[0], state)); + if (error !== undefined) { + return {value: undefined, error}; } - return { instance: result, error }; - } - - private static setPathToValue(expression: Expression, state: any): { value: any; error: string } { + if (left) { + // the expression can't be fully merged as a path + return {value: undefined, error:`${ expression.children[0].toString() } is not a valid path to set value`}; + } let value: any; - let error: string; - const path: Expression = expression.children[0]; - const valueExpr: Expression = expression.children[1]; - ({ value, error } = valueExpr.tryEvaluate(state)); - if (error === undefined) { - let instance: any; - ({ instance, error } = BuiltInFunctions.trySetPathToValue(path, value, state)); - if (error !== undefined) { - value = undefined; - } + let err: string; + ({value, error: err} = expression.children[1].tryEvaluate(state)); + if (err) { + return {value: undefined, error: err}; } - return {value, error}; + return state.setValue(path, value); } - private static foreach(expression: Expression, state: any): { value: any; error: string } { + private static foreach(expression: Expression, state: MemoryInterface): { value: any; error: string } { let result: any[]; let error: string; let collection: any; ({ value: collection, error } = expression.children[0].tryEvaluate(state)); - if (error === undefined) { + if (!error) { // 2nd parameter has been rewrite to $local.item const iteratorName: string = (expression.children[1].children[0] as Constant).value as string; - if (!(collection instanceof Array)) { + if (!Array.isArray(collection)) { error = `${ expression.children[0] } is not a collection to run foreach`; } else { result = []; @@ -921,12 +915,12 @@ export class BuiltInFunctions { [iteratorName, item] ]); - const newScope: Map = new Map([ + const newMemory: Map = new Map([ ['$global', state], - ['$local', local] + ['$local', new SimpleObjectMemory(local)] ]); - const { value: r, error: e } = expression.children[2].tryEvaluate(newScope); + const { value: r, error: e } = expression.children[2].tryEvaluate(new ComposedMemory(newMemory)); if (e !== undefined) { return { value: undefined, error: e }; } @@ -938,16 +932,16 @@ export class BuiltInFunctions { return { value: result, error }; } - private static where(expression: Expression, state: any): { value: any; error: string } { + private static where(expression: Expression, state: MemoryInterface): { value: any; error: string } { let result: any[]; let error: string; let collection: any; ({ value: collection, error } = expression.children[0].tryEvaluate(state)); - if (error === undefined) { + if (!error) { const iteratorName: string = (expression.children[1].children[0] as Constant).value as string; - if (!(collection instanceof Array)) { + if (!Array.isArray(collection)) { error = `${ expression.children[0] } is not a collection to run where`; } else { result = []; @@ -956,12 +950,12 @@ export class BuiltInFunctions { [iteratorName, item] ]); - const newScope: Map = new Map([ + const newMemory: Map = new Map([ ['$global', state], - ['$local', local] + ['$local', new SimpleObjectMemory(local)] ]); - const { value: r, error: e } = expression.children[2].tryEvaluate(newScope); + const { value: r, error: e } = expression.children[2].tryEvaluate(new ComposedMemory(newMemory)); if (e !== undefined) { return { value: undefined, error: e }; } @@ -1042,7 +1036,7 @@ export class BuiltInFunctions { result = true; } else if (typeof instance === 'string') { result = instance === ''; - } else if (instance instanceof Array) { + } else if (Array.isArray(instance)) { result = instance.length === 0; } else if (instance instanceof Map) { result = instance.size === 0; @@ -1063,19 +1057,19 @@ export class BuiltInFunctions { if (typeof instance === 'boolean') { result = instance; - } else if (instance === undefined) { + } else if (instance === undefined || instance === null) { result = false; } return result; } - private static _and(expression: Expression, state: any): { value: any; error: string } { + private static _and(expression: Expression, state: MemoryInterface): { value: any; error: string } { let result = false; let error: string; for (const child of expression.children) { ({ value: result, error } = child.tryEvaluate(state)); - if (error === undefined) { + if (!error) { if (this.isLogicTrue(result)) { result = true; } else { @@ -1092,12 +1086,12 @@ export class BuiltInFunctions { return { value: result, error }; } - private static _or(expression: Expression, state: any): { value: any; error: string } { + private static _or(expression: Expression, state: MemoryInterface): { value: any; error: string } { let result = false; let error: string; for (const child of expression.children) { ({ value: result, error } = child.tryEvaluate(state)); - if (error === undefined) { + if (!error) { if (this.isLogicTrue(result)) { result = true; break; @@ -1110,11 +1104,11 @@ export class BuiltInFunctions { return { value: result, error }; } - private static _not(expression: Expression, state: any): { value: any; error: string } { + private static _not(expression: Expression, state: MemoryInterface): { value: any; error: string } { let result = false; let error: string; ({ value: result, error } = expression.children[0].tryEvaluate(state)); - if (error === undefined) { + if (!error) { result = !this.isLogicTrue(result); } else { error = undefined; @@ -1124,11 +1118,11 @@ export class BuiltInFunctions { return { value: result, error }; } - private static _if(expression: Expression, state: any): { value: any; error: string } { + private static _if(expression: Expression, state: MemoryInterface): { value: any; error: string } { let result: any; let error: string; ({ value: result, error } = expression.children[0].tryEvaluate(state)); - if (error === undefined && this.isLogicTrue(result)) { + if (!error && this.isLogicTrue(result)) { ({ value: result, error } = expression.children[1].tryEvaluate(state)); } else { ({ value: result, error } = expression.children[2].tryEvaluate(state)); @@ -1137,24 +1131,24 @@ export class BuiltInFunctions { return { value: result, error }; } - private static substring(expression: Expression, state: any): { value: any; error: string } { + private static substring(expression: Expression, state: MemoryInterface): { value: any; error: string } { let result: any; let error: any; let str: string; ({ value: str, error } = expression.children[0].tryEvaluate(state)); - if (error === undefined) { + if (!error) { if (typeof str === 'string') { let start: number; const startExpr: Expression = expression.children[1]; ({ value: start, error } = startExpr.tryEvaluate(state)); - if (error === undefined && !Number.isInteger(start)) { + if (!error && !Number.isInteger(start)) { error = `${ startExpr } is not an integer.`; } else if (start < 0 || start >= str.length) { error = `${ startExpr }=${ start } which is out of range for ${ str }`; } - if (error === undefined) { + if (!error) { let length: number; if (expression.children.length === 2) { // Without length, compute to end @@ -1162,13 +1156,13 @@ export class BuiltInFunctions { } else { const lengthExpr: Expression = expression.children[2]; ({ value: length, error } = lengthExpr.tryEvaluate(state)); - if (error === undefined && !Number.isInteger(length)) { + if (!error && !Number.isInteger(length)) { error = `${ lengthExpr } is not an integer`; } else if (length < 0 || Number(start) + Number(length) > str.length) { error = `${ lengthExpr }=${ length } which is out of range for ${ str }`; } } - if (error === undefined) { + if (!error) { result = str.substr(start, length); } } @@ -1188,18 +1182,18 @@ export class BuiltInFunctions { let arr: any; ({ value: arr, error } = expression.children[0].tryEvaluate(state)); - if (error === undefined) { - if (arr instanceof Array) { + if (!error) { + if (Array.isArray(arr)) { let start: number; const startExpr: Expression = expression.children[1]; ({ value: start, error } = startExpr.tryEvaluate(state)); - if (error === undefined && !Number.isInteger(start)) { + if (!error && !Number.isInteger(start)) { error = `${ startExpr } is not an integer.`; } else if (start < 0 || start >= arr.length) { error = `${ startExpr }=${ start } which is out of range for ${ arr }`; } - if (error === undefined) { + if (!error) { result = arr.slice(start); } } else { @@ -1216,18 +1210,18 @@ export class BuiltInFunctions { let arr: any; ({ value: arr, error } = expression.children[0].tryEvaluate(state)); - if (error === undefined) { - if (arr instanceof Array || typeof arr === 'string') { + if (!error) { + if (Array.isArray(arr) || typeof arr === 'string') { let start: number; const startExpr: Expression = expression.children[1]; ({ value: start, error } = startExpr.tryEvaluate(state)); - if (error === undefined && !Number.isInteger(start)) { + if (!error && !Number.isInteger(start)) { error = `${ startExpr } is not an integer.`; } else if (start < 0 || start >= arr.length) { error = `${ startExpr }=${ start } which is out of range for ${ arr }`; } - if (error === undefined) { + if (!error) { result = arr.slice(0, start); } } else { @@ -1244,31 +1238,31 @@ export class BuiltInFunctions { let arr: any; ({ value: arr, error } = expression.children[0].tryEvaluate(state)); - if (error === undefined) { - if (arr instanceof Array) { + if (!error) { + if (Array.isArray(arr)) { let start: number; const startExpr: Expression = expression.children[1]; ({ value: start, error } = startExpr.tryEvaluate(state)); - if (error === undefined && !Number.isInteger(start)) { + if (!error && !Number.isInteger(start)) { error = `${ startExpr } is not an integer.`; } else if (start < 0 || start >= arr.length) { error = `${ startExpr }=${ start } which is out of range for ${ arr }`; } - if (error === undefined) { + if (!error) { let end: number; if (expression.children.length === 2) { end = arr.length; } else { const endExpr: Expression = expression.children[2]; ({ value: end, error } = endExpr.tryEvaluate(state)); - if (error === undefined && !Number.isInteger(end)) { + if (!error && !Number.isInteger(end)) { error = `${ endExpr } is not an integer`; } else if (end < 0 || end > arr.length) { error = `${ endExpr }=${ end } which is out of range for ${ arr }`; } } - if (error === undefined) { + if (!error) { result = arr.slice(start, end); } } @@ -1286,8 +1280,8 @@ export class BuiltInFunctions { let error: string; let oriArr: any; ({ value: oriArr, error } = expression.children[0].tryEvaluate(state)); - if (error === undefined) { - if (oriArr instanceof Array) { + if (!error) { + if (Array.isArray(oriArr)) { const arr: any = oriArr.slice(0); if (expression.children.length === 1) { if (isDescending) { @@ -1299,7 +1293,7 @@ export class BuiltInFunctions { let propertyName: string; ({value: propertyName, error} = expression.children[1].tryEvaluate(state)); - if (error === undefined) { + if (!error) { propertyName = propertyName === undefined ? '' : propertyName; } if (isDescending) { @@ -1335,7 +1329,7 @@ export class BuiltInFunctions { let error: string; let parsed: any; ({value: parsed, error} = BuiltInFunctions.parseTimestamp(timeStamp)); - if (error === undefined) { + if (!error) { let addedTime: moment.Moment = parsed; let timeUnitMark: string; switch (timeUnit) { @@ -1380,7 +1374,7 @@ export class BuiltInFunctions { } } - if (error === undefined) { + if (!error) { addedTime = parsed.add(interval, timeUnitMark); ({value: result, error} = this.returnFormattedTimeStampStr(addedTime, format)); } @@ -1410,7 +1404,7 @@ export class BuiltInFunctions { error = `${ destinationTimeZone } is not a valid timezone`; } - if (error === undefined) { + if (!error) { try { result = timezone.tz(timeStamp, timeZone).format(format); } catch (e) { @@ -1441,9 +1435,9 @@ export class BuiltInFunctions { error = `${ sourceTimezone } is not a valid timezone`; } - if (error === undefined) { + if (!error) { error = this.verifyTimeStamp(timeStamp); - if (error === undefined) { + if (!error) { try { const sourceTime: moment.Moment = timezone.tz(timeStamp, timeZone); formattedSourceTime = sourceTime.format(); @@ -1451,7 +1445,7 @@ export class BuiltInFunctions { error = `${ timeStamp } with ${ timeZone } is not a valid timestamp with specified timeZone:`; } - if (error === undefined) { + if (!error) { try { result = timezone.tz(formattedSourceTime, 'Etc/UTC').format(format); } catch (e) { @@ -1469,7 +1463,7 @@ export class BuiltInFunctions { let result: number; let error: string; ({value: parsed, error} = BuiltInFunctions.parseTimestamp(timeStamp)); - if (error === undefined) { + if (!error) { const unixMilliSec: number = parseInt(parsed.format('x'), 10); result = this.UnixMilliSecondToTicksConstant + unixMilliSec * 10000; } @@ -1482,7 +1476,7 @@ export class BuiltInFunctions { let error: string; let parsed: moment.Moment; ({value: parsed, error} = BuiltInFunctions.parseTimestamp(timeStamp)); - if (error === undefined) { + if (!error) { const startOfDay: moment.Moment = parsed.hours(0).minutes(0).second(0).millisecond(0); ({value: result, error} = BuiltInFunctions.returnFormattedTimeStampStr(startOfDay, format)); } @@ -1495,7 +1489,7 @@ export class BuiltInFunctions { let error: string; let parsed: moment.Moment; ({value: parsed, error} = BuiltInFunctions.parseTimestamp(timeStamp)); - if (error === undefined) { + if (!error) { const startofHour: moment.Moment = parsed.minutes(0).second(0).millisecond(0); ({value: result, error} = BuiltInFunctions.returnFormattedTimeStampStr(startofHour, format)); } @@ -1508,7 +1502,7 @@ export class BuiltInFunctions { let error: string; let parsed: moment.Moment; ({value: parsed, error} = BuiltInFunctions.parseTimestamp(timeStamp)); - if (error === undefined) { + if (!error) { const startofMonth: moment.Moment = parsed.date(1).hours(0).minutes(0).second(0).millisecond(0); ({value: result, error} = BuiltInFunctions.returnFormattedTimeStampStr(startofMonth, format)); } @@ -1534,7 +1528,7 @@ export class BuiltInFunctions { let error: string; let parsed: URL; ({value: parsed, error} = this.parseUri(uri)); - if (error === undefined) { + if (!error) { try { result = parsed.hostname; } catch (e) { @@ -1550,7 +1544,7 @@ export class BuiltInFunctions { let error: string; let parsed: URL; ({value: parsed, error} = this.parseUri(uri)); - if (error === undefined) { + if (!error) { try { const uriObj: URL = new URL(uri); result = uriObj.pathname; @@ -1567,7 +1561,7 @@ export class BuiltInFunctions { let error: string; let parsed: URL; ({value: parsed, error} = this.parseUri(uri)); - if (error === undefined) { + if (!error) { try { result = parsed.pathname + parsed.search; } catch (e) { @@ -1583,7 +1577,7 @@ export class BuiltInFunctions { let error: string; let parsed: URL; ({value: parsed, error} = this.parseUri(uri)); - if (error === undefined) { + if (!error) { try { result = parsed.port; } catch (e) { @@ -1599,7 +1593,7 @@ export class BuiltInFunctions { let error: string; let parsed: URL; ({value: parsed, error} = this.parseUri(uri)); - if (error === undefined) { + if (!error) { try { result = parsed.search; } catch (e) { @@ -1615,7 +1609,7 @@ export class BuiltInFunctions { let error: string; let parsed: URL; ({value: parsed, error} = this.parseUri(uri)); - if (error === undefined) { + if (!error) { try { result = parsed.protocol.replace(':', ''); } catch (e) { @@ -1626,33 +1620,6 @@ export class BuiltInFunctions { return {value: result, error}; } - private static callstack(expression: Expression, state: any): { value: any; error: string } { - let result: any = state; - let error: string; - - // get collection - ({ value: result, error} = Extensions.accessProperty(state, 'callstack')); - if (result !== undefined) { - const items: any[] = result as any[]; - let property: any; - ({value: property, error} = expression.children[0].tryEvaluate(state)); - if (property !== undefined && error === undefined) { - for (const item of items) { - // get property off of item - ({ value: result, error } = Extensions.accessProperty(item, property.toString())); - - // if not null - if (error === undefined && result !== undefined) { - // return it - return { value: result, error }; - } - } - } - } - - return { value: undefined, error }; - } - // tslint:disable-next-line: max-func-body-length private static buildFunctionLookup(): Map { // tslint:disable-next-line: no-unnecessary-local-variable @@ -1667,7 +1634,7 @@ export class BuiltInFunctions { (args: any []): number => Math.floor(Number(args[0]) / Number(args[1])), (val: any, expression: Expression, pos: number): string => { let error: string = this.verifyNumber(val, expression, pos); - if (error === undefined && (pos > 0 && Number(val) === 0)) { + if (!error && (pos > 0 && Number(val) === 0)) { error = `Cannot divide by 0 from ${ expression }`; } @@ -1712,7 +1679,7 @@ export class BuiltInFunctions { BuiltInFunctions.apply( (args: any []): number => { let count: number; - if (typeof args[0] === 'string' || args[0] instanceof Array) { + if (typeof args[0] === 'string' || Array.isArray(args[0])) { count = args[0].length; } @@ -1833,15 +1800,15 @@ export class BuiltInFunctions { let args: any []; ({ args, error } = BuiltInFunctions.evaluateChildren(expression, state)); - if (error === undefined) { - if (typeof args[0] === 'string' && typeof args[1] === 'string' || args[0] instanceof Array) { + if (!error) { + if (typeof args[0] === 'string' && typeof args[1] === 'string' || Array.isArray(args[0])) { found = args[0].includes(args[1]); } else if (args[0] instanceof Map) { found = (args[0] as Map).get(args[1]) !== undefined; } else if (typeof args[1] === 'string') { let value: any; ({ value, error } = Extensions.accessProperty(args[0], args[1])); - found = error === undefined && value !== undefined; + found = !error && value !== undefined; } } @@ -1856,17 +1823,17 @@ export class BuiltInFunctions { BuiltInFunctions.verifyNumberOrString), new ExpressionEvaluator( ExpressionType.And, - (expression: Expression, state: any): { value: any; error: string } => BuiltInFunctions._and(expression, state), + (expression: Expression, state: MemoryInterface): { value: any; error: string } => BuiltInFunctions._and(expression, state), ReturnType.Boolean, BuiltInFunctions.validateAtLeastOne), new ExpressionEvaluator( ExpressionType.Or, - (expression: Expression, state: any): { value: any; error: string } => BuiltInFunctions._or(expression, state), + (expression: Expression, state: MemoryInterface): { value: any; error: string } => BuiltInFunctions._or(expression, state), ReturnType.Boolean, BuiltInFunctions.validateAtLeastOne), new ExpressionEvaluator( ExpressionType.Not, - (expression: Expression, state: any): { value: any; error: string } => BuiltInFunctions._not(expression, state), + (expression: Expression, state: MemoryInterface): { value: any; error: string } => BuiltInFunctions._not(expression, state), ReturnType.Boolean, BuiltInFunctions.validateUnary), new ExpressionEvaluator( @@ -1884,13 +1851,13 @@ export class BuiltInFunctions { BuiltInFunctions.applyWithError(( args: any []): any => { - let error = undefined;1; + let error = undefined; let result = undefined; if (BuiltInFunctions.parseStringOrNull(args[1]).length === 0) { error = `${ args[1] } should be a string with length at least 1`; } - if (error === undefined) { + if (!error) { result = BuiltInFunctions.parseStringOrNull(args[0]).split(BuiltInFunctions.parseStringOrNull(args[1])).join(BuiltInFunctions.parseStringOrNull(args[2])); } @@ -1909,7 +1876,7 @@ export class BuiltInFunctions { error = `${ args[1] } should be a string with length at least 1`; } - if (error === undefined) { + if (!error) { result = BuiltInFunctions.parseStringOrNull(args[0]).replace(new RegExp(BuiltInFunctions.parseStringOrNull(args[1]), 'gi'), BuiltInFunctions.parseStringOrNull(args[2])); } @@ -1979,8 +1946,8 @@ export class BuiltInFunctions { let error: string; let args: any []; ({ args, error } = BuiltInFunctions.evaluateChildren(expression, state)); - if (error === undefined) { - if (!(args[0] instanceof Array)) { + if (!error) { + if (!Array.isArray(args[0])) { error = `${ expression.children[0] } evaluates to ${ args[0] } which is not a list.`; } else { if (args.length === 2) { @@ -2066,7 +2033,7 @@ export class BuiltInFunctions { } let value: any; - if (error === undefined) { + if (!error) { const dateString: string = new Date(arg).toISOString(); value = args.length === 2 ? moment(dateString).format(BuiltInFunctions.timestampFormatter(args[1])) : dateString; } @@ -2082,7 +2049,7 @@ export class BuiltInFunctions { let error: any; let args: any []; ({ args, error } = BuiltInFunctions.evaluateChildren(expr, state)); - if (error === undefined) { + if (!error) { if (typeof args[0] === 'string' && Number.isInteger(args[1]) && typeof args[2] === 'string') { const format: string = (args.length === 4 ? BuiltInFunctions.timestampFormatter(args[3]) : BuiltInFunctions.DefaultDateTimeFormat); const { duration, tsStr } = BuiltInFunctions.timeUnitTransformer(args[1], args[2]); @@ -2109,7 +2076,7 @@ export class BuiltInFunctions { let value: any; let error: string; ({ value, error } = BuiltInFunctions.parseTimestamp(args[0])); - if (error === undefined) { + if (!error) { const timestamp1: Date = value.startOf('day').toDate(); ({ value, error } = BuiltInFunctions.parseTimestamp(args[1])); const timestamp2: string = value.startOf('day').local().format('YYYY-MM-DD'); @@ -2127,7 +2094,7 @@ export class BuiltInFunctions { (args: any []): any => { let value: any; const error: string = BuiltInFunctions.verifyISOTimestamp(args[0]); - if (error === undefined) { + if (!error) { const thisTime: number = moment.parseZone(args[0]).hour() * 100 + moment.parseZone(args[0]).minute(); if (thisTime === 0) { value = 'midnight'; @@ -2156,7 +2123,7 @@ export class BuiltInFunctions { let error: any; let args: any []; ({ args, error } = BuiltInFunctions.evaluateChildren(expr, state)); - if (error === undefined) { + if (!error) { if (Number.isInteger(args[0]) && typeof args[1] === 'string') { const format: string = (args.length === 3 ? BuiltInFunctions.timestampFormatter(args[2]) : BuiltInFunctions.DefaultDateTimeFormat); const { duration, tsStr } = BuiltInFunctions.timeUnitTransformer(args[0], args[1]); @@ -2183,7 +2150,7 @@ export class BuiltInFunctions { let error: any; let args: any []; ({ args, error } = BuiltInFunctions.evaluateChildren(expr, state)); - if (error === undefined) { + if (!error) { if (Number.isInteger(args[0]) && typeof args[1] === 'string') { const format: string = (args.length === 3 ? BuiltInFunctions.timestampFormatter(args[2]) : BuiltInFunctions.DefaultDateTimeFormat); const { duration, tsStr } = BuiltInFunctions.timeUnitTransformer(args[0], args[1]); @@ -2210,7 +2177,7 @@ export class BuiltInFunctions { let error: string; let args: any []; ({args, error} = BuiltInFunctions.evaluateChildren(expr, state)); - if (error === undefined) { + if (!error) { const format: string = (args.length === 3) ? BuiltInFunctions.timestampFormatter(args[2]) : this.DefaultDateTimeFormat; if (typeof(args[0]) === 'string' && typeof(args[1]) === 'string') { ({value, error} = BuiltInFunctions.convertFromUTC(args[0], args[1], format)); @@ -2231,7 +2198,7 @@ export class BuiltInFunctions { let error: string; let args: any []; ({args, error} = BuiltInFunctions.evaluateChildren(expr, state)); - if (error === undefined) { + if (!error) { const format: string = (args.length === 3) ? BuiltInFunctions.timestampFormatter(args[2]) : this.DefaultDateTimeFormat; if (typeof(args[0]) === 'string' && typeof(args[1]) === 'string') { ({value, error} = BuiltInFunctions.convertToUTC(args[0], args[1], format)); @@ -2252,7 +2219,7 @@ export class BuiltInFunctions { let error: string; let args: any []; ({args, error} = BuiltInFunctions.evaluateChildren(expr, state)); - if (error === undefined) { + if (!error) { const format: string = (args.length === 4) ? BuiltInFunctions.timestampFormatter(args[3]) : this.DefaultDateTimeFormat; if (typeof(args[0]) === 'string' && Number.isInteger(args[1]) && typeof(args[2]) === 'string') { ({value, error} = BuiltInFunctions.addToTime(args[0], args[1], args[2], format)); @@ -2273,7 +2240,7 @@ export class BuiltInFunctions { let error: string; let args: any []; ({args, error} = BuiltInFunctions.evaluateChildren(expr, state)); - if (error === undefined) { + if (!error) { const format: string = (args.length === 2) ? BuiltInFunctions.timestampFormatter(args[1]) : this.DefaultDateTimeFormat; if (typeof(args[0]) === 'string') { ({value, error} = BuiltInFunctions.startOfDay(args[0], format)); @@ -2294,7 +2261,7 @@ export class BuiltInFunctions { let error: string; let args: any []; ({args, error} = BuiltInFunctions.evaluateChildren(expr, state)); - if (error === undefined) { + if (!error) { const format: string = (args.length === 2) ? BuiltInFunctions.timestampFormatter(args[1]) : this.DefaultDateTimeFormat; if (typeof(args[0]) === 'string') { ({value, error} = BuiltInFunctions.startOfHour(args[0], format)); @@ -2315,7 +2282,7 @@ export class BuiltInFunctions { let error: string; let args: any []; ({args, error} = BuiltInFunctions.evaluateChildren(expr, state)); - if (error === undefined) { + if (!error) { const format: string = (args.length === 2) ? BuiltInFunctions.timestampFormatter(args[1]) : this.DefaultDateTimeFormat; if (typeof(args[0]) === 'string') { ({value, error} = BuiltInFunctions.startOfMonth(args[0], format)); @@ -2336,7 +2303,7 @@ export class BuiltInFunctions { let error: string; let args: any []; ({args, error} = BuiltInFunctions.evaluateChildren(expr, state)); - if (error === undefined) { + if (!error) { if (typeof(args[0]) === 'string') { ({value, error} = BuiltInFunctions.ticks(args[0])); } else { @@ -2355,7 +2322,7 @@ export class BuiltInFunctions { let error: string; let args: any []; ({args, error} = BuiltInFunctions.evaluateChildren(expr, state)); - if (error === undefined) { + if (!error) { if (typeof(args[0]) === 'string') { ({value, error} = BuiltInFunctions.uriHost(args[0])); } else { @@ -2374,7 +2341,7 @@ export class BuiltInFunctions { let error: string; let args: any []; ({args, error} = BuiltInFunctions.evaluateChildren(expr, state)); - if (error === undefined) { + if (!error) { if (typeof(args[0]) === 'string') { ({value, error} = BuiltInFunctions.uriPath(args[0])); } else { @@ -2393,7 +2360,7 @@ export class BuiltInFunctions { let error: string; let args: any []; ({args, error} = BuiltInFunctions.evaluateChildren(expr, state)); - if (error === undefined) { + if (!error) { if (typeof(args[0]) === 'string') { ({value, error} = BuiltInFunctions.uriPathAndQuery(args[0])); } else { @@ -2412,7 +2379,7 @@ export class BuiltInFunctions { let error: string; let args: any []; ({args, error} = BuiltInFunctions.evaluateChildren(expr, state)); - if (error === undefined) { + if (!error) { if (typeof(args[0]) === 'string') { ({value, error} = BuiltInFunctions.uriQuery(args[0])); } else { @@ -2431,7 +2398,7 @@ export class BuiltInFunctions { let error: string; let args: any []; ({args, error} = BuiltInFunctions.evaluateChildren(expr, state)); - if (error === undefined) { + if (!error) { if (typeof(args[0]) === 'string') { ({value, error} = BuiltInFunctions.uriPort(args[0])); } else { @@ -2450,7 +2417,7 @@ export class BuiltInFunctions { let error: string; let args: any []; ({args, error} = BuiltInFunctions.evaluateChildren(expr, state)); - if (error === undefined) { + if (!error) { if (typeof(args[0]) === 'string') { ({value, error} = BuiltInFunctions.uriScheme(args[0])); } else { @@ -2512,7 +2479,7 @@ export class BuiltInFunctions { (expression: Expression): void => BuiltInFunctions.validateOrder(expression, undefined, ReturnType.Object, ReturnType.String)), new ExpressionEvaluator( ExpressionType.If, - (expression: Expression, state: any): { value: any; error: string } => BuiltInFunctions._if(expression, state), + (expression: Expression, state: MemoryInterface): { value: any; error: string } => BuiltInFunctions._if(expression, state), ReturnType.Object, (expr: Expression): void => BuiltInFunctions.validateArityAndAnyType(expr, 3, 3)), new ExpressionEvaluator( @@ -2594,7 +2561,7 @@ export class BuiltInFunctions { first = args[0][0]; } - if (args[0] instanceof Array && args[0].length > 0) { + if (Array.isArray(args[0]) && args[0].length > 0) { first = Extensions.accessIndex(args[0], 0).value; } @@ -2611,7 +2578,7 @@ export class BuiltInFunctions { last = args[0][args[0].length - 1]; } - if (args[0] instanceof Array && args[0].length > 0) { + if (Array.isArray(args[0]) && args[0].length > 0) { last = Extensions.accessIndex(args[0], args[0].length - 1).value; } @@ -2622,7 +2589,7 @@ export class BuiltInFunctions { new ExpressionEvaluator( ExpressionType.Json, BuiltInFunctions.apply((args: any []): any => JSON.parse(args[0])), - ReturnType.String, + ReturnType.Object, (expression: Expression): void => BuiltInFunctions.validateOrder(expression, undefined, ReturnType.String)), new ExpressionEvaluator( ExpressionType.AddProperty, @@ -2704,23 +2671,7 @@ export class BuiltInFunctions { return {value, error}; }), ReturnType.Boolean, - BuiltInFunctions.validateIsMatch), - - // Shorthand functions - new ExpressionEvaluator(ExpressionType.Callstack, this.callstack, ReturnType.Object, this.validateUnary), - new ExpressionEvaluator( - ExpressionType.SimpleEntity, - BuiltInFunctions.apply( - (args: any []): any => { - let result: any = args[0]; - while (Array.isArray(result) && result.length === 1) { - result = result[0]; - } - - return result; - }), - ReturnType.Object, - this.validateUnary) + BuiltInFunctions.validateIsMatch) ]; const lookup: Map = new Map(); diff --git a/libraries/botframework-expressions/src/commonRegex.ts b/libraries/botframework-expressions/src/commonRegex.ts index fc7385e754..ddc50f3a28 100644 --- a/libraries/botframework-expressions/src/commonRegex.ts +++ b/libraries/botframework-expressions/src/commonRegex.ts @@ -1,4 +1,3 @@ - /** * @module botframework-expressions */ @@ -11,16 +10,32 @@ import { ParseTree } from 'antlr4ts/tree/ParseTree'; import * as LRUCache from 'lru-cache'; import { CommonRegexLexer, CommonRegexParser } from './generated'; +// tslint:disable-next-line: completed-docs +export class ErrorListener implements ANTLRErrorListener { + public static readonly Instance: ErrorListener = new ErrorListener(); + + public syntaxError( + _recognizer: Recognizer,// eslint-disable-line @typescript-eslint/no-unused-vars + _offendingSymbol: T,// eslint-disable-line @typescript-eslint/no-unused-vars + line: number,// eslint-disable-line @typescript-eslint/no-unused-vars + charPositionInLine: number,// eslint-disable-line @typescript-eslint/no-unused-vars + msg: string,// eslint-disable-line @typescript-eslint/no-unused-vars + _e: RecognitionException | undefined): void {// eslint-disable-line @typescript-eslint/no-unused-vars + + throw Error(`Regular expression is invalid.`); + } +} + // tslint:disable-next-line: completed-docs export class CommonRegex { public static regexCache: LRUCache = new LRUCache(15); public static CreateRegex(pattern: string): RegExp { let result: RegExp; - if (pattern !== undefined && pattern !== '' && this.regexCache.has(pattern)) { + if (pattern && this.regexCache.has(pattern)) { result = this.regexCache.get(pattern); } else { - if (pattern === undefined || pattern === '' || !this.isCommonRegex(pattern)) { + if (!pattern || !this.isCommonRegex(pattern)) { throw new Error(`A regular expression parsing error occurred.`); } @@ -42,7 +57,7 @@ export class CommonRegex { }); let regexp: RegExp; - if (flag !== '') { + if (flag) { regexp = new RegExp(`${ pattern }`, flag); } else { regexp = new RegExp(`${ pattern }`); @@ -73,19 +88,4 @@ export class CommonRegex { return parser.parse(); } -} - -// tslint:disable-next-line: completed-docs -export class ErrorListener implements ANTLRErrorListener { - public static readonly Instance: ErrorListener = new ErrorListener(); - - public syntaxError( - _recognizer: Recognizer, - _offendingSymbol: T, - line: number, - charPositionInLine: number, - msg: string, - _e: RecognitionException | undefined): void { - throw Error(`Regular expression is invalid.`); - } -} +} \ No newline at end of file diff --git a/libraries/botframework-expressions/src/constant.ts b/libraries/botframework-expressions/src/constant.ts index a6acbd86f3..e6baeabe7c 100644 --- a/libraries/botframework-expressions/src/constant.ts +++ b/libraries/botframework-expressions/src/constant.ts @@ -1,4 +1,3 @@ - /** * @module botframework-expressions */ @@ -51,6 +50,6 @@ export class Constant extends Expression { return `'${ this.value }'`; } - return this.value === undefined ? undefined : this.value.toString(); + return !this.value ? undefined : this.value.toString(); } } diff --git a/libraries/botframework-expressions/src/expression.ts b/libraries/botframework-expressions/src/expression.ts index 4835d351e8..cbde9d048f 100644 --- a/libraries/botframework-expressions/src/expression.ts +++ b/libraries/botframework-expressions/src/expression.ts @@ -9,6 +9,8 @@ import { BuiltInFunctions } from './builtInFunction'; import { Constant } from './constant'; import { ExpressionEvaluator } from './expressionEvaluator'; import { ExpressionType } from './expressionType'; +import { SimpleObjectMemory, MemoryInterface } from './memory'; +import { Extensions } from './extensions'; /** * Type expected from evaluating an expression. @@ -108,7 +110,10 @@ export class Expression { * Global state to evaluate accessor expressions against. Can Dictionary be otherwise reflection is used to access property and then indexer. * @param state */ - public tryEvaluate(state: any): { value: any; error: string } { + public tryEvaluate(state: MemoryInterface | any): { value: any; error: string } { + if(!Extensions.isMemoryInterface(state)) { + state = SimpleObjectMemory.wrap(state); + } return this.evaluator.tryEvaluate(this, state); } diff --git a/libraries/botframework-expressions/src/expressionEvaluator.ts b/libraries/botframework-expressions/src/expressionEvaluator.ts index a9467b16f9..dc8de0ed98 100644 --- a/libraries/botframework-expressions/src/expressionEvaluator.ts +++ b/libraries/botframework-expressions/src/expressionEvaluator.ts @@ -1,4 +1,3 @@ - /** * @module botframework-expressions */ @@ -7,6 +6,7 @@ * Licensed under the MIT License. */ import { Expression, ReturnType } from './expression'; +import { MemoryInterface } from './memory'; /** * Delegate for doing static validation on an expression. @@ -18,7 +18,7 @@ export type ValidateExpressionDelegate = (expression: Expression) => any; * Delegate to evaluate an expression. * Evaluators should verify runtime arguments when appropriate and return an error rather than throw exceptions if possible. */ -export type EvaluateExpressionDelegate = (expression: Expression, state: any) => { value: any; error: string }; +export type EvaluateExpressionDelegate = (expression: Expression, state: MemoryInterface) => { value: any; error: string }; /** * Delegate to lookup function information from the type. @@ -57,6 +57,7 @@ export class ExpressionEvaluator { this._evaluator = evaluator; this.returnType = returnType; // tslint:disable-next-line: no-empty + // eslint-disable-next-line @typescript-eslint/no-unused-vars this._validator = validator === undefined ? ((expr: Expression): any => { }) : validator; } @@ -65,7 +66,7 @@ export class ExpressionEvaluator { * @param expression Expression to evaluate. * @param state Global state information. */ - public tryEvaluate = (expression: Expression, state: any): { value: any; error: string } => this._evaluator(expression, state); + public tryEvaluate = (expression: Expression, state: MemoryInterface): { value: any; error: string } => this._evaluator(expression, state); /** * Validate an expression. * @param expression Expression to validate. diff --git a/libraries/botframework-expressions/src/expressionParser.ts b/libraries/botframework-expressions/src/expressionParser.ts index 294c482201..ad30db1f3c 100644 --- a/libraries/botframework-expressions/src/expressionParser.ts +++ b/libraries/botframework-expressions/src/expressionParser.ts @@ -1,4 +1,3 @@ - /** * @module botframework-expressions */ diff --git a/libraries/botframework-expressions/src/expressionType.ts b/libraries/botframework-expressions/src/expressionType.ts index 32ea642084..8d3597bcb4 100644 --- a/libraries/botframework-expressions/src/expressionType.ts +++ b/libraries/botframework-expressions/src/expressionType.ts @@ -1,4 +1,3 @@ - /** * @module botframework-expressions */ @@ -40,7 +39,6 @@ export class ExpressionType { public static readonly And: string = '&&'; public static readonly Or: string = '||'; public static readonly Not: string = '!'; - public static readonly Optional: string = 'optional'; // String public static readonly Concat: string = '&'; @@ -102,6 +100,9 @@ export class ExpressionType { public static readonly Base64ToBinary: string = 'base64ToBinary'; public static readonly Base64ToString: string = 'base64ToString'; public static readonly UriComponent: string = 'uriComponent'; + // TODO + // xml + // Memory public static readonly Accessor: string = 'Accessor'; public static readonly Element: string = 'Element'; @@ -136,6 +137,10 @@ export class ExpressionType { public static readonly Coalesce: string = 'coalesce'; public static readonly JPath: string = 'jPath'; public static readonly SetPathToValue: string = 'setPathToValue'; + + // TODO + // xPath + // jPath // URI parsing functions public static readonly UriHost: string = 'uriHost'; @@ -147,8 +152,4 @@ export class ExpressionType { // Regar expression public static readonly IsMatch: string = 'isMatch'; - - // Short hand functions - public static readonly SimpleEntity: string = 'simpleEntity'; - public static readonly Callstack: string = 'callstack'; } diff --git a/libraries/botframework-expressions/src/extensions.ts b/libraries/botframework-expressions/src/extensions.ts index 33e705e2a0..a08ce416d8 100644 --- a/libraries/botframework-expressions/src/extensions.ts +++ b/libraries/botframework-expressions/src/extensions.ts @@ -1,4 +1,3 @@ - /** * @module botframework-expressions */ @@ -43,6 +42,24 @@ export class Extensions { return Array.from(filteredReferences); } + /** + * Patch method + * TODO: is there any better solution? + * To judge if an object is implements MemoryInterface. Same with 'is MemoryInterface' in C# + */ + public static isMemoryInterface(obj: any): boolean { + if (obj === undefined) { + return false; + } + + if (typeof obj !== 'object') { + return false; + } + + return 'getValue' in obj && 'setValue' in obj && 'version' in obj + && typeof obj.getValue === 'function' && typeof obj.setValue === 'function' && typeof obj.version === 'function'; + } + /** * Walking function for identifying static memory references in an expression. * @param expression Expression to analyze. @@ -109,7 +126,7 @@ export class Extensions { */ public static accessProperty(instance: any, property: string): { value: any; error: string } { // NOTE: This returns null rather than an error if property is not present - if (instance === null || instance === undefined) { + if (!instance) { return { value: undefined, error: undefined }; } @@ -143,7 +160,7 @@ export class Extensions { * @param value Value to set. * @returns set value. */ - public static setProperty(instance: any, property: string, value: any): any { + public static setProperty(instance: any, property: string, value: any): { value: any; error: string } { const result: any = value; if (instance instanceof Map) { instance.set(property, value); @@ -151,7 +168,7 @@ export class Extensions { instance[property] = value; } - return result; + return {value: result, error: undefined}; } /** @@ -170,8 +187,8 @@ export class Extensions { let error: string; let count = -1; - if (instance instanceof Array) { - count = (instance).length; + if (Array.isArray(instance)) { + count = instance.length; } else if (instance instanceof Map) { count = (instance as Map).size; } diff --git a/libraries/botframework-expressions/src/index.ts b/libraries/botframework-expressions/src/index.ts index 36238882a3..01820c8458 100644 --- a/libraries/botframework-expressions/src/index.ts +++ b/libraries/botframework-expressions/src/index.ts @@ -1,4 +1,3 @@ - /** * @module botframework-expressions */ @@ -17,3 +16,4 @@ export * from './timeZoneConverter'; export * from './generated'; export * from './commonRegex'; export * from './parser'; +export * from './memory'; diff --git a/libraries/botframework-expressions/src/memory/composedMemory.ts b/libraries/botframework-expressions/src/memory/composedMemory.ts new file mode 100644 index 0000000000..19a3bcdc3d --- /dev/null +++ b/libraries/botframework-expressions/src/memory/composedMemory.ts @@ -0,0 +1,35 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +import { MemoryInterface } from './memoryInterface'; + +/** + * @module botframework-expressions + */ +/** + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. + */ + +export class ComposedMemory implements MemoryInterface { + + private memoryMap: Map = undefined; + + public constructor(memoryMap: Map) { + this.memoryMap = memoryMap; + } + + public getValue(path: string): { value: any; error: string } { + const prefix: string = path.split('.')[0]; + if (this.memoryMap.has(prefix)) { + return this.memoryMap.get(prefix).getValue(path.substr(prefix.length + 1)); + } + return {value: undefined, error: `path not exists at ${ path }`}; + } + + public setValue(_path: string, _value: any): { value: any; error: string } { + throw new Error('Method not implemented.'); + } + + public version(): string { + return '0'; + } +} \ No newline at end of file diff --git a/libraries/botframework-expressions/src/memory/index.ts b/libraries/botframework-expressions/src/memory/index.ts new file mode 100644 index 0000000000..9891769d1b --- /dev/null +++ b/libraries/botframework-expressions/src/memory/index.ts @@ -0,0 +1,11 @@ + +/** + * @module botframework-expressions + */ +/** + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. + */ +export * from './memoryInterface'; +export * from './composedMemory'; +export * from './simpleObjectMemory'; diff --git a/libraries/botframework-expressions/src/memory/memoryInterface.ts b/libraries/botframework-expressions/src/memory/memoryInterface.ts new file mode 100644 index 0000000000..b8ea97f6a6 --- /dev/null +++ b/libraries/botframework-expressions/src/memory/memoryInterface.ts @@ -0,0 +1,36 @@ + +/** + * @module botframework-expressions + */ +/** + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. + */ + +/** + * Interface to parse a string into an Expression + */ +export interface MemoryInterface { + + /** + * get value from a given path, it can be a simple indenfiter like "a", or + * a combined path like "a.b", "a.b[2]", "a.b[2].c", inside [] is guranteed to be a int number or a string. + * @param path memory path. + * @returnsresovled value and error messsage if any. + */ + getValue(path: string): {value: string; error: string}; + + /** + * Set value to a given path. + * @param path memory path. + * @param value value to set. + * @returns value set and error message if any. + */ + setValue(path: string, value: any): {value: string; error: string}; + + /** + * Version is used to identify whether the a particular memory instance has been updated or not. + * If version is not changed, the caller may choose to use the cached result instead of recomputing everything. + */ + version(): string; +} diff --git a/libraries/botframework-expressions/src/memory/simpleObjectMemory.ts b/libraries/botframework-expressions/src/memory/simpleObjectMemory.ts new file mode 100644 index 0000000000..d9db915d81 --- /dev/null +++ b/libraries/botframework-expressions/src/memory/simpleObjectMemory.ts @@ -0,0 +1,143 @@ +import { MemoryInterface } from './memoryInterface'; +import { Extensions } from '../extensions'; + +/** + * @module botframework-expressions + */ +/** + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. + */ + +export class SimpleObjectMemory implements MemoryInterface { + + private memory: any = undefined; + private versionNumber: number = 0; + + public constructor(memory: any) { + this.memory = memory; + } + + public static wrap(obj: any): MemoryInterface { + if(Extensions.isMemoryInterface(obj)) { + return obj; + } + + return new SimpleObjectMemory(obj); + } + + public getValue(path: string): { value: any; error: string } { + if (this.memory === undefined) { + return {value: undefined, error: undefined}; + } + + const parts: string[] = path.split(/[.\[\]]+/).filter((u: string): boolean => u !== undefined && u !== ''); + let value: any; + let curScope = this.memory; + + for (const part of parts) { + let error: string; + const idx = parseInt(part); + if(!isNaN(idx) && Array.isArray(curScope)) { + ({value, error} = Extensions.accessIndex(curScope, idx)); + } else { + ({value, error} = Extensions.accessProperty(curScope, part)); + } + + if (error) { + return {value: undefined, error }; + } + + curScope = value; + } + + return {value, error: undefined}; + } + + /** + * In this simple object scope, we don't allow you to set a path in which some parts in middle don't exist + * for example + * if you set dialog.a.b = x, but dialog.a don't exist, this will result in an error + * because we can't and shouldn't smart create structure in the middle + * you can implement a customzied Scope that support such behavior + */ + public setValue(path: string, input: any): { value: any; error: string } { + if (this.memory === undefined) { + return {value: undefined, error: `Can't set value with in a null memory`}; + } + + const parts: string[] = path.split(/[.\[\]]+/).filter((u: string): boolean => u !== undefined && u !== ''); + let curScope: any = this.memory; + let curPath = ''; + let error: string = undefined; + + // find the 2nd last value, ie, the container + for(let i = 0; i < parts.length - 1; i++) { + const idx = parseInt(parts[i]); + if(!isNaN(idx) && Array.isArray(curScope)) { + curPath = `[${ parts[i] }]`; + ({value: curScope, error} = Extensions.accessIndex(curScope, idx)); + } else { + curPath = `.${ parts[i] }`; + ({value: curScope, error} = Extensions.accessProperty(curScope, parts[i])); + } + + if (error) { + return {value: undefined, error }; + } + + if (curScope === undefined) { + curPath = curPath.replace(/(^\.*)/g, ''); + return {value: undefined, error: `Can't set value to path: '${ path }', reason: '${ curPath }' is null` }; + } + } + + // set the last value + const idx = parseInt(parts[parts.length - 1]); + if(!isNaN(idx)) { + if (Array.isArray(curScope)) { + if (idx > curScope.length) { + error = `${ idx } index out of range`; + } else if(idx === curScope.length) { + curScope.push(input); + } else { + curScope[idx] = input; + } + } else { + error = `set value for an index to a non-list object`; + } + + if (error) { + return {value: undefined, error}; + } + } else { + error = Extensions.setProperty(curScope,parts[parts.length - 1], input).error; + if (error) { + return {value: undefined, error: `Can set value to path: '${ path }', reason: ${ error }`}; + } + } + + this.versionNumber++; + return {value: input, error: undefined}; + } + + public version(): string { + return this.versionNumber.toString(); + } + public toString(): string { + return JSON.stringify(this.memory, this.getCircularReplacer()); + } + + private getCircularReplacer(): any { + const seen = new WeakSet(); + return (_key: any, value: object): any => { + if (typeof value === 'object' && value) { + if (seen.has(value)) { + return; + } + seen.add(value); + } + return value; + }; + }; +} \ No newline at end of file diff --git a/libraries/botframework-expressions/src/parser/Expression.g4 b/libraries/botframework-expressions/src/parser/Expression.g4 index dfe2cd36fd..ceed9e67df 100644 --- a/libraries/botframework-expressions/src/parser/Expression.g4 +++ b/libraries/botframework-expressions/src/parser/Expression.g4 @@ -16,15 +16,13 @@ expression ; primaryExpression - : '(' expression ')' #parenthesisExp - | NUMBER #numericAtom - | STRING #stringAtom - | IDENTIFIER #idAtom - | ('#'|'@'|'@@'|'$'|'%'|'^'|'~') #shorthandAtom - | primaryExpression IDENTIFIER #shorthandAccessorExp - | primaryExpression '.' IDENTIFIER #memberAccessExp - | primaryExpression '(' argsList? ')' #funcInvokeExp - | primaryExpression '[' expression ']' #indexAccessExp + : '(' expression ')' #parenthesisExp + | NUMBER #numericAtom + | STRING #stringAtom + | IDENTIFIER #idAtom + | primaryExpression '.' IDENTIFIER #memberAccessExp + | primaryExpression '(' argsList? ')' #funcInvokeExp + | primaryExpression '[' expression ']' #indexAccessExp ; argsList @@ -36,9 +34,9 @@ fragment DIGIT : [0-9]; NUMBER : DIGIT + ( '.' DIGIT +)? ; -WHITESPACE : (' '|'\t'|'\u00a0'|'\ufeff') -> skip; +WHITESPACE : (' '|'\t'|'\ufeff'|'\u00a0') -> skip; -IDENTIFIER : (LETTER | '_') (LETTER | DIGIT | '-' | '_')*; +IDENTIFIER : (LETTER | '_' | '#' | '@' | '@@' | '$' | '%') (LETTER | DIGIT | '-' | '_')*; NEWLINE : '\r'? '\n' -> skip; diff --git a/libraries/botframework-expressions/src/parser/expressionEngine.ts b/libraries/botframework-expressions/src/parser/expressionEngine.ts index 1ab9a19497..045e8a57ad 100644 --- a/libraries/botframework-expressions/src/parser/expressionEngine.ts +++ b/libraries/botframework-expressions/src/parser/expressionEngine.ts @@ -28,15 +28,6 @@ export class ExpressionEngine implements ExpressionParserInterface { // tslint:disable-next-line: typedef private readonly ExpressionTransformer = class extends AbstractParseTreeVisitor implements ExpressionVisitor { - private readonly ShorthandPrefixMap: Map = new Map([ - ['#', 'turn.recognized.intents'], - ['@', 'turn.recognized.entities'], - ['@@', 'turn.recognized.entities'], - ['$', 'dialog'], - ['^', ''], - ['%', 'dialog.options'], - ['~', 'dialog.instance'] - ]); private readonly _lookup: EvaluatorLookup = undefined; public constructor(lookup: EvaluatorLookup) { @@ -64,28 +55,6 @@ export class ExpressionEngine implements ExpressionParserInterface { return this.MakeExpression(binaryOperationName, left, right); } - public visitShorthandAccessorExp(context: ep.ShorthandAccessorExpContext): Expression { - if (context.primaryExpression() instanceof ep.ShorthandAtomContext) { - const shorthandAtom: ep.ShorthandAtomContext = context.primaryExpression() as ep.ShorthandAtomContext; - const shorthandMark: string = shorthandAtom.text; - - if (!this.ShorthandPrefixMap.has(shorthandMark)) { - throw new Error(`${ shorthandMark } is not a shorthand`); - } - - const property: Constant = new Constant(context.IDENTIFIER().text); - - if (shorthandMark === '^') { - return this.MakeExpression(ExpressionType.Callstack, property); - } - - const accessorExpression: Expression = this.transform(ExpressionEngine.antlrParse(this.ShorthandPrefixMap.get(shorthandMark))); - const expression: Expression = this.MakeExpression(ExpressionType.Accessor, property, accessorExpression); - - return shorthandMark === '@' ? this.MakeExpression(ExpressionType.SimpleEntity, expression) : expression; - } - } - public visitFuncInvokeExp(context: ep.FuncInvokeExpContext): Expression { const parameters: Expression[] = this.processArgsList(context.argsList()); @@ -116,24 +85,6 @@ export class ExpressionEngine implements ExpressionParserInterface { let instance: Expression; const property: Expression = this.visit(context.expression()); - if (context.primaryExpression() instanceof ep.ShorthandAtomContext) { - const shorthandAtom: ep.ShorthandAtomContext = context.primaryExpression() as ep.ShorthandAtomContext; - const shorthandMark: string = shorthandAtom.text; - - if (!this.ShorthandPrefixMap.has(shorthandMark)) { - throw new Error(`${ shorthandMark } is not a shorthand`); - } - - if (shorthandMark === '^') { - return this.MakeExpression(ExpressionType.Callstack, property); - } - - instance = this.transform(ExpressionEngine.antlrParse(this.ShorthandPrefixMap.get(shorthandMark))); - const expression: Expression = this.MakeExpression(ExpressionType.Element, instance, property); - - return shorthandMark === '@' ? this.MakeExpression(ExpressionType.SimpleEntity, expression) : expression; - } - instance = this.visit(context.primaryExpression()); return this.MakeExpression(ExpressionType.Element, instance, property); @@ -141,9 +92,6 @@ export class ExpressionEngine implements ExpressionParserInterface { public visitMemberAccessExp(context: ep.MemberAccessExpContext): Expression { const property: string = context.IDENTIFIER().text; - if (context.primaryExpression() instanceof ep.ShorthandAtomContext) { - throw new Error(`${ context.text } is not a valid shorthand. Maybe you mean '${ context.primaryExpression().text }${ property }'?`); - } const instance: Expression = this.visit(context.primaryExpression()); return this.MakeExpression(ExpressionType.Accessor, new Constant(property), instance); diff --git a/libraries/botframework-expressions/src/parser/generated/ExpressionLexer.ts b/libraries/botframework-expressions/src/parser/generated/ExpressionLexer.ts index bdf1a2e1d8..ce344b40c6 100644 --- a/libraries/botframework-expressions/src/parser/generated/ExpressionLexer.ts +++ b/libraries/botframework-expressions/src/parser/generated/ExpressionLexer.ts @@ -46,17 +46,12 @@ export class ExpressionLexer extends Lexer { public static readonly T__20 = 21; public static readonly T__21 = 22; public static readonly T__22 = 23; - public static readonly T__23 = 24; - public static readonly T__24 = 25; - public static readonly T__25 = 26; - public static readonly T__26 = 27; - public static readonly T__27 = 28; - public static readonly NUMBER = 29; - public static readonly WHITESPACE = 30; - public static readonly IDENTIFIER = 31; - public static readonly NEWLINE = 32; - public static readonly STRING = 33; - public static readonly INVALID_TOKEN_DEFAULT_MODE = 34; + public static readonly NUMBER = 24; + public static readonly WHITESPACE = 25; + public static readonly IDENTIFIER = 26; + public static readonly NEWLINE = 27; + public static readonly STRING = 28; + public static readonly INVALID_TOKEN_DEFAULT_MODE = 29; // tslint:disable:no-trailing-whitespace public static readonly modeNames: string[] = [ "DEFAULT_MODE", @@ -65,23 +60,21 @@ export class ExpressionLexer extends Lexer { public static readonly ruleNames: string[] = [ "T__0", "T__1", "T__2", "T__3", "T__4", "T__5", "T__6", "T__7", "T__8", "T__9", "T__10", "T__11", "T__12", "T__13", "T__14", "T__15", "T__16", - "T__17", "T__18", "T__19", "T__20", "T__21", "T__22", "T__23", "T__24", - "T__25", "T__26", "T__27", "LETTER", "DIGIT", "NUMBER", "WHITESPACE", - "IDENTIFIER", "NEWLINE", "STRING", "INVALID_TOKEN_DEFAULT_MODE", + "T__17", "T__18", "T__19", "T__20", "T__21", "T__22", "LETTER", "DIGIT", + "NUMBER", "WHITESPACE", "IDENTIFIER", "NEWLINE", "STRING", "INVALID_TOKEN_DEFAULT_MODE", ]; private static readonly _LITERAL_NAMES: Array = [ undefined, "'!'", "'-'", "'+'", "'^'", "'*'", "'/'", "'%'", "'=='", "'!='", "'<>'", "'&'", "'<'", "'<='", "'>'", "'>='", "'&&'", "'||'", "'('", "')'", - "'#'", "'@'", "'@@'", "'$'", "'~'", "'.'", "'['", "']'", "','", + "'.'", "'['", "']'", "','", ]; private static readonly _SYMBOLIC_NAMES: Array = [ undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, - undefined, undefined, undefined, undefined, undefined, undefined, undefined, - undefined, "NUMBER", "WHITESPACE", "IDENTIFIER", "NEWLINE", "STRING", - "INVALID_TOKEN_DEFAULT_MODE", + undefined, undefined, undefined, "NUMBER", "WHITESPACE", "IDENTIFIER", + "NEWLINE", "STRING", "INVALID_TOKEN_DEFAULT_MODE", ]; public static readonly VOCABULARY: Vocabulary = new VocabularyImpl(ExpressionLexer._LITERAL_NAMES, ExpressionLexer._SYMBOLIC_NAMES, []); @@ -111,93 +104,87 @@ export class ExpressionLexer extends Lexer { public get modeNames(): string[] { return ExpressionLexer.modeNames; } public static readonly _serializedATN: string = - "\x03\uAF6F\u8320\u479D\uB75C\u4880\u1605\u191C\uAB37\x02$\xC7\b\x01\x04" + - "\x02\t\x02\x04\x03\t\x03\x04\x04\t\x04\x04\x05\t\x05\x04\x06\t\x06\x04" + - "\x07\t\x07\x04\b\t\b\x04\t\t\t\x04\n\t\n\x04\v\t\v\x04\f\t\f\x04\r\t\r" + - "\x04\x0E\t\x0E\x04\x0F\t\x0F\x04\x10\t\x10\x04\x11\t\x11\x04\x12\t\x12" + - "\x04\x13\t\x13\x04\x14\t\x14\x04\x15\t\x15\x04\x16\t\x16\x04\x17\t\x17" + - "\x04\x18\t\x18\x04\x19\t\x19\x04\x1A\t\x1A\x04\x1B\t\x1B\x04\x1C\t\x1C" + - "\x04\x1D\t\x1D\x04\x1E\t\x1E\x04\x1F\t\x1F\x04 \t \x04!\t!\x04\"\t\"\x04" + - "#\t#\x04$\t$\x04%\t%\x03\x02\x03\x02\x03\x03\x03\x03\x03\x04\x03\x04\x03" + - "\x05\x03\x05\x03\x06\x03\x06\x03\x07\x03\x07\x03\b\x03\b\x03\t\x03\t\x03" + - "\t\x03\n\x03\n\x03\n\x03\v\x03\v\x03\v\x03\f\x03\f\x03\r\x03\r\x03\x0E" + - "\x03\x0E\x03\x0E\x03\x0F\x03\x0F\x03\x10\x03\x10\x03\x10\x03\x11\x03\x11" + - "\x03\x11\x03\x12\x03\x12\x03\x12\x03\x13\x03\x13\x03\x14\x03\x14\x03\x15" + - "\x03\x15\x03\x16\x03\x16\x03\x17\x03\x17\x03\x17\x03\x18\x03\x18\x03\x19" + - "\x03\x19\x03\x1A\x03\x1A\x03\x1B\x03\x1B\x03\x1C\x03\x1C\x03\x1D\x03\x1D" + - "\x03\x1E\x03\x1E\x03\x1F\x03\x1F\x03 \x06 \x91\n \r \x0E \x92\x03 \x03" + - " \x06 \x97\n \r \x0E \x98\x05 \x9B\n \x03!\x03!\x03!\x03!\x03\"\x03\"" + - "\x05\"\xA3\n\"\x03\"\x03\"\x03\"\x07\"\xA8\n\"\f\"\x0E\"\xAB\v\"\x03#" + - "\x05#\xAE\n#\x03#\x03#\x03#\x03#\x03$\x03$\x07$\xB6\n$\f$\x0E$\xB9\v$" + - "\x03$\x03$\x03$\x07$\xBE\n$\f$\x0E$\xC1\v$\x03$\x05$\xC4\n$\x03%\x03%" + - "\x02\x02\x02&\x03\x02\x03\x05\x02\x04\x07\x02\x05\t\x02\x06\v\x02\x07" + - "\r\x02\b\x0F\x02\t\x11\x02\n\x13\x02\v\x15\x02\f\x17\x02\r\x19\x02\x0E" + - "\x1B\x02\x0F\x1D\x02\x10\x1F\x02\x11!\x02\x12#\x02\x13%\x02\x14\'\x02" + - "\x15)\x02\x16+\x02\x17-\x02\x18/\x02\x191\x02\x1A3\x02\x1B5\x02\x1C7\x02" + - "\x1D9\x02\x1E;\x02\x02=\x02\x02?\x02\x1FA\x02 C\x02!E\x02\"G\x02#I\x02" + - "$\x03\x02\b\x04\x02C\\c|\x03\x022;\x06\x02\v\v\"\"\xA2\xA2\uFF01\uFF01" + - "\x04\x02//aa\x03\x02))\x03\x02$$\xCF\x02\x03\x03\x02\x02\x02\x02\x05\x03" + - "\x02\x02\x02\x02\x07\x03\x02\x02\x02\x02\t\x03\x02\x02\x02\x02\v\x03\x02" + - "\x02\x02\x02\r\x03\x02\x02\x02\x02\x0F\x03\x02\x02\x02\x02\x11\x03\x02" + - "\x02\x02\x02\x13\x03\x02\x02\x02\x02\x15\x03\x02\x02\x02\x02\x17\x03\x02" + - "\x02\x02\x02\x19\x03\x02\x02\x02\x02\x1B\x03\x02\x02\x02\x02\x1D\x03\x02" + - "\x02\x02\x02\x1F\x03\x02\x02\x02\x02!\x03\x02\x02\x02\x02#\x03\x02\x02" + - "\x02\x02%\x03\x02\x02\x02\x02\'\x03\x02\x02\x02\x02)\x03\x02\x02\x02\x02" + - "+\x03\x02\x02\x02\x02-\x03\x02\x02\x02\x02/\x03\x02\x02\x02\x021\x03\x02" + - "\x02\x02\x023\x03\x02\x02\x02\x025\x03\x02\x02\x02\x027\x03\x02\x02\x02" + - "\x029\x03\x02\x02\x02\x02?\x03\x02\x02\x02\x02A\x03\x02\x02\x02\x02C\x03" + - "\x02\x02\x02\x02E\x03\x02\x02\x02\x02G\x03\x02\x02\x02\x02I\x03\x02\x02" + - "\x02\x03K\x03\x02\x02\x02\x05M\x03\x02\x02\x02\x07O\x03\x02\x02\x02\t" + - "Q\x03\x02\x02\x02\vS\x03\x02\x02\x02\rU\x03\x02\x02\x02\x0FW\x03\x02\x02" + - "\x02\x11Y\x03\x02\x02\x02\x13\\\x03\x02\x02\x02\x15_\x03\x02\x02\x02\x17" + - "b\x03\x02\x02\x02\x19d\x03\x02\x02\x02\x1Bf\x03\x02\x02\x02\x1Di\x03\x02" + - "\x02\x02\x1Fk\x03\x02\x02\x02!n\x03\x02\x02\x02#q\x03\x02\x02\x02%t\x03" + - "\x02\x02\x02\'v\x03\x02\x02\x02)x\x03\x02\x02\x02+z\x03\x02\x02\x02-|" + - "\x03\x02\x02\x02/\x7F\x03\x02\x02\x021\x81\x03\x02\x02\x023\x83\x03\x02" + - "\x02\x025\x85\x03\x02\x02\x027\x87\x03\x02\x02\x029\x89\x03\x02\x02\x02" + - ";\x8B\x03\x02\x02\x02=\x8D\x03\x02\x02\x02?\x90\x03\x02\x02\x02A\x9C\x03" + - "\x02\x02\x02C\xA2\x03\x02\x02\x02E\xAD\x03\x02\x02\x02G\xC3\x03\x02\x02" + - "\x02I\xC5\x03\x02\x02\x02KL\x07#\x02\x02L\x04\x03\x02\x02\x02MN\x07/\x02" + - "\x02N\x06\x03\x02\x02\x02OP\x07-\x02\x02P\b\x03\x02\x02\x02QR\x07`\x02" + - "\x02R\n\x03\x02\x02\x02ST\x07,\x02\x02T\f\x03\x02\x02\x02UV\x071\x02\x02" + - "V\x0E\x03\x02\x02\x02WX\x07\'\x02\x02X\x10\x03\x02\x02\x02YZ\x07?\x02" + - "\x02Z[\x07?\x02\x02[\x12\x03\x02\x02\x02\\]\x07#\x02\x02]^\x07?\x02\x02" + - "^\x14\x03\x02\x02\x02_`\x07>\x02\x02`a\x07@\x02\x02a\x16\x03\x02\x02\x02" + - "bc\x07(\x02\x02c\x18\x03\x02\x02\x02de\x07>\x02\x02e\x1A\x03\x02\x02\x02" + - "fg\x07>\x02\x02gh\x07?\x02\x02h\x1C\x03\x02\x02\x02ij\x07@\x02\x02j\x1E" + - "\x03\x02\x02\x02kl\x07@\x02\x02lm\x07?\x02\x02m \x03\x02\x02\x02no\x07" + - "(\x02\x02op\x07(\x02\x02p\"\x03\x02\x02\x02qr\x07~\x02\x02rs\x07~\x02" + - "\x02s$\x03\x02\x02\x02tu\x07*\x02\x02u&\x03\x02\x02\x02vw\x07+\x02\x02" + - "w(\x03\x02\x02\x02xy\x07%\x02\x02y*\x03\x02\x02\x02z{\x07B\x02\x02{,\x03" + - "\x02\x02\x02|}\x07B\x02\x02}~\x07B\x02\x02~.\x03\x02\x02\x02\x7F\x80\x07" + - "&\x02\x02\x800\x03\x02\x02\x02\x81\x82\x07\x80\x02\x02\x822\x03\x02\x02" + - "\x02\x83\x84\x070\x02\x02\x844\x03\x02\x02\x02\x85\x86\x07]\x02\x02\x86" + - "6\x03\x02\x02\x02\x87\x88\x07_\x02\x02\x888\x03\x02\x02\x02\x89\x8A\x07" + - ".\x02\x02\x8A:\x03\x02\x02\x02\x8B\x8C\t\x02\x02\x02\x8C<\x03\x02\x02" + - "\x02\x8D\x8E\t\x03\x02\x02\x8E>\x03\x02\x02\x02\x8F\x91\x05=\x1F\x02\x90" + - "\x8F\x03\x02\x02\x02\x91\x92\x03\x02\x02\x02\x92\x90\x03\x02\x02\x02\x92" + - "\x93\x03\x02\x02\x02\x93\x9A\x03\x02\x02\x02\x94\x96\x070\x02\x02\x95" + - "\x97\x05=\x1F\x02\x96\x95\x03\x02\x02\x02\x97\x98\x03\x02\x02\x02\x98" + - "\x96\x03\x02\x02\x02\x98\x99\x03\x02\x02\x02\x99\x9B\x03\x02\x02\x02\x9A" + - "\x94\x03\x02\x02\x02\x9A\x9B\x03\x02\x02\x02\x9B@\x03\x02\x02\x02\x9C" + - "\x9D\t\x04\x02\x02\x9D\x9E\x03\x02\x02\x02\x9E\x9F\b!\x02\x02\x9FB\x03" + - "\x02\x02\x02\xA0\xA3\x05;\x1E\x02\xA1\xA3\x07a\x02\x02\xA2\xA0\x03\x02" + - "\x02\x02\xA2\xA1\x03\x02\x02\x02\xA3\xA9\x03\x02\x02\x02\xA4\xA8\x05;" + - "\x1E\x02\xA5\xA8\x05=\x1F\x02\xA6\xA8\t\x05\x02\x02\xA7\xA4\x03\x02\x02" + - "\x02\xA7\xA5\x03\x02\x02\x02\xA7\xA6\x03\x02\x02\x02\xA8\xAB\x03\x02\x02" + - "\x02\xA9\xA7\x03\x02\x02\x02\xA9\xAA\x03\x02\x02\x02\xAAD\x03\x02\x02" + - "\x02\xAB\xA9\x03\x02\x02\x02\xAC\xAE\x07\x0F\x02\x02\xAD\xAC\x03\x02\x02" + - "\x02\xAD\xAE\x03\x02\x02\x02\xAE\xAF\x03\x02\x02\x02\xAF\xB0\x07\f\x02" + - "\x02\xB0\xB1\x03\x02\x02\x02\xB1\xB2\b#\x02\x02\xB2F\x03\x02\x02\x02\xB3" + - "\xB7\x07)\x02\x02\xB4\xB6\n\x06\x02\x02\xB5\xB4\x03\x02\x02\x02\xB6\xB9" + - "\x03\x02\x02\x02\xB7\xB5\x03\x02\x02\x02\xB7\xB8\x03\x02\x02\x02\xB8\xBA" + - "\x03\x02\x02\x02\xB9\xB7\x03\x02\x02\x02\xBA\xC4\x07)\x02\x02\xBB\xBF" + - "\x07$\x02\x02\xBC\xBE\n\x07\x02\x02\xBD\xBC\x03\x02\x02\x02\xBE\xC1\x03" + - "\x02\x02\x02\xBF\xBD\x03\x02\x02\x02\xBF\xC0\x03\x02\x02\x02\xC0\xC2\x03" + - "\x02\x02\x02\xC1\xBF\x03\x02\x02\x02\xC2\xC4\x07$\x02\x02\xC3\xB3\x03" + - "\x02\x02\x02\xC3\xBB\x03\x02\x02\x02\xC4H\x03\x02\x02\x02\xC5\xC6\v\x02" + - "\x02\x02\xC6J\x03\x02\x02\x02\r\x02\x92\x98\x9A\xA2\xA7\xA9\xAD\xB7\xBF" + - "\xC3\x03\b\x02\x02"; + "\x03\uAF6F\u8320\u479D\uB75C\u4880\u1605\u191C\uAB37\x02\x1F\xB5\b\x01" + + "\x04\x02\t\x02\x04\x03\t\x03\x04\x04\t\x04\x04\x05\t\x05\x04\x06\t\x06" + + "\x04\x07\t\x07\x04\b\t\b\x04\t\t\t\x04\n\t\n\x04\v\t\v\x04\f\t\f\x04\r" + + "\t\r\x04\x0E\t\x0E\x04\x0F\t\x0F\x04\x10\t\x10\x04\x11\t\x11\x04\x12\t" + + "\x12\x04\x13\t\x13\x04\x14\t\x14\x04\x15\t\x15\x04\x16\t\x16\x04\x17\t" + + "\x17\x04\x18\t\x18\x04\x19\t\x19\x04\x1A\t\x1A\x04\x1B\t\x1B\x04\x1C\t" + + "\x1C\x04\x1D\t\x1D\x04\x1E\t\x1E\x04\x1F\t\x1F\x04 \t \x03\x02\x03\x02" + + "\x03\x03\x03\x03\x03\x04\x03\x04\x03\x05\x03\x05\x03\x06\x03\x06\x03\x07" + + "\x03\x07\x03\b\x03\b\x03\t\x03\t\x03\t\x03\n\x03\n\x03\n\x03\v\x03\v\x03" + + "\v\x03\f\x03\f\x03\r\x03\r\x03\x0E\x03\x0E\x03\x0E\x03\x0F\x03\x0F\x03" + + "\x10\x03\x10\x03\x10\x03\x11\x03\x11\x03\x11\x03\x12\x03\x12\x03\x12\x03" + + "\x13\x03\x13\x03\x14\x03\x14\x03\x15\x03\x15\x03\x16\x03\x16\x03\x17\x03" + + "\x17\x03\x18\x03\x18\x03\x19\x03\x19\x03\x1A\x03\x1A\x03\x1B\x06\x1B|" + + "\n\x1B\r\x1B\x0E\x1B}\x03\x1B\x03\x1B\x06\x1B\x82\n\x1B\r\x1B\x0E\x1B" + + "\x83\x05\x1B\x86\n\x1B\x03\x1C\x03\x1C\x03\x1C\x03\x1C\x03\x1D\x03\x1D" + + "\x03\x1D\x03\x1D\x03\x1D\x05\x1D\x91\n\x1D\x03\x1D\x03\x1D\x03\x1D\x07" + + "\x1D\x96\n\x1D\f\x1D\x0E\x1D\x99\v\x1D\x03\x1E\x05\x1E\x9C\n\x1E\x03\x1E" + + "\x03\x1E\x03\x1E\x03\x1E\x03\x1F\x03\x1F\x07\x1F\xA4\n\x1F\f\x1F\x0E\x1F" + + "\xA7\v\x1F\x03\x1F\x03\x1F\x03\x1F\x07\x1F\xAC\n\x1F\f\x1F\x0E\x1F\xAF" + + "\v\x1F\x03\x1F\x05\x1F\xB2\n\x1F\x03 \x03 \x02\x02\x02!\x03\x02\x03\x05" + + "\x02\x04\x07\x02\x05\t\x02\x06\v\x02\x07\r\x02\b\x0F\x02\t\x11\x02\n\x13" + + "\x02\v\x15\x02\f\x17\x02\r\x19\x02\x0E\x1B\x02\x0F\x1D\x02\x10\x1F\x02" + + "\x11!\x02\x12#\x02\x13%\x02\x14\'\x02\x15)\x02\x16+\x02\x17-\x02\x18/" + + "\x02\x191\x02\x023\x02\x025\x02\x1A7\x02\x1B9\x02\x1C;\x02\x1D=\x02\x1E" + + "?\x02\x1F\x03\x02\t\x04\x02C\\c|\x03\x022;\x06\x02\v\v\"\"\xA2\xA2\uFF01" + + "\uFF01\x05\x02%%BBaa\x04\x02//aa\x03\x02))\x03\x02$$\xBF\x02\x03\x03\x02" + + "\x02\x02\x02\x05\x03\x02\x02\x02\x02\x07\x03\x02\x02\x02\x02\t\x03\x02" + + "\x02\x02\x02\v\x03\x02\x02\x02\x02\r\x03\x02\x02\x02\x02\x0F\x03\x02\x02" + + "\x02\x02\x11\x03\x02\x02\x02\x02\x13\x03\x02\x02\x02\x02\x15\x03\x02\x02" + + "\x02\x02\x17\x03\x02\x02\x02\x02\x19\x03\x02\x02\x02\x02\x1B\x03\x02\x02" + + "\x02\x02\x1D\x03\x02\x02\x02\x02\x1F\x03\x02\x02\x02\x02!\x03\x02\x02" + + "\x02\x02#\x03\x02\x02\x02\x02%\x03\x02\x02\x02\x02\'\x03\x02\x02\x02\x02" + + ")\x03\x02\x02\x02\x02+\x03\x02\x02\x02\x02-\x03\x02\x02\x02\x02/\x03\x02" + + "\x02\x02\x025\x03\x02\x02\x02\x027\x03\x02\x02\x02\x029\x03\x02\x02\x02" + + "\x02;\x03\x02\x02\x02\x02=\x03\x02\x02\x02\x02?\x03\x02\x02\x02\x03A\x03" + + "\x02\x02\x02\x05C\x03\x02\x02\x02\x07E\x03\x02\x02\x02\tG\x03\x02\x02" + + "\x02\vI\x03\x02\x02\x02\rK\x03\x02\x02\x02\x0FM\x03\x02\x02\x02\x11O\x03" + + "\x02\x02\x02\x13R\x03\x02\x02\x02\x15U\x03\x02\x02\x02\x17X\x03\x02\x02" + + "\x02\x19Z\x03\x02\x02\x02\x1B\\\x03\x02\x02\x02\x1D_\x03\x02\x02\x02\x1F" + + "a\x03\x02\x02\x02!d\x03\x02\x02\x02#g\x03\x02\x02\x02%j\x03\x02\x02\x02" + + "\'l\x03\x02\x02\x02)n\x03\x02\x02\x02+p\x03\x02\x02\x02-r\x03\x02\x02" + + "\x02/t\x03\x02\x02\x021v\x03\x02\x02\x023x\x03\x02\x02\x025{\x03\x02\x02" + + "\x027\x87\x03\x02\x02\x029\x90\x03\x02\x02\x02;\x9B\x03\x02\x02\x02=\xB1" + + "\x03\x02\x02\x02?\xB3\x03\x02\x02\x02AB\x07#\x02\x02B\x04\x03\x02\x02" + + "\x02CD\x07/\x02\x02D\x06\x03\x02\x02\x02EF\x07-\x02\x02F\b\x03\x02\x02" + + "\x02GH\x07`\x02\x02H\n\x03\x02\x02\x02IJ\x07,\x02\x02J\f\x03\x02\x02\x02" + + "KL\x071\x02\x02L\x0E\x03\x02\x02\x02MN\x07\'\x02\x02N\x10\x03\x02\x02" + + "\x02OP\x07?\x02\x02PQ\x07?\x02\x02Q\x12\x03\x02\x02\x02RS\x07#\x02\x02" + + "ST\x07?\x02\x02T\x14\x03\x02\x02\x02UV\x07>\x02\x02VW\x07@\x02\x02W\x16" + + "\x03\x02\x02\x02XY\x07(\x02\x02Y\x18\x03\x02\x02\x02Z[\x07>\x02\x02[\x1A" + + "\x03\x02\x02\x02\\]\x07>\x02\x02]^\x07?\x02\x02^\x1C\x03\x02\x02\x02_" + + "`\x07@\x02\x02`\x1E\x03\x02\x02\x02ab\x07@\x02\x02bc\x07?\x02\x02c \x03" + + "\x02\x02\x02de\x07(\x02\x02ef\x07(\x02\x02f\"\x03\x02\x02\x02gh\x07~\x02" + + "\x02hi\x07~\x02\x02i$\x03\x02\x02\x02jk\x07*\x02\x02k&\x03\x02\x02\x02" + + "lm\x07+\x02\x02m(\x03\x02\x02\x02no\x070\x02\x02o*\x03\x02\x02\x02pq\x07" + + "]\x02\x02q,\x03\x02\x02\x02rs\x07_\x02\x02s.\x03\x02\x02\x02tu\x07.\x02" + + "\x02u0\x03\x02\x02\x02vw\t\x02\x02\x02w2\x03\x02\x02\x02xy\t\x03\x02\x02" + + "y4\x03\x02\x02\x02z|\x053\x1A\x02{z\x03\x02\x02\x02|}\x03\x02\x02\x02" + + "}{\x03\x02\x02\x02}~\x03\x02\x02\x02~\x85\x03\x02\x02\x02\x7F\x81\x07" + + "0\x02\x02\x80\x82\x053\x1A\x02\x81\x80\x03\x02\x02\x02\x82\x83\x03\x02" + + "\x02\x02\x83\x81\x03\x02\x02\x02\x83\x84\x03\x02\x02\x02\x84\x86\x03\x02" + + "\x02\x02\x85\x7F\x03\x02\x02\x02\x85\x86\x03\x02\x02\x02\x866\x03\x02" + + "\x02\x02\x87\x88\t\x04\x02\x02\x88\x89\x03\x02\x02\x02\x89\x8A\b\x1C\x02" + + "\x02\x8A8\x03\x02\x02\x02\x8B\x91\x051\x19\x02\x8C\x91\t\x05\x02\x02\x8D" + + "\x8E\x07B\x02\x02\x8E\x91\x07B\x02\x02\x8F\x91\x04&\'\x02\x90\x8B\x03" + + "\x02\x02\x02\x90\x8C\x03\x02\x02\x02\x90\x8D\x03\x02\x02\x02\x90\x8F\x03" + + "\x02\x02\x02\x91\x97\x03\x02\x02\x02\x92\x96\x051\x19\x02\x93\x96\x05" + + "3\x1A\x02\x94\x96\t\x06\x02\x02\x95\x92\x03\x02\x02\x02\x95\x93\x03\x02" + + "\x02\x02\x95\x94\x03\x02\x02\x02\x96\x99\x03\x02\x02\x02\x97\x95\x03\x02" + + "\x02\x02\x97\x98\x03\x02\x02\x02\x98:\x03\x02\x02\x02\x99\x97\x03\x02" + + "\x02\x02\x9A\x9C\x07\x0F\x02\x02\x9B\x9A\x03\x02\x02\x02\x9B\x9C\x03\x02" + + "\x02\x02\x9C\x9D\x03\x02\x02\x02\x9D\x9E\x07\f\x02\x02\x9E\x9F\x03\x02" + + "\x02\x02\x9F\xA0\b\x1E\x02\x02\xA0<\x03\x02\x02\x02\xA1\xA5\x07)\x02\x02" + + "\xA2\xA4\n\x07\x02\x02\xA3\xA2\x03\x02\x02\x02\xA4\xA7\x03\x02\x02\x02" + + "\xA5\xA3\x03\x02\x02\x02\xA5\xA6\x03\x02\x02\x02\xA6\xA8\x03\x02\x02\x02" + + "\xA7\xA5\x03\x02\x02\x02\xA8\xB2\x07)\x02\x02\xA9\xAD\x07$\x02\x02\xAA" + + "\xAC\n\b\x02\x02\xAB\xAA\x03\x02\x02\x02\xAC\xAF\x03\x02\x02\x02\xAD\xAB" + + "\x03\x02\x02\x02\xAD\xAE\x03\x02\x02\x02\xAE\xB0\x03\x02\x02\x02\xAF\xAD" + + "\x03\x02\x02\x02\xB0\xB2\x07$\x02\x02\xB1\xA1\x03\x02\x02\x02\xB1\xA9" + + "\x03\x02\x02\x02\xB2>\x03\x02\x02\x02\xB3\xB4\v\x02\x02\x02\xB4@\x03\x02" + + "\x02\x02\r\x02}\x83\x85\x90\x95\x97\x9B\xA5\xAD\xB1\x03\b\x02\x02"; public static __ATN: ATN; public static get _ATN(): ATN { if (!ExpressionLexer.__ATN) { diff --git a/libraries/botframework-expressions/src/parser/generated/ExpressionListener.ts b/libraries/botframework-expressions/src/parser/generated/ExpressionListener.ts index d291d35739..1936de9b96 100644 --- a/libraries/botframework-expressions/src/parser/generated/ExpressionListener.ts +++ b/libraries/botframework-expressions/src/parser/generated/ExpressionListener.ts @@ -12,13 +12,11 @@ import { ParseTreeListener } from "antlr4ts/tree/ParseTreeListener"; import { FuncInvokeExpContext } from "./ExpressionParser"; import { IdAtomContext } from "./ExpressionParser"; -import { ShorthandAccessorExpContext } from "./ExpressionParser"; import { StringAtomContext } from "./ExpressionParser"; import { IndexAccessExpContext } from "./ExpressionParser"; import { MemberAccessExpContext } from "./ExpressionParser"; import { ParenthesisExpContext } from "./ExpressionParser"; import { NumericAtomContext } from "./ExpressionParser"; -import { ShorthandAtomContext } from "./ExpressionParser"; import { UnaryOpExpContext } from "./ExpressionParser"; import { BinaryOpExpContext } from "./ExpressionParser"; import { PrimaryExpContext } from "./ExpressionParser"; @@ -59,19 +57,6 @@ export interface ExpressionListener extends ParseTreeListener { */ exitIdAtom?: (ctx: IdAtomContext) => void; - /** - * Enter a parse tree produced by the `shorthandAccessorExp` - * labeled alternative in `ExpressionParser.primaryExpression`. - * @param ctx the parse tree - */ - enterShorthandAccessorExp?: (ctx: ShorthandAccessorExpContext) => void; - /** - * Exit a parse tree produced by the `shorthandAccessorExp` - * labeled alternative in `ExpressionParser.primaryExpression`. - * @param ctx the parse tree - */ - exitShorthandAccessorExp?: (ctx: ShorthandAccessorExpContext) => void; - /** * Enter a parse tree produced by the `stringAtom` * labeled alternative in `ExpressionParser.primaryExpression`. @@ -137,19 +122,6 @@ export interface ExpressionListener extends ParseTreeListener { */ exitNumericAtom?: (ctx: NumericAtomContext) => void; - /** - * Enter a parse tree produced by the `shorthandAtom` - * labeled alternative in `ExpressionParser.primaryExpression`. - * @param ctx the parse tree - */ - enterShorthandAtom?: (ctx: ShorthandAtomContext) => void; - /** - * Exit a parse tree produced by the `shorthandAtom` - * labeled alternative in `ExpressionParser.primaryExpression`. - * @param ctx the parse tree - */ - exitShorthandAtom?: (ctx: ShorthandAtomContext) => void; - /** * Enter a parse tree produced by the `unaryOpExp` * labeled alternative in `ExpressionParser.expression`. diff --git a/libraries/botframework-expressions/src/parser/generated/ExpressionParser.ts b/libraries/botframework-expressions/src/parser/generated/ExpressionParser.ts index f5f5673558..a35de4fc25 100644 --- a/libraries/botframework-expressions/src/parser/generated/ExpressionParser.ts +++ b/libraries/botframework-expressions/src/parser/generated/ExpressionParser.ts @@ -58,17 +58,12 @@ export class ExpressionParser extends Parser { public static readonly T__20 = 21; public static readonly T__21 = 22; public static readonly T__22 = 23; - public static readonly T__23 = 24; - public static readonly T__24 = 25; - public static readonly T__25 = 26; - public static readonly T__26 = 27; - public static readonly T__27 = 28; - public static readonly NUMBER = 29; - public static readonly WHITESPACE = 30; - public static readonly IDENTIFIER = 31; - public static readonly NEWLINE = 32; - public static readonly STRING = 33; - public static readonly INVALID_TOKEN_DEFAULT_MODE = 34; + public static readonly NUMBER = 24; + public static readonly WHITESPACE = 25; + public static readonly IDENTIFIER = 26; + public static readonly NEWLINE = 27; + public static readonly STRING = 28; + public static readonly INVALID_TOKEN_DEFAULT_MODE = 29; public static readonly RULE_file = 0; public static readonly RULE_expression = 1; public static readonly RULE_primaryExpression = 2; @@ -81,15 +76,14 @@ export class ExpressionParser extends Parser { private static readonly _LITERAL_NAMES: Array = [ undefined, "'!'", "'-'", "'+'", "'^'", "'*'", "'/'", "'%'", "'=='", "'!='", "'<>'", "'&'", "'<'", "'<='", "'>'", "'>='", "'&&'", "'||'", "'('", "')'", - "'#'", "'@'", "'@@'", "'$'", "'~'", "'.'", "'['", "']'", "','", + "'.'", "'['", "']'", "','", ]; private static readonly _SYMBOLIC_NAMES: Array = [ undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, - undefined, undefined, undefined, undefined, undefined, undefined, undefined, - undefined, "NUMBER", "WHITESPACE", "IDENTIFIER", "NEWLINE", "STRING", - "INVALID_TOKEN_DEFAULT_MODE", + undefined, undefined, undefined, "NUMBER", "WHITESPACE", "IDENTIFIER", + "NEWLINE", "STRING", "INVALID_TOKEN_DEFAULT_MODE", ]; public static readonly VOCABULARY: Vocabulary = new VocabularyImpl(ExpressionParser._LITERAL_NAMES, ExpressionParser._SYMBOLIC_NAMES, []); @@ -187,14 +181,7 @@ export class ExpressionParser extends Parser { this.expression(10); } break; - case ExpressionParser.T__3: - case ExpressionParser.T__6: case ExpressionParser.T__17: - case ExpressionParser.T__19: - case ExpressionParser.T__20: - case ExpressionParser.T__21: - case ExpressionParser.T__22: - case ExpressionParser.T__23: case ExpressionParser.NUMBER: case ExpressionParser.IDENTIFIER: case ExpressionParser.STRING: @@ -427,7 +414,7 @@ export class ExpressionParser extends Parser { let _alt: number; this.enterOuterAlt(_localctx, 1); { - this.state = 55; + this.state = 54; this._errHandler.sync(this); switch (this._input.LA(1)) { case ExpressionParser.T__17: @@ -471,36 +458,11 @@ export class ExpressionParser extends Parser { this.match(ExpressionParser.IDENTIFIER); } break; - case ExpressionParser.T__3: - case ExpressionParser.T__6: - case ExpressionParser.T__19: - case ExpressionParser.T__20: - case ExpressionParser.T__21: - case ExpressionParser.T__22: - case ExpressionParser.T__23: - { - _localctx = new ShorthandAtomContext(_localctx); - this._ctx = _localctx; - _prevctx = _localctx; - this.state = 54; - _la = this._input.LA(1); - if (!((((_la) & ~0x1F) === 0 && ((1 << _la) & ((1 << ExpressionParser.T__3) | (1 << ExpressionParser.T__6) | (1 << ExpressionParser.T__19) | (1 << ExpressionParser.T__20) | (1 << ExpressionParser.T__21) | (1 << ExpressionParser.T__22) | (1 << ExpressionParser.T__23))) !== 0))) { - this._errHandler.recoverInline(this); - } else { - if (this._input.LA(1) === Token.EOF) { - this.matchedEOF = true; - } - - this._errHandler.reportMatch(this); - this.consume(); - } - } - break; default: throw new NoViableAltException(this); } this._ctx._stop = this._input.tryLT(-1); - this.state = 75; + this.state = 72; this._errHandler.sync(this); _alt = this.interpreter.adaptivePredict(this._input, 6, this._ctx); while (_alt !== 2 && _alt !== ATN.INVALID_ALT_NUMBER) { @@ -510,82 +472,69 @@ export class ExpressionParser extends Parser { } _prevctx = _localctx; { - this.state = 73; + this.state = 70; this._errHandler.sync(this); switch ( this.interpreter.adaptivePredict(this._input, 5, this._ctx) ) { case 1: - { - _localctx = new ShorthandAccessorExpContext(new PrimaryExpressionContext(_parentctx, _parentState)); - this.pushNewRecursionContext(_localctx, _startState, ExpressionParser.RULE_primaryExpression); - this.state = 57; - if (!(this.precpred(this._ctx, 4))) { - throw new FailedPredicateException(this, "this.precpred(this._ctx, 4)"); - } - this.state = 58; - this.match(ExpressionParser.IDENTIFIER); - } - break; - - case 2: { _localctx = new MemberAccessExpContext(new PrimaryExpressionContext(_parentctx, _parentState)); this.pushNewRecursionContext(_localctx, _startState, ExpressionParser.RULE_primaryExpression); - this.state = 59; + this.state = 56; if (!(this.precpred(this._ctx, 3))) { throw new FailedPredicateException(this, "this.precpred(this._ctx, 3)"); } - this.state = 60; - this.match(ExpressionParser.T__24); - this.state = 61; + this.state = 57; + this.match(ExpressionParser.T__19); + this.state = 58; this.match(ExpressionParser.IDENTIFIER); } break; - case 3: + case 2: { _localctx = new FuncInvokeExpContext(new PrimaryExpressionContext(_parentctx, _parentState)); this.pushNewRecursionContext(_localctx, _startState, ExpressionParser.RULE_primaryExpression); - this.state = 62; + this.state = 59; if (!(this.precpred(this._ctx, 2))) { throw new FailedPredicateException(this, "this.precpred(this._ctx, 2)"); } - this.state = 63; + this.state = 60; this.match(ExpressionParser.T__17); - this.state = 65; + this.state = 62; this._errHandler.sync(this); _la = this._input.LA(1); - if ((((_la) & ~0x1F) === 0 && ((1 << _la) & ((1 << ExpressionParser.T__0) | (1 << ExpressionParser.T__1) | (1 << ExpressionParser.T__2) | (1 << ExpressionParser.T__3) | (1 << ExpressionParser.T__6) | (1 << ExpressionParser.T__17) | (1 << ExpressionParser.T__19) | (1 << ExpressionParser.T__20) | (1 << ExpressionParser.T__21) | (1 << ExpressionParser.T__22) | (1 << ExpressionParser.T__23) | (1 << ExpressionParser.NUMBER) | (1 << ExpressionParser.IDENTIFIER))) !== 0) || _la === ExpressionParser.STRING) { + if ((((_la) & ~0x1F) === 0 && ((1 << _la) & ((1 << ExpressionParser.T__0) | (1 << ExpressionParser.T__1) | (1 << ExpressionParser.T__2) | (1 << ExpressionParser.T__17) | (1 << ExpressionParser.NUMBER) | (1 << ExpressionParser.IDENTIFIER) | (1 << ExpressionParser.STRING))) !== 0)) { { - this.state = 64; + this.state = 61; this.argsList(); } } - this.state = 67; + this.state = 64; this.match(ExpressionParser.T__18); } break; - case 4: + case 3: { _localctx = new IndexAccessExpContext(new PrimaryExpressionContext(_parentctx, _parentState)); this.pushNewRecursionContext(_localctx, _startState, ExpressionParser.RULE_primaryExpression); - this.state = 68; + this.state = 65; if (!(this.precpred(this._ctx, 1))) { throw new FailedPredicateException(this, "this.precpred(this._ctx, 1)"); } - this.state = 69; - this.match(ExpressionParser.T__25); - this.state = 70; + this.state = 66; + this.match(ExpressionParser.T__20); + this.state = 67; this.expression(0); - this.state = 71; - this.match(ExpressionParser.T__26); + this.state = 68; + this.match(ExpressionParser.T__21); } break; } } } - this.state = 77; + this.state = 74; this._errHandler.sync(this); _alt = this.interpreter.adaptivePredict(this._input, 6, this._ctx); } @@ -613,21 +562,21 @@ export class ExpressionParser extends Parser { try { this.enterOuterAlt(_localctx, 1); { - this.state = 78; + this.state = 75; this.expression(0); - this.state = 83; + this.state = 80; this._errHandler.sync(this); _la = this._input.LA(1); - while (_la === ExpressionParser.T__27) { + while (_la === ExpressionParser.T__22) { { { - this.state = 79; - this.match(ExpressionParser.T__27); - this.state = 80; + this.state = 76; + this.match(ExpressionParser.T__22); + this.state = 77; this.expression(0); } } - this.state = 85; + this.state = 82; this._errHandler.sync(this); _la = this._input.LA(1); } @@ -689,61 +638,56 @@ export class ExpressionParser extends Parser { private primaryExpression_sempred(_localctx: PrimaryExpressionContext, predIndex: number): boolean { switch (predIndex) { case 8: - return this.precpred(this._ctx, 4); - - case 9: return this.precpred(this._ctx, 3); - case 10: + case 9: return this.precpred(this._ctx, 2); - case 11: + case 10: return this.precpred(this._ctx, 1); } return true; } public static readonly _serializedATN: string = - "\x03\uAF6F\u8320\u479D\uB75C\u4880\u1605\u191C\uAB37\x03$Y\x04\x02\t\x02" + - "\x04\x03\t\x03\x04\x04\t\x04\x04\x05\t\x05\x03\x02\x03\x02\x03\x02\x03" + - "\x03\x03\x03\x03\x03\x03\x03\x05\x03\x12\n\x03\x03\x03\x03\x03\x03\x03" + + "\x03\uAF6F\u8320\u479D\uB75C\u4880\u1605\u191C\uAB37\x03\x1FV\x04\x02" + + "\t\x02\x04\x03\t\x03\x04\x04\t\x04\x04\x05\t\x05\x03\x02\x03\x02\x03\x02" + + "\x03\x03\x03\x03\x03\x03\x03\x03\x05\x03\x12\n\x03\x03\x03\x03\x03\x03" + "\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03" + "\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03" + - "\x03\x03\x03\x03\x03\x03\x07\x03,\n\x03\f\x03\x0E\x03/\v\x03\x03\x04\x03" + - "\x04\x03\x04\x03\x04\x03\x04\x03\x04\x03\x04\x03\x04\x03\x04\x05\x04:" + - "\n\x04\x03\x04\x03\x04\x03\x04\x03\x04\x03\x04\x03\x04\x03\x04\x03\x04" + - "\x05\x04D\n\x04\x03\x04\x03\x04\x03\x04\x03\x04\x03\x04\x03\x04\x07\x04" + - "L\n\x04\f\x04\x0E\x04O\v\x04\x03\x05\x03\x05\x03\x05\x07\x05T\n\x05\f" + - "\x05\x0E\x05W\v\x05\x03\x05\x02\x02\x04\x04\x06\x06\x02\x02\x04\x02\x06" + - "\x02\b\x02\x02\b\x03\x02\x03\x05\x03\x02\x07\t\x03\x02\x04\x05\x03\x02" + - "\n\f\x03\x02\x0E\x11\x05\x02\x06\x06\t\t\x16\x1Ag\x02\n\x03\x02\x02\x02" + - "\x04\x11\x03\x02\x02\x02\x069\x03\x02\x02\x02\bP\x03\x02\x02\x02\n\v\x05" + - "\x04\x03\x02\v\f\x07\x02\x02\x03\f\x03\x03\x02\x02\x02\r\x0E\b\x03\x01" + - "\x02\x0E\x0F\t\x02\x02\x02\x0F\x12\x05\x04\x03\f\x10\x12\x05\x06\x04\x02" + - "\x11\r\x03\x02\x02\x02\x11\x10\x03\x02\x02\x02\x12-\x03\x02\x02\x02\x13" + - "\x14\f\v\x02\x02\x14\x15\x07\x06\x02\x02\x15,\x05\x04\x03\v\x16\x17\f" + - "\n\x02\x02\x17\x18\t\x03\x02\x02\x18,\x05\x04\x03\v\x19\x1A\f\t\x02\x02" + - "\x1A\x1B\t\x04\x02\x02\x1B,\x05\x04\x03\n\x1C\x1D\f\b\x02\x02\x1D\x1E" + - "\t\x05\x02\x02\x1E,\x05\x04\x03\t\x1F \f\x07\x02\x02 !\x07\r\x02\x02!" + - ",\x05\x04\x03\b\"#\f\x06\x02\x02#$\t\x06\x02\x02$,\x05\x04\x03\x07%&\f" + - "\x05\x02\x02&\'\x07\x12\x02\x02\',\x05\x04\x03\x06()\f\x04\x02\x02)*\x07" + - "\x13\x02\x02*,\x05\x04\x03\x05+\x13\x03\x02\x02\x02+\x16\x03\x02\x02\x02" + - "+\x19\x03\x02\x02\x02+\x1C\x03\x02\x02\x02+\x1F\x03\x02\x02\x02+\"\x03" + - "\x02\x02\x02+%\x03\x02\x02\x02+(\x03\x02\x02\x02,/\x03\x02\x02\x02-+\x03" + - "\x02\x02\x02-.\x03\x02\x02\x02.\x05\x03\x02\x02\x02/-\x03\x02\x02\x02" + - "01\b\x04\x01\x0212\x07\x14\x02\x0223\x05\x04\x03\x0234\x07\x15\x02\x02" + - "4:\x03\x02\x02\x025:\x07\x1F\x02\x026:\x07#\x02\x027:\x07!\x02\x028:\t" + - "\x07\x02\x0290\x03\x02\x02\x0295\x03\x02\x02\x0296\x03\x02\x02\x0297\x03" + - "\x02\x02\x0298\x03\x02\x02\x02:M\x03\x02\x02\x02;<\f\x06\x02\x02\f\x05\x02\x02>?\x07\x1B\x02\x02?L\x07!\x02\x02@A\f\x04\x02" + - "\x02AC\x07\x14\x02\x02BD\x05\b\x05\x02CB\x03\x02\x02\x02CD\x03\x02\x02" + - "\x02DE\x03\x02\x02\x02EL\x07\x15\x02\x02FG\f\x03\x02\x02GH\x07\x1C\x02" + - "\x02HI\x05\x04\x03\x02IJ\x07\x1D\x02\x02JL\x03\x02\x02\x02K;\x03\x02\x02" + - "\x02K=\x03\x02\x02\x02K@\x03\x02\x02\x02KF\x03\x02\x02\x02LO\x03\x02\x02" + - "\x02MK\x03\x02\x02\x02MN\x03\x02\x02\x02N\x07\x03\x02\x02\x02OM\x03\x02" + - "\x02\x02PU\x05\x04\x03\x02QR\x07\x1E\x02\x02RT\x05\x04\x03\x02SQ\x03\x02" + - "\x02\x02TW\x03\x02\x02\x02US\x03\x02\x02\x02UV\x03\x02\x02\x02V\t\x03" + - "\x02\x02\x02WU\x03\x02\x02\x02\n\x11+-9CKMU"; + "\x03\x03\x03\x03\x03\x03\x03\x07\x03,\n\x03\f\x03\x0E\x03/\v\x03\x03\x04" + + "\x03\x04\x03\x04\x03\x04\x03\x04\x03\x04\x03\x04\x03\x04\x05\x049\n\x04" + + "\x03\x04\x03\x04\x03\x04\x03\x04\x03\x04\x03\x04\x05\x04A\n\x04\x03\x04" + + "\x03\x04\x03\x04\x03\x04\x03\x04\x03\x04\x07\x04I\n\x04\f\x04\x0E\x04" + + "L\v\x04\x03\x05\x03\x05\x03\x05\x07\x05Q\n\x05\f\x05\x0E\x05T\v\x05\x03" + + "\x05\x02\x02\x04\x04\x06\x06\x02\x02\x04\x02\x06\x02\b\x02\x02\x07\x03" + + "\x02\x03\x05\x03\x02\x07\t\x03\x02\x04\x05\x03\x02\n\f\x03\x02\x0E\x11" + + "b\x02\n\x03\x02\x02\x02\x04\x11\x03\x02\x02\x02\x068\x03\x02\x02\x02\b" + + "M\x03\x02\x02\x02\n\v\x05\x04\x03\x02\v\f\x07\x02\x02\x03\f\x03\x03\x02" + + "\x02\x02\r\x0E\b\x03\x01\x02\x0E\x0F\t\x02\x02\x02\x0F\x12\x05\x04\x03" + + "\f\x10\x12\x05\x06\x04\x02\x11\r\x03\x02\x02\x02\x11\x10\x03\x02\x02\x02" + + "\x12-\x03\x02\x02\x02\x13\x14\f\v\x02\x02\x14\x15\x07\x06\x02\x02\x15" + + ",\x05\x04\x03\v\x16\x17\f\n\x02\x02\x17\x18\t\x03\x02\x02\x18,\x05\x04" + + "\x03\v\x19\x1A\f\t\x02\x02\x1A\x1B\t\x04\x02\x02\x1B,\x05\x04\x03\n\x1C" + + "\x1D\f\b\x02\x02\x1D\x1E\t\x05\x02\x02\x1E,\x05\x04\x03\t\x1F \f\x07\x02" + + "\x02 !\x07\r\x02\x02!,\x05\x04\x03\b\"#\f\x06\x02\x02#$\t\x06\x02\x02" + + "$,\x05\x04\x03\x07%&\f\x05\x02\x02&\'\x07\x12\x02\x02\',\x05\x04\x03\x06" + + "()\f\x04\x02\x02)*\x07\x13\x02\x02*,\x05\x04\x03\x05+\x13\x03\x02\x02" + + "\x02+\x16\x03\x02\x02\x02+\x19\x03\x02\x02\x02+\x1C\x03\x02\x02\x02+\x1F" + + "\x03\x02\x02\x02+\"\x03\x02\x02\x02+%\x03\x02\x02\x02+(\x03\x02\x02\x02" + + ",/\x03\x02\x02\x02-+\x03\x02\x02\x02-.\x03\x02\x02\x02.\x05\x03\x02\x02" + + "\x02/-\x03\x02\x02\x0201\b\x04\x01\x0212\x07\x14\x02\x0223\x05\x04\x03" + + "\x0234\x07\x15\x02\x0249\x03\x02\x02\x0259\x07\x1A\x02\x0269\x07\x1E\x02" + + "\x0279\x07\x1C\x02\x0280\x03\x02\x02\x0285\x03\x02\x02\x0286\x03\x02\x02" + + "\x0287\x03\x02\x02\x029J\x03\x02\x02\x02:;\f\x05\x02\x02;<\x07\x16\x02" + + "\x02\f\x04\x02\x02>@\x07\x14\x02\x02?A\x05\b\x05\x02" + + "@?\x03\x02\x02\x02@A\x03\x02\x02\x02AB\x03\x02\x02\x02BI\x07\x15\x02\x02" + + "CD\f\x03\x02\x02DE\x07\x17\x02\x02EF\x05\x04\x03\x02FG\x07\x18\x02\x02" + + "GI\x03\x02\x02\x02H:\x03\x02\x02\x02H=\x03\x02\x02\x02HC\x03\x02\x02\x02" + + "IL\x03\x02\x02\x02JH\x03\x02\x02\x02JK\x03\x02\x02\x02K\x07\x03\x02\x02" + + "\x02LJ\x03\x02\x02\x02MR\x05\x04\x03\x02NO\x07\x19\x02\x02OQ\x05\x04\x03" + + "\x02PN\x03\x02\x02\x02QT\x03\x02\x02\x02RP\x03\x02\x02\x02RS\x03\x02\x02" + + "\x02S\t\x03\x02\x02\x02TR\x03\x02\x02\x02\n\x11+-8@HJR"; public static __ATN: ATN; public static get _ATN(): ATN { if (!ExpressionParser.__ATN) { @@ -962,36 +906,6 @@ export class IdAtomContext extends PrimaryExpressionContext { } } } -export class ShorthandAccessorExpContext extends PrimaryExpressionContext { - public primaryExpression(): PrimaryExpressionContext { - return this.getRuleContext(0, PrimaryExpressionContext); - } - public IDENTIFIER(): TerminalNode { return this.getToken(ExpressionParser.IDENTIFIER, 0); } - constructor(ctx: PrimaryExpressionContext) { - super(ctx.parent, ctx.invokingState); - this.copyFrom(ctx); - } - // @Override - public enterRule(listener: ExpressionListener): void { - if (listener.enterShorthandAccessorExp) { - listener.enterShorthandAccessorExp(this); - } - } - // @Override - public exitRule(listener: ExpressionListener): void { - if (listener.exitShorthandAccessorExp) { - listener.exitShorthandAccessorExp(this); - } - } - // @Override - public accept(visitor: ExpressionVisitor): Result { - if (visitor.visitShorthandAccessorExp) { - return visitor.visitShorthandAccessorExp(this); - } else { - return visitor.visitChildren(this); - } - } -} export class StringAtomContext extends PrimaryExpressionContext { public STRING(): TerminalNode { return this.getToken(ExpressionParser.STRING, 0); } constructor(ctx: PrimaryExpressionContext) { @@ -1137,32 +1051,6 @@ export class NumericAtomContext extends PrimaryExpressionContext { } } } -export class ShorthandAtomContext extends PrimaryExpressionContext { - constructor(ctx: PrimaryExpressionContext) { - super(ctx.parent, ctx.invokingState); - this.copyFrom(ctx); - } - // @Override - public enterRule(listener: ExpressionListener): void { - if (listener.enterShorthandAtom) { - listener.enterShorthandAtom(this); - } - } - // @Override - public exitRule(listener: ExpressionListener): void { - if (listener.exitShorthandAtom) { - listener.exitShorthandAtom(this); - } - } - // @Override - public accept(visitor: ExpressionVisitor): Result { - if (visitor.visitShorthandAtom) { - return visitor.visitShorthandAtom(this); - } else { - return visitor.visitChildren(this); - } - } -} export class ArgsListContext extends ParserRuleContext { diff --git a/libraries/botframework-expressions/src/parser/generated/ExpressionVisitor.ts b/libraries/botframework-expressions/src/parser/generated/ExpressionVisitor.ts index a2b7ca3578..4be66a547c 100644 --- a/libraries/botframework-expressions/src/parser/generated/ExpressionVisitor.ts +++ b/libraries/botframework-expressions/src/parser/generated/ExpressionVisitor.ts @@ -12,13 +12,11 @@ import { ParseTreeVisitor } from "antlr4ts/tree/ParseTreeVisitor"; import { FuncInvokeExpContext } from "./ExpressionParser"; import { IdAtomContext } from "./ExpressionParser"; -import { ShorthandAccessorExpContext } from "./ExpressionParser"; import { StringAtomContext } from "./ExpressionParser"; import { IndexAccessExpContext } from "./ExpressionParser"; import { MemberAccessExpContext } from "./ExpressionParser"; import { ParenthesisExpContext } from "./ExpressionParser"; import { NumericAtomContext } from "./ExpressionParser"; -import { ShorthandAtomContext } from "./ExpressionParser"; import { UnaryOpExpContext } from "./ExpressionParser"; import { BinaryOpExpContext } from "./ExpressionParser"; import { PrimaryExpContext } from "./ExpressionParser"; @@ -52,14 +50,6 @@ export interface ExpressionVisitor extends ParseTreeVisitor { */ visitIdAtom?: (ctx: IdAtomContext) => Result; - /** - * Visit a parse tree produced by the `shorthandAccessorExp` - * labeled alternative in `ExpressionParser.primaryExpression`. - * @param ctx the parse tree - * @return the visitor result - */ - visitShorthandAccessorExp?: (ctx: ShorthandAccessorExpContext) => Result; - /** * Visit a parse tree produced by the `stringAtom` * labeled alternative in `ExpressionParser.primaryExpression`. @@ -100,14 +90,6 @@ export interface ExpressionVisitor extends ParseTreeVisitor { */ visitNumericAtom?: (ctx: NumericAtomContext) => Result; - /** - * Visit a parse tree produced by the `shorthandAtom` - * labeled alternative in `ExpressionParser.primaryExpression`. - * @param ctx the parse tree - * @return the visitor result - */ - visitShorthandAtom?: (ctx: ShorthandAtomContext) => Result; - /** * Visit a parse tree produced by the `unaryOpExp` * labeled alternative in `ExpressionParser.expression`. diff --git a/libraries/botframework-expressions/src/parser/parseErrorListener.ts b/libraries/botframework-expressions/src/parser/parseErrorListener.ts index e3ba79efa5..cdce993fc6 100644 --- a/libraries/botframework-expressions/src/parser/parseErrorListener.ts +++ b/libraries/botframework-expressions/src/parser/parseErrorListener.ts @@ -17,6 +17,7 @@ export class ParseErrorListener implements ANTLRErrorListener { line: number, charPositionInLine: number, msg: string, + // eslint-disable-next-line @typescript-eslint/no-unused-vars _e: RecognitionException | undefined): void { throw Error(`syntax error at line ${ line }:${ charPositionInLine } ${ msg }`); } diff --git a/libraries/botframework-expressions/tests/badExpression.test.js b/libraries/botframework-expressions/tests/badExpression.test.js index ca8ade0117..21514c7980 100644 --- a/libraries/botframework-expressions/tests/badExpression.test.js +++ b/libraries/botframework-expressions/tests/badExpression.test.js @@ -8,10 +8,9 @@ const invalidExpressions = [ 'a+b*', 'fun(a, b, c', 'func(A,b,b,)', - 'a.#title', '"hello\'', 'user.lists.{dialog.listName}', - 'a===a' + '\'hello\'.length()' ]; const badExpressions = @@ -327,15 +326,8 @@ const badExpressions = 'jPath(hello, \'.key\')', //bad json 'jPath(json(\'{"key1":"value1","key2":"value2"}\'), \'getTotal\')', //bad path - - // Short Hand Expression - '%.xxx', // not supported shorthand pattern - '@[city]', // city is not provided. - '@[0]', // entities is not a collection. - // Memory access test 'getProperty(bag, 1)',// second param should be string - 'bag[1]',// first param should be string 'Accessor(1)',// first param should be string 'Accessor(bag, 1)', // second should be object 'one[0]', // one is not list @@ -349,7 +341,6 @@ const badExpressions = 'isMatch(\'abC\', \'^[a-z+$\')',// bad regular expression // SetPathToValue tests - 'setPathToValue(@foo, 3)', // Cannot set simple entities 'setPathToValue(2+3, 4)', // Not a real path 'setPathToValue(a)' // Missing value ]; diff --git a/libraries/botframework-expressions/tests/expression.test.js b/libraries/botframework-expressions/tests/expression.test.js index 8fcc91e18b..f22fa0c8bd 100644 --- a/libraries/botframework-expressions/tests/expression.test.js +++ b/libraries/botframework-expressions/tests/expression.test.js @@ -144,10 +144,6 @@ const dataSource = [ ['lastIndexOf(newGuid(), \'-\')', 23], ['lastIndexOf(newGuid(), \'-\')', 23], ['lastIndexOf(hello, \'-\')', -1], - ['sortBy(items)', ['one', 'two', 'zero']], - ['sortBy(nestedItems, \'x\')[0].x', 1], - ['sortByDescending(items)', ['zero', 'two', 'one']], - ['sortByDescending(nestedItems, \'x\')[0].x', 3], // Logical comparison functions tests ['and(1 == 1, 1 < 2, 1 > 2)', false], @@ -384,6 +380,10 @@ const dataSource = [ ['subArray(createArray(\'a\', \'b\', \'c\', \'d\'), 1)', ['b', 'c', 'd']], ['range(1, 4)', [1, 2, 3, 4]], ['range(-1, 3)', [-1, 0, 1]], + ['sortBy(items)', ['one', 'two', 'zero']], + ['sortBy(nestedItems, \'x\')[0].x', 1], + ['sortByDescending(items)', ['zero', 'two', 'one']], + ['sortByDescending(nestedItems, \'x\')[0].x', 3], // Object manipulation and construction functions tests ['string(addProperty(json(\'{"key1":"value1"}\'), \'key2\',\'value2\'))', '{"key1":"value1","key2":"value2"}'], @@ -393,52 +393,17 @@ const dataSource = [ ['jPath(jsonStr, pathStr )', ['Jazz', 'Accord']], ['jPath(jsonStr, \'.automobiles[0].maker\' )', ['Nissan']], - // Short hand expression tests - ['@city == \'Bellevue\'', false, ['turn.recognized.entities.city']], - ['@city', 'Seattle', ['turn.recognized.entities.city']], - ['@city == \'Seattle\'', true, ['turn.recognized.entities.city']], - ['@ordinal[1]', '2', ['turn.recognized.entities.ordinal']], - ['@[\'city\']', 'Seattle', ['turn.recognized.entities.city']], - ['@[concat(\'cit\', \'y\')]', 'Seattle', ['turn.recognized.entities']], - ['@[concat(cit, y)]', 'Seattle', ['turn.recognized.entities','cit','y']], - ['#BookFlight == \'BookFlight\'', true, ['turn.recognized.intents.BookFlight']], - ['#BookHotel[1].Where', 'Kirkland', ['turn.recognized.intents.BookHotel[1].Where']], - ['exists(#BookFlight)', true, ['turn.recognized.intents.BookFlight']], - ['$title', 'Dialog Title', ['dialog.title']], - ['$subTitle', 'Dialog Sub Title', ['dialog.subTitle']], - ['~xxx', 'instance', ['dialog.instance.xxx']], - ['~[\'yyy\'].instanceY', 'instanceY', ['dialog.instance.yyy.instanceY']], - ['%xxx', 'options', ['dialog.options.xxx']], - ['%[\'xxx\']', 'options', ['dialog.options.xxx']], - ['%yyy[1]', 'optionY2', ['dialog.options.yyy[1]']], - ['^x', 3], - ['^y', 2], - ['^z', 1], - ['count(@@CompositeList1) == 1 && count(@@CompositeList1[0]) == 1', true, ['turn.recognized.entities.CompositeList1', 'turn.recognized.entities.CompositeList1[0]']], - ['count(@CompositeList2) == 2 && (@CompositeList2)[0] == \'firstItem\'', true, ['turn.recognized.entities.CompositeList2']], - // Memory access tests ['getProperty(bag, concat(\'na\',\'me\'))', 'mybag'], - ['getProperty(bag, \'Name\')', 'mybag'], - ['getProperty(bag.set, \'FOUR\')', 4.0], ['items[2]', 'two', ['items[2]']], ['bag.list[bag.index - 2]', 'blue', ['bag.list', 'bag.index']], ['items[nestedItems[1].x]', 'two', ['items', 'nestedItems[1].x']], ['bag[\'name\']', 'mybag'], ['bag[substring(concat(\'na\',\'me\',\'more\'), 0, length(\'name\'))]', 'mybag'], - ['bag[\'NAME\']', 'mybag'], - ['bag.set[concat(\'Fo\', \'UR\')]', 4.0], + ['items[1+1]', 'two'], ['getProperty(undefined, \'p\')', undefined], ['(getProperty(undefined, \'p\'))[1]', undefined], - // Dialog tests - ['user.lists.todo[int(@ordinal[0]) - 1] != null', true], - ['user.lists.todo[int(@ordinal[0]) + 3] != null', false], - ['count(user.lists.todo) > int(@ordinal[0])', true], - ['count(user.lists.todo) >= int(@ordinal[0])', true], - ['user.lists.todo[int(@ordinal[0]) - 1]', 'todo1'], - ['user.lists[user.listType][int(@ordinal[0]) - 1]', 'todo1'], - // regex test ['isMatch(\'abc\', \'^[ab]+$\')', false], // simple character classes ([abc]), "+" (one or more) ['isMatch(\'abb\', \'^[ab]+$\')', true], // simple character classes ([abc]) @@ -469,20 +434,25 @@ const dataSource = [ ['', ''], // SetPathToValue tests - ['setPathToValue(@@blah.woof, 1+2) + @@blah.woof', 6], ['setPathToValue(path.simple, 3) + path.simple', 6], ['setPathToValue(path.simple, 5) + path.simple', 10], ['setPathToValue(path.array[0], 7) + path.array[0]', 14], ['setPathToValue(path.array[1], 9) + path.array[1]', 18], + /* ['setPathToValue(path.darray[2][0], 11) + path.darray[2][0]', 22], ['setPathToValue(path.darray[2][3].foo, 13) + path.darray[2][3].foo', 26], ['setPathToValue(path.overwrite, 3) + setPathToValue(path.overwrite[0], 4) + path.overwrite[0]', 11], ['setPathToValue(path.overwrite[0], 3) + setPathToValue(path.overwrite, 4) + path.overwrite', 11], ['setPathToValue(path.overwrite.prop, 3) + setPathToValue(path.overwrite, 4) + path.overwrite', 11], ['setPathToValue(path.overwrite.prop, 3) + setPathToValue(path.overwrite[0], 4) + path.overwrite[0]', 11], + */ + ['setPathToValue(path.x, null)', undefined], ]; const scope = { + path: { + array: [1] + }, one: 1.0, two: 2.0, hello: 'hello', @@ -558,13 +528,7 @@ const scope = { options: { xxx: 'options', yyy : ['optionY1', 'optionY2' ] }, title: 'Dialog Title', subTitle: 'Dialog Sub Title' - }, - callstack: - [ - { x: 3 }, - { x: 2, y: 2 }, - { x: 1, y: 1, z: 1 } - ] + } }; describe('expression functional test', () => { @@ -580,7 +544,7 @@ describe('expression functional test', () => { const expected = data[1]; //Assert Object Equals - if (actual instanceof Array && expected instanceof Array) { + if (Array.isArray(actual) && Array.isArray(expected)) { const [isSuccess, errorMessage] = isArraySame(actual, expected); if (!isSuccess) { assert.fail(errorMessage); From 64db55cb64cf6f368369d50a2f589c046c196356 Mon Sep 17 00:00:00 2001 From: "Hongyang Du (hond)" Date: Wed, 4 Dec 2019 10:37:19 +0800 Subject: [PATCH 4/6] Update README.MD (#1463) --- libraries/botbuilder-lg/README.MD | 70 +++++++++++++------------------ 1 file changed, 28 insertions(+), 42 deletions(-) diff --git a/libraries/botbuilder-lg/README.MD b/libraries/botbuilder-lg/README.MD index 61dfc0829d..cfcfcc0e28 100644 --- a/libraries/botbuilder-lg/README.MD +++ b/libraries/botbuilder-lg/README.MD @@ -22,46 +22,26 @@ You can use language generation to: - achieve a coherent personality, tone of voice for your bot - separate business logic from presentation - include variations and sophisticated composition based resolution for any of your bot's replies -- construct speak .vs. display adaptations -- construct cards, suggested actions and attachments. +- structured LG -## Speak .vs. display adaptation -By design, the .lg file format does not explicitly support the ability to provide speak .vs. display adaptation. The file format supports simple constructs that are composable and supports resolution on multi-line text and so you can have syntax and semantics for speak .vs. display adaptation, cards, suggested actions etc that can be interpreted as simple text and transformed into the Bot Framework [activity][1] by a layer above language generation. +## structured LG +The type of LG output could be string or object, string is by default. But LG could generate a json object by Structured LG feature. -Bot Builder SDK supports a short hand notation that can parse and transform a piece of text separated by `displayText`||`spokenText` into speak and display text. - -```markdown -# greetingTemplate -- hi || hi there -- hello || hello, what can I help with today -``` - -You can use the `TextMessageActivityGenerator.CreateActityFromText` method to transform the command into a Bot Framework activity to post back to the user. - -## Using Chatdown style cards - -[Chatdown][6] introduced a simple markdown based way to write mock conversations. Also introduced as part of the [.chat][7] file format was the ability to express different [message commands][9] via simple text representation. Message commands include [cards][10], [Attachments][11] and suggested actions. - -You can include message commands via multi-line text in the .lg file format and use the `TextMessageActivityGenerator.CreateActityFromText` method to transform the command into a Bot Framework activity to post back to the user. - -See [here][8] for examples of how different card types are represented in .chat file format. - -Here is an example of a card definition. +Example here: ```markdown # HeroCardTemplate(buttonsCollection) - - ``` [Herocard title=@{TitleText())} subtitle=@{SubText())} text=@{DescriptionText())} images=@{CardImages())} - buttons=@{join(buttonsCollection, '|')] - ``` + buttons=@{buttonsCollection} + ] # TitleText - - Here are some [TitleSuffix] + - Here are some @{TitleSuffix()} # TitleSuffix - cool photos @@ -82,6 +62,25 @@ Here is an example of a card definition. - https://picsum.photos/200/200?image=400 ``` +the result could be: +```json +{ + "lgType": "Herocard", + "title": "Here are some pictures", + "text": "This is description for the hero card", + "images": "https://picsum.photos/200/200?image=100", + "buttons": [ + "click to see", + "back" + ] +} +``` +the structured name would be placed into property 'lgType'. +See more tests here : [structured LG test][4] + +By this, You can use the `ActivityFactory.createActivity(lgResult)` method to transform the lg output into a Bot Framework activity to post back to the user. + +see more samples here: [Structured LG to Activity][5] ## Language Generation in action @@ -114,22 +113,9 @@ If your template needs specific entity values to be passed for resolution/ expan await turnContext.sendActivity(lgEngine.evaluateTemplate("WordGameReply", { GameName = "MarcoPolo" } )); ``` -## Multi-lingual generation and language fallback policy -Quite often your bot might target more than one spoken/ display language. To help with resource management as well as implement a default language fall back policy, you can either use `MultiLanguageGenerator` or `ResourceMultiLanguageGenerator`. See [here][25] for an example. - -## Grammar check and correction - -The current library does not include any capabilities for grammar check or correction. - - [1]:https://github.com/Microsoft/BotBuilder/blob/master/specs/botframework-activity/botframework-activity.md [2]:https://github.com/microsoft/BotBuilder-Samples/blob/master/experimental/language-generation/docs/api-reference.md [3]:https://github.com/microsoft/BotBuilder-Samples/blob/master/experimental/language-generation/docs/lg-file-format.md -[6]:https://github.com/Microsoft/botbuilder-tools/tree/master/packages/Chatdown -[7]:https://github.com/Microsoft/botbuilder-tools/tree/master/packages/Chatdown#chat-file-format -[8]:https://github.com/Microsoft/botbuilder-tools/blob/master/packages/Chatdown/Examples/CardExamples.chat -[9]:https://github.com/Microsoft/botbuilder-tools/tree/master/packages/Chatdown#message-commands -[10]:https://github.com/Microsoft/botbuilder-tools/tree/master/packages/Chatdown#message-cards -[11]:https://github.com/Microsoft/botbuilder-tools/tree/master/packages/Chatdown#message-attachments -[25]:https://github.com/microsoft/botbuilder-dotnet/blob/d953d1b7fe548cdb1800f1c2e85fe35c34edf75c/tests/Microsoft.Bot.Builder.LanguageGeneration.Renderer.Tests/LGGeneratorTests.cs#L78 +[4]:https://github.com/microsoft/botbuilder-js/blob/master/libraries/botbuilder-lg/tests/testData/examples/StructuredTemplate.lg +[5]:https://github.com/microsoft/botbuilder-js/blob/master/libraries/botbuilder-lg/tests/testData/examples/NormalStructuredLG.lg From 44ba293957dbda1e522d62cb3177584b4bdffe33 Mon Sep 17 00:00:00 2001 From: hond Date: Wed, 4 Dec 2019 15:24:20 +0800 Subject: [PATCH 5/6] merge master --- .../package-lock.json | 415 +----------------- .../botbuilder-dialogs-adaptive/package.json | 1 + .../src/stringTemplate.ts | 8 + .../03 - IfCondition/IfCondition.main.dialog | 2 +- .../04 - TextInput/NumberInput.main.dialog | 6 +- .../04 - TextInput/TextInput.main.dialog | 2 +- .../05 - WelcomeRule/WelcomeRule.main.dialog | 2 +- .../06 - DoSteps/DoSteps.main.dialog | 2 +- .../07 - BeginDialog/BeginDialog.main.dialog | 2 +- .../ExternalLanguage.Greeting.dialog | 8 +- .../ExternalLanguage.main.dialog | 6 +- .../ExternalLanguage.main.lg | 12 +- .../09 - EndDialog/EndDialog.main.dialog | 2 +- .../10 - ChoiceInput/ChoiceInput.main.dialog | 2 +- .../11 - HttpRequest/HttpRequest.main.dialog | 8 +- .../GithubIssueBot.main.dialog | 2 +- .../13 - CustomStep/CustomStep.dialog | 10 +- .../RepeatDialog.main.dialog | 2 +- samples/02. textInput/src/index.ts | 2 +- samples/03. ifCondition/src/index.ts | 2 +- samples/04. onIntent/src/index.ts | 2 +- samples/05. beginDialog/src/index.ts | 2 +- samples/06. codeAction/src/index.ts | 2 +- samples/07. initProperty/src/index.ts | 2 +- samples/50. todo-bot/src/addToDo/index.ts | 2 +- samples/50. todo-bot/src/clearToDos/index.ts | 2 +- samples/50. todo-bot/src/deleteToDo/index.ts | 2 +- samples/50. todo-bot/src/rootDialog/index.ts | 2 +- 28 files changed, 57 insertions(+), 455 deletions(-) diff --git a/libraries/botbuilder-dialogs-adaptive/package-lock.json b/libraries/botbuilder-dialogs-adaptive/package-lock.json index bb18f1610c..ef2d949c71 100644 --- a/libraries/botbuilder-dialogs-adaptive/package-lock.json +++ b/libraries/botbuilder-dialogs-adaptive/package-lock.json @@ -143,265 +143,6 @@ } } }, - "@types/caseless": { - "version": "0.12.2", - "resolved": "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.2.tgz", - "integrity": "sha512-6ckxMjBBD8URvjB6J3NcnuAn5Pkl7t3TizAg+xdlzzQGSPSmBcXf8KoIH0ua/i+tio+ZRUHEXp0HEmvaR4kt0w==" - }, - "@types/request": { - "version": "2.48.3", - "resolved": "https://registry.npmjs.org/@types/request/-/request-2.48.3.tgz", - "integrity": "sha512-3Wo2jNYwqgXcIz/rrq18AdOZUQB8cQ34CXZo+LUwPJNpvRAL86+Kc2wwI8mqpz9Cr1V+enIox5v+WZhy/p3h8w==", - "requires": { - "@types/caseless": "*", - "@types/node": "*", - "@types/tough-cookie": "*", - "form-data": "^2.5.0" - }, - "dependencies": { - "@types/node": { - "version": "12.12.7", - "resolved": "https://registry.npmjs.org/@types/node/-/node-12.12.7.tgz", - "integrity": "sha512-E6Zn0rffhgd130zbCbAr/JdXfXkoOUFAKNs/rF8qnafSJ8KYaA/j3oz7dcwal+lYjLA7xvdd5J4wdYpCTlP8+w==" - }, - "form-data": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.1.tgz", - "integrity": "sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA==", - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", - "mime-types": "^2.1.12" - } - } - } - }, - "@types/request-promise-native": { - "version": "1.0.17", - "resolved": "https://registry.npmjs.org/@types/request-promise-native/-/request-promise-native-1.0.17.tgz", - "integrity": "sha512-05/d0WbmuwjtGMYEdHIBZ0tqMJJQ2AD9LG2F6rKNBGX1SSFR27XveajH//2N/XYtual8T9Axwl+4v7oBtPUZqg==", - "requires": { - "@types/request": "*" - } - }, - "@types/tough-cookie": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-2.3.5.tgz", - "integrity": "sha512-SCcK7mvGi3+ZNz833RRjFIxrn4gI1PPR3NtuIS+6vMkvmsGjosqTJwRt5bAEFLRz+wtJMWv8+uOnZf2hi2QXTg==" - }, - "ajv": { - "version": "6.10.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.2.tgz", - "integrity": "sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==", - "requires": { - "fast-deep-equal": "^2.0.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "asn1": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", - "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", - "requires": { - "safer-buffer": "~2.1.0" - } - }, - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" - }, - "asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" - }, - "aws-sign2": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" - }, - "aws4": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", - "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==" - }, - "bcrypt-pbkdf": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", - "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", - "requires": { - "tweetnacl": "^0.14.3" - } - }, - "caseless": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" - }, - "combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "requires": { - "delayed-stream": "~1.0.0" - } - }, - "core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" - }, - "dashdash": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", - "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", - "requires": { - "assert-plus": "^1.0.0" - } - }, - "delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" - }, - "ecc-jsbn": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", - "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", - "requires": { - "jsbn": "~0.1.0", - "safer-buffer": "^2.1.0" - } - }, - "extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" - }, - "extsprintf": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" - }, - "fast-deep-equal": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", - "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=" - }, - "fast-json-stable-stringify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", - "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" - }, - "forever-agent": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" - }, - "form-data": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", - "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", - "mime-types": "^2.1.12" - } - }, - "getpass": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", - "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", - "requires": { - "assert-plus": "^1.0.0" - } - }, - "har-schema": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", - "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" - }, - "har-validator": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", - "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", - "requires": { - "ajv": "^6.5.5", - "har-schema": "^2.0.0" - } - }, - "http-signature": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", - "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", - "requires": { - "assert-plus": "^1.0.0", - "jsprim": "^1.2.2", - "sshpk": "^1.7.0" - } - }, - "is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" - }, - "isstream": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" - }, - "jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" - }, - "json-schema": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", - "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" - }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" - }, - "json-stringify-safe": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" - }, - "jsprim": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", - "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", - "requires": { - "assert-plus": "1.0.0", - "extsprintf": "1.3.0", - "json-schema": "0.2.3", - "verror": "1.10.0" - } - }, - "lodash": { - "version": "4.17.15", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", - "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" - }, - "mime-db": { - "version": "1.42.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.42.0.tgz", - "integrity": "sha512-UbfJCR4UAVRNgMpfImz05smAXK7+c+ZntjaA26ANtkXLlOe947Aag5zdIcKQULAiF9Cq4WxBi9jUs5zkA84bYQ==" - }, - "mime-types": { - "version": "2.1.25", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.25.tgz", - "integrity": "sha512-5KhStqB5xpTAeGqKBAMgwaYMnQik7teQN4IAzC7npDv6kzeU6prfkR67bc87J1kWMPGkoaZSq1npmexMgkmEVg==", - "requires": { - "mime-db": "1.42.0" - } - }, "mocha": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.2.0.tgz", @@ -592,158 +333,10 @@ } } }, - "oauth-sign": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", - "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" - }, - "performance-now": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" - }, - "psl": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.4.0.tgz", - "integrity": "sha512-HZzqCGPecFLyoRj5HLfuDSKYTJkAfB5thKBIkRHtGjWwY7p1dAyveIbXIq4tO0KYfDF2tHqPUgY9SDnGm00uFw==" - }, - "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" - }, - "qs": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", - "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" - }, - "request": { - "version": "2.88.0", - "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", - "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", - "requires": { - "aws-sign2": "~0.7.0", - "aws4": "^1.8.0", - "caseless": "~0.12.0", - "combined-stream": "~1.0.6", - "extend": "~3.0.2", - "forever-agent": "~0.6.1", - "form-data": "~2.3.2", - "har-validator": "~5.1.0", - "http-signature": "~1.2.0", - "is-typedarray": "~1.0.0", - "isstream": "~0.1.2", - "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.19", - "oauth-sign": "~0.9.0", - "performance-now": "^2.1.0", - "qs": "~6.5.2", - "safe-buffer": "^5.1.2", - "tough-cookie": "~2.4.3", - "tunnel-agent": "^0.6.0", - "uuid": "^3.3.2" - } - }, - "request-promise-core": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.3.tgz", - "integrity": "sha512-QIs2+ArIGQVp5ZYbWD5ZLCY29D5CfWizP8eWnm8FoGD1TX61veauETVQbrV60662V0oFBkrDOuaBI8XgtuyYAQ==", - "requires": { - "lodash": "^4.17.15" - } - }, - "request-promise-native": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.8.tgz", - "integrity": "sha512-dapwLGqkHtwL5AEbfenuzjTYg35Jd6KPytsC2/TLkVMz8rm+tNt72MGUWT1RP/aYawMpN6HqbNGBQaRcBtjQMQ==", - "requires": { - "request-promise-core": "1.1.3", - "stealthy-require": "^1.1.1", - "tough-cookie": "^2.3.3" - } - }, - "safe-buffer": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz", - "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==" - }, - "safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" - }, - "sshpk": { - "version": "1.16.1", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", - "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", - "requires": { - "asn1": "~0.2.3", - "assert-plus": "^1.0.0", - "bcrypt-pbkdf": "^1.0.0", - "dashdash": "^1.12.0", - "ecc-jsbn": "~0.1.1", - "getpass": "^0.1.1", - "jsbn": "~0.1.0", - "safer-buffer": "^2.0.2", - "tweetnacl": "~0.14.0" - } - }, - "stealthy-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", - "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=" - }, - "tough-cookie": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", - "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", - "requires": { - "psl": "^1.1.24", - "punycode": "^1.4.1" - }, - "dependencies": { - "punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" - } - } - }, - "tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", - "requires": { - "safe-buffer": "^5.0.1" - } - }, - "tweetnacl": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" - }, - "uri-js": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", - "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", - "requires": { - "punycode": "^2.1.0" - } - }, - "uuid": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.3.tgz", - "integrity": "sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ==" - }, - "verror": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", - "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", - "requires": { - "assert-plus": "^1.0.0", - "core-util-is": "1.0.2", - "extsprintf": "^1.2.0" - } + "node-fetch": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz", + "integrity": "sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==" } } } diff --git a/libraries/botbuilder-dialogs-adaptive/package.json b/libraries/botbuilder-dialogs-adaptive/package.json index cf6173797b..ef2e0c7554 100644 --- a/libraries/botbuilder-dialogs-adaptive/package.json +++ b/libraries/botbuilder-dialogs-adaptive/package.json @@ -31,6 +31,7 @@ "@types/node-fetch": "^2.5.3", "botbuilder-core": "~4.1.6", "botbuilder-dialogs": "~4.1.6", + "botbuilder-lg": "~4.1.6", "botframework-expressions": "~4.1.6", "botframework-connector": "~4.1.6", "botframework-schema": "~4.1.6", diff --git a/libraries/botbuilder-dialogs-adaptive/src/stringTemplate.ts b/libraries/botbuilder-dialogs-adaptive/src/stringTemplate.ts index eb492722be..13b59f140b 100644 --- a/libraries/botbuilder-dialogs-adaptive/src/stringTemplate.ts +++ b/libraries/botbuilder-dialogs-adaptive/src/stringTemplate.ts @@ -47,6 +47,14 @@ export function compile(template: string): (dc: DialogContext) => string { buffer = ''; } inSlot = true; + } else if (chr === '@' && i < template.length - 1 && template[i+1] === '{') { + // support @{} + if (buffer.length > 0) { + chunks.push(textLiteral(buffer)); + buffer = ''; + } + i++; + inSlot = true; } else { buffer += chr; } diff --git a/libraries/botbuilder-dialogs-declarative/tests/resources/03 - IfCondition/IfCondition.main.dialog b/libraries/botbuilder-dialogs-declarative/tests/resources/03 - IfCondition/IfCondition.main.dialog index 84f8d5472a..e46ba2c58e 100644 --- a/libraries/botbuilder-dialogs-declarative/tests/resources/03 - IfCondition/IfCondition.main.dialog +++ b/libraries/botbuilder-dialogs-declarative/tests/resources/03 - IfCondition/IfCondition.main.dialog @@ -15,7 +15,7 @@ }, { "$type": "Microsoft.SendActivity", - "activity": "Hello {user.name}, nice to talk to you!" + "activity": "Hello @{user.name}, nice to talk to you!" } ] } \ No newline at end of file diff --git a/libraries/botbuilder-dialogs-declarative/tests/resources/04 - TextInput/NumberInput.main.dialog b/libraries/botbuilder-dialogs-declarative/tests/resources/04 - TextInput/NumberInput.main.dialog index 6f4ba1eaf3..82deb3198d 100644 --- a/libraries/botbuilder-dialogs-declarative/tests/resources/04 - TextInput/NumberInput.main.dialog +++ b/libraries/botbuilder-dialogs-declarative/tests/resources/04 - TextInput/NumberInput.main.dialog @@ -11,7 +11,7 @@ }, { "$type": "Microsoft.SendActivity", - "activity": "Hello, your age is {user.age}!" + "activity": "Hello, your age is @{user.age}!" }, { "$type": "Microsoft.FloatInput", @@ -25,13 +25,13 @@ "actions": [ { "$type": "Microsoft.SendActivity", - "activity": "2 * 2.2 equals {user.result}, that's right!" + "activity": "2 * 2.2 equals @{user.result}, that's right!" } ], "elseActions": [ { "$type": "Microsoft.SendActivity", - "activity": "2 * 2.2 equals {user.result}, that's wrong!" + "activity": "2 * 2.2 equals @{user.result}, that's wrong!" } ] } diff --git a/libraries/botbuilder-dialogs-declarative/tests/resources/04 - TextInput/TextInput.main.dialog b/libraries/botbuilder-dialogs-declarative/tests/resources/04 - TextInput/TextInput.main.dialog index 8787b608cf..70ea7a6f9e 100644 --- a/libraries/botbuilder-dialogs-declarative/tests/resources/04 - TextInput/TextInput.main.dialog +++ b/libraries/botbuilder-dialogs-declarative/tests/resources/04 - TextInput/TextInput.main.dialog @@ -15,7 +15,7 @@ }, { "$type": "Microsoft.SendActivity", - "activity": "Hello {user.name}, nice to talk to you!" + "activity": "Hello @{user.name}, nice to talk to you!" } ] } \ No newline at end of file diff --git a/libraries/botbuilder-dialogs-declarative/tests/resources/05 - WelcomeRule/WelcomeRule.main.dialog b/libraries/botbuilder-dialogs-declarative/tests/resources/05 - WelcomeRule/WelcomeRule.main.dialog index c0a29bc1b3..ef3c94003a 100644 --- a/libraries/botbuilder-dialogs-declarative/tests/resources/05 - WelcomeRule/WelcomeRule.main.dialog +++ b/libraries/botbuilder-dialogs-declarative/tests/resources/05 - WelcomeRule/WelcomeRule.main.dialog @@ -15,7 +15,7 @@ }, { "$type": "Microsoft.SendActivity", - "activity": "Hello {user.name}, nice to talk to you!" + "activity": "Hello @{user.name}, nice to talk to you!" } ], "rules": [ diff --git a/libraries/botbuilder-dialogs-declarative/tests/resources/06 - DoSteps/DoSteps.main.dialog b/libraries/botbuilder-dialogs-declarative/tests/resources/06 - DoSteps/DoSteps.main.dialog index aafe4e5e1d..d8c394f54e 100644 --- a/libraries/botbuilder-dialogs-declarative/tests/resources/06 - DoSteps/DoSteps.main.dialog +++ b/libraries/botbuilder-dialogs-declarative/tests/resources/06 - DoSteps/DoSteps.main.dialog @@ -23,7 +23,7 @@ }, { "$type": "Microsoft.SendActivity", - "activity": "Hello {user.name}, nice to talk to you!" + "activity": "Hello @{user.name}, nice to talk to you!" } ], "rules": [ diff --git a/libraries/botbuilder-dialogs-declarative/tests/resources/07 - BeginDialog/BeginDialog.main.dialog b/libraries/botbuilder-dialogs-declarative/tests/resources/07 - BeginDialog/BeginDialog.main.dialog index 375fba5c24..67489b0bbc 100644 --- a/libraries/botbuilder-dialogs-declarative/tests/resources/07 - BeginDialog/BeginDialog.main.dialog +++ b/libraries/botbuilder-dialogs-declarative/tests/resources/07 - BeginDialog/BeginDialog.main.dialog @@ -23,7 +23,7 @@ }, { "$type": "Microsoft.SendActivity", - "activity": "Hello {user.name}, nice to talk to you!" + "activity": "Hello @{user.name}, nice to talk to you!" } ], "rules": [ diff --git a/libraries/botbuilder-dialogs-declarative/tests/resources/08 - ExternalLanguage/ExternalLanguage.Greeting.dialog b/libraries/botbuilder-dialogs-declarative/tests/resources/08 - ExternalLanguage/ExternalLanguage.Greeting.dialog index 220715e9e9..535572b180 100644 --- a/libraries/botbuilder-dialogs-declarative/tests/resources/08 - ExternalLanguage/ExternalLanguage.Greeting.dialog +++ b/libraries/botbuilder-dialogs-declarative/tests/resources/08 - ExternalLanguage/ExternalLanguage.Greeting.dialog @@ -10,15 +10,15 @@ "$type": "Microsoft.TextInput", "pattern": "\\w{3,50}", "property": "user.name", - "prompt": "[Greeting.Name.initialPrompt]", - "retryPrompt": "[Greeting.Name.retryPrompt]", - "invalidPrompt": "[Greeting.Name.notMatched]" + "prompt": "@{Greeting.Name.initialPrompt()}", + "retryPrompt": "@{Greeting.Name.retryPrompt()}", + "invalidPrompt": "@{Greeting.Name.notMatched()}" } ] }, { "$type": "Microsoft.SendActivity", - "activity": "[Greeting.GreetUser]" + "activity": "@{Greeting.GreetUser()}" } ] } \ No newline at end of file diff --git a/libraries/botbuilder-dialogs-declarative/tests/resources/08 - ExternalLanguage/ExternalLanguage.main.dialog b/libraries/botbuilder-dialogs-declarative/tests/resources/08 - ExternalLanguage/ExternalLanguage.main.dialog index 389a31b094..3bfd41fa43 100644 --- a/libraries/botbuilder-dialogs-declarative/tests/resources/08 - ExternalLanguage/ExternalLanguage.main.dialog +++ b/libraries/botbuilder-dialogs-declarative/tests/resources/08 - ExternalLanguage/ExternalLanguage.main.dialog @@ -14,7 +14,7 @@ "actions": [ { "$type": "Microsoft.SendActivity", - "activity": "[welcome]" + "activity": "@{welcome}" }, "ExternalLanguage.Greeting" ], @@ -46,7 +46,7 @@ "actions": [ { "$type": "Microsoft.SendActivity", - "activity": "[help]" + "activity": "@{help()}" } ] }, @@ -55,7 +55,7 @@ "actions": [ { "$type": "Microsoft.SendActivity", - "activity": "[help]" + "activity": "@{help()}" } ] } diff --git a/libraries/botbuilder-dialogs-declarative/tests/resources/08 - ExternalLanguage/ExternalLanguage.main.lg b/libraries/botbuilder-dialogs-declarative/tests/resources/08 - ExternalLanguage/ExternalLanguage.main.lg index 81a91e2a7c..d858e51400 100644 --- a/libraries/botbuilder-dialogs-declarative/tests/resources/08 - ExternalLanguage/ExternalLanguage.main.lg +++ b/libraries/botbuilder-dialogs-declarative/tests/resources/08 - ExternalLanguage/ExternalLanguage.main.lg @@ -5,8 +5,8 @@ ``` # welcome-user -- Hello {user.name}, nice talking to you! -- Howdy {user.name}! +- Hello @{user.name}, nice talking to you! +- Howdy @{user.name}! # prompt-name - What's your name? @@ -22,14 +22,14 @@ - I would like to know you better, what's your name? # Greeting.Name.retryPrompt -- [Greeting.Name.initialPrompt] +- @{Greeting.Name.initialPrompt()} # Greeting.Name.notMatched - That didn't match what I'm looking for, your name needs to be between 3 and 50 characters. - Oh no, I'm silly and confused again. # Greeting.GreetUser -- Hello {user.name}, nice to talk to you! -- Hi {user.name}, you seem nice! -- Whassup {user.name}? +- Hello @{user.name}, nice to talk to you! +- Hi @{user.name}, you seem nice! +- Whassup @{user.name}? diff --git a/libraries/botbuilder-dialogs-declarative/tests/resources/09 - EndDialog/EndDialog.main.dialog b/libraries/botbuilder-dialogs-declarative/tests/resources/09 - EndDialog/EndDialog.main.dialog index 33c7b4173d..36d95a56a5 100644 --- a/libraries/botbuilder-dialogs-declarative/tests/resources/09 - EndDialog/EndDialog.main.dialog +++ b/libraries/botbuilder-dialogs-declarative/tests/resources/09 - EndDialog/EndDialog.main.dialog @@ -23,7 +23,7 @@ }, { "$type": "Microsoft.SendActivity", - "activity": "Hello {user.name}, nice to talk to you!" + "activity": "Hello @{user.name}, nice to talk to you!" }, { "$type": "Microsoft.SendActivity", diff --git a/libraries/botbuilder-dialogs-declarative/tests/resources/10 - ChoiceInput/ChoiceInput.main.dialog b/libraries/botbuilder-dialogs-declarative/tests/resources/10 - ChoiceInput/ChoiceInput.main.dialog index 44a1162cc0..6c393d0631 100644 --- a/libraries/botbuilder-dialogs-declarative/tests/resources/10 - ChoiceInput/ChoiceInput.main.dialog +++ b/libraries/botbuilder-dialogs-declarative/tests/resources/10 - ChoiceInput/ChoiceInput.main.dialog @@ -22,7 +22,7 @@ }, { "$type": "Microsoft.SendActivity", - "activity": "You select: {user.style.Value}" + "activity": "You select: @{user.style.Value}" } ] } \ No newline at end of file diff --git a/libraries/botbuilder-dialogs-declarative/tests/resources/11 - HttpRequest/HttpRequest.main.dialog b/libraries/botbuilder-dialogs-declarative/tests/resources/11 - HttpRequest/HttpRequest.main.dialog index fad987b8f1..2e4bfd4c17 100644 --- a/libraries/botbuilder-dialogs-declarative/tests/resources/11 - HttpRequest/HttpRequest.main.dialog +++ b/libraries/botbuilder-dialogs-declarative/tests/resources/11 - HttpRequest/HttpRequest.main.dialog @@ -10,7 +10,7 @@ }, { "$type": "Microsoft.SendActivity", - "activity": "Great! Your pet's name is {dialog.petname}" + "activity": "Great! Your pet's name is @{dialog.petname}" }, { "$type": "Microsoft.TextInput", @@ -27,12 +27,12 @@ }, "body": { - "id": "{dialog.petid}", + "id": "@{dialog.petid}", "category": { "id": 0, "name": "string" }, - "name": "{dialog.petname}", + "name": "@{dialog.petname}", "photoUrls": [ "string" ], "tags": [ { @@ -46,7 +46,7 @@ }, { "$type": "Microsoft.SendActivity", - "activity": "Done! You have added a pet named \"{dialog.postResponse.name}\" with id \"{dialog.postResponse.id}\"" + "activity": "Done! You have added a pet named \"@{dialog.postResponse.name}\" with id \"@{dialog.postResponse.id}\"" }, { "$type": "Microsoft.TextInput", diff --git a/libraries/botbuilder-dialogs-declarative/tests/resources/12 - GithubIssueBot/GithubIssueBot.main.dialog b/libraries/botbuilder-dialogs-declarative/tests/resources/12 - GithubIssueBot/GithubIssueBot.main.dialog index be178c5a69..9129ccf7d8 100644 --- a/libraries/botbuilder-dialogs-declarative/tests/resources/12 - GithubIssueBot/GithubIssueBot.main.dialog +++ b/libraries/botbuilder-dialogs-declarative/tests/resources/12 - GithubIssueBot/GithubIssueBot.main.dialog @@ -17,7 +17,7 @@ }, { "$type": "Microsoft.SendActivity", - "activity": "[Show]" + "activity": "@{Show()}" } ] } \ No newline at end of file diff --git a/libraries/botbuilder-dialogs-declarative/tests/resources/13 - CustomStep/CustomStep.dialog b/libraries/botbuilder-dialogs-declarative/tests/resources/13 - CustomStep/CustomStep.dialog index 7195d4344c..7c1baf76dd 100644 --- a/libraries/botbuilder-dialogs-declarative/tests/resources/13 - CustomStep/CustomStep.dialog +++ b/libraries/botbuilder-dialogs-declarative/tests/resources/13 - CustomStep/CustomStep.dialog @@ -10,7 +10,7 @@ }, { "$type": "Microsoft.SendActivity", - "activity": "Your age is: {user.age}" + "activity": "Your age is: @{user.age}" }, { "$type": "Testbot.JavascriptAction", @@ -19,7 +19,7 @@ }, { "$type": "Microsoft.SendActivity", - "activity": "Your age in dog years is: {user.dogyears}" + "activity": "Your age in dog years is: @{user.dogyears}" }, { "$type": "Testbot.CSharpAction", @@ -28,7 +28,7 @@ }, { "$type": "Microsoft.SendActivity", - "activity": "Your age in cat years is: {user.catyears}" + "activity": "Your age in cat years is: @{user.catyears}" }, { "$type": "Testbot.CalculateDogYears", @@ -37,11 +37,11 @@ }, { "$type": "Microsoft.SendActivity", - "activity": "A dog that is {conversation.dog} years old is the same age as you" + "activity": "A dog that is @{conversation.dog} years old is the same age as you" }, { "$type": "Microsoft.SendActivity", - "activity": "A cat that is {conversation.cat} years old is the same age as you" + "activity": "A cat that is @{conversation.cat} years old is the same age as you" }, ] } \ No newline at end of file diff --git a/libraries/botbuilder-dialogs-declarative/tests/resources/14 - RepeatDialog/RepeatDialog.main.dialog b/libraries/botbuilder-dialogs-declarative/tests/resources/14 - RepeatDialog/RepeatDialog.main.dialog index 6b5f370a41..d829bac18a 100644 --- a/libraries/botbuilder-dialogs-declarative/tests/resources/14 - RepeatDialog/RepeatDialog.main.dialog +++ b/libraries/botbuilder-dialogs-declarative/tests/resources/14 - RepeatDialog/RepeatDialog.main.dialog @@ -10,7 +10,7 @@ }, { "$type":"Microsoft.SendActivity", - "activity": "Hello {user.name}, nice to meet you!" + "activity": "Hello @{user.name}, nice to meet you!" }, { "$type":"Microsoft.EndTurn" diff --git a/samples/02. textInput/src/index.ts b/samples/02. textInput/src/index.ts index 48bdcc0d96..27fa0fc1d1 100644 --- a/samples/02. textInput/src/index.ts +++ b/samples/02. textInput/src/index.ts @@ -42,5 +42,5 @@ bot.rootDialog = dialogs; dialogs.addRule(new OnUnknownIntent([ new SetProperty('user.name', 'null'), new TextInput('user.name', `Hi! what's your name?`), - new SendActivity(`Hi {user.name}. It's nice to meet you.`) + new SendActivity(`Hi @{user.name}. It's nice to meet you.`) ])); diff --git a/samples/03. ifCondition/src/index.ts b/samples/03. ifCondition/src/index.ts index 5ab8c69519..5c485de940 100644 --- a/samples/03. ifCondition/src/index.ts +++ b/samples/03. ifCondition/src/index.ts @@ -43,5 +43,5 @@ dialogs.addRule(new OnUnknownIntent([ new IfCondition('user.name == null', [ new TextInput('user.name', `Hi! what's your name?`), ]), - new SendActivity(`Hi {user.name}. It's nice to meet you.`) + new SendActivity(`Hi @{user.name}. It's nice to meet you.`) ])); diff --git a/samples/04. onIntent/src/index.ts b/samples/04. onIntent/src/index.ts index cdaf8183f0..304a508ef6 100644 --- a/samples/04. onIntent/src/index.ts +++ b/samples/04. onIntent/src/index.ts @@ -53,6 +53,6 @@ dialogs.addRule(new OnUnknownIntent([ new IfCondition('user.name == null', [ new TextInput('user.name', `Hi! what's your name?`) ]), - new SendActivity(`Hi {user.name}. It's nice to meet you.`) + new SendActivity(`Hi @{user.name}. It's nice to meet you.`) ])); diff --git a/samples/05. beginDialog/src/index.ts b/samples/05. beginDialog/src/index.ts index ca370a6378..a571043090 100644 --- a/samples/05. beginDialog/src/index.ts +++ b/samples/05. beginDialog/src/index.ts @@ -63,7 +63,7 @@ const askNameDialog = new AdaptiveDialog('AskNameDialog', [ new IfCondition('user.name == null', [ new TextInput('user.name', `Hi! what's your name?`) ]), - new SendActivity(`Hi {user.name}. It's nice to meet you.`), + new SendActivity(`Hi @{user.name}. It's nice to meet you.`), new EndDialog() ]); dialogs.actions.push(askNameDialog); diff --git a/samples/06. codeAction/src/index.ts b/samples/06. codeAction/src/index.ts index 23a67bce46..90f532b87d 100644 --- a/samples/06. codeAction/src/index.ts +++ b/samples/06. codeAction/src/index.ts @@ -45,5 +45,5 @@ dialogs.addRule(new OnUnknownIntent([ dc.state.setValue('conversation.count', count + 1); return await dc.endDialog(); }), - new SendActivity('{conversation.count}') + new SendActivity('@{conversation.count}') ])); diff --git a/samples/07. initProperty/src/index.ts b/samples/07. initProperty/src/index.ts index 34da92e703..85e3c75f6d 100644 --- a/samples/07. initProperty/src/index.ts +++ b/samples/07. initProperty/src/index.ts @@ -42,5 +42,5 @@ dialogs.addRule(new OnUnknownIntent([ new InitProperty('dialog.list', 'array'), new TextInput("dialog.item", "What is your name?"), new EditArray(ArrayChangeType.push, 'dialog.list', 'dialog.item'), - new SendActivity('Your name in an array: {dialog.list}') + new SendActivity('Your name in an array: @{dialog.list}') ])); diff --git a/samples/50. todo-bot/src/addToDo/index.ts b/samples/50. todo-bot/src/addToDo/index.ts index 7076ab0431..065e38a04d 100644 --- a/samples/50. todo-bot/src/addToDo/index.ts +++ b/samples/50. todo-bot/src/addToDo/index.ts @@ -9,7 +9,7 @@ export class AddToDo extends AdaptiveDialog { super('AddToDo', [ new TextInput('$title', '@title', `What would you like to call your new todo?`), new EditArray(ArrayChangeType.push, 'user.todos', '$title'), - new SendActivity(`Added a todo named "{$title}". You can delete it by saying "delete todo named {$title}".`), + new SendActivity(`Added a todo named "@{$title}". You can delete it by saying "delete todo named {$title}".`), new IfCondition(`user.tips.showToDos != true`, [ new SendActivity(`To view your todos just ask me to "show my todos".`), new SetProperty('user.tips.showToDos', 'true') diff --git a/samples/50. todo-bot/src/clearToDos/index.ts b/samples/50. todo-bot/src/clearToDos/index.ts index 51201b4c75..ea9931765c 100644 --- a/samples/50. todo-bot/src/clearToDos/index.ts +++ b/samples/50. todo-bot/src/clearToDos/index.ts @@ -7,7 +7,7 @@ import { getRecognizer } from "../recognizer"; export class ClearToDos extends AdaptiveDialog { constructor() { super('ClearToDos', [ - new LogAction(`ClearToDos: todos = {user.todos}`), + new LogAction(`ClearToDos: todos = @{user.todos}`), new IfCondition(`user.todos != null`, [ new EditArray(ArrayChangeType.clear, 'user.todos'), new SendActivity(`All todos removed.`) diff --git a/samples/50. todo-bot/src/deleteToDo/index.ts b/samples/50. todo-bot/src/deleteToDo/index.ts index 9c913de3ff..1907b15cb3 100644 --- a/samples/50. todo-bot/src/deleteToDo/index.ts +++ b/samples/50. todo-bot/src/deleteToDo/index.ts @@ -12,7 +12,7 @@ export class DeleteToDo extends AdaptiveDialog { new SetProperty('$title', '@title'), new ChoiceInput('$title', `Which todo would you like to remove?`, 'user.todos'), new EditArray(ArrayChangeType.remove, 'user.todos', '$title'), - new SendActivity(`Deleted the todo named "{$title}".`), + new SendActivity(`Deleted the todo named "@{$title}".`), new IfCondition(`user.tips.clearToDos != true`, [ new SendActivity(`You can delete all your todos by saying "delete all todos".`), new SetProperty('user.tips.clearToDos', 'true') diff --git a/samples/50. todo-bot/src/rootDialog/index.ts b/samples/50. todo-bot/src/rootDialog/index.ts index e13985459e..0cfc0e0c1d 100644 --- a/samples/50. todo-bot/src/rootDialog/index.ts +++ b/samples/50. todo-bot/src/rootDialog/index.ts @@ -52,7 +52,7 @@ export class RootDialog extends AdaptiveDialog { // Define rules for handling errors this.addRule(new OnDialogEvent('error', [ - new SendActivity(`Oops. An error occurred: {message}`) + new SendActivity(`Oops. An error occurred: @{message}`) ])); } } From 5e200091edcf66aaa017a26abcebce61debc0a8b Mon Sep 17 00:00:00 2001 From: hond Date: Wed, 4 Dec 2019 16:26:25 +0800 Subject: [PATCH 6/6] revert lock file --- .../package-lock.json | 415 +++++++++++++++++- 1 file changed, 411 insertions(+), 4 deletions(-) diff --git a/libraries/botbuilder-dialogs-adaptive/package-lock.json b/libraries/botbuilder-dialogs-adaptive/package-lock.json index ef2d949c71..bb18f1610c 100644 --- a/libraries/botbuilder-dialogs-adaptive/package-lock.json +++ b/libraries/botbuilder-dialogs-adaptive/package-lock.json @@ -143,6 +143,265 @@ } } }, + "@types/caseless": { + "version": "0.12.2", + "resolved": "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.2.tgz", + "integrity": "sha512-6ckxMjBBD8URvjB6J3NcnuAn5Pkl7t3TizAg+xdlzzQGSPSmBcXf8KoIH0ua/i+tio+ZRUHEXp0HEmvaR4kt0w==" + }, + "@types/request": { + "version": "2.48.3", + "resolved": "https://registry.npmjs.org/@types/request/-/request-2.48.3.tgz", + "integrity": "sha512-3Wo2jNYwqgXcIz/rrq18AdOZUQB8cQ34CXZo+LUwPJNpvRAL86+Kc2wwI8mqpz9Cr1V+enIox5v+WZhy/p3h8w==", + "requires": { + "@types/caseless": "*", + "@types/node": "*", + "@types/tough-cookie": "*", + "form-data": "^2.5.0" + }, + "dependencies": { + "@types/node": { + "version": "12.12.7", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.12.7.tgz", + "integrity": "sha512-E6Zn0rffhgd130zbCbAr/JdXfXkoOUFAKNs/rF8qnafSJ8KYaA/j3oz7dcwal+lYjLA7xvdd5J4wdYpCTlP8+w==" + }, + "form-data": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.1.tgz", + "integrity": "sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } + } + } + }, + "@types/request-promise-native": { + "version": "1.0.17", + "resolved": "https://registry.npmjs.org/@types/request-promise-native/-/request-promise-native-1.0.17.tgz", + "integrity": "sha512-05/d0WbmuwjtGMYEdHIBZ0tqMJJQ2AD9LG2F6rKNBGX1SSFR27XveajH//2N/XYtual8T9Axwl+4v7oBtPUZqg==", + "requires": { + "@types/request": "*" + } + }, + "@types/tough-cookie": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-2.3.5.tgz", + "integrity": "sha512-SCcK7mvGi3+ZNz833RRjFIxrn4gI1PPR3NtuIS+6vMkvmsGjosqTJwRt5bAEFLRz+wtJMWv8+uOnZf2hi2QXTg==" + }, + "ajv": { + "version": "6.10.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.2.tgz", + "integrity": "sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==", + "requires": { + "fast-deep-equal": "^2.0.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "asn1": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", + "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "requires": { + "safer-buffer": "~2.1.0" + } + }, + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" + }, + "aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" + }, + "aws4": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", + "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==" + }, + "bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "requires": { + "tweetnacl": "^0.14.3" + } + }, + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" + }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, + "dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "requires": { + "assert-plus": "^1.0.0" + } + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" + }, + "ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "requires": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, + "extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" + }, + "fast-deep-equal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=" + }, + "fast-json-stable-stringify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", + "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" + }, + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" + }, + "form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } + }, + "getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "requires": { + "assert-plus": "^1.0.0" + } + }, + "har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" + }, + "har-validator": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", + "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", + "requires": { + "ajv": "^6.5.5", + "har-schema": "^2.0.0" + } + }, + "http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "requires": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + } + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" + }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" + }, + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" + }, + "json-schema": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" + }, + "jsprim": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + } + }, + "lodash": { + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" + }, + "mime-db": { + "version": "1.42.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.42.0.tgz", + "integrity": "sha512-UbfJCR4UAVRNgMpfImz05smAXK7+c+ZntjaA26ANtkXLlOe947Aag5zdIcKQULAiF9Cq4WxBi9jUs5zkA84bYQ==" + }, + "mime-types": { + "version": "2.1.25", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.25.tgz", + "integrity": "sha512-5KhStqB5xpTAeGqKBAMgwaYMnQik7teQN4IAzC7npDv6kzeU6prfkR67bc87J1kWMPGkoaZSq1npmexMgkmEVg==", + "requires": { + "mime-db": "1.42.0" + } + }, "mocha": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.2.0.tgz", @@ -333,10 +592,158 @@ } } }, - "node-fetch": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz", - "integrity": "sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==" + "oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" + }, + "performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" + }, + "psl": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.4.0.tgz", + "integrity": "sha512-HZzqCGPecFLyoRj5HLfuDSKYTJkAfB5thKBIkRHtGjWwY7p1dAyveIbXIq4tO0KYfDF2tHqPUgY9SDnGm00uFw==" + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" + }, + "qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" + }, + "request": { + "version": "2.88.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", + "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", + "requires": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.0", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.4.3", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + } + }, + "request-promise-core": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.3.tgz", + "integrity": "sha512-QIs2+ArIGQVp5ZYbWD5ZLCY29D5CfWizP8eWnm8FoGD1TX61veauETVQbrV60662V0oFBkrDOuaBI8XgtuyYAQ==", + "requires": { + "lodash": "^4.17.15" + } + }, + "request-promise-native": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.8.tgz", + "integrity": "sha512-dapwLGqkHtwL5AEbfenuzjTYg35Jd6KPytsC2/TLkVMz8rm+tNt72MGUWT1RP/aYawMpN6HqbNGBQaRcBtjQMQ==", + "requires": { + "request-promise-core": "1.1.3", + "stealthy-require": "^1.1.1", + "tough-cookie": "^2.3.3" + } + }, + "safe-buffer": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz", + "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==" + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "sshpk": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", + "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", + "requires": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + } + }, + "stealthy-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", + "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=" + }, + "tough-cookie": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", + "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", + "requires": { + "psl": "^1.1.24", + "punycode": "^1.4.1" + }, + "dependencies": { + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" + } + } + }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" + }, + "uri-js": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", + "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "requires": { + "punycode": "^2.1.0" + } + }, + "uuid": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.3.tgz", + "integrity": "sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ==" + }, + "verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "requires": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } } } }