* init

* update

* clean LG/Expression

* revert ts-node version
This commit is contained in:
Hongyang Du (hond) 2020-03-08 16:44:33 +08:00 коммит произвёл GitHub
Родитель 1a02dff37b
Коммит efdd58ff2d
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
29 изменённых файлов: 227 добавлений и 720 удалений

Просмотреть файл

@ -42,30 +42,22 @@ export class Constant extends Expression {
}
public toString(): string {
if (this.value === undefined) {
return 'null';
}
if (typeof this.value === 'string') {
if (this.value.includes('\\')) {
this.value = this.value.replace(/\\/g, '\\\\');
} else if (typeof this.value === 'string') {
let result = this.value;
if (result.includes('\\')) {
result = result.replace(/\\/g, '\\\\');
}
return this.value.includes(`'`) ? `"${ this.value }"` : `'${ this.value }'`;
}
if (typeof this.value === 'number') {
return result.includes(`'`) ? `"${ result }"` : `'${ result }'`;
} else if (typeof this.value === 'number') {
return this.value.toString();
} else if(typeof this.value === 'object') {
return JSON.stringify(this.value);
}
if (Array.isArray(this.value)) {
this.value = '[' + this.value.join(' ') + ']';
}
if(typeof this.value === 'object') {
this.value = JSON.stringify(this.value);
}
return this.value === undefined ? undefined : this.value.toString();
return this.value.toString();
}
}

Просмотреть файл

