Skip to content

Commit 0160cd4

Browse files
authored
Move deepEquals and deferences from extensions to Expression class (#1927)
* Move DeepEquals and References from extensions to Expression class * revert package.json * revert package.json * revert package.json * retrigger ci
1 parent 3481a14 commit 0160cd4

File tree

11 files changed

+258
-213
lines changed

11 files changed

+258
-213
lines changed

libraries/adaptive-expressions/src/constant.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,19 @@ export class Constant extends Expression {
4141
this.value = value;
4242
}
4343

44+
45+
public deepEquals(other: Expression): boolean {
46+
let eq: boolean;
47+
if (!other || other.type !== this.type) {
48+
eq = false;
49+
} else {
50+
let otherVal = (other as Constant).value;
51+
eq = this.value === otherVal;
52+
}
53+
54+
return eq;
55+
}
56+
4457
public toString(): string {
4558

4659
if (this.value === undefined) {

libraries/adaptive-expressions/src/expression.ts

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,142 @@ export class Expression {
177177
}
178178
}
179179

180+
/**
181+
* Do a deep equality between expressions.
182+
* @param other Other expression.
183+
* @returns True if expressions are the same.
184+
*/
185+
public deepEquals(other: Expression): boolean {
186+
let eq = false;
187+
if (!other) {
188+
eq = this.type === other.type;
189+
if (eq) {
190+
eq = this.children.length === other.children.length;
191+
if (this.type === ExpressionType.And || this.type === ExpressionType.Or) {
192+
// And/Or do not depand on order
193+
for(let i = 0; eq && i< this.children.length; i++) {
194+
const primary = this.children[0];
195+
let found = false;
196+
for (var j = 0; j < this.children.length; j++) {
197+
if (primary.deepEquals(other.children[j])) {
198+
found = true;
199+
break;
200+
}
201+
}
202+
203+
eq = found;
204+
}
205+
} else {
206+
for (let i = 0; eq && i< this.children.length; i++) {
207+
eq = this.children[i].deepEquals(other.children[i]);
208+
}
209+
}
210+
}
211+
}
212+
return eq;
213+
}
214+
215+
/**
216+
* Return the static reference paths to memory.
217+
* Return all static paths to memory. If there is a computed element index, then the path is terminated there,
218+
* but you might get other paths from the computed part as well.
219+
* @param expression Expression to get references from.
220+
* @returns List of the static reference paths.
221+
*/
222+
public references(): string[] {
223+
const {path, refs} = this.referenceWalk(this);
224+
if (path !== undefined) {
225+
refs.add(path);
226+
}
227+
return Array.from(refs);
228+
}
229+
230+
/**
231+
* Walking function for identifying static memory references in an expression.
232+
* @param expression Expression to analyze.
233+
* @param references Tracking for references found.
234+
* @param extension If present, called to override lookup for things like template expansion.
235+
* @returns Accessor path of expression.
236+
*/
237+
public referenceWalk(expression: Expression,
238+
extension?: (arg0: Expression) => boolean): {path: string; refs: Set<string>} {
239+
let path: string;
240+
let refs = new Set<string>();
241+
if (extension === undefined || !extension(expression)) {
242+
const children: Expression[] = expression.children;
243+
if (expression.type === ExpressionType.Accessor) {
244+
const prop: string = (children[0] as Constant).value as string;
245+
246+
if (children.length === 1) {
247+
path = prop;
248+
}
249+
250+
if (children.length === 2) {
251+
({path, refs} = this.referenceWalk(children[1], extension));
252+
if (path !== undefined) {
253+
path = path.concat('.', prop);
254+
}
255+
// if path is null we still keep it null, won't append prop
256+
// because for example, first(items).x should not return x as refs
257+
}
258+
} else if (expression.type === ExpressionType.Element) {
259+
({path, refs} = this.referenceWalk(children[0], extension));
260+
if (path !== undefined) {
261+
if (children[1] instanceof Constant) {
262+
const cnst: Constant = children[1] as Constant;
263+
if (cnst.returnType === ReturnType.String) {
264+
path += `.${ cnst.value }`;
265+
} else {
266+
path += `[${ cnst.value }]`;
267+
}
268+
} else {
269+
refs.add(path);
270+
}
271+
}
272+
const result = this.referenceWalk(children[1], extension);
273+
const idxPath = result.path;
274+
const refs1 = result.refs;
275+
refs = new Set([...refs, ...refs1]);
276+
if (idxPath !== undefined) {
277+
refs.add(idxPath);
278+
}
279+
} else if (expression.type === ExpressionType.Foreach ||
280+
expression.type === ExpressionType.Where ||
281+
expression.type === ExpressionType.Select ) {
282+
let result = this.referenceWalk(children[0], extension);
283+
const child0Path = result.path;
284+
const refs0 = result.refs;
285+
if (child0Path !== undefined) {
286+
refs0.add(child0Path);
287+
}
288+
289+
result = this.referenceWalk(children[2], extension);
290+
const child2Path = result.path;
291+
const refs2 = result.refs;
292+
if (child2Path !== undefined) {
293+
refs2.add(child2Path);
294+
}
295+
296+
const iteratorName = (children[1].children[0] as Constant).value as string;
297+
var nonLocalRefs2 = Array.from(refs2).filter((x): boolean => !(x === iteratorName || x.startsWith(iteratorName + '.') || x.startsWith(iteratorName + '[')));
298+
refs = new Set([...refs, ...refs0, ...nonLocalRefs2]);
299+
300+
} else {
301+
for (const child of expression.children) {
302+
const result = this.referenceWalk(child, extension);
303+
const childPath = result.path;
304+
const refs0 = result.refs;
305+
refs = new Set([...refs, ...refs0]);
306+
if (childPath !== undefined) {
307+
refs.add(childPath);
308+
}
309+
}
310+
}
311+
}
312+
313+
return {path, refs};
314+
}
315+
180316
public static parse(expression: string, lookup?: EvaluatorLookup): Expression {
181317
return new ExpressionParser(lookup || Expression.lookup).parse(expression);
182318
}

libraries/adaptive-expressions/src/expressionFunctions.ts

Lines changed: 85 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -693,6 +693,86 @@ export class ExpressionFunctions {
693693
(expr: Expression): void => ExpressionFunctions.validateArityAndAnyType(expr, 2, 3, ReturnType.String, ReturnType.Number));
694694
}
695695

696+
/**
697+
* Lookup a property in IDictionary, JObject or through reflection.
698+
* @param instance Instance with property.
699+
* @param property Property to lookup.
700+
* @returns Value and error information if any.
701+
*/
702+
public static accessProperty(instance: any, property: string): { value: any; error: string } {
703+
// NOTE: This returns null rather than an error if property is not present
704+
if (!instance) {
705+
return { value: undefined, error: undefined };
706+
}
707+
708+
let value: any;
709+
let error: string;
710+
// todo, Is there a better way to access value, or any case is not listed below?
711+
if (instance instanceof Map && instance as Map<string, any>!== undefined) {
712+
const instanceMap: Map<string, any> = instance as Map<string, any>;
713+
value = instanceMap.get(property);
714+
if (value === undefined) {
715+
const prop: string = Array.from(instanceMap.keys()).find((k: string): boolean => k.toLowerCase() === property.toLowerCase());
716+
if (prop !== undefined) {
717+
value = instanceMap.get(prop);
718+
}
719+
}
720+
} else {
721+
const prop: string = Object.keys(instance).find((k: string): boolean => k.toLowerCase() === property.toLowerCase());
722+
if (prop !== undefined) {
723+
value = instance[prop];
724+
}
725+
}
726+
727+
return { value, error };
728+
}
729+
730+
/**
731+
* Set a property in Map or Object.
732+
* @param instance Instance to set.
733+
* @param property Property to set.
734+
* @param value Value to set.
735+
* @returns set value.
736+
*/
737+
public static setProperty(instance: any, property: string, value: any): { value: any; error: string } {
738+
const result: any = value;
739+
if (instance instanceof Map) {
740+
instance.set(property, value);
741+
} else {
742+
instance[property] = value;
743+
}
744+
745+
return {value: result, error: undefined};
746+
}
747+
748+
/**
749+
* Lookup a property in IDictionary, JObject or through reflection.
750+
* @param instance Instance with property.
751+
* @param property Property to lookup.
752+
* @returns Value and error information if any.
753+
*/
754+
public static accessIndex(instance: any, index: number): { value: any; error: string } {
755+
// NOTE: This returns null rather than an error if property is not present
756+
if (instance === null || instance === undefined) {
757+
return { value: undefined, error: undefined };
758+
}
759+
760+
let value: any;
761+
let error: string;
762+
763+
if (Array.isArray(instance)) {
764+
if (index >= 0 && index < instance.length) {
765+
value = instance[index];
766+
} else {
767+
error = `${ index } is out of range for ${ instance }`;
768+
}
769+
} else {
770+
error = `${ instance } is not a collection.`;
771+
}
772+
773+
return { value, error };
774+
}
775+
696776
private static parseTimestamp(timeStamp: string, transform?: (arg0: moment.Moment) => any): { value: any; error: string } {
697777
let value: any;
698778
const error: string = this.verifyISOTimestamp(timeStamp);
@@ -937,9 +1017,9 @@ export class ExpressionFunctions {
9371017
({ value: idxValue, error } = index.tryEvaluate(state));
9381018
if (!error) {
9391019
if (Number.isInteger(idxValue)) {
940-
({ value, error } = Extensions.accessIndex(inst, Number(idxValue)));
1020+
({ value, error } = ExpressionFunctions.accessIndex(inst, Number(idxValue)));
9411021
} else if (typeof idxValue === 'string') {
942-
({ value, error } = Extensions.accessProperty(inst, idxValue.toString()));
1022+
({ value, error } = ExpressionFunctions.accessProperty(inst, idxValue.toString()));
9431023
} else {
9441024
error = `Could not coerce ${ index } to an int or string.`;
9451025
}
@@ -2074,7 +2154,7 @@ export class ExpressionFunctions {
20742154
found = (args[0] as Map<string, any>).get(args[1]) !== undefined;
20752155
} else if (typeof args[1] === 'string') {
20762156
let value: any;
2077-
({ value, error } = Extensions.accessProperty(args[0], args[1]));
2157+
({ value, error } = ExpressionFunctions.accessProperty(args[0], args[1]));
20782158
found = !error && value !== undefined;
20792159
}
20802160
}
@@ -2874,7 +2954,7 @@ export class ExpressionFunctions {
28742954
}
28752955

28762956
if (Array.isArray(args[0]) && args[0].length > 0) {
2877-
first = Extensions.accessIndex(args[0], 0).value;
2957+
first = ExpressionFunctions.accessIndex(args[0], 0).value;
28782958
}
28792959

28802960
return first;
@@ -2891,7 +2971,7 @@ export class ExpressionFunctions {
28912971
}
28922972

28932973
if (Array.isArray(args[0]) && args[0].length > 0) {
2894-
last = Extensions.accessIndex(args[0], args[0].length - 1).value;
2974+
last = ExpressionFunctions.accessIndex(args[0], args[0].length - 1).value;
28952975
}
28962976

28972977
return last;

0 commit comments

Comments
 (0)