Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move DeepEquals and References from extensions to Expression class #3588

Merged
merged 3 commits into from
Mar 19, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions libraries/AdaptiveExpressions/Constant.cs
Original file line number Diff line number Diff line change
Expand Up @@ -70,5 +70,21 @@ public override string ToString()
return Value?.ToString();
}
}

public override bool DeepEquals(Expression other)
{
bool eq;
if (other == null || other.Type != ExpressionType.Constant)
{
eq = false;
}
else
{
var otherVal = ((Constant)other).Value;
eq = Value == otherVal || (Value != null && Value.Equals(otherVal));
}

return eq;
}
}
}
172 changes: 172 additions & 0 deletions libraries/AdaptiveExpressions/Expression.cs
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,178 @@ public static Expression Accessor(string property, Expression instance = null)
? MakeExpression(ExpressionType.Accessor, ConstantExpression(property))
: MakeExpression(ExpressionType.Accessor, ConstantExpression(property), instance);

/// <summary>
/// Do a deep equality between expressions.
/// </summary>
/// <param name="other">Other expression.</param>
/// <returns>True if expressions are the same.</returns>
public virtual bool DeepEquals(Expression other)
{
var eq = false;
if (other != null)
{
eq = this.Type == other.Type;
if (eq)
{
eq = this.Children.Count() == other.Children.Count();
if (this.Type == ExpressionType.And || this.Type == ExpressionType.Or)
{
// And/Or do not depend on order
for (var i = 0; eq && i < this.Children.Count(); ++i)
{
var primary = this.Children[i];
var found = false;
for (var j = 0; j < this.Children.Count(); ++j)
{
if (primary.DeepEquals(other.Children[j]))
{
found = true;
break;
}
}

eq = found;
}
}
else
{
for (var i = 0; eq && i < this.Children.Count(); ++i)
{
eq = this.Children[i].DeepEquals(other.Children[i]);
}
}
}
}

return eq;
}

/// <summary>
/// Return the static reference paths to memory.
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
/// <param name="expression">Expression to get references from.</param>
/// <returns>List of the static reference paths.</returns>
public IReadOnlyList<string> References()
{
var (path, refs) = ReferenceWalk(this);
if (path != null)
{
refs.Add(path);
}

return refs.ToList();
}