@ -13,14 +13,6 @@ import { SimpleObjectMemory, MemoryInterface } from './memory';
import { Extensions } from './extensions';
import { ExpressionParser } from './parser';
/**
* key value pair to add method in FunctionTable
*/
type keyValuePair = {
key: string;
value: ExpressionEvaluator;
}
/**
* Type expected from evalating an expression.
*/
@ -121,7 +113,7 @@ export class Expression {
}
public add(item: keyValuePair | string, value: ExpressionEvaluator = undefined): void{
public add(item: {key: string; value: ExpressionEvaluator} | string, value: ExpressionEvaluator|undefined): void{
if(arguments.length === 1 && item instanceof Object) {
this.set(item.key, item.value);
} else if (arguments.length == 2 && typeof item === 'string') {
@ -141,7 +133,8 @@ export class Expression {
return this.customFunctions.delete(key);
}
public forEach(callbackfn: (value: ExpressionEvaluator, key: string, map: Map<string, ExpressionEvaluator>) => void, thisArg?: any): void {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
public forEach(_callbackfn: (value: ExpressionEvaluator, key: string, map: Map<string, ExpressionEvaluator>) => void, thisArg?: any): void {
throw Error(`forEach function not implemented`);
}
@ -184,8 +177,8 @@ export class Expression {
}
}
public static parse(expression: string, lookup: EvaluatorLookup): Expression {
return new ExpressionParser(lookup? lookup : Expression.lookup).parse(expression);
public static parse(expression: string, lookup?: EvaluatorLookup): Expression {
return new ExpressionParser(lookup || Expression.lookup).parse(expression);
}
/**

Просмотреть файл

@ -56,9 +56,8 @@ export class ExpressionEvaluator {
this.type = type;
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;
this._validator = validator || ((expr: Expression): any => { });
}
/**
@ -71,6 +70,5 @@ export class ExpressionEvaluator {
* Validate an expression.
* @param expression Expression to validate.
*/
// tslint:disable-next-line: informative-docs
public validateExpression = (expression: Expression): void => this._validator(expression);
}

Просмотреть файл

@ -18,7 +18,7 @@ import { EvaluateExpressionDelegate, ExpressionEvaluator, ValidateExpressionDele
import { ExpressionType } from './expressionType';
import { Extensions } from './extensions';
import { TimeZoneConverter } from './timeZoneConverter';
import { convertCSharpDateTimeToMomentJS } from './formatConverter';
import { convertCSharpDateTimeToMomentJS } from './datetimeFormatConverter';
import { MemoryInterface, SimpleObjectMemory, StackedMemory } from './memory';
/**
@ -531,7 +531,6 @@ export class ExpressionFunctions {
(args: any []): any => {
const binaryArgs: any[] = [undefined, undefined];
let soFar: any = args[0];
// tslint:disable-next-line: prefer-for-of
for (let i = 1; i < args.length; i++) {
binaryArgs[0] = soFar;
binaryArgs[1] = args[i];
@ -557,7 +556,6 @@ export class ExpressionFunctions {
let soFar: any = args[0];
let value: any;
let error: string;
// tslint:disable-next-line: prefer-for-of
for (let i = 1; i < args.length; i++) {
binaryArgs[0] = soFar;
binaryArgs[1] = args[i];
@ -602,7 +600,7 @@ export class ExpressionFunctions {
* @param func Function to apply.
*/
public static multivariateNumeric(type: string, func: (arg0: any []) => any, verify?: VerifyExpression): ExpressionEvaluator {
return new ExpressionEvaluator(type, ExpressionFunctions.applySequence(func, verify !== undefined ? verify : ExpressionFunctions.verifyNumber),
return new ExpressionEvaluator(type, ExpressionFunctions.applySequence(func, verify || ExpressionFunctions.verifyNumber),
ReturnType.Number, ExpressionFunctions.validateTwoOrMoreThanTwoNumbers);
}
/**
@ -692,7 +690,6 @@ export class ExpressionFunctions {
return { value: result, error };
},
ReturnType.String,
// tslint:disable-next-line: no-void-expression
(expr: Expression): void => ExpressionFunctions.validateArityAndAnyType(expr, 2, 3, ReturnType.String, ReturnType.Number));
}
@ -770,7 +767,6 @@ export class ExpressionFunctions {
private static newGuid(): string {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c: any): string => {
const r: number = Math.random() * 16 | 0;
// tslint:disable-next-line: no-bitwise
const v: number = c === 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16);
@ -1006,13 +1002,12 @@ export class ExpressionFunctions {
}
if (!error) {
// 2nd parameter has been rewrite to $local.item
const iteratorName = (expression.children[1].children[0] as Constant).value as string;
let arr = [];
if (Array.isArray(instance)) {
arr = instance;
} else if (typeof instance === 'object') {
Object.keys(instance).forEach(u => arr.push({key: u, value: instance[u]}));
Object.keys(instance).forEach((u): number => arr.push({key: u, value: instance[u]}));
} else {
error = `${ expression.children[0] } is not a collection or structure object to run foreach`;
}
@ -1054,7 +1049,7 @@ export class ExpressionFunctions {
arr = instance;
isInstanceArray = true;
} else if (typeof instance === 'object') {
Object.keys(instance).forEach(u => arr.push({key: u, value: instance[u]}));
Object.keys(instance).forEach((u): number => arr.push({key: u, value: instance[u]}));
} else {
error = `${ expression.children[0] } is not a collection or structure object to run foreach`;
}
@ -1116,8 +1111,7 @@ export class ExpressionFunctions {
const second: Expression = expression.children[1];
if (second.returnType === ReturnType.String && second.type === ExpressionType.Constant) {
// tslint:disable-next-line: restrict-plus-operands
CommonRegex.CreateRegex((second as Constant).value + '');
CommonRegex.CreateRegex((second as Constant).value.toString());
}
}
@ -1389,7 +1383,7 @@ export class ExpressionFunctions {
({value: propertyName, error} = expression.children[1].tryEvaluate(state));
if (!error) {
propertyName = propertyName === undefined ? '' : propertyName;
propertyName = propertyName || '';
}
if (isDescending) {
result = lodash.sortBy(arr, propertyName).reverse();
@ -1439,7 +1433,6 @@ export class ExpressionFunctions {
let result = '';
for (const element of stringToConvert) {
const binaryElement: string = element.charCodeAt(0).toString(2);
// tslint:disable-next-line: prefer-array-literal
result += new Array(9 - binaryElement.length).join('0').concat(binaryElement);
}
@ -1784,9 +1777,8 @@ export class ExpressionFunctions {
}
private static flatten(arr: any[], dept: number): any[]{
dept = typeof dept === 'undefined' ? 1 : dept;
if (typeof dept !== 'number') {
return;
if (!ExpressionFunctions.isNumber(dept) || dept < 1) {
dept = 1;
}
let res = JSON.parse(JSON.stringify(arr));
@ -1802,11 +1794,7 @@ export class ExpressionFunctions {
return res;
}
// tslint:disable-next-line: max-func-body-length
private static getStandardFunctions(): ReadonlyMap<string, ExpressionEvaluator> {
// tslint:disable-next-line: no-unnecessary-local-variable
const functions: ExpressionEvaluator[] = [
//Math
new ExpressionEvaluator(ExpressionType.Element, ExpressionFunctions.extractElement, ReturnType.Object, this.validateBinary),
@ -1961,7 +1949,6 @@ export class ExpressionFunctions {
error = 'Second paramter must be more than zero';
}
// tslint:disable-next-line: prefer-array-literal
const result: number[] = [...Array(args[1]).keys()].map((u: number): number => u + Number(args[0]));
return { value: result, error };
@ -2034,7 +2021,7 @@ export class ExpressionFunctions {
new ExpressionEvaluator(
ExpressionType.Flatten,
ExpressionFunctions.apply(
args => {
(args: any []): any[] => {
let array = args[0];
let depth = args.length > 1 ? args[1] : 100;
return ExpressionFunctions.flatten(array, depth);
@ -2044,7 +2031,7 @@ export class ExpressionFunctions {
),
new ExpressionEvaluator(
ExpressionType.Unique,
ExpressionFunctions.apply(args => [... new Set(args[0])]),
ExpressionFunctions.apply((args: any []): any[] => [... new Set(args[0])]),
ReturnType.Object,
(expression: Expression): void => ExpressionFunctions.validateOrder(expression, [], ReturnType.Object)
),
@ -2175,7 +2162,7 @@ export class ExpressionFunctions {
(expression: Expression): void => ExpressionFunctions.validateArityAndAnyType(expression, 3, 3, ReturnType.String)),
new ExpressionEvaluator(
ExpressionType.Split,
ExpressionFunctions.apply((args: any []): string[] => ExpressionFunctions.parseStringOrNull(args[0]).split(ExpressionFunctions.parseStringOrNull(args[1]? args[1]: '')), ExpressionFunctions.verifyStringOrNull),
ExpressionFunctions.apply((args: any []): string[] => ExpressionFunctions.parseStringOrNull(args[0]).split(ExpressionFunctions.parseStringOrNull(args[1] || '')), ExpressionFunctions.verifyStringOrNull),
ReturnType.Object,
(expression: Expression): void => ExpressionFunctions.validateArityAndAnyType(expression, 1, 2, ReturnType.String)),
new ExpressionEvaluator(
@ -2823,7 +2810,6 @@ export class ExpressionFunctions {
error = `Min value ${ args[0] } cannot be greater than max value ${ args[1] }.`;
}
// tslint:disable-next-line: insecure-random
const value: any = Math.floor(Math.random() * (Number(args[1]) - Number(args[0])) + Number(args[0]));
return { value, error };
@ -2855,7 +2841,6 @@ export class ExpressionFunctions {
ExpressionFunctions.validateUnary),
new ExpressionEvaluator(
ExpressionType.DataUriToString,
// tslint:disable-next-line: restrict-plus-operands
ExpressionFunctions.apply((args: Readonly<any>): string => Buffer.from(args[0].slice(args[0].indexOf(',') + 1), 'base64').toString(), ExpressionFunctions.verifyString),
ReturnType.String,
ExpressionFunctions.validateUnary),

Просмотреть файл

@ -100,8 +100,6 @@ 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';
@ -144,7 +142,7 @@ export class ExpressionType {
// TODO
// xPath
// jPath
// xml
// URI parsing functions
public static readonly UriHost: string = 'uriHost';

Просмотреть файл

@ -55,7 +55,7 @@ export class Extensions {
* @returns Accessor path of expression.
*/
public static referenceWalk(expression: Expression,
extension?: (arg0: Expression) => boolean): {path:string; refs:Set<string>} {
extension?: (arg0: Expression) => boolean): {path: string; refs: Set<string>} {
let path: string;
let refs = new Set<string>();
if (extension === undefined || !extension(expression)) {
@ -114,7 +114,7 @@ export class Extensions {
}
const iteratorName = (children[1].children[0] as Constant).value as string;
var nonLocalRefs2 = Array.from(refs2).filter(x => !(x === iteratorName || x.startsWith(iteratorName + '.') || x.startsWith(iteratorName + '[')));
var nonLocalRefs2 = Array.from(refs2).filter((x): boolean => !(x === iteratorName || x.startsWith(iteratorName + '.') || x.startsWith(iteratorName + '[')));
refs = new Set([...refs, ...refs0, ...nonLocalRefs2]);
} else {
@ -122,7 +122,7 @@ export class Extensions {
const result = Extensions.referenceWalk(child, extension);
const childPath = result.path;
const refs0 = result.refs;
refs = new Set([...refs, ...refs0])
refs = new Set([...refs, ...refs0]);
if (childPath !== undefined) {
refs.add(childPath);
}
@ -130,7 +130,7 @@ export class Extensions {
}
}
return {path, refs}
return {path, refs};
}
/**
@ -146,7 +146,6 @@ export class Extensions {
}
let value: any;
// tslint:disable-next-line: prefer-const
let error: string;
// todo, Is there a better way to access value, or any case is not listed below?
if (instance instanceof Map && instance as Map<string, any>!== undefined) {

Просмотреть файл

@ -19,4 +19,4 @@ export * from './parser';
export * from './memory';
export * from './regexErrorListener';
export * from './componentExpressionFunctions';
export * from './formatConverter';
export * from './datetimeFormatConverter';

Просмотреть файл

@ -1,6 +1,5 @@
import { MemoryInterface } from './memoryInterface';
import { Extensions } from '../extensions';
import { Util } from '../parser/util';
/**
* @module adaptive-expressions

Просмотреть файл

@ -1,4 +1,3 @@
/**
* @module adaptive-expressions
*/
@ -7,9 +6,7 @@
* Licensed under the MIT License.
*/
import { ANTLRInputStream, CommonTokenStream } from 'antlr4ts';
// tslint:disable-next-line: no-submodule-imports
import { AbstractParseTreeVisitor, ParseTree, TerminalNode } from 'antlr4ts/tree';
import { ExpressionFunctions } from '../expressionFunctions';
import { Constant } from '../constant';
import { Expression } from '../expression';
import { EvaluatorLookup } from '../expressionEvaluator';
@ -29,7 +26,6 @@ export class ExpressionParser implements ExpressionParserInterface {
*/
public readonly EvaluatorLookup: EvaluatorLookup;
// tslint:disable-next-line: typedef
private readonly ExpressionTransformer = class extends AbstractParseTreeVisitor<Expression> implements ExpressionAntlrParserVisitor<Expression> {
private readonly _lookupFunction: EvaluatorLookup = undefined;
@ -209,7 +205,7 @@ export class ExpressionParser implements ExpressionParserInterface {
};
public constructor(lookup?: EvaluatorLookup) {
this.EvaluatorLookup = lookup === undefined ? Expression.lookup : lookup;
this.EvaluatorLookup = lookup || Expression.lookup;
}
protected static antlrParse(expression: string): ParseTree {

Просмотреть файл

@ -10,6 +10,11 @@
* util class
*/
export class Util {
/**
* trim char
* @param str input string
* @param char trim character
*/
public static trim(str: string, char: string): string {
if (char !== undefined) {
return str.replace(new RegExp(''.concat('^\\', char, '+|\\', char, '+$'), 'g'), '');
@ -18,6 +23,10 @@ export class Util {
return str.trim();
}
/**
* escape \r \n \t and other characters
* @param exp
*/
public static unescape(exp: string): string {
const validCharactersDict: any = {
'\\r': '\r',

Просмотреть файл

@ -522,14 +522,6 @@ const dataSource = [
['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],
];
@ -636,42 +628,18 @@ describe('expression parser functional test', () => {
const expected = data[1];
//Assert Object Equals
if (Array.isArray(actual) && Array.isArray(expected)) {
const [isSuccess, errorMessage] = isArraySame(actual, expected);
if (!isSuccess) {
assert.fail(errorMessage);
}
} else if (typeof expected === 'number') {
assert(parseFloat(actual) === expected, `actual is: ${ actual } for case ${ input }`);
}
else {
assert(actual === expected, `actual is: ${ actual } for case ${ input }`);
}
assertObjectEquals(actual, expected);
//Assert ExpectedRefs
if (data.length === 3) {
const actualRefs = Extensions.references(parsed);
const [isSuccess, errorMessage] = isArraySame(actualRefs.sort(), data[2].sort());
if (!isSuccess) {
assert.fail(errorMessage);
}
assertObjectEquals(actualRefs.sort(), data[2].sort());
}
//ToString re-parse
const newExpr = Expression.parse(parsed.toString());
const newActual = newExpr.tryEvaluate(scope).value;
if (Array.isArray(actual) && Array.isArray(newActual)) {
const [isSuccess, errorMessage] = isArraySame(actual, newActual);
if (!isSuccess) {
assert.fail(errorMessage);
}
} else if (typeof newActual === 'number') {
assert(parseFloat(actual) === newActual, `actual is: ${ actual } for case ${ input }`);
}
else {
assert(actual === newActual, `actual is: ${ actual } for case ${ input }`);
}
assertObjectEquals(newActual, actual);
}
});
@ -689,8 +657,6 @@ describe('expression parser functional test', () => {
// normal case, note, we doesn't append a " yet
let exp = Expression.parse('a[f].b[n].z');
let path = undefined;
let left = undefined;
let error = undefined;
({path, left, error} = ExpressionFunctions.tryAccumulatePath(exp, memory));
assert.strictEqual(path, 'a[\'foo\'].b[2].z');
@ -712,20 +678,17 @@ describe('expression parser functional test', () => {
});
});
var isArraySame = (actual, expected) => { //return [isSuccess, errorMessage]
if (actual.length !== expected.length) return [false, `expected length: ${ expected.length }, actual length: ${ actual.length }`];
for (let i = 0; i < actual.length; i++) {
if (Array.isArray(actual[i]) && Array.isArray(expected[i])) {
if (!isArraySame(actual[i], expected[i])) {
return [false, `actual is: ${ actual[i] }, expected is: ${ expected[i] }`]
}
} else if (Array.isArray(actual[i]) || Array.isArray(expected[i])){
return [false, `actual is: ${ actual[i] }, expected is: ${ expected[i] }`]
} else if (actual[i] !== expected[i]) {
return [false, `actual is: ${ actual[i] }, expected is: ${ expected[i] }`];
var assertObjectEquals = (actual, expected) => {
if (actual === undefined || expected === undefined) {
return;
} else if (typeof actual === 'number' && typeof expected === 'number') {
assert.equal(parseFloat(actual), parseFloat(expected), `actual is: ${ actual }, expected is ${ expected }`);
} else if (Array.isArray(actual) && Array.isArray(expected)) {
assert.equal(actual.length, expected.length);
for(let i = 0; i< actual.length; i++) {
assertObjectEquals(actual[i], expected[i], `actual is: ${ actual[i] }, expected is ${ expected[i] }`);
}
} else {
assert.equal(actual, expected, `actual is: ${ actual }, expected is ${ expected }`);
}
return [true, ''];
};
};

Просмотреть файл

@ -78,7 +78,7 @@ the result could be:
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.
By this, You can use the `ActivityFactory.fromObject(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]
@ -91,26 +91,22 @@ Then make sure you include the platform specific language generation library.
For C#, add Microsoft.Bot.Builder.LanguageGeneration.
For NodeJS, add botbuilder-lg
Load the template manager with your .lg file(s)
Load the template manager with your .lg file
```typescript
// multi lg files
let lgEngine = new TemplateEngine.addFiles(filePaths, importResolver?);
// single lg file
let lgEngine = new TemplateEngine.addFile(filePath, importResolver?);
let lgFile = new LGParser.parseFile(filePath, importResolver?, expressionParser?);
```
When you need template expansion, call the templateEngine and pass in the relevant template name
When you need template expansion, call the LGFile and pass in the relevant template name
```typescript
await turnContext.sendActivity(lgEngine.evaluateTemplate("<TemplateName>", entitiesCollection));
await turnContext.sendActivity(lgFile.evaluateTemplate("<TemplateName>", entitiesCollection));
```
If your template needs specific entity values to be passed for resolution/ expansion, you can pass them in on the call to `evaluateTemplate`
```typescript
await turnContext.sendActivity(lgEngine.evaluateTemplate("WordGameReply", { GameName = "MarcoPolo" } ));
await turnContext.sendActivity(lgFile.evaluateTemplate("WordGameReply", { GameName = "MarcoPolo" } ));
```
[1]:https://github.com/Microsoft/BotBuilder/blob/master/specs/botframework-activity/botframework-activity.md

Просмотреть файл

@ -1,4 +1,3 @@
/**
* @module botbuilder-lg
*/
@ -6,7 +5,6 @@
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
// tslint:disable-next-line: no-submodule-imports
import { AbstractParseTreeVisitor, TerminalNode } from 'antlr4ts/tree';
import { Expression, ExpressionParserInterface, Extensions, ExpressionParser } from 'adaptive-expressions';
import { keyBy } from 'lodash';
@ -19,9 +17,8 @@ import { LGExtensions } from './lgExtensions';
import { AnalyzerResult } from './analyzerResult';
import {LGErrors} from './lgErrors';
// tslint:disable-next-line: max-classes-per-file
/**
* Analyzer engine. To analyse which variable may be used
* Analyzer engine. To to get the static analyzer results.
*/
export class Analyzer extends AbstractParseTreeVisitor<AnalyzerResult> implements LGFileParserVisitor<AnalyzerResult> {
/**
@ -38,7 +35,7 @@ export class Analyzer extends AbstractParseTreeVisitor<AnalyzerResult> implement
this.templates = templates;
this.templateMap = keyBy(templates, (t: LGTemplate): string => t.name);
// create an evaluator to leverage it's customized function look up for checking
// create an evaluator to leverage its customized function look up for checking
const evaluator: Evaluator = new Evaluator(this.templates, expressionParser);
this._expressionParser = evaluator.expressionParser;
}
@ -117,8 +114,8 @@ export class Analyzer extends AbstractParseTreeVisitor<AnalyzerResult> implement
const values = ctx.keyValueStructureValue();
for (const value of values) {
if (this.isPureExpression(value).hasExpr) {
result.union(this.analyzeExpression(this.isPureExpression(value).expression));
if (LGExtensions.isPureExpression(value).hasExpr) {
result.union(this.analyzeExpression(LGExtensions.isPureExpression(value).expression));
} else {
const exprs = value.EXPRESSION_IN_STRUCTURE_BODY();
for (const expr of exprs) {
@ -214,31 +211,4 @@ export class Analyzer extends AbstractParseTreeVisitor<AnalyzerResult> implement
private currentTarget(): EvaluationTarget {
return this.evalutationTargetStack[this.evalutationTargetStack.length - 1];
}
public isPureExpression(ctx: lp.KeyValueStructureValueContext): {hasExpr: boolean; expression: string | undefined} {
let expression = ctx.text;
let hasExpr = false;
for (const node of ctx.children) {
switch ((node as TerminalNode).symbol.type) {
case (lp.LGFileParser.ESCAPE_CHARACTER_IN_STRUCTURE_BODY):
return {hasExpr, expression};
case (lp.LGFileParser.EXPRESSION_IN_STRUCTURE_BODY):
if (hasExpr) {
return {hasExpr: false, expression: expression};
}
hasExpr = true;
expression = node.text;
break;
default:
if (node !== undefined && node.text !== '' && node.text !== ' ') {
return {hasExpr: false, expression: expression};
}
break;
}
}
return {hasExpr: hasExpr, expression: expression};
}
}

Просмотреть файл

@ -25,7 +25,7 @@ export class CustomizedMemory implements MemoryInterface {
*/
public localMemory: MemoryInterface;
public constructor(scope?: any, localMemory: MemoryInterface = undefined) {
public constructor(scope?: any, localMemory?: MemoryInterface) {
this.globalMemory = !scope ? undefined : SimpleObjectMemory.wrap(scope);
this.localMemory = localMemory;
}

Просмотреть файл

@ -32,8 +32,8 @@ export class Diagnostic {
range: Range,
message: string,
severity: DiagnosticSeverity = DiagnosticSeverity.Error,
source: string = undefined,
code: string = undefined) {
source?: string ,
code?: string) {
this.message = message;
this.range = range;
this.severity = severity;

Просмотреть файл

@ -12,7 +12,6 @@ import { Position } from './position';
import { Range } from './range';
import { LGErrors } from './lgErrors';
// tslint:disable-next-line: completed-docs
/**
* LG parser error listener.
*/
@ -27,12 +26,11 @@ export class ErrorListener implements ANTLRErrorListener<any> {
offendingSymbol: any,
line: number,
charPositionInLine: number,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
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
// tslint:disable-next-line: restrict-plus-operands
const stopPosition: Position = new Position(line, charPositionInLine + offendingSymbol.stopIndex - offendingSymbol.startIndex + 1);
const range: Range = new Range(startPosition, stopPosition);
const diagnostic: Diagnostic = new Diagnostic(range, LGErrors.syntaxError, DiagnosticSeverity.Error, this.source);

Просмотреть файл

@ -27,7 +27,7 @@ export class EvaluationTarget {
public scope: any;
/**
* The children template that this template has evaluated currently.
* The children templates that this template has evaluated currently.
*/
public evaluatedChildren: Map<string, any>;
public constructor(templateName: string, scope: any) {
@ -38,7 +38,7 @@ export class EvaluationTarget {
/**
* Get current instance id. If two target has the same Id,
* we can say they have the same template evaluation.
* we can say they have the same template evaluation result.
* @returns id.
*/
public getId(): string {

Просмотреть файл

@ -5,10 +5,9 @@
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
// tslint:disable-next-line: no-submodule-imports
import { AbstractParseTreeVisitor, TerminalNode } from 'antlr4ts/tree';
import { ParserRuleContext } from 'antlr4ts/ParserRuleContext';
import { ExpressionFunctions, Constant, EvaluatorLookup, Expression, ExpressionParser, ExpressionEvaluator, ExpressionType, ReturnType, SimpleObjectMemory } from 'adaptive-expressions';
import { ExpressionFunctions, Constant, EvaluatorLookup, Expression, ExpressionParser, ExpressionEvaluator, ExpressionType, ReturnType, SimpleObjectMemory} from 'adaptive-expressions';
import { keyBy } from 'lodash';
import { CustomizedMemory } from './customizedMemory';
import { EvaluationTarget } from './evaluationTarget';
@ -22,7 +21,6 @@ import { LGErrors } from './lgErrors';
/**
* Evaluation runtime engine
*/
// tslint:disable-next-line: max-classes-per-file
export class Evaluator extends AbstractParseTreeVisitor<any> implements LGFileParserVisitor<any> {
/**
@ -59,7 +57,7 @@ export class Evaluator extends AbstractParseTreeVisitor<any> implements LGFilePa
this.templateMap = keyBy(templates, (t: LGTemplate): string => t.name);
this.strictMode = strictMode;
// generate a new customzied expression parser by injecting the template as functions
// generate a new customzied expression parser by injecting the templates as functions
this.expressionParser = new ExpressionParser(this.customizedEvaluatorLookup(expressionParser.EvaluatorLookup));
}
@ -82,7 +80,7 @@ export class Evaluator extends AbstractParseTreeVisitor<any> implements LGFilePa
throw new Error(LGErrors.templateNotExist(templateName));
}
if (this.evaluationTargetStack.find((u: EvaluationTarget): boolean => u.templateName === templateName) !== undefined) {
if (this.evaluationTargetStack.some((u: EvaluationTarget): boolean => u.templateName === templateName)) {
throw new Error(`${ LGErrors.loopDetected } ${ this.evaluationTargetStack.reverse()
.map((u: EvaluationTarget): string => u.templateName)
.join(' => ') }`);
@ -150,14 +148,14 @@ export class Evaluator extends AbstractParseTreeVisitor<any> implements LGFilePa
const result = [];
for(const item of values) {
if (Evaluator.isPureExpression(item).hasExpr) {
result.push(this.evalExpression(Evaluator.isPureExpression(item).expression, ctx));
if (LGExtensions.isPureExpression(item).hasExpr) {
result.push(this.evalExpression(LGExtensions.isPureExpression(item).expression, ctx));
} else {
let itemStringResult = '';
for(const node of item.children) {
switch ((node as TerminalNode).symbol.type) {
case (lp.LGFileParser.ESCAPE_CHARACTER_IN_STRUCTURE_BODY):
itemStringResult += this.evalEscape(node.text);
itemStringResult += LGExtensions.evalEscape(node.text);
break;
case (lp.LGFileParser.EXPRESSION_IN_STRUCTURE_BODY):
@ -193,7 +191,6 @@ export class Evaluator extends AbstractParseTreeVisitor<any> implements LGFilePa
public visitNormalTemplateBody(ctx: lp.NormalTemplateBodyContext): any {
const normalTemplateStrs: lp.TemplateStringContext[] = ctx.templateString();
// tslint:disable-next-line: insecure-random
const randomNumber: number = Math.floor(Math.random() * normalTemplateStrs.length);
return this.visit(normalTemplateStrs[randomNumber].normalTemplateString());
@ -221,7 +218,7 @@ export class Evaluator extends AbstractParseTreeVisitor<any> implements LGFilePa
case lp.LGFileParser.DASH:
break;
case lp.LGFileParser.ESCAPE_CHARACTER:
result.push(this.evalEscape(innerNode.text));
result.push(LGExtensions.evalEscape(innerNode.text));
break;
case lp.LGFileParser.EXPRESSION:
result.push(this.evalExpression(innerNode.text, ctx, prefixErrorMsg));
@ -249,7 +246,7 @@ export class Evaluator extends AbstractParseTreeVisitor<any> implements LGFilePa
public constructScope(inputTemplateName: string, args: any[]): any {
var templateName = this.parseTemplateName(inputTemplateName).pureTemplateName;
if (!this.templateMap[templateName]) {
if (!(templateName in this.templateMap)) {
throw new Error(LGErrors.templateNotExist(templateName));
}
@ -335,7 +332,7 @@ export class Evaluator extends AbstractParseTreeVisitor<any> implements LGFilePa
return false;
}
private evalExpressionInCondition(exp: string, context: ParserRuleContext = undefined, errorPrefix: string = ''): boolean {
private evalExpressionInCondition(exp: string, context?: ParserRuleContext, errorPrefix: string = ''): boolean {
exp = LGExtensions.trimExpression(exp);
let result: any;
let error: string;
@ -374,7 +371,7 @@ export class Evaluator extends AbstractParseTreeVisitor<any> implements LGFilePa
return true;
}
private evalExpression(exp: string, context: ParserRuleContext = undefined, errorPrefix: string = ''): any
private evalExpression(exp: string, context?: ParserRuleContext, errorPrefix: string = ''): any
{
exp = LGExtensions.trimExpression(exp);
let result: any;
@ -415,33 +412,6 @@ export class Evaluator extends AbstractParseTreeVisitor<any> implements LGFilePa
return result;
}
public static isPureExpression(ctx: lp.KeyValueStructureValueContext): {hasExpr: boolean; expression: string | undefined} {
let expression = ctx.text;
let hasExpr = false;
for (const node of ctx.children) {
switch ((node as TerminalNode).symbol.type) {
case (lp.LGFileParser.ESCAPE_CHARACTER_IN_STRUCTURE_BODY):
return {hasExpr, expression};
case (lp.LGFileParser.EXPRESSION_IN_STRUCTURE_BODY):
if (hasExpr) {
return {hasExpr: false, expression: expression};
}
hasExpr = true;
expression = node.text;
break;
default:
if (node !== undefined && node.text !== '' && node.text !== ' ') {
return {hasExpr: false, expression: expression};
}
break;
}
}
return {hasExpr: hasExpr, expression: expression};
}
private evalByAdaptiveExpression(exp: string, scope: any): { value: any; error: string } {
const parse: Expression = this.expressionParser.parse(exp);
@ -462,7 +432,6 @@ export class Evaluator extends AbstractParseTreeVisitor<any> implements LGFilePa
var templateName = this.parseTemplateName(name).pureTemplateName;
if (templateName in this.templateMap) {
// tslint:disable-next-line: max-line-length
return new ExpressionEvaluator(templateName, ExpressionFunctions.apply(this.templateEvaluator(name)), ReturnType.Object, this.validTemplateReference);
}
@ -489,22 +458,6 @@ export class Evaluator extends AbstractParseTreeVisitor<any> implements LGFilePa
return undefined;
}
private evalEscape(exp: string): string {
const validCharactersDict: any = {
'\\r': '\r',
'\\n': '\n',
'\\t': '\t'
};
return exp.replace(/\\[^\r\n]?/g, (sub: string): string => {
if (sub in validCharactersDict) {
return validCharactersDict[sub];
} else {
return sub.substr(1);
}
});
}
private readonly isTemplate = (): any => (args: readonly any[]): boolean => {
const templateName = args[0].toString();
return templateName in this.templateMap;
@ -516,7 +469,7 @@ export class Evaluator extends AbstractParseTreeVisitor<any> implements LGFilePa
const stringContent = fs.readFileSync(resourcePath, 'utf-8');
const result = this.wrappedEvalTextContainsExpression(stringContent, Evaluator.expressionRecognizeReverseRegex);
return this.evalEscape(result);
return LGExtensions.evalEscape(result);
}
private getResourcePath(filePath: string): string {
@ -599,7 +552,7 @@ export class Evaluator extends AbstractParseTreeVisitor<any> implements LGFilePa
private readonly validTemplateReference = (expression: Expression): void => {
const templateName: string = expression.type;
if (!this.templateMap[templateName]) {
if (!(templateName in this.templateMap)) {
throw new Error(`no such template '${ templateName }' to call in ${ expression }`);
}

Просмотреть файл

@ -1,4 +1,3 @@
/**
* @module botbuilder-lg
*/
@ -6,7 +5,6 @@
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
// tslint:disable-next-line: no-submodule-imports
import { AbstractParseTreeVisitor, TerminalNode } from 'antlr4ts/tree';
import { ParserRuleContext } from 'antlr4ts/ParserRuleContext';
import { ExpressionFunctions, EvaluatorLookup, Expression, ExpressionParser, ExpressionEvaluator, ReturnType, SimpleObjectMemory } from 'adaptive-expressions';
@ -20,8 +18,6 @@ import { LGExtensions } from './lgExtensions';
import { CustomizedMemory } from './customizedMemory';
import { LGErrors } from './lgErrors';
// tslint:disable-next-line: max-classes-per-file
// tslint:disable-next-line: completed-docs
/**
* LG template expander.
*/
@ -114,7 +110,6 @@ export class Expander extends AbstractParseTreeVisitor<string[]> implements LGFi
return undefined;
}
// tslint:disable-next-line: cyclomatic-complexity
public visitStructuredBody(ctx: lp.StructuredBodyContext): string[] {
const templateRefValues: Map<string, string> = new Map<string, string>();
const stb: lp.StructuredTemplateBodyContext = ctx.structuredTemplateBody();
@ -131,20 +126,20 @@ export class Expander extends AbstractParseTreeVisitor<string[]> implements LGFi
if (value.length > 1) {
const valueList = [];
for (const item of value) {
const id = this.newGuid();
const id = LGExtensions.newGuid();
valueList.push(id);
templateRefValues.set(id, item);
}
expandedResult.forEach(x => x[property] = valueList);
expandedResult.forEach((x): any[] => x[property] = valueList);
} else {
const id = this.newGuid();
expandedResult.forEach(x => x[property] = id);
const id = LGExtensions.newGuid();
expandedResult.forEach((x): string => x[property] = id);
templateRefValues.set(id, value[0]);
}
} else {
const propertyObjects: object[] = [];
this.evalExpression(body.objectStructureLine().text, body.objectStructureLine()).forEach(x => propertyObjects.push(JSON.parse(x)));
this.evalExpression(body.objectStructureLine().text, body.objectStructureLine()).forEach((x): number => propertyObjects.push(JSON.parse(x)));
const tempResult = [];
for (const res of expandedResult) {
for (const propertyObject of propertyObjects) {
@ -189,17 +184,17 @@ export class Expander extends AbstractParseTreeVisitor<string[]> implements LGFi
let result: any[] = [];
for (const item of values) {
if (Evaluator.isPureExpression(item).hasExpr) {
result.push(this.evalExpression(Evaluator.isPureExpression(item).expression, ctx));
if (LGExtensions.isPureExpression(item).hasExpr) {
result.push(this.evalExpression(LGExtensions.isPureExpression(item).expression, ctx));
} else {
let itemStringResult = [''];
for (const node of item.children) {
switch ((node as TerminalNode).symbol.type) {
case (lp.LGFileParser.ESCAPE_CHARACTER_IN_STRUCTURE_BODY):
itemStringResult = this.stringArrayConcat(itemStringResult, [this.evalEscape(node.text)]);
itemStringResult = this.stringArrayConcat(itemStringResult, [LGExtensions.evalEscape(node.text)]);
break;
case (lp.LGFileParser.EXPRESSION_IN_STRUCTURE_BODY):
const errorPrefix = `Property '` + ctx.STRUCTURE_IDENTIFIER().text + `':`;
const errorPrefix = `Property '${ ctx.STRUCTURE_IDENTIFIER().text }':`;
itemStringResult =this.stringArrayConcat(itemStringResult, this.evalExpression(node.text, ctx, errorPrefix));
break;
default:
@ -220,7 +215,7 @@ export class Expander extends AbstractParseTreeVisitor<string[]> implements LGFi
const length: number = switchcaseNodes.length;
const switchNode: lp.SwitchCaseRuleContext = switchcaseNodes[0];
const switchExprs: TerminalNode[] = switchNode.switchCaseStat().EXPRESSION();
const switchErrorPrefix = `Switch '` + switchExprs[0].text + `': `;
const switchErrorPrefix = `Switch '${ switchExprs[0].text }': `;
const switchExprResult = this.evalExpression(switchExprs[0].text, switchcaseNodes[0].switchCaseStat(), switchErrorPrefix);
let idx = 0;
for (const caseNode of switchcaseNodes) {
@ -239,7 +234,7 @@ export class Expander extends AbstractParseTreeVisitor<string[]> implements LGFi
}
const caseExprs: TerminalNode[] = caseNode.switchCaseStat().EXPRESSION();
const caseErrorPrefix = `Case '` + caseExprs[0].text + `': `;
const caseErrorPrefix = `Case '${ caseExprs[0].text }': `;
var caseExprResult = this.evalExpression(caseExprs[0].text, caseNode.switchCaseStat(), caseErrorPrefix);
//condition: check whether two string array have same elements
if (switchExprResult.sort().toString() === caseExprResult.sort().toString()) {
@ -263,7 +258,7 @@ export class Expander extends AbstractParseTreeVisitor<string[]> implements LGFi
case lp.LGFileParser.DASH:
break;
case lp.LGFileParser.ESCAPE_CHARACTER:
result = this.stringArrayConcat(result, [this.evalEscape(innerNode.text)]);
result = this.stringArrayConcat(result, [LGExtensions.evalEscape(innerNode.text)]);
break;
case lp.LGFileParser.EXPRESSION: {
result = this.stringArrayConcat(result, this.evalExpression(innerNode.text, ctx, prefixErrorMsg));
@ -301,23 +296,6 @@ export class Expander extends AbstractParseTreeVisitor<string[]> implements LGFi
return this.evaluationTargetStack[this.evaluationTargetStack.length - 1];
}
private evalEscape(exp: string): string {
const validCharactersDict: any = {
'\\r': '\r',
'\\n': '\n',
'\\t': '\t',
'\\\\': '\\',
};
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 {
const expression: TerminalNode = condition.EXPRESSION()[0];
if (!expression) {
@ -331,7 +309,7 @@ export class Expander extends AbstractParseTreeVisitor<string[]> implements LGFi
return false;
}
private evalExpressionInCondition(exp: string, context: ParserRuleContext = undefined, errorPrefix: string = ''): boolean {
private evalExpressionInCondition(exp: string, context?: ParserRuleContext, errorPrefix: string = ''): boolean {
exp = LGExtensions.trimExpression(exp);
let result: any;
let error: string;
@ -408,7 +386,7 @@ export class Expander extends AbstractParseTreeVisitor<string[]> implements LGFi
if (Array.isArray(result))
{
return result.map(u => u.toString());
return result.map((u): string => u.toString());
}
return [ result.toString() ];
@ -433,7 +411,6 @@ export class Expander extends AbstractParseTreeVisitor<string[]> implements LGFi
return result;
}
// Genearte a new lookup function based on one lookup function
private readonly customizedEvaluatorLookup = (baseLookup: EvaluatorLookup, isExpander: boolean): any => (name: string): ExpressionEvaluator => {
const prebuiltPrefix = 'prebuilt.';
@ -456,7 +433,6 @@ export class Expander extends AbstractParseTreeVisitor<string[]> implements LGFi
const newScope: any = this.constructScope(templateName, Array.from(args));
const value: string[] = this.expandTemplate(templateName, newScope);
// tslint:disable-next-line: insecure-random
const randomNumber: number = Math.floor(Math.random() * value.length);
return value[randomNumber];
@ -498,14 +474,4 @@ export class Expander extends AbstractParseTreeVisitor<string[]> implements LGFi
return expanderExpression;
}
private newGuid(): string {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c: any): string => {
const r: number = Math.random() * 16 | 0;
// tslint:disable-next-line: no-bitwise
const v: number = c === 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16);
});
}
}

Просмотреть файл

@ -6,14 +6,12 @@
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
// tslint:disable-next-line: no-submodule-imports
import { AbstractParseTreeVisitor, TerminalNode } from 'antlr4ts/tree';
import { keyBy } from 'lodash';
import * as lp from './generated/LGFileParser';
import { LGFileParserVisitor } from './generated/LGFileParserVisitor';
import { LGTemplate } from './lgTemplate';
// tslint:disable-next-line: completed-docs
/**
* Lg template extracter.
*/
@ -69,7 +67,7 @@ export class Extractor extends AbstractParseTreeVisitor<Map<string, any>> implem
const lineStart = ' ';
const structName = context.structuredTemplateBody().structuredBodyNameLine().text;
let fullStr = structName + '\n';
context.structuredTemplateBody().structuredBodyContentLine().forEach(line => fullStr += lineStart + line.text + '\n');
context.structuredTemplateBody().structuredBodyContentLine().forEach((line): string => fullStr += lineStart + line.text + '\n');
fullStr += context.structuredTemplateBody().structuredBodyEndLine().text;
result.set(fullStr, undefined);
@ -100,7 +98,6 @@ export class Extractor extends AbstractParseTreeVisitor<Map<string, any>> implem
result.set(conditionLabel.toUpperCase().concat(' ') + expressions[0].text, childTemplateBodyResult);
}
} else {
// tslint:disable-next-line: no-backbone-get-set-outside-model
result.set('ELSE:', childTemplateBodyResult);
}
}
@ -133,7 +130,6 @@ export class Extractor extends AbstractParseTreeVisitor<Map<string, any>> implem
if (caseExpr) {
result.set(conditionLabel.toUpperCase().concat(' ') + expressions[0].text, childTemplateBodyResult);
} else {
// tslint:disable-next-line: no-backbone-get-set-outside-model
result.set('DEFALUT:', childTemplateBodyResult);
}
}

Просмотреть файл

@ -11,7 +11,6 @@ export * from './lgParser';
export * from './generated';
export * from './staticChecker';
export * from './analyzer';
export * from './mslgTool';
export * from './lgTemplate';
export * from './diagnostic';
export * from './lgException';

Просмотреть файл

@ -8,6 +8,7 @@
import * as path from 'path';
import * as lp from './generated/LGFileParser';
import { TerminalNode } from 'antlr4ts/tree';
/**
* Extension methods for LG.
*/
@ -102,4 +103,63 @@ export class LGExtensions {
return errorPrefix;
}
/**
* If a value is pure Expression.
* @param ctx Key value structure value context.
*/
public static isPureExpression(ctx: lp.KeyValueStructureValueContext): {hasExpr: boolean; expression: string | undefined} {
let expression = ctx.text;
let hasExpr = false;
for (const node of ctx.children) {
switch ((node as TerminalNode).symbol.type) {
case (lp.LGFileParser.ESCAPE_CHARACTER_IN_STRUCTURE_BODY):
return {hasExpr, expression};
case (lp.LGFileParser.EXPRESSION_IN_STRUCTURE_BODY):
if (hasExpr) {
return {hasExpr: false, expression: expression};
}
hasExpr = true;
expression = node.text;
break;
default:
if (node !== undefined && node.text !== '' && node.text !== ' ') {
return {hasExpr: false, expression: expression};
}
break;
}
}
return {hasExpr: hasExpr, expression: expression};
}
public static evalEscape(exp: string): string {
const validCharactersDict: any = {
'\\r': '\r',
'\\n': '\n',
'\\t': '\t'
};
return exp.replace(/\\[^\r\n]?/g, (sub: string): string => {
if (sub in validCharactersDict) {
return validCharactersDict[sub];
} else {
return sub.substr(1);
}
});
}
/**
* Generate new guid string.
*/
public static newGuid(): string {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c: any): string => {
const r: number = Math.random() * 16 | 0;
const v: number = c === 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16);
});
}
}

Просмотреть файл

@ -17,6 +17,7 @@ import { Analyzer } from './analyzer';
import { LGParser } from './lgParser';
import { AnalyzerResult } from './analyzerResult';
import { LGErrors } from './lgErrors';
import { LGExtensions } from './lgExtensions';
/// <summary>
/// LG entrance, including properties that LG file has, and evaluate functions.
@ -48,13 +49,13 @@ export class LGFile {
public diagnostics: Diagnostic[];
/// <summary>
/// Gets or sets all references that this LG file has from <see cref="Imports"/>.
/// Gets or sets all references that this LG file has from Imports
/// Notice: reference includs all child imports from the lg file,
/// not only the children belong to this lgfile directly.
/// so, reference count may >= imports count.
/// </summary>
/// <value>
/// all references that this LG file has from <see cref="Imports"/>.
/// all references that this LG file has from Imports
/// </value>
public references: LGFile[];
@ -98,18 +99,24 @@ export class LGFile {
/// </value>
public options: string[];
public constructor(templates: LGTemplate[] = undefined, imports: LGImport[] = undefined, diagnostics: Diagnostic[] = undefined, references: LGFile[] = undefined,
content: string = undefined, id: string = undefined, expressionParser: ExpressionParser = undefined, importResolverDelegate: ImportResolverDelegate = undefined,
options: string[] = undefined) {
this.templates = templates? templates : [];
this.imports = imports? imports : [];
this.diagnostics = diagnostics? diagnostics : [];
this.references = references? references : [];
this.content = content? content : '';
this.id = id? id : '';
public constructor(templates?: LGTemplate[],
imports?: LGImport[],
diagnostics?: Diagnostic[],
references?: LGFile[],
content?: string,
id?: string,
expressionParser?: ExpressionParser,
importResolverDelegate?: ImportResolverDelegate,
options?: string[]) {
this.templates = templates || [];
this.imports = imports || [];
this.diagnostics = diagnostics || [];
this.references = references || [];
this.content = content || '';
this.id = id || '';
this.expressionParser = expressionParser || new ExpressionParser();
this.importResolver = importResolverDelegate;
this.options = options? options : [];
this.options = options || [];
}
/// <summary>
@ -122,10 +129,9 @@ export class LGFile {
/// If strict mode is on, expression would throw exception instead of return
/// null or make the condition failed.
/// </value>
public strictMode: boolean;
public updateStrictMode = (): void => {this.strictMode = this.getStrictModeFromOptions(this.options);};
public get strictMode(): boolean {
return this.getStrictModeFromOptions(this.options);
}
/// <summary>
/// Gets get all templates from current lg file and reference lg files.
@ -136,7 +142,7 @@ export class LGFile {
public get allTemplates(): LGTemplate[] {
let result = this.templates;
this.references.forEach((ref): LGTemplate[] => result = result.concat(ref.templates));
return result;
return Array.from(new Set(result));
}
/// <summary>
@ -148,7 +154,7 @@ export class LGFile {
public get allDiagnostics(): Diagnostic[] {
let result = this.diagnostics;
this.references.forEach((ref): Diagnostic[] => result = result.concat(ref.diagnostics));
return result;
return Array.from(new Set(result));
}
/// <summary>
@ -157,7 +163,7 @@ export class LGFile {
/// <param name="templateName">Template name to be evaluated.</param>
/// <param name="scope">The state visible in the evaluation.</param>
/// <returns>Evaluate result.</returns>
public evaluateTemplate(templateName: string, scope: object = undefined): object {
public evaluateTemplate(templateName: string, scope?: object): any {
this.checkErrors();
const evaluator = new Evaluator(this.allTemplates, this.expressionParser, this.strictMode);
@ -171,7 +177,7 @@ export class LGFile {
/// <param name="templateName">Template name to be evaluated.</param>
/// <param name="scope">The state visible in the evaluation.</param>
/// <returns>Expand result.</returns>
public expandTemplate(templateName: string, scope: object = undefined): string[] {
public expandTemplate(templateName: string, scope?: object): string[] {
this.checkErrors();
const expander = new Expander(this.allTemplates, this.expressionParser, this.strictMode);
@ -197,17 +203,17 @@ export class LGFile {
/// <param name="inlineStr">inline string which will be evaluated.</param>
/// <param name="scope">scope object or JToken.</param>
/// <returns>Evaluate result.</returns>
public evaluate(inlineStr: string, scope: any = undefined): any
public evaluate(inlineStr: string, scope?: object): any
{
if (inlineStr === undefined)
{
throw Error(`inline string is null.`);
throw Error('inline string is empty');
}
this.checkErrors();
// wrap inline string with "# name and -" to align the evaluation process
const fakeTemplateId = this.newGuid();
const fakeTemplateId = LGExtensions.newGuid();
const multiLineMark = '```';
inlineStr = !(inlineStr.trim().startsWith(multiLineMark) && inlineStr.includes('\n'))
@ -298,7 +304,7 @@ export class LGFile {
const destList: string[] = [];
if (startLine < 0 || startLine > stopLine || stopLine >= originList.length) {
throw new Error(`index out of range.`);
throw new Error('index out of range.');
}
destList.push(...this.trimList(originList.slice(0, startLine)));
@ -379,7 +385,6 @@ export class LGFile {
}
private wrapTemplateBodyString(replaceItem: string): string {
// tslint:disable-next-line: newline-per-chained-call
const isStartWithHash: boolean = replaceItem.trimLeft().startsWith('#');
if (isStartWithHash) {
return `- ${ replaceItem.trimLeft() }`;
@ -389,6 +394,7 @@ export class LGFile {
}
private buildTemplateNameLine(templateName: string, parameters: string[]): string {
// if parameters is null or undefined, ignore ()
if (parameters === undefined || parameters === undefined) {
return `# ${ templateName }`;
} else {
@ -405,11 +411,12 @@ export class LGFile {
this.importResolver = lgfile.importResolver;
this.id = lgfile.id;
this.expressionParser = lgfile.expressionParser;
this.options = lgfile.options;
}
private checkErrors(): void {
if (this.allDiagnostics) {
const errors = this.allDiagnostics.filter(u => u.severity === DiagnosticSeverity.Error);
const errors = this.allDiagnostics.filter((u): boolean => u.severity === DiagnosticSeverity.Error);
if (errors.length !== 0) {
throw Error(errors.join('\n'));
}
@ -446,14 +453,4 @@ export class LGFile {
return result;
}
private newGuid(): string {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c: any): string => {
const r: number = Math.random() * 16 | 0;
// tslint:disable-next-line: no-bitwise
const v: number = c === 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16);
});
}
}

Просмотреть файл

@ -5,13 +5,11 @@
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
// tslint:disable-next-line: no-submodule-imports
import { ANTLRInputStream } from 'antlr4ts/ANTLRInputStream';
// tslint:disable-next-line: no-submodule-imports
import { CommonTokenStream } from 'antlr4ts/CommonTokenStream';
import { ErrorListener } from './errorListener';
import { LGFileLexer } from './generated/LGFileLexer';
import { FileContext, ImportDefinitionContext, LGFileParser, ParagraphContext, TemplateDefinitionContext } from './generated/LGFileParser';
import { FileContext, ImportDefinitionContext, LGFileParser, ParagraphContext, TemplateDefinitionContext, OptionsDefinitionContext } from './generated/LGFileParser';
import { LGImport } from './lgImport';
import { LGTemplate } from './lgTemplate';
import { LGFile } from './lgFile';
@ -54,14 +52,14 @@ export class LGParser {
/**
* Parser to turn lg content into a LGFile.
* @param content ext content contains lg templates.
* @param id id is the identifier of content. If importResolver is null, id must be a full path string.
* @param content text content contains lg templates.
* @param id id is the identifier of content. If importResolver is undefined, id must be a full path string.
* @param importResolver resolver to resolve LG import id to template text.
* @param expressionParser Expression parser for evaluating expressions.
* @returns entity.
*/
public static parseText(content: string, id: string = '', importResolver?: ImportResolverDelegate, expressionParser?: ExpressionParser): LGFile {
importResolver = importResolver ? importResolver : LGParser.defaultFileResolver;
importResolver = importResolver || LGParser.defaultFileResolver;
let lgFile = new LGFile();
lgFile.content = content;
lgFile.id = id;
@ -75,7 +73,7 @@ export class LGParser {
lgFile.templates = parsedResult.templates;
lgFile.imports = parsedResult.imports;
lgFile.options = parsedResult.options;
lgFile.updateStrictMode();
diagnostics = diagnostics.concat(parsedResult.invalidTemplateErrors);
lgFile.references = this.getReferences(lgFile, importResolver);
const semanticErrors = new StaticChecker(lgFile, lgFile.expressionParser).check();
@ -94,11 +92,11 @@ export class LGParser {
}
/// <summary>
/// Parser to turn lg content into a <see cref="LGFile"/> based on the original LGFile.
/// Parser to turn lg content into a LGFile based on the original LGFile.
/// </summary>
/// <param name="content">Text content contains lg templates.</param>
/// <param name="lgFile">original LGFile.</param>
/// <returns>new <see cref="LGFile"/> entity.</returns>
/// <returns>new LGFile entity.</returns>
public static parseTextWithRef(content: string, lgFile: LGFile): LGFile {
if (!lgFile) {
throw Error(`LGFile`);
@ -148,7 +146,7 @@ export class LGParser {
importPath = LGExtensions.normalizePath(path.join(path.dirname(sourceId), importPath));
}
if (!fs.existsSync(importPath) || !fs.statSync(importPath).isFile()) {
throw Error(`Could not find file: ${importPath}`);
throw Error(`Could not find file: ${ importPath }`);
}
const content: string = fs.readFileSync(importPath, 'utf-8');
@ -181,7 +179,7 @@ export class LGParser {
const result = importResolver(start.id, id);
const content = result.content;
const path = result.id;
const notExist = Array.from(resourcesFound).filter(u => u.id === path).length === 0;
const notExist = Array.from(resourcesFound).filter((u): boolean => u.id === path).length === 0;
if (notExist) {
var childResource = LGParser.parseText(content, path, importResolver, start.expressionParser);
this.resolveImportResources(childResource, resourcesFound, importResolver);
@ -197,8 +195,8 @@ export class LGParser {
}
}
private static buildDiagnostic(message: string, context: ParserRuleContext = undefined, source: string = undefined): Diagnostic {
message = message === undefined ? '' : message;
private static buildDiagnostic(message: string, context?: ParserRuleContext, source?: string): Diagnostic {
message = message || '';
const startPosition: Position = context === undefined ? new Position(0, 0) : new Position(context.start.line, context.start.charPositionInLine);
const endPosition: Position = context === undefined ? new Position(0, 0) : new Position(context.stop.line, context.stop.charPositionInLine + context.stop.text.length);
return new Diagnostic(new Range(startPosition, endPosition), message, DiagnosticSeverity.Error, source);
@ -212,10 +210,10 @@ export class LGParser {
private static extractLGOptions(file: FileContext): string[] {
return !file ? [] :
file.paragraph()
.map(x => x.optionsDefinition())
.filter(x => x !== undefined)
.map(t => this.extractOption(t.text))
.filter(t => t !== undefined && t !== '');
.map((x): OptionsDefinitionContext => x.optionsDefinition())
.filter((x): boolean => x !== undefined)
.map((t): string => this.extractOption(t.text))
.filter((t): boolean => t !== undefined && t !== '');
}
private static extractOption(originalText: string): string
@ -246,7 +244,7 @@ export class LGParser {
}
}
return errorTemplates.map(u => this.buildDiagnostic("error context.", u, id));
return errorTemplates.map((u): Diagnostic => this.buildDiagnostic('error context.', u, id));
}
private static getFileContentContext(text: string, source: string): FileContext {

Просмотреть файл

@ -5,9 +5,8 @@
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
// tslint:disable-next-line: no-submodule-imports
import { TerminalNode } from 'antlr4ts/tree';
import { ParametersContext, TemplateBodyContext, TemplateDefinitionContext, TemplateNameContext, FileContext} from './generated/LGFileParser';
import { ParametersContext, TemplateDefinitionContext, TemplateNameContext, FileContext} from './generated/LGFileParser';
/**
* Here is a data model that can easily understanded and used as the context or all kinds of visitors
@ -71,7 +70,6 @@ export class LGTemplate {
return {startLine, stopLine};
}
private readonly extractName = (): string => {
// tslint:disable-next-line: newline-per-chained-call
const nameContext: TemplateNameContext = this.parseTree.templateNameLine().templateName();
if (!nameContext || !nameContext.text) {
return '';
@ -81,10 +79,8 @@ export class LGTemplate {
}
private readonly extractParameters = (): string[] => {
// tslint:disable-next-line: newline-per-chained-call
const parameters: ParametersContext = this.parseTree.templateNameLine().parameters();
if (parameters !== undefined) {
// tslint:disable-next-line: newline-per-chained-call
return parameters.IDENTIFIER().map((x: TerminalNode): string => x.text);
}

Просмотреть файл

@ -1,142 +0,0 @@
/**
* @module botbuilder-lg
*/
/**
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
import { ExpressionParser } from 'adaptive-expressions';
import { Analyzer } from './analyzer';
import { Diagnostic } from './diagnostic';
import { Expander } from './expander';
import { Extractor } from './extractor';
import { LGParser } from './lgParser';
import { LGTemplate } from './lgTemplate';
import { Position } from './position';
import { Range } from './range';
// tslint:disable-next-line: completed-docs
/**
* mslg tool, that make some api for mslg cli tool and other debug tools.
*/
export class MSLGTool {
public collationMessages: string[] = [];
public collatedTemplates: Map<string, any> = new Map<string, any>();
public nameCollisions: string[] = [];
private templates: LGTemplate[];
private readonly expressionParser: ExpressionParser;
public constructor(expressionParser?: ExpressionParser) {
this.expressionParser = expressionParser !== undefined ? expressionParser : new ExpressionParser();
}
/**
* validate a lg file.
* @param lgFileContent lg file content.
* @param id id.
* @returns diagnostics.
*/
public validateFile(lgFileContent: string, id?: string): string[] {
const lgFile = LGParser.parseText(lgFileContent, id);
const diagnostic: Diagnostic[] = lgFile.diagnostics;
if (diagnostic.length !== 0) {
return diagnostic.map((error: Diagnostic): string => error.toString());
}
// extract templates
this.templates = lgFile.templates;
if (this.templates && this.templates.length > 0) {
this.runTemplateExtractor(this.templates);
}
return [];
}
public getTemplateVariables(templateName: string): string[] {
const analyzer: Analyzer = new Analyzer(this.templates, this.expressionParser);
return analyzer.analyzeTemplate(templateName).Variables;
}
/**
* expand a template.
* @param templateName template name.
* @param scope scope.
*/
public expandTemplate(templateName: string, scope?: any): string[] {
const expander: Expander = new Expander(this.templates, this.expressionParser);
return expander.expandTemplate(templateName, scope);
}
/**
* collate templates.
*/
public collateTemplates(): string {
let result = '';
if (!this.collationMessages || this.collationMessages.length === 0) {
for (const template of this.collatedTemplates) {
result += '# ' + template[0] + '\n';
if (Array.isArray(template[1])) {
const templateStrs: string[] = template[1] as string[];
for (const templateStr of templateStrs) {
if (templateStr.startsWith('-')) {
result += templateStr.slice(0, 1) + ' ' + templateStr.slice(1) + '\n';
} else {
result += templateStr + '\n';
break;
}
}
} else if (template[1] instanceof Map) {
for (const condition of (template[1] as Map<string, string[]>)) {
const conditionStr: string = condition[0];
result += '- ' + conditionStr + '\n';
condition[1].forEach((templateStr: string): any => {
result += ' ' + templateStr.slice(0, 1) + ' ' + templateStr.slice(1) + '\n';
});
}
} else {
return undefined;
}
result += '\n';
}
}
return result;
}
private runTemplateExtractor(lgtemplates: LGTemplate[]): void {
const extractor: Extractor = new Extractor(lgtemplates);
const templates: Map<string, any>[] = extractor.extract();
for (const item of templates) {
const template: any = item.entries().next().value;
if (this.collatedTemplates.has(template[0])) {
this.nameCollisions.push(template[0]);
if (this.collatedTemplates.get(template[0]) instanceof Map && template[1] instanceof Map) {
for (const condition of (template[1] as Map<string, string[]>)) {
const mergedCondtions: Map<string, string[]> = this.collatedTemplates.get(template[0]) as Map<string, string[]>;
if (mergedCondtions.has(condition[0])) {
// tslint:disable-next-line: max-line-length
this.collatedTemplates.set(template[0], this.collatedTemplates.get(template[0]).set(condition[0], Array.from(new Set(mergedCondtions.get(condition[0]).concat(condition[1])))));
} else {
// tslint:disable-next-line: max-line-length
this.collatedTemplates.set(template[0], this.collatedTemplates.get(template[0]).set(condition[0], condition[1]));
}
}
} 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 {
const range: Range = new Range(new Position(0, 0), new Position(0, 0));
// tslint:disable-next-line: max-line-length
const mergeError: Diagnostic = new Diagnostic(range, `Template ${ template[0] } occurred in both normal and condition templates`);
this.collationMessages.push(mergeError.toString());
}
} else {
this.collatedTemplates.set(template[0], template[1]);
}
}
}
}

Просмотреть файл

@ -5,7 +5,6 @@
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
// tslint:disable-next-line: no-submodule-imports
import { ParserRuleContext } from 'antlr4ts';
import { AbstractParseTreeVisitor, TerminalNode } from 'antlr4ts/tree';
import { ExpressionParser, ExpressionParserInterface } from 'adaptive-expressions';
@ -28,7 +27,7 @@ export class StaticChecker extends AbstractParseTreeVisitor<Diagnostic[]> implem
private visitedTemplateNames: string[];
private _expressionParser: ExpressionParserInterface;
public constructor(lgFile: LGFile, expressionParser: ExpressionParser = undefined) {
public constructor(lgFile: LGFile, expressionParser?: ExpressionParser) {
super();
this.lgFile = lgFile;
this.baseExpressionParser = expressionParser || new ExpressionParser();
@ -60,7 +59,7 @@ export class StaticChecker extends AbstractParseTreeVisitor<Diagnostic[]> implem
return result;
}
this.lgFile.templates.forEach(t => result = result.concat(this.visit(t.parseTree)));
this.lgFile.templates.forEach((t): Diagnostic[] => result = result.concat(this.visit(t.parseTree)));
return result;
}
@ -69,40 +68,28 @@ export class StaticChecker extends AbstractParseTreeVisitor<Diagnostic[]> implem
var result = [];
var templateNameLine = context.templateNameLine();
var errorTemplateName = templateNameLine.errorTemplateName();
if (errorTemplateName)
{
if (errorTemplateName) {
result.push(this.buildLGDiagnostic(LGErrors.invalidTemplateName, undefined, errorTemplateName, false));
}
else {
} else {
var templateName = context.templateNameLine().templateName().text;
if (this.visitedTemplateNames.includes(templateName))
{
if (this.visitedTemplateNames.includes(templateName)) {
result.push(this.buildLGDiagnostic(LGErrors.duplicatedTemplateInSameTemplate(templateName),undefined, templateNameLine));
}
else {
} else {
this.visitedTemplateNames.push(templateName);
for (const reference of this.lgFile.references)
{
var sameTemplates = reference.templates.filter(u => u.name === templateName);
for(const sameTemplate of sameTemplates)
{
for (const reference of this.lgFile.references) {
var sameTemplates = reference.templates.filter((u): boolean => u.name === templateName);
for(const sameTemplate of sameTemplates) {
result.push(this.buildLGDiagnostic( LGErrors.duplicatedTemplateInDiffTemplate(sameTemplate.name, sameTemplate.source), undefined, templateNameLine));
}
}
if (result.length > 0) {
return result;
}
else
{
if (!context.templateBody())
{
result.push(this.buildLGDiagnostic(LGErrors.noTemplateBody(templateName), DiagnosticSeverity.Warning, context.templateNameLine()));
}
else {
result = result.concat(this.visit(context.templateBody()));
}
} else if (!context.templateBody()) {
result.push(this.buildLGDiagnostic(LGErrors.noTemplateBody(templateName), DiagnosticSeverity.Warning, context.templateNameLine()));
} else {
result = result.concat(this.visit(context.templateBody()));
}
}
}
@ -219,7 +206,6 @@ export class StaticChecker extends AbstractParseTreeVisitor<Diagnostic[]> implem
return result;
}
// tslint:disable-next-line: cyclomatic-complexity
public visitSwitchCaseBody(context: lp.SwitchCaseBodyContext): Diagnostic[] {
let result: Diagnostic[] = [];
const switchCaseNodes: lp.SwitchCaseRuleContext[] = context.switchCaseTemplateBody().switchCaseRule();
@ -263,8 +249,8 @@ export class StaticChecker extends AbstractParseTreeVisitor<Diagnostic[]> implem
if (switchCaseStat.EXPRESSION().length !== 1) {
result.push(this.buildLGDiagnostic(LGErrors.invalidExpressionInSwiathCase, undefined, switchCaseStat));
} else {
let errorPrefix = switchExpr? `Switch` : `Case`;
errorPrefix += ` '` + switchCaseStat.EXPRESSION(0).text + `': `;
let errorPrefix = switchExpr ? 'Switch' : 'Case';
errorPrefix += ` '${ switchCaseStat.EXPRESSION(0).text }': `;
result = result.concat(this.checkExpression(switchCaseStat.EXPRESSION(0).text, switchCaseStat, errorPrefix));
}
} else {
@ -331,7 +317,7 @@ export class StaticChecker extends AbstractParseTreeVisitor<Diagnostic[]> implem
const stopPosition = context === undefined ? new Position(0, 0) : new Position(context.stop.line, context.stop.charPositionInLine + context.stop.text.length);
severity = severity? severity : DiagnosticSeverity.Error;
const range = new Range(startPosition, stopPosition);
message = (this.visitedTemplateNames.length > 0 && includeTemplateNameInfo)? `[${ this.visitedTemplateNames.reverse().find(x => true) }]`+ message : message;
message = (this.visitedTemplateNames.length > 0 && includeTemplateNameInfo)? `[${ this.visitedTemplateNames[this.visitedTemplateNames.length - 1] }]`+ message : message;
return new Diagnostic(range, message, severity, this.lgFile.id);
}

Просмотреть файл

@ -1,198 +0,0 @@
const { MSLGTool, LGErrors } = require('../');
const assert = require('assert');
const fs = require('fs');
function GetErrors(mslgtool, fileName){
const path = `${ __dirname }/testData/mslgTool/`+ fileName;
const text = fs.readFileSync(path, 'utf-8');
return mslgtool.validateFile(text, path);
}
describe('MSLGTool', function() {
it('TestValidateReturnStaticCheckerErrors', function() {
let errors = GetErrors(new MSLGTool(),'StaticCheckerErrors.lg');
assert.strictEqual(errors.length,6);
assert(errors[0].includes(LGErrors.noTemplateBody('template')));
assert(errors[1].includes(LGErrors.notEndWithElseInCondition));
assert(errors[2].includes(LGErrors.notStartWithSwitchInSwitchCase));
assert(errors[3].includes(LGErrors.notEndWithDefaultInSwitchCase));
assert(errors[4].includes(LGErrors.missingCaseInSwitchCase));
assert(errors[5].includes(LGErrors.invalidTemplateName));
});
it('TestValidateReturnNoErrors', function() {
const errors = GetErrors(new MSLGTool(),'ValidFile.lg');
assert.strictEqual(errors.length, 0);
});
it('TestCollateTemplates', function() {
const mslgTool = new MSLGTool();
let errors = GetErrors(mslgTool, 'CollateFile1.lg');
assert.strictEqual(errors.length, 0);
errors = GetErrors(mslgTool, 'CollateFile2.lg');
assert.strictEqual(errors.length, 0);
errors = GetErrors(mslgTool, 'CollateFile3.lg');
assert.strictEqual(errors.length, 0);
assert.strictEqual(mslgTool.collationMessages.length, 0);
assert.strictEqual(mslgTool.nameCollisions.length, 3);
assert.strictEqual(mslgTool.collatedTemplates.size, 5);
assert.strictEqual(mslgTool.collatedTemplates.get('Greeting').length, 3);
assert.strictEqual(mslgTool.collatedTemplates.get('TimeOfDayWithCondition').size, 3);
assert.strictEqual(mslgTool.collatedTemplates.get('TimeOfDay').length, 3);
});
it('TestCollateTemplatesOfStructuredLG', function() {
const mslgTool = new MSLGTool();
errors = GetErrors(mslgTool, 'CollateFile4.lg');
assert.strictEqual(errors.length, 0);
errors = GetErrors(mslgTool, 'CollateFile5.lg');
assert.strictEqual(errors.length, 0);
assert.strictEqual(mslgTool.collationMessages.length, 0);
assert.strictEqual(mslgTool.nameCollisions.length, 1);
assert.strictEqual(mslgTool.collatedTemplates.size, 1);
assert.strictEqual(mslgTool.collatedTemplates.get('ST2')[0].replace(/\r\n/g, '\n'), '[MyStruct\n Speak=bar\n Text=zoo\n]');
assert.strictEqual(mslgTool.collatedTemplates.get('ST2')[1].replace(/\r\n/g, '\n'), '[MyStruct\n Speak=hello\n Text=world\n]');
let result = mslgTool.collateTemplates();
assert.strictEqual(result.replace(/\r\n/g, '\n'), '# ST2\n[MyStruct\n Speak=bar\n Text=zoo\n]\n\n');
});
it('TestExpandTemplate', function() {
const mslgTool = new MSLGTool();
let errors = GetErrors(mslgTool, 'CollateFile1.lg');
assert.strictEqual(errors.length, 0);
let expandedTemplate = mslgTool.expandTemplate('FinalGreeting', undefined);
assert.strictEqual(expandedTemplate.length, 4);
let expectedResults = ['Hi Morning', 'Hi Evening', 'Hello Morning', 'Hello Evening'];
expectedResults.forEach(element => {
assert.strictEqual(expandedTemplate.includes(element), true);
});
});
it('TestExpandTemplateWithScope', function() {
const mslgTool = new MSLGTool();
let errors = GetErrors(mslgTool, 'CollateFile3.lg');
assert.strictEqual(errors.length, 0);
let expandedTemplate = mslgTool.expandTemplate('TimeOfDayWithCondition', { time: 'evening' });
assert.strictEqual(expandedTemplate.length, 2);
let expectedResults = ['Hi Evening', 'Hey Evening'];
expectedResults.forEach(element => {
assert.strictEqual(expandedTemplate.includes(element), true);
});
let expandedTemplate2 = mslgTool.expandTemplate('greetInAWeek', { day: 'Sunday' });
assert.strictEqual(expandedTemplate.length, 2);
let expected2Results = ['Nice Sunday!', 'Happy Sunday!'];
expected2Results.forEach(element => {
assert.strictEqual(expandedTemplate2.includes(element), true);
});
});
it('TestExpandTemplateWithRef', function() {
const mslgTool = new MSLGTool();
let errors = GetErrors(mslgTool, 'ValidFile.lg');
assert.strictEqual(errors.length, 0);
const alarms = [
{
time: '7 am',
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');
});
it('TestExpandTemplateWithFunction', function() {
const mslgTool = new MSLGTool();
let errors = GetErrors(mslgTool, 'ValidFile.lg');
assert.strictEqual(errors.length, 0);
const alarms = [
{
time: '7 am',
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'
];
assert.strictEqual(evaled.length, 1);
assert.strictEqual(evalOptions.includes(evaled[0]), true);
evaled = mslgTool.expandTemplate('T2');
assert.strictEqual(evaled.length, 1);
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);
evaled = mslgTool.expandTemplate('T4');
assert.strictEqual(evaled.length, 1);
assert.strictEqual(evaled[0] === 'ey' || evaled[0] === 'el', true);
});
it('TestExpandTemplateWithRefInMultiLine', function() {
const mslgTool = new MSLGTool();
let errors = GetErrors(mslgTool, 'ValidFile.lg');
assert.strictEqual(errors.length, 0);
const alarms = [
{
time: '7 am',
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'];
assert(eval1Options.includes(expandedTemplate[0]));
assert(eval2Options.includes(expandedTemplate[1]));
});
it('TestExpandTemplateWithStructuredLG', function() {
const mslgTool = new MSLGTool();
let errors = GetErrors(mslgTool, 'StructuredLG.lg');
assert.strictEqual(errors.length, 0);
let expandedTemplates = mslgTool.expandTemplate('AskForAge.prompt');
assert.strictEqual(expandedTemplates.length, 4);
let evalOptions = [
'{"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 = [
'{"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)));
});
});