adaptive-expressions
Version:
Common Expression Language
213 lines (187 loc) • 6.5 kB
text/typescript
import { Extensions } from '../extensions';
import { InternalFunctionUtils } from '../functionUtils.internal';
import { MemoryInterface } from './memoryInterface';
/**
* @module adaptive-expressions
*/
/**
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
/**
* Simple implement of MemoryInterface
*/
export class SimpleObjectMemory implements MemoryInterface {
private memory: any = undefined;
/**
* Initializes a new instance of the [SimpleObjectMemory](xref:adaptive-expressions.SimpleObjectMemory) class.
* This wraps a simple object as [MemoryInterface](xref:adaptive-expressions.MemoryInterface).
*
* @param memory The object to wrap.
*/
constructor(memory: any) {
this.memory = memory;
}
/**
* Transfer a common object to simple memory.
*
* @param obj Common object.
* @returns Simple memory instance.
*/
static wrap(obj: any): MemoryInterface {
if (Extensions.isMemoryInterface(obj)) {
return obj;
}
return new SimpleObjectMemory(obj);
}
/**
* Gets the value from a given path.
*
* @param path Given path.
* @returns The value in the given path or undefined.
*/
getValue(path: string): any {
if (this.memory === undefined || path.length === 0) {
return undefined;
}
const parts: string[] = path
.split(/[.[\]]+/)
.filter((u: string): boolean => u !== undefined && u !== '')
.map((u: string): string => {
if ((u.startsWith('"') && u.endsWith('"')) || (u.startsWith("'") && u.endsWith("'"))) {
return u.substr(1, u.length - 2);
} else {
return u;
}
});
let value: any;
let curScope = this.memory;
for (const part of parts) {
let error: string;
const idx = parseInt(part);
if (!isNaN(idx) && Array.isArray(curScope)) {
({ value, error } = InternalFunctionUtils.accessIndex(curScope, idx));
} else {
({ value, error } = InternalFunctionUtils.accessProperty(curScope, part));
}
if (error) {
return undefined;
}
curScope = value;
}
return value;
}
/**
* In this simple object scope, we don't allow you to set a path in which some parts in middle don't exist
* for example
* if you set dialog.a.b = x, but dialog.a don't exist, this will result in an error
* because we can't and shouldn't smart create structure in the middle
* you can implement a customzied Scope that support such behavior
*
* @param path Memory path.
* @param input Value to set.
*/
setValue(path: string, input: any): void {
if (this.memory === undefined) {
return;
}
const parts: string[] = path
.split(/[.[\]]+/)
.filter((u: string): boolean => u !== undefined && u !== '')
.map((u: string): string => {
if ((u.startsWith('"') && u.endsWith('"')) || (u.startsWith("'") && u.endsWith("'"))) {
return u.substr(1, u.length - 2);
} else {
return u;
}
});
let curScope: any = this.memory;
let curPath = '';
let error: string = undefined;
// find the 2nd last value, ie, the container
for (let i = 0; i < parts.length - 1; i++) {
const idx = parseInt(parts[i]);
if (!isNaN(idx) && Array.isArray(curScope)) {
curPath = `[${parts[i]}]`;
({ value: curScope, error } = InternalFunctionUtils.accessIndex(curScope, idx));
} else {
curPath = `.${parts[i]}`;
({ value: curScope, error } = InternalFunctionUtils.accessProperty(curScope, parts[i]));
}
if (error) {
return;
}
if (curScope === undefined) {
curPath = curPath.replace(/(^\.*)/g, '');
return;
}
}
// set the last value
const idx = parseInt(parts[parts.length - 1]);
if (!isNaN(idx)) {
if (Array.isArray(curScope)) {
if (idx > curScope.length) {
error = `${idx} index out of range`;
} else if (idx === curScope.length) {
curScope.push(input);
} else {
curScope[idx] = input;
}
} else {
error = 'set value for an index to a non-list object';
}
if (error) {
return;
}
} else {
error = this.setProperty(curScope, parts[parts.length - 1], input).error;
if (error) {
return;
}
}
return;
}
/**
* Returns the version info of [SimpleObjectMemory](xref:adaptive-expressions.SimpleObjectMemory).
*
* @returns A string value representing the version info.
*/
version(): string {
return this.toString();
}
/**
* Returns a string that represents the current [SimpleObjectMemory](xref:adaptive-expressions.SimpleObjectMemory) object.
*
* @returns A string value representing the current [SimpleObjectMemory](xref:adaptive-expressions.SimpleObjectMemory) object.
*/
toString(): string {
return JSON.stringify(this.memory, this.getCircularReplacer());
}
/**
* @private
*/
private getCircularReplacer(): any {
const seen = new WeakSet();
return (_key: any, value: object): any => {
if (typeof value === 'object' && value) {
if (seen.has(value)) {
return;
}
seen.add(value);
}
return value;
};
}
/**
* @private
*/
private setProperty(instance: any, property: string, value: any): { value: any; error: string } {
const result: any = value;
if (instance instanceof Map) {
instance.set(property, value);
} else {
instance[property] = value;
}
return { value: result, error: undefined };
}
}