Skip to content

Commit

Permalink
Move deepEquals and deferences from extensions to Expression class (#…
Browse files Browse the repository at this point in the history
…1927)

* Move DeepEquals and References from extensions to Expression class

* revert package.json

* revert package.json

* revert package.json

* retrigger ci
  • Loading branch information
Danieladu authored Mar 19, 2020
1 parent 3481a14 commit 0160cd4
Show file tree
Hide file tree
Showing 11 changed files with 258 additions and 213 deletions.
13 changes: 13 additions & 0 deletions libraries/adaptive-expressions/src/constant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,19 @@ export class Constant extends Expression {
this.value = value;
}


public deepEquals(other: Expression): boolean {
let eq: boolean;
if (!other || other.type !== this.type) {
eq = false;
} else {
let otherVal = (other as Constant).value;
eq = this.value === otherVal;
}

return eq;
}

public toString(): string {

if (this.value === undefined) {
Expand Down
136 changes: 136 additions & 0 deletions libraries/adaptive-expressions/src/expression.ts
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,142 @@ export class Expression {
}
}

/**
* Do a deep equality between expressions.
* @param other Other expression.
* @returns True if expressions are the same.
*/
public deepEquals(other: Expression): boolean {
let eq = false;
if (!other) {
eq = this.type === other.type;
if (eq) {
eq = this.children.length === other.children.length;
if (this.type === ExpressionType.And || this.type === ExpressionType.Or) {
// And/Or do not depand on order
for(let i = 0; eq && i< this.children.length; i++) {
const primary = this.children[0];
let found = false;
for (var j = 0; j < this.children.length; j++) {
if (primary.deepEquals(other.children[j])) {
found = true;
break;
}
}

eq = found;
}
} else {
for (let i = 0; eq && i< this.children.length; i++) {
eq = this.children[i].deepEquals(other.children[i]);
}
}
}
}
return eq;
}

/**
* Return the static reference paths to memory.
* Return all static paths to memory. If there is a computed element index, then the path is terminated there,
* but you might get other paths from the computed part as well.
* @param expression Expression to get references from.
* @returns List of the static reference paths.
*/
public references(): string[] {
const {path, refs} = this.referenceWalk(this);
if (path !== undefined) {
refs.add(path);
}
return Array.from(refs);
}

/**
* Walking function for identifying static memory references in an expression.
* @param expression Expression to analyze.
* @param references Tracking for references found.
* @param extension If present, called to override lookup for things like template expansion.
* @returns Accessor path of expression.
*/
public referenceWalk(expression: Expression,
extension?: (arg0: Expression) => boolean): {path: string; refs: Set<string>} {
let path: string;
let refs = new Set<string>();
if (extension === undefined || !extension(expression)) {
const children: Expression[] = expression.children;
if (expression.type === ExpressionType.Accessor) {
const prop: string = (children[0] as Constant).value as string;

if (children.length === 1) {
path = prop;
}

if (children.length === 2) {
({path, refs} = this.referenceWalk(children[1], extension));
if (path !== undefined) {
path = path.concat('.', prop);
}
// if path is null we still keep it null, won't append prop
// because for example, first(items).x should not return x as refs
}
} else if (expression.type === ExpressionType.Element) {
({path, refs} = this.referenceWalk(children[0], extension));
if (path !== undefined) {
if (children[1] instanceof Constant) {
const cnst: Constant = children[1] as Constant;
if (cnst.returnType === ReturnType.String) {
path += `.${ cnst.value }`;
} else {
path += `[${ cnst.value }]`;
}
} else {
refs.add(path);
}
}
const result = this.referenceWalk(children[1], extension);
const idxPath = result.path;
const refs1 = result.refs;
refs = new Set([...refs, ...refs1]);
if (idxPath !== undefined) {
refs.add(idxPath);
}
} else if (expression.type === ExpressionType.Foreach ||
expression.type === ExpressionType.Where ||
expression.type === ExpressionType.Select ) {
let result = this.referenceWalk(children[0], extension);
const child0Path = result.path;
const refs0 = result.refs;
if (child0Path !== undefined) {
refs0.add(child0Path);
}

result = this.referenceWalk(children[2], extension);
const child2Path = result.path;
const refs2 = result.refs;
if (child2Path !== undefined) {
refs2.add(child2Path);
}

const iteratorName = (children[1].children[0] as Constant).value as string;
var nonLocalRefs2 = Array.from(refs2).filter((x): boolean => !(x === iteratorName || x.startsWith(iteratorName + '.') || x.startsWith(iteratorName + '[')));
refs = new Set([...refs, ...refs0, ...nonLocalRefs2]);

} else {
for (const child of expression.children) {
const result = this.referenceWalk(child, extension);
const childPath = result.path;
const refs0 = result.refs;
refs = new Set([...refs, ...refs0]);
if (childPath !== undefined) {
refs.add(childPath);
}
}
}
}

return {path, refs};
}

public static parse(expression: string, lookup?: EvaluatorLookup): Expression {
return new ExpressionParser(lookup || Expression.lookup).parse(expression);
}
Expand Down
90 changes: 85 additions & 5 deletions libraries/adaptive-expressions/src/expressionFunctions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -693,6 +693,86 @@ export class ExpressionFunctions {
(expr: Expression): void => ExpressionFunctions.validateArityAndAnyType(expr, 2, 3, ReturnType.String, ReturnType.Number));
}