/// <summary>
/// Walking function for identifying static memory references in an expression.
/// </summary>
/// <param name="expression">Expression to analyze.</param>
/// <param name="extension">If present, called to override lookup for things like template expansion.</param>
/// <returns>Accessor path of expression which is a potential partial path and the full path found so far.</returns>
public (string path, HashSet<string> references) ReferenceWalk(Expression expression, Func<Expression, bool> extension = null)
{
string path = null;
var refs = new HashSet<string>();
if (extension == null || !extension(expression))
{
var children = expression.Children;
if (expression.Type == ExpressionType.Accessor)
{
var prop = (string)((Constant)children[0]).Value;

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

if (children.Length == 2)
{
(path, refs) = ReferenceWalk(children[1], extension);
if (path != null)
{
path = path + "." + 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) = ReferenceWalk(children[0], extension);
if (path != null)
{
if (children[1] is Constant cnst)
{
if (cnst.ReturnType == ReturnType.String)
{
path += $".{cnst.Value}";
}
else
{
path += $"[{cnst.Value}]";
}
}
else
{
refs.Add(path);
}
}

var (idxPath, refs1) = ReferenceWalk(children[1], extension);
refs.UnionWith(refs1);

if (idxPath != null)
{
refs.Add(idxPath);
}
}
else if (expression.Type == ExpressionType.Foreach ||
expression.Type == ExpressionType.Where ||
expression.Type == ExpressionType.Select)
{
var (child0Path, refs0) = ReferenceWalk(children[0], extension);
if (child0Path != null)
{
refs0.Add(child0Path);
}

var (child2Path, refs2) = ReferenceWalk(children[2], extension);
if (child2Path != null)
{
refs2.Add(child2Path);
}

var iteratorName = (string)(children[1].Children[0] as Constant).Value;

// filter references found in children 2 with iterator name
var nonLocalRefs2 = refs2.Where(x => !(x.Equals(iteratorName) || x.StartsWith(iteratorName + '.') || x.StartsWith(iteratorName + '[')))
.ToList();

refs.UnionWith(refs0);
refs.UnionWith(nonLocalRefs2);
}
else
{
foreach (var child in expression.Children)
{
var (childPath, refs0) = ReferenceWalk(child, extension);
refs.UnionWith(refs0);
if (childPath != null)
{
refs.Add(childPath);
}
}
}
}

return (path, refs);
}

/// <summary>
/// Validate immediate expression.
/// </summary>
Expand Down
182 changes: 0 additions & 182 deletions libraries/AdaptiveExpressions/Extensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,187 +44,5 @@ public static bool IsInteger(this object value)
|| value is uint
|| value is long
|| value is ulong;

/// <summary>
/// Do a deep equality between expressions.
/// </summary>
/// <param name="expr">Base expression.</param>
/// <param name="other">Other expression.</param>
/// <returns>True if expressions are the same.</returns>
public static bool DeepEquals(this Expression expr, Expression other)
{
var eq = true;
if (expr != null && other != null)
{
eq = expr.Type == other.Type;
if (eq)
{
if (expr.Type == ExpressionType.Constant)
{
var val = ((Constant)expr).Value;
var otherVal = ((Constant)other).Value;
eq = val == otherVal || (val != null && val.Equals(otherVal));
}
else
{
eq = expr.Children.Count() == other.Children.Count();
if (expr.Type == ExpressionType.And || expr.Type == ExpressionType.Or)
{
// And/Or do not depend on order
for (var i = 0; eq && i < expr.Children.Count(); ++i)
{
var primary = expr.Children[i];
var found = false;
for (var j = 0; j < expr.Children.Count(); ++j)
{
if (primary.DeepEquals(other.Children[j]))
{
found = true;
break;
}
}

eq = found;
}
}
else
{
for (var i = 0; eq && i < expr.Children.Count(); ++i)
{
eq = expr.Children[i].DeepEquals(other.Children[i]);
}
}
}
}
}

return eq;
}

/// <summary>
/// Return the static reference paths to memory.
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
/// <param name="expression">Expression to get references from.</param>
/// <returns>List of the static reference paths.</returns>
public static IReadOnlyList<string> References(this Expression expression)
{
var (path, refs) = ReferenceWalk(expression);
if (path != null)
{
refs.Add(path);
}

return refs.ToList();
}

/// <summary>
/// Walking function for identifying static memory references in an expression.
/// </summary>
/// <param name="expression">Expression to analyze.</param>
/// <param name="extension">If present, called to override lookup for things like template expansion.</param>
/// <returns>Accessor path of expression which is a potential partial path and the full path found so far.</returns>
public static (string path, HashSet<string> references) ReferenceWalk(Expression expression, Func<Expression, bool> extension = null)
{
string path = null;
var refs = new HashSet<string>();
if (extension == null || !extension(expression))
{
var children = expression.Children;
if (expression.Type == ExpressionType.Accessor)
{
var prop = (string)((Constant)children[0]).Value;

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

if (children.Length == 2)
{
(path, refs) = ReferenceWalk(children[1], extension);
if (path != null)
{
path = path + "." + 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) = ReferenceWalk(children[0], extension);
if (path != null)
{
if (children[1] is Constant cnst)
{
if (cnst.ReturnType == ReturnType.String)
{
path += $".{cnst.Value}";
}
else
{
path += $"[{cnst.Value}]";
}
}
else
{
refs.Add(path);
}
}

var (idxPath, refs1) = ReferenceWalk(children[1], extension);
refs.UnionWith(refs1);

if (idxPath != null)
{
refs.Add(idxPath);
}
}
else if (expression.Type == ExpressionType.Foreach ||
expression.Type == ExpressionType.Where ||
expression.Type == ExpressionType.Select)
{
var (child0Path, refs0) = ReferenceWalk(children[0], extension);
if (child0Path != null)
{
refs0.Add(child0Path);
}

var (child2Path, refs2) = ReferenceWalk(children[2], extension);
if (child2Path != null)
{
refs2.Add(child2Path);
}

var iteratorName = (string)(children[1].Children[0] as Constant).Value;

// filter references found in children 2 with iterator name
var nonLocalRefs2 = refs2.Where(x => !(x.Equals(iteratorName) || x.StartsWith(iteratorName + '.') || x.StartsWith(iteratorName + '[')))
.ToList();

refs.UnionWith(refs0);
refs.UnionWith(nonLocalRefs2);
}
else
{
foreach (var child in expression.Children)
{
var (childPath, refs0) = ReferenceWalk(child, extension);
refs.UnionWith(refs0);
if (childPath != null)
{
refs.Add(childPath);
}
}
}
}

return (path, refs);
}
}
}