Clean LG/Expression (#1873)
* init * update * clean LG/Expression * revert ts-node version
This commit is contained in:
Родитель
1a02dff37b
Коммит
efdd58ff2d
|
@ -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)));
|
||||
});
|
||||
});
|
||||
|
Загрузка…
Ссылка в новой задаче