/**
* Lookup a property in IDictionary, JObject or through reflection.
* @param instance Instance with property.
* @param property Property to lookup.
* @returns Value and error information if any.
*/
public static accessProperty(instance: any, property: string): { value: any; error: string } {
// NOTE: This returns null rather than an error if property is not present
if (!instance) {
return { value: undefined, error: undefined };
}

let value: any;
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) {
const instanceMap: Map<string, any> = instance as Map<string, any>;
value = instanceMap.get(property);
if (value === undefined) {
const prop: string = Array.from(instanceMap.keys()).find((k: string): boolean => k.toLowerCase() === property.toLowerCase());
if (prop !== undefined) {
value = instanceMap.get(prop);
}
}
} else {
const prop: string = Object.keys(instance).find((k: string): boolean => k.toLowerCase() === property.toLowerCase());
if (prop !== undefined) {
value = instance[prop];
}
}

return { value, error };
}

/**
* Set a property in Map or Object.
* @param instance Instance to set.
* @param property Property to set.
* @param value Value to set.
* @returns set value.
*/
public static 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};
}

/**
* Lookup a property in IDictionary, JObject or through reflection.
* @param instance Instance with property.
* @param property Property to lookup.
* @returns Value and error information if any.
*/
public static accessIndex(instance: any, index: number): { value: any; error: string } {
// NOTE: This returns null rather than an error if property is not present
if (instance === null || instance === undefined) {
return { value: undefined, error: undefined };
}

let value: any;
let error: string;

if (Array.isArray(instance)) {
if (index >= 0 && index < instance.length) {
value = instance[index];
} else {
error = `${ index } is out of range for ${ instance }`;
}
} else {
error = `${ instance } is not a collection.`;
}

return { value, error };
}

private static parseTimestamp(timeStamp: string, transform?: (arg0: moment.Moment) => any): { value: any; error: string } {
let value: any;
const error: string = this.verifyISOTimestamp(timeStamp);
Expand Down Expand Up @@ -937,9 +1017,9 @@ export class ExpressionFunctions {
({ value: idxValue, error } = index.tryEvaluate(state));
if (!error) {
if (Number.isInteger(idxValue)) {
({ value, error } = Extensions.accessIndex(inst, Number(idxValue)));
({ value, error } = ExpressionFunctions.accessIndex(inst, Number(idxValue)));
} else if (typeof idxValue === 'string') {
({ value, error } = Extensions.accessProperty(inst, idxValue.toString()));
({ value, error } = ExpressionFunctions.accessProperty(inst, idxValue.toString()));
} else {
error = `Could not coerce ${ index } to an int or string.`;
}
Expand Down Expand Up @@ -2074,7 +2154,7 @@ export class ExpressionFunctions {
found = (args[0] as Map<string, any>).get(args[1]) !== undefined;
} else if (typeof args[1] === 'string') {
let value: any;
({ value, error } = Extensions.accessProperty(args[0], args[1]));
({ value, error } = ExpressionFunctions.accessProperty(args[0], args[1]));
found = !error && value !== undefined;
}
}
Expand Down Expand Up @@ -2874,7 +2954,7 @@ export class ExpressionFunctions {
}

if (Array.isArray(args[0]) && args[0].length > 0) {
first = Extensions.accessIndex(args[0], 0).value;
first = ExpressionFunctions.accessIndex(args[0], 0).value;
}

return first;
Expand All @@ -2891,7 +2971,7 @@ export class ExpressionFunctions {
}

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

return last;
Expand Down
Loading

0 comments on commit 0160cd4

Please sign in to comment.