[PORT]Refine template loop detection to enable recursive functions (#2305)
* Refine template loop detection to enable recursive functions * Reduce the recursion stack depth to pass MacOS test
This commit is contained in:
Родитель
69bb9563ee
Коммит
c3cfaad2df
|
@ -16,7 +16,6 @@ import { ExpressionFunctions } from '../expressionFunctions';
|
|||
export class SimpleObjectMemory implements MemoryInterface {
|
||||
|
||||
private memory: any = undefined;
|
||||
private versionNumber: number = 0;
|
||||
|
||||
public constructor(memory: any) {
|
||||
this.memory = memory;
|
||||
|
@ -142,13 +141,13 @@ export class SimpleObjectMemory implements MemoryInterface {
|
|||
}
|
||||
}
|
||||
|
||||
this.versionNumber++;
|
||||
return;
|
||||
}
|
||||
|
||||
public version(): string {
|
||||
return this.versionNumber.toString();
|
||||
return this.toString();
|
||||
}
|
||||
|
||||
public toString(): string {
|
||||
return JSON.stringify(this.memory, this.getCircularReplacer());
|
||||
}
|
||||
|
|
|
@ -57,6 +57,21 @@ export class CustomizedMemory implements MemoryInterface {
|
|||
}
|
||||
|
||||
public version(): string {
|
||||
return '0';
|
||||
let result = '';
|
||||
if (this.globalMemory) {
|
||||
const version = this.globalMemory.version();
|
||||
if (version) {
|
||||
result = result.concat(version);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.localMemory) {
|
||||
const localVersion = this.localMemory.version();
|
||||
if (localVersion) {
|
||||
result = result.concat(localVersion);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
|
@ -6,7 +6,7 @@
|
|||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License.
|
||||
*/
|
||||
import { CustomizedMemory } from './customizedMemory';
|
||||
import { MemoryInterface } from 'adaptive-expressions';
|
||||
|
||||
|
||||
/**
|
||||
|
@ -22,13 +22,13 @@ export class EvaluationTarget {
|
|||
/**
|
||||
* Scope.
|
||||
*/
|
||||
public scope: any;
|
||||
public scope: MemoryInterface;
|
||||
|
||||
/**
|
||||
* The children templates that this template has evaluated currently.
|
||||
*/
|
||||
public evaluatedChildren: Map<string, any>;
|
||||
public constructor(templateName: string, scope: any) {
|
||||
public constructor(templateName: string, scope: MemoryInterface) {
|
||||
this.templateName = templateName;
|
||||
this.scope = scope;
|
||||
this.evaluatedChildren = new Map<string, any>();
|
||||
|
@ -40,24 +40,7 @@ export class EvaluationTarget {
|
|||
* @returns Id.
|
||||
*/
|
||||
public getId(): string {
|
||||
const memory = this.scope as CustomizedMemory;
|
||||
let result = this.templateName;
|
||||
if (memory) {
|
||||
if (memory.globalMemory) {
|
||||
const version = memory.globalMemory.version();
|
||||
if (version) {
|
||||
result = result.concat(version);
|
||||
}
|
||||
}
|
||||
|
||||
if (memory.localMemory) {
|
||||
const localMemoryString = memory.localMemory.toString();
|
||||
if (localMemoryString) {
|
||||
result = result.concat(localMemoryString);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
const scopeVersion = this.scope ? this.scope.version() : '';
|
||||
return this.templateName + scopeVersion;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -73,6 +73,8 @@ export class Evaluator extends AbstractParseTreeVisitor<any> implements LGTempla
|
|||
* @returns Evaluate result.
|
||||
*/
|
||||
public evaluateTemplate(inputTemplateName: string, scope: any): any {
|
||||
|
||||
const memory = scope instanceof CustomizedMemory ? scope : new CustomizedMemory(scope);
|
||||
let templateName: string;
|
||||
let reExecute: boolean;
|
||||
({reExecute, pureTemplateName: templateName} = this.parseTemplateName(inputTemplateName));
|
||||
|
@ -81,19 +83,15 @@ export class Evaluator extends AbstractParseTreeVisitor<any> implements LGTempla
|
|||
throw new Error(TemplateErrors.templateNotExist(templateName));
|
||||
}
|
||||
|
||||
if (this.evaluationTargetStack.some((u: EvaluationTarget): boolean => u.templateName === templateName)) {
|
||||
const templateTarget: EvaluationTarget = new EvaluationTarget(templateName, memory);
|
||||
const currentEvulateId: string = templateTarget.getId();
|
||||
|
||||
if (this.evaluationTargetStack.some((u: EvaluationTarget): boolean => u.getId() === currentEvulateId)) {
|
||||
throw new Error(`${ TemplateErrors.loopDetected } ${ this.evaluationTargetStack.reverse()
|
||||
.map((u: EvaluationTarget): string => u.templateName)
|
||||
.join(' => ') }`);
|
||||
}
|
||||
|
||||
if (!(scope instanceof CustomizedMemory)) {
|
||||
scope = new CustomizedMemory(SimpleObjectMemory.wrap(scope));
|
||||
}
|
||||
|
||||
const templateTarget: EvaluationTarget = new EvaluationTarget(templateName, scope);
|
||||
const currentEvulateId: string = templateTarget.getId();
|
||||
|
||||
let previousEvaluateTarget: EvaluationTarget;
|
||||
|
||||
if (this.evaluationTargetStack.length !== 0) {
|
||||
|
|
|
@ -64,22 +64,22 @@ export class Expander extends AbstractParseTreeVisitor<string[]> implements LGTe
|
|||
* @returns All possiable results.
|
||||
*/
|
||||
public expandTemplate(templateName: string, scope: any): any[] {
|
||||
const memory = scope instanceof CustomizedMemory ? scope : new CustomizedMemory(scope);
|
||||
if (!(templateName in this.templateMap)) {
|
||||
throw new Error(TemplateErrors.templateNotExist(templateName));
|
||||
}
|
||||
|
||||
if (this.evaluationTargetStack.find((u: EvaluationTarget): boolean => u.templateName === templateName)) {
|
||||
const templateTarget: EvaluationTarget = new EvaluationTarget(templateName, memory);
|
||||
const currentEvulateId: string = templateTarget.getId();
|
||||
|
||||
if (this.evaluationTargetStack.find((u: EvaluationTarget): boolean => u.getId() === currentEvulateId)) {
|
||||
throw new Error(`${ TemplateErrors.loopDetected } ${ this.evaluationTargetStack.reverse()
|
||||
.map((u: EvaluationTarget): string => u.templateName)
|
||||
.join(' => ') }`);
|
||||
}
|
||||
|
||||
if (!(scope instanceof CustomizedMemory)) {
|
||||
scope = new CustomizedMemory(SimpleObjectMemory.wrap(scope));
|
||||
}
|
||||
|
||||
// Using a stack to track the evalution trace
|
||||
this.evaluationTargetStack.push(new EvaluationTarget(templateName, scope));
|
||||
this.evaluationTargetStack.push(templateTarget);
|
||||
const result: any[] = this.visit(this.templateMap[templateName].templateBodyParseTree);
|
||||
this.evaluationTargetStack.pop();
|
||||
|
||||
|
|
|
@ -645,6 +645,17 @@ describe('LG', function() {
|
|||
assert.strictEqual(evaled, 18, `Evaled is ${ evaled }`);
|
||||
});
|
||||
|
||||
it('TestRecursiveTemplate', function() {
|
||||
var templates = Templates.parseFile(GetExampleFilePath('RecursiveTemplate.lg'));
|
||||
var evaled = templates.evaluate('RecursiveAccumulate', {number: 10});
|
||||
assert.strictEqual(evaled, 55);
|
||||
|
||||
var evaled = templates.evaluate('RecursiveFactorial', {number: 5});
|
||||
assert.strictEqual(evaled, 1*2*3*4*5);
|
||||
|
||||
var evaled = templates.evaluate('RecursiveFibonacciSequence', {number: 5});
|
||||
assert.strictEqual(evaled, 5);
|
||||
});
|
||||
|
||||
it('TestLGResource', function() {
|
||||
var templates = Templates.parseText(fs.readFileSync(GetExampleFilePath('2.lg'), 'utf-8'));
|
||||
|
|
|
@ -214,6 +214,8 @@ describe(`LGExceptionTest`, function() {
|
|||
assert.throws(() => templates.evaluate(`wPhrase`), Error(`Loop detected: welcome_user => wPhrase [wPhrase] Error occurred when evaluating '-\${wPhrase()}'. [welcome_user] Error occurred when evaluating '-\${welcome_user()}'.`));
|
||||
|
||||
assert.throws(() => templates.analyzeTemplate(`wPhrase`), Error('Loop detected: welcome_user => wPhrase'),);
|
||||
|
||||
assert.throws(() => templates.analyzeTemplate(`shouldFail`), Error('Loop detected: shouldFail'),);
|
||||
});
|
||||
|
||||
it(`AddTextWithWrongId`, function() {
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
> 1 + 2 + 3 +...+ number
|
||||
# RecursiveAccumulate(number)
|
||||
- IF:${number <= 1}
|
||||
- ${1}
|
||||
- ELSE:
|
||||
- ${RecursiveAccumulate(number - 1) + number}
|
||||
|
||||
> 1 * 2 * 3 * 4 * 5 * ... * number
|
||||
# RecursiveFactorial(number)
|
||||
- IF: ${number <= 1}
|
||||
- ${1}
|
||||
- ELSE:
|
||||
- ${RecursiveFactorial(number - 1) * number}
|
||||
|
||||
> 1, 1, 2, 3, 5, 8, 13 ... number th
|
||||
# RecursiveFibonacciSequence(number)
|
||||
- IF: ${number <= 2}
|
||||
- ${1}
|
||||
- ELSE:
|
||||
- ${RecursiveFibonacciSequence(number - 1) + RecursiveFibonacciSequence(number - 2)}
|
|
@ -8,3 +8,6 @@
|
|||
# welcome_user
|
||||
- ${wPhrase()}
|
||||
|
||||
> self loop
|
||||
# shouldFail(x)
|
||||
- ${shouldFail(x)}
|
Загрузка…
Ссылка в новой задаче