Flee
This commit is contained in:
350
CalcEngine/InternalTypes/DependencyManager.cs
Normal file
350
CalcEngine/InternalTypes/DependencyManager.cs
Normal file
@@ -0,0 +1,350 @@
|
||||
using Flee.CalcEngine.PublicTypes;
|
||||
|
||||
namespace Flee.CalcEngine.InternalTypes
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Keeps track of our dependencies
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
internal class DependencyManager<T>
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Map of a node and the nodes that depend on it
|
||||
/// </summary>
|
||||
private readonly Dictionary<T, Dictionary<T, object>> _myDependentsMap;
|
||||
private readonly IEqualityComparer<T> _myEqualityComparer;
|
||||
|
||||
/// <summary>
|
||||
/// Map of a node and the number of nodes that point to it
|
||||
/// </summary>
|
||||
private readonly Dictionary<T, int> _myPrecedentsMap;
|
||||
public DependencyManager(IEqualityComparer<T> comparer)
|
||||
{
|
||||
_myEqualityComparer = comparer;
|
||||
_myDependentsMap = new Dictionary<T, Dictionary<T, object>>(_myEqualityComparer);
|
||||
_myPrecedentsMap = new Dictionary<T, int>(_myEqualityComparer);
|
||||
}
|
||||
|
||||
private IDictionary<T, object> CreateInnerDictionary()
|
||||
{
|
||||
return new Dictionary<T, object>(_myEqualityComparer);
|
||||
}
|
||||
|
||||
private IDictionary<T, object> GetInnerDictionary(T tail)
|
||||
{
|
||||
Dictionary<T, object> value = null;
|
||||
|
||||
if (_myDependentsMap.TryGetValue(tail, out value) == true)
|
||||
{
|
||||
return value;
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Create a dependency list with only the dependents of the given tails
|
||||
public DependencyManager<T> CloneDependents(T[] tails)
|
||||
{
|
||||
IDictionary<T, object> seenNodes = this.CreateInnerDictionary();
|
||||
DependencyManager<T> copy = new DependencyManager<T>(_myEqualityComparer);
|
||||
|
||||
foreach (T tail in tails)
|
||||
{
|
||||
this.CloneDependentsInternal(tail, copy, seenNodes);
|
||||
}
|
||||
|
||||
return copy;
|
||||
}
|
||||
|
||||
private void CloneDependentsInternal(T tail, DependencyManager<T> target, IDictionary<T, object> seenNodes)
|
||||
{
|
||||
if (seenNodes.ContainsKey(tail) == true)
|
||||
{
|
||||
// We've already added this node so just return
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Haven't seen this node yet; mark it as visited
|
||||
seenNodes.Add(tail, null);
|
||||
target.AddTail(tail);
|
||||
}
|
||||
|
||||
IDictionary<T, object> innerDict = this.GetInnerDictionary(tail);
|
||||
|
||||
// Do the recursive add
|
||||
foreach (T head in innerDict.Keys)
|
||||
{
|
||||
target.AddDepedency(tail, head);
|
||||
this.CloneDependentsInternal(head, target, seenNodes);
|
||||
}
|
||||
}
|
||||
|
||||
public T[] GetTails()
|
||||
{
|
||||
T[] arr = new T[_myDependentsMap.Keys.Count];
|
||||
_myDependentsMap.Keys.CopyTo(arr, 0);
|
||||
return arr;
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
_myDependentsMap.Clear();
|
||||
_myPrecedentsMap.Clear();
|
||||
}
|
||||
|
||||
public void ReplaceDependency(T old, T replaceWith)
|
||||
{
|
||||
Dictionary<T, object> value = _myDependentsMap[old];
|
||||
|
||||
_myDependentsMap.Remove(old);
|
||||
_myDependentsMap.Add(replaceWith, value);
|
||||
|
||||
foreach (Dictionary<T, object> innerDict in _myDependentsMap.Values)
|
||||
{
|
||||
if (innerDict.ContainsKey(old) == true)
|
||||
{
|
||||
innerDict.Remove(old);
|
||||
innerDict.Add(replaceWith, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void AddTail(T tail)
|
||||
{
|
||||
if (_myDependentsMap.ContainsKey(tail) == false)
|
||||
{
|
||||
_myDependentsMap.Add(tail, (Dictionary<T, object>)this.CreateInnerDictionary());
|
||||
}
|
||||
}
|
||||
|
||||
public void AddDepedency(T tail, T head)
|
||||
{
|
||||
IDictionary<T, object> innerDict = this.GetInnerDictionary(tail);
|
||||
|
||||
if (innerDict.ContainsKey(head) == false)
|
||||
{
|
||||
innerDict.Add(head, head);
|
||||
this.AddPrecedent(head);
|
||||
}
|
||||
}
|
||||
|
||||
public void RemoveDependency(T tail, T head)
|
||||
{
|
||||
IDictionary<T, object> innerDict = this.GetInnerDictionary(tail);
|
||||
this.RemoveHead(head, innerDict);
|
||||
}
|
||||
|
||||
private void RemoveHead(T head, IDictionary<T, object> dict)
|
||||
{
|
||||
if (dict.Remove(head) == true)
|
||||
{
|
||||
this.RemovePrecedent(head);
|
||||
}
|
||||
}
|
||||
|
||||
public void Remove(T[] tails)
|
||||
{
|
||||
foreach (Dictionary<T, object> innerDict in _myDependentsMap.Values)
|
||||
{
|
||||
foreach (T tail in tails)
|
||||
{
|
||||
this.RemoveHead(tail, innerDict);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (T tail in tails)
|
||||
{
|
||||
_myDependentsMap.Remove(tail);
|
||||
}
|
||||
}
|
||||
|
||||
public void GetDirectDependents(T tail, List<T> dest)
|
||||
{
|
||||
Dictionary<T, object> innerDict = (Dictionary<T, object>)this.GetInnerDictionary(tail);
|
||||
dest.AddRange(innerDict.Keys);
|
||||
}
|
||||
|
||||
public T[] GetDependents(T tail)
|
||||
{
|
||||
Dictionary<T, object> dependents = (Dictionary<T, object>)this.CreateInnerDictionary();
|
||||
this.GetDependentsRecursive(tail, dependents);
|
||||
|
||||
T[] arr = new T[dependents.Count];
|
||||
dependents.Keys.CopyTo(arr, 0);
|
||||
return arr;
|
||||
}
|
||||
|
||||
private void GetDependentsRecursive(T tail, Dictionary<T, object> dependents)
|
||||
{
|
||||
dependents[tail] = null;
|
||||
Dictionary<T, object> directDependents = (Dictionary<T, object>)this.GetInnerDictionary(tail);
|
||||
|
||||
foreach (T pair in directDependents.Keys)
|
||||
{
|
||||
this.GetDependentsRecursive(pair, dependents);
|
||||
}
|
||||
}
|
||||
|
||||
public void GetDirectPrecedents(T head, IList<T> dest)
|
||||
{
|
||||
foreach (T tail in _myDependentsMap.Keys)
|
||||
{
|
||||
Dictionary<T, object> innerDict = (Dictionary<T, object>)this.GetInnerDictionary(tail);
|
||||
if (innerDict.ContainsKey(head) == true)
|
||||
{
|
||||
dest.Add(tail);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void AddPrecedent(T head)
|
||||
{
|
||||
int count = 0;
|
||||
_myPrecedentsMap.TryGetValue(head, out count);
|
||||
_myPrecedentsMap[head] = count + 1;
|
||||
}
|
||||
|
||||
private void RemovePrecedent(T head)
|
||||
{
|
||||
int count = _myPrecedentsMap[head] - 1;
|
||||
|
||||
if (count == 0)
|
||||
{
|
||||
_myPrecedentsMap.Remove(head);
|
||||
}
|
||||
else
|
||||
{
|
||||
_myPrecedentsMap[head] = count;
|
||||
}
|
||||
}
|
||||
|
||||
public bool HasPrecedents(T head)
|
||||
{
|
||||
return _myPrecedentsMap.ContainsKey(head);
|
||||
}
|
||||
|
||||
public bool HasDependents(T tail)
|
||||
{
|
||||
Dictionary<T, object> innerDict = (Dictionary<T, object>)this.GetInnerDictionary(tail);
|
||||
return innerDict.Count > 0;
|
||||
}
|
||||
|
||||
private string FormatValues(ICollection<T> values)
|
||||
{
|
||||
string[] strings = new string[values.Count];
|
||||
T[] keys = new T[values.Count];
|
||||
values.CopyTo(keys, 0);
|
||||
|
||||
for (int i = 0; i <= keys.Length - 1; i++)
|
||||
{
|
||||
strings[i] = keys[i].ToString();
|
||||
}
|
||||
|
||||
if (strings.Length == 0)
|
||||
{
|
||||
return "<empty>";
|
||||
}
|
||||
else
|
||||
{
|
||||
return string.Join(",", strings);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add all nodes that don't have any incoming edges into a queue
|
||||
/// </summary>
|
||||
/// <param name="rootTails"></param>
|
||||
/// <returns></returns>
|
||||
public Queue<T> GetSources(T[] rootTails)
|
||||
{
|
||||
Queue<T> q = new Queue<T>();
|
||||
|
||||
foreach (T rootTail in rootTails)
|
||||
{
|
||||
if (this.HasPrecedents(rootTail) == false)
|
||||
{
|
||||
q.Enqueue(rootTail);
|
||||
}
|
||||
}
|
||||
|
||||
return q;
|
||||
}
|
||||
|
||||
public IList<T> TopologicalSort(Queue<T> sources)
|
||||
{
|
||||
IList<T> output = new List<T>();
|
||||
List<T> directDependents = new List<T>();
|
||||
|
||||
while (sources.Count > 0)
|
||||
{
|
||||
T n = sources.Dequeue();
|
||||
output.Add(n);
|
||||
|
||||
directDependents.Clear();
|
||||
this.GetDirectDependents(n, directDependents);
|
||||
|
||||
foreach (T m in directDependents)
|
||||
{
|
||||
this.RemoveDependency(n, m);
|
||||
|
||||
if (this.HasPrecedents(m) == false)
|
||||
{
|
||||
sources.Enqueue(m);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (output.Count != this.Count)
|
||||
{
|
||||
throw new CircularReferenceException();
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
public string Precedents
|
||||
{
|
||||
get
|
||||
{
|
||||
List<string> list = new List<string>();
|
||||
|
||||
foreach (KeyValuePair<T, int> pair in _myPrecedentsMap)
|
||||
{
|
||||
list.Add(pair.ToString());
|
||||
}
|
||||
|
||||
return string.Join(System.Environment.NewLine, list.ToArray());
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
public string DependencyGraph
|
||||
{
|
||||
get
|
||||
{
|
||||
string[] lines = new string[_myDependentsMap.Count];
|
||||
int index = 0;
|
||||
|
||||
foreach (KeyValuePair<T, Dictionary<T, object>> pair in _myDependentsMap)
|
||||
{
|
||||
T key = pair.Key;
|
||||
string s = this.FormatValues(pair.Value.Keys);
|
||||
lines[index] = $"{key} -> {s}";
|
||||
index += 1;
|
||||
}
|
||||
|
||||
return string.Join(System.Environment.NewLine, lines);
|
||||
}
|
||||
}
|
||||
|
||||
public int Count => _myDependentsMap.Count;
|
||||
}
|
||||
|
||||
}
|
||||
|
105
CalcEngine/InternalTypes/IdentifierAnalyzer.cs
Normal file
105
CalcEngine/InternalTypes/IdentifierAnalyzer.cs
Normal file
@@ -0,0 +1,105 @@
|
||||
using Flee.Parsing;
|
||||
using Flee.PublicTypes;
|
||||
|
||||
namespace Flee.CalcEngine.InternalTypes
|
||||
{
|
||||
internal class IdentifierAnalyzer : Analyzer
|
||||
{
|
||||
|
||||
private readonly IDictionary<int, string> _myIdentifiers;
|
||||
private int _myMemberExpressionCount;
|
||||
|
||||
private bool _myInFieldPropertyExpression;
|
||||
public IdentifierAnalyzer()
|
||||
{
|
||||
_myIdentifiers = new Dictionary<int, string>();
|
||||
}
|
||||
|
||||
public override Node Exit(Node node)
|
||||
{
|
||||
switch (node.Id)
|
||||
{
|
||||
case (int)ExpressionConstants.IDENTIFIER:
|
||||
this.ExitIdentifier((Token)node);
|
||||
break;
|
||||
case (int)ExpressionConstants.FIELD_PROPERTY_EXPRESSION:
|
||||
this.ExitFieldPropertyExpression();
|
||||
break;
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
public override void Enter(Node node)
|
||||
{
|
||||
switch (node.Id)
|
||||
{
|
||||
case (int)ExpressionConstants.MEMBER_EXPRESSION:
|
||||
this.EnterMemberExpression();
|
||||
break;
|
||||
case (int)ExpressionConstants.FIELD_PROPERTY_EXPRESSION:
|
||||
this.EnterFieldPropertyExpression();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void ExitIdentifier(Token node)
|
||||
{
|
||||
if (_myInFieldPropertyExpression == false)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_myIdentifiers.ContainsKey(_myMemberExpressionCount) == false)
|
||||
{
|
||||
_myIdentifiers.Add(_myMemberExpressionCount, node.Image);
|
||||
}
|
||||
}
|
||||
|
||||
private void EnterMemberExpression()
|
||||
{
|
||||
_myMemberExpressionCount += 1;
|
||||
}
|
||||
|
||||
private void EnterFieldPropertyExpression()
|
||||
{
|
||||
_myInFieldPropertyExpression = true;
|
||||
}
|
||||
|
||||
private void ExitFieldPropertyExpression()
|
||||
{
|
||||
_myInFieldPropertyExpression = false;
|
||||
}
|
||||
|
||||
public override void Reset()
|
||||
{
|
||||
_myIdentifiers.Clear();
|
||||
_myMemberExpressionCount = -1;
|
||||
}
|
||||
|
||||
public ICollection<string> GetIdentifiers(ExpressionContext context)
|
||||
{
|
||||
Dictionary<string, object> dict = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
|
||||
ExpressionImports ei = context.Imports;
|
||||
|
||||
foreach (string identifier in _myIdentifiers.Values)
|
||||
{
|
||||
// Skip names registered as namespaces
|
||||
if (ei.HasNamespace(identifier) == true)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
else if (context.Variables.ContainsKey(identifier) == true)
|
||||
{
|
||||
// Identifier is a variable
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get only the unique values
|
||||
dict[identifier] = null;
|
||||
}
|
||||
|
||||
return dict.Keys;
|
||||
}
|
||||
}
|
||||
}
|
114
CalcEngine/InternalTypes/Miscellaneous.cs
Normal file
114
CalcEngine/InternalTypes/Miscellaneous.cs
Normal file
@@ -0,0 +1,114 @@
|
||||
using Flee.PublicTypes;
|
||||
|
||||
namespace Flee.CalcEngine.InternalTypes
|
||||
{
|
||||
internal class PairEqualityComparer : EqualityComparer<ExpressionResultPair>
|
||||
{
|
||||
public override bool Equals(ExpressionResultPair x, ExpressionResultPair y)
|
||||
{
|
||||
return string.Equals(x.Name, y.Name, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
public override int GetHashCode(ExpressionResultPair obj)
|
||||
{
|
||||
return StringComparer.OrdinalIgnoreCase.GetHashCode(obj.Name);
|
||||
}
|
||||
}
|
||||
|
||||
internal abstract class ExpressionResultPair
|
||||
{
|
||||
|
||||
private string _myName;
|
||||
|
||||
protected IDynamicExpression MyExpression;
|
||||
|
||||
protected ExpressionResultPair()
|
||||
{
|
||||
}
|
||||
|
||||
public abstract void Recalculate();
|
||||
|
||||
public void SetExpression(IDynamicExpression e)
|
||||
{
|
||||
MyExpression = e;
|
||||
}
|
||||
|
||||
public void SetName(string name)
|
||||
{
|
||||
_myName = name;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return _myName;
|
||||
}
|
||||
|
||||
public string Name => _myName;
|
||||
|
||||
public abstract Type ResultType { get; }
|
||||
public abstract object ResultAsObject { get; set; }
|
||||
|
||||
public IDynamicExpression Expression => MyExpression;
|
||||
}
|
||||
|
||||
internal class GenericExpressionResultPair<T> : ExpressionResultPair
|
||||
{
|
||||
public T MyResult;
|
||||
public GenericExpressionResultPair()
|
||||
{
|
||||
}
|
||||
|
||||
public override void Recalculate()
|
||||
{
|
||||
MyResult = (T)MyExpression.Evaluate();
|
||||
}
|
||||
|
||||
public T Result => MyResult;
|
||||
|
||||
public override System.Type ResultType => typeof(T);
|
||||
|
||||
public override object ResultAsObject
|
||||
{
|
||||
get { return MyResult; }
|
||||
set { MyResult = (T)value; }
|
||||
}
|
||||
}
|
||||
|
||||
internal class BatchLoadInfo
|
||||
{
|
||||
public string Name;
|
||||
public string ExpressionText;
|
||||
|
||||
public ExpressionContext Context;
|
||||
public BatchLoadInfo(string name, string text, ExpressionContext context)
|
||||
{
|
||||
this.Name = name;
|
||||
this.ExpressionText = text;
|
||||
this.Context = context;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class NodeEventArgs : EventArgs
|
||||
{
|
||||
|
||||
private string _myName;
|
||||
|
||||
private object _myResult;
|
||||
|
||||
internal NodeEventArgs()
|
||||
{
|
||||
}
|
||||
|
||||
internal void SetData(string name, object result)
|
||||
{
|
||||
_myName = name;
|
||||
_myResult = result;
|
||||
}
|
||||
|
||||
public string Name => _myName;
|
||||
|
||||
public object Result => _myResult;
|
||||
}
|
||||
|
||||
}
|
||||
|
69
CalcEngine/PublicTypes/BatchLoader.cs
Normal file
69
CalcEngine/PublicTypes/BatchLoader.cs
Normal file
@@ -0,0 +1,69 @@
|
||||
using Flee.CalcEngine.InternalTypes;
|
||||
using Flee.InternalTypes;
|
||||
using Flee.PublicTypes;
|
||||
|
||||
namespace Flee.CalcEngine.PublicTypes
|
||||
{
|
||||
public sealed class BatchLoader
|
||||
{
|
||||
|
||||
private readonly IDictionary<string, BatchLoadInfo> _myNameInfoMap;
|
||||
|
||||
private readonly DependencyManager<string> _myDependencies;
|
||||
internal BatchLoader()
|
||||
{
|
||||
_myNameInfoMap = new Dictionary<string, BatchLoadInfo>(StringComparer.OrdinalIgnoreCase);
|
||||
_myDependencies = new DependencyManager<string>(StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
public void Add(string atomName, string expression, ExpressionContext context)
|
||||
{
|
||||
Utility.AssertNotNull(atomName, "atomName");
|
||||
Utility.AssertNotNull(expression, "expression");
|
||||
Utility.AssertNotNull(context, "context");
|
||||
|
||||
BatchLoadInfo info = new BatchLoadInfo(atomName, expression, context);
|
||||
_myNameInfoMap.Add(atomName, info);
|
||||
_myDependencies.AddTail(atomName);
|
||||
|
||||
ICollection<string> references = this.GetReferences(expression, context);
|
||||
|
||||
foreach (string reference in references)
|
||||
{
|
||||
_myDependencies.AddTail(reference);
|
||||
_myDependencies.AddDepedency(reference, atomName);
|
||||
}
|
||||
}
|
||||
|
||||
public bool Contains(string atomName)
|
||||
{
|
||||
return _myNameInfoMap.ContainsKey(atomName);
|
||||
}
|
||||
|
||||
internal BatchLoadInfo[] GetBachInfos()
|
||||
{
|
||||
string[] tails = _myDependencies.GetTails();
|
||||
Queue<string> sources = _myDependencies.GetSources(tails);
|
||||
|
||||
IList<string> result = _myDependencies.TopologicalSort(sources);
|
||||
|
||||
BatchLoadInfo[] infos = new BatchLoadInfo[result.Count];
|
||||
|
||||
for (int i = 0; i <= result.Count - 1; i++)
|
||||
{
|
||||
infos[i] = _myNameInfoMap[result[i]];
|
||||
}
|
||||
|
||||
return infos;
|
||||
}
|
||||
|
||||
private ICollection<string> GetReferences(string expression, ExpressionContext context)
|
||||
{
|
||||
IdentifierAnalyzer analyzer = context.ParseIdentifiers(expression);
|
||||
|
||||
return analyzer.GetIdentifiers(context);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
360
CalcEngine/PublicTypes/CalculationEngine.cs
Normal file
360
CalcEngine/PublicTypes/CalculationEngine.cs
Normal file
@@ -0,0 +1,360 @@
|
||||
using System.Reflection.Emit;
|
||||
using System.Reflection;
|
||||
using Flee.CalcEngine.InternalTypes;
|
||||
using Flee.InternalTypes;
|
||||
using Flee.PublicTypes;
|
||||
|
||||
namespace Flee.CalcEngine.PublicTypes
|
||||
{
|
||||
public class CalculationEngine
|
||||
{
|
||||
#region "Fields"
|
||||
private readonly DependencyManager<ExpressionResultPair> _myDependencies;
|
||||
/// <summary>
|
||||
/// Map of name to node
|
||||
/// </summary>
|
||||
private readonly Dictionary<string, ExpressionResultPair> _myNameNodeMap;
|
||||
#endregion
|
||||
|
||||
#region "Events"
|
||||
public event EventHandler<NodeEventArgs> NodeRecalculated;
|
||||
#endregion
|
||||
|
||||
#region "Constructor"
|
||||
public CalculationEngine()
|
||||
{
|
||||
_myDependencies = new DependencyManager<ExpressionResultPair>(new PairEqualityComparer());
|
||||
_myNameNodeMap = new Dictionary<string, ExpressionResultPair>(StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region "Methods - Private"
|
||||
private void AddTemporaryHead(string headName)
|
||||
{
|
||||
GenericExpressionResultPair<int> pair = new GenericExpressionResultPair<int>();
|
||||
pair.SetName(headName);
|
||||
|
||||
if (_myNameNodeMap.ContainsKey(headName) == false)
|
||||
{
|
||||
_myDependencies.AddTail(pair);
|
||||
_myNameNodeMap.Add(headName, pair);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException($"An expression already exists at '{headName}'");
|
||||
}
|
||||
}
|
||||
|
||||
private void DoBatchLoadAdd(BatchLoadInfo info)
|
||||
{
|
||||
try
|
||||
{
|
||||
this.Add(info.Name, info.ExpressionText, info.Context);
|
||||
}
|
||||
catch (ExpressionCompileException ex)
|
||||
{
|
||||
this.Clear();
|
||||
throw new BatchLoadCompileException(info.Name, info.ExpressionText, ex);
|
||||
}
|
||||
}
|
||||
|
||||
private ExpressionResultPair GetTail(string tailName)
|
||||
{
|
||||
Utility.AssertNotNull(tailName, "name");
|
||||
ExpressionResultPair pair = null;
|
||||
_myNameNodeMap.TryGetValue(tailName, out pair);
|
||||
return pair;
|
||||
}
|
||||
|
||||
private ExpressionResultPair GetTailWithValidate(string tailName)
|
||||
{
|
||||
Utility.AssertNotNull(tailName, "name");
|
||||
ExpressionResultPair pair = this.GetTail(tailName);
|
||||
|
||||
if (pair == null)
|
||||
{
|
||||
throw new ArgumentException($"No expression is associated with the name '{tailName}'");
|
||||
}
|
||||
else
|
||||
{
|
||||
return pair;
|
||||
}
|
||||
}
|
||||
|
||||
private string[] GetNames(IList<ExpressionResultPair> pairs)
|
||||
{
|
||||
string[] names = new string[pairs.Count];
|
||||
|
||||
for (int i = 0; i <= names.Length - 1; i++)
|
||||
{
|
||||
names[i] = pairs[i].Name;
|
||||
}
|
||||
|
||||
return names;
|
||||
}
|
||||
|
||||
private ExpressionResultPair[] GetRootTails(string[] roots)
|
||||
{
|
||||
// No roots supplied so get everything
|
||||
if (roots.Length == 0)
|
||||
{
|
||||
return _myDependencies.GetTails();
|
||||
}
|
||||
|
||||
// Get the tail for each name
|
||||
ExpressionResultPair[] arr = new ExpressionResultPair[roots.Length];
|
||||
|
||||
for (int i = 0; i <= arr.Length - 1; i++)
|
||||
{
|
||||
arr[i] = this.GetTailWithValidate(roots[i]);
|
||||
}
|
||||
|
||||
return arr;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region "Methods - Internal"
|
||||
|
||||
internal void FixTemporaryHead(IDynamicExpression expression, ExpressionContext context, Type resultType)
|
||||
{
|
||||
Type pairType = typeof(GenericExpressionResultPair<>);
|
||||
pairType = pairType.MakeGenericType(resultType);
|
||||
|
||||
ExpressionResultPair pair = (ExpressionResultPair)Activator.CreateInstance(pairType);
|
||||
string headName = context.CalcEngineExpressionName;
|
||||
pair.SetName(headName);
|
||||
pair.SetExpression(expression);
|
||||
|
||||
ExpressionResultPair oldPair = _myNameNodeMap[headName];
|
||||
_myDependencies.ReplaceDependency(oldPair, pair);
|
||||
_myNameNodeMap[headName] = pair;
|
||||
|
||||
// Let the pair store the result of its expression
|
||||
pair.Recalculate();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called by an expression when it references another expression in the engine
|
||||
/// </summary>
|
||||
/// <param name="tailName"></param>
|
||||
/// <param name="context"></param>
|
||||
internal void AddDependency(string tailName, ExpressionContext context)
|
||||
{
|
||||
ExpressionResultPair actualTail = this.GetTail(tailName);
|
||||
string headName = context.CalcEngineExpressionName;
|
||||
ExpressionResultPair actualHead = this.GetTail(headName);
|
||||
|
||||
// An expression could depend on the same reference more than once (ie: "a + a * a")
|
||||
_myDependencies.AddDepedency(actualTail, actualHead);
|
||||
}
|
||||
|
||||
internal Type ResolveTailType(string tailName)
|
||||
{
|
||||
ExpressionResultPair actualTail = this.GetTail(tailName);
|
||||
return actualTail.ResultType;
|
||||
}
|
||||
|
||||
internal bool HasTail(string tailName)
|
||||
{
|
||||
return _myNameNodeMap.ContainsKey(tailName);
|
||||
}
|
||||
|
||||
internal void EmitLoad(string tailName, FleeILGenerator ilg)
|
||||
{
|
||||
PropertyInfo pi = typeof(ExpressionContext).GetProperty("CalculationEngine");
|
||||
ilg.Emit(OpCodes.Callvirt, pi.GetGetMethod());
|
||||
|
||||
// Load the tail
|
||||
MemberInfo[] methods = typeof(CalculationEngine).FindMembers(MemberTypes.Method, BindingFlags.Instance | BindingFlags.Public, Type.FilterNameIgnoreCase, "GetResult");
|
||||
MethodInfo mi = null;
|
||||
|
||||
foreach (MethodInfo method in methods)
|
||||
{
|
||||
if (method.IsGenericMethod == true)
|
||||
{
|
||||
mi = method;
|
||||
break; // TODO: might not be correct. Was : Exit For
|
||||
}
|
||||
}
|
||||
|
||||
Type resultType = this.ResolveTailType(tailName);
|
||||
|
||||
mi = mi.MakeGenericMethod(resultType);
|
||||
|
||||
ilg.Emit(OpCodes.Ldstr, tailName);
|
||||
ilg.Emit(OpCodes.Call, mi);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region "Methods - Public"
|
||||
public void Add(string atomName, string expression, ExpressionContext context)
|
||||
{
|
||||
Utility.AssertNotNull(atomName, "atomName");
|
||||
Utility.AssertNotNull(expression, "expression");
|
||||
Utility.AssertNotNull(context, "context");
|
||||
|
||||
this.AddTemporaryHead(atomName);
|
||||
|
||||
context.SetCalcEngine(this, atomName);
|
||||
|
||||
context.CompileDynamic(expression);
|
||||
}
|
||||
|
||||
public bool Remove(string name)
|
||||
{
|
||||
ExpressionResultPair tail = this.GetTail(name);
|
||||
|
||||
if (tail == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
ExpressionResultPair[] dependents = _myDependencies.GetDependents(tail);
|
||||
_myDependencies.Remove(dependents);
|
||||
|
||||
foreach (ExpressionResultPair pair in dependents)
|
||||
{
|
||||
_myNameNodeMap.Remove(pair.Name);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public BatchLoader CreateBatchLoader()
|
||||
{
|
||||
BatchLoader loader = new BatchLoader();
|
||||
return loader;
|
||||
}
|
||||
|
||||
public void BatchLoad(BatchLoader loader)
|
||||
{
|
||||
Utility.AssertNotNull(loader, "loader");
|
||||
this.Clear();
|
||||
|
||||
BatchLoadInfo[] infos = loader.GetBachInfos();
|
||||
|
||||
foreach (BatchLoadInfo info in infos)
|
||||
{
|
||||
this.DoBatchLoadAdd(info);
|
||||
}
|
||||
}
|
||||
|
||||
public T GetResult<T>(string name)
|
||||
{
|
||||
ExpressionResultPair tail = this.GetTailWithValidate(name);
|
||||
|
||||
if ((!object.ReferenceEquals(typeof(T), tail.ResultType)))
|
||||
{
|
||||
string msg = $"The result type of '{name}' ('{tail.ResultType.Name}') does not match the supplied type argument ('{typeof(T).Name}')";
|
||||
throw new ArgumentException(msg);
|
||||
}
|
||||
|
||||
GenericExpressionResultPair<T> actualTail = (GenericExpressionResultPair<T>)tail;
|
||||
return actualTail.Result;
|
||||
}
|
||||
|
||||
public object GetResult(string name)
|
||||
{
|
||||
ExpressionResultPair tail = this.GetTailWithValidate(name);
|
||||
return tail.ResultAsObject;
|
||||
}
|
||||
|
||||
public IExpression GetExpression(string name)
|
||||
{
|
||||
ExpressionResultPair tail = this.GetTailWithValidate(name);
|
||||
return tail.Expression;
|
||||
}
|
||||
|
||||
public string[] GetDependents(string name)
|
||||
{
|
||||
ExpressionResultPair pair = this.GetTail(name);
|
||||
List<ExpressionResultPair> dependents = new List<ExpressionResultPair>();
|
||||
|
||||
if ((pair != null))
|
||||
{
|
||||
_myDependencies.GetDirectDependents(pair, dependents);
|
||||
}
|
||||
|
||||
return this.GetNames(dependents);
|
||||
}
|
||||
|
||||
public string[] GetPrecedents(string name)
|
||||
{
|
||||
ExpressionResultPair pair = this.GetTail(name);
|
||||
List<ExpressionResultPair> dependents = new List<ExpressionResultPair>();
|
||||
|
||||
if ((pair != null))
|
||||
{
|
||||
_myDependencies.GetDirectPrecedents(pair, dependents);
|
||||
}
|
||||
|
||||
return this.GetNames(dependents);
|
||||
}
|
||||
|
||||
public bool HasDependents(string name)
|
||||
{
|
||||
ExpressionResultPair pair = this.GetTail(name);
|
||||
return (pair != null) && _myDependencies.HasDependents(pair);
|
||||
}
|
||||
|
||||
public bool HasPrecedents(string name)
|
||||
{
|
||||
ExpressionResultPair pair = this.GetTail(name);
|
||||
return (pair != null) && _myDependencies.HasPrecedents(pair);
|
||||
}
|
||||
|
||||
public bool Contains(string name)
|
||||
{
|
||||
Utility.AssertNotNull(name, "name");
|
||||
return _myNameNodeMap.ContainsKey(name);
|
||||
}
|
||||
|
||||
public void Recalculate(params string[] roots)
|
||||
{
|
||||
// Get the tails corresponding to the names
|
||||
ExpressionResultPair[] rootTails = this.GetRootTails(roots);
|
||||
// Create a dependency list based on the tails
|
||||
DependencyManager<ExpressionResultPair> tempDependents = _myDependencies.CloneDependents(rootTails);
|
||||
// Get the sources (ie: nodes with no incoming edges) since that's what the sort requires
|
||||
Queue<ExpressionResultPair> sources = tempDependents.GetSources(rootTails);
|
||||
// Do the topological sort
|
||||
IList<ExpressionResultPair> calcList = tempDependents.TopologicalSort(sources);
|
||||
|
||||
NodeEventArgs args = new NodeEventArgs();
|
||||
|
||||
// Recalculate the sorted expressions
|
||||
foreach (ExpressionResultPair pair in calcList)
|
||||
{
|
||||
pair.Recalculate();
|
||||
args.SetData(pair.Name, pair.ResultAsObject);
|
||||
if (NodeRecalculated != null)
|
||||
{
|
||||
NodeRecalculated(this, args);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
_myDependencies.Clear();
|
||||
_myNameNodeMap.Clear();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region "Properties - Public"
|
||||
public int Count
|
||||
{
|
||||
get { return _myDependencies.Count; }
|
||||
}
|
||||
|
||||
public string DependencyGraph
|
||||
{
|
||||
get { return _myDependencies.DependencyGraph; }
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
|
||||
}
|
54
CalcEngine/PublicTypes/Exceptions.cs
Normal file
54
CalcEngine/PublicTypes/Exceptions.cs
Normal file
@@ -0,0 +1,54 @@
|
||||
using Flee.PublicTypes;
|
||||
|
||||
namespace Flee.CalcEngine.PublicTypes
|
||||
{
|
||||
|
||||
public class CircularReferenceException : System.Exception
|
||||
{
|
||||
private readonly string _myCircularReferenceSource;
|
||||
|
||||
internal CircularReferenceException()
|
||||
{
|
||||
}
|
||||
|
||||
internal CircularReferenceException(string circularReferenceSource)
|
||||
{
|
||||
_myCircularReferenceSource = circularReferenceSource;
|
||||
}
|
||||
|
||||
public override string Message
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_myCircularReferenceSource == null)
|
||||
{
|
||||
return "Circular reference detected in calculation engine";
|
||||
}
|
||||
else
|
||||
{
|
||||
return $"Circular reference detected in calculation engine at '{_myCircularReferenceSource}'";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class BatchLoadCompileException : Exception
|
||||
{
|
||||
|
||||
private readonly string _myAtomName;
|
||||
|
||||
private readonly string _myExpressionText;
|
||||
internal BatchLoadCompileException(string atomName, string expressionText, ExpressionCompileException innerException) : base(
|
||||
$"Batch Load: The expression for atom '${atomName}' could not be compiled", innerException)
|
||||
{
|
||||
_myAtomName = atomName;
|
||||
_myExpressionText = expressionText;
|
||||
}
|
||||
|
||||
public string AtomName => _myAtomName;
|
||||
|
||||
public string ExpressionText => _myExpressionText;
|
||||
}
|
||||
|
||||
}
|
||||
|
121
CalcEngine/PublicTypes/SimpleCalcEngine.cs
Normal file
121
CalcEngine/PublicTypes/SimpleCalcEngine.cs
Normal file
@@ -0,0 +1,121 @@
|
||||
using Flee.CalcEngine.InternalTypes;
|
||||
using Flee.PublicTypes;
|
||||
|
||||
namespace Flee.CalcEngine.PublicTypes
|
||||
{
|
||||
public class SimpleCalcEngine
|
||||
{
|
||||
|
||||
#region "Fields"
|
||||
|
||||
private readonly IDictionary<string, IExpression> _myExpressions;
|
||||
|
||||
private ExpressionContext _myContext;
|
||||
#endregion
|
||||
|
||||
#region "Constructor"
|
||||
|
||||
public SimpleCalcEngine()
|
||||
{
|
||||
_myExpressions = new Dictionary<string, IExpression>(StringComparer.OrdinalIgnoreCase);
|
||||
_myContext = new ExpressionContext();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region "Methods - Private"
|
||||
|
||||
private void AddCompiledExpression(string expressionName, IExpression expression)
|
||||
{
|
||||
if (_myExpressions.ContainsKey(expressionName) == true)
|
||||
{
|
||||
throw new InvalidOperationException($"The calc engine already contains an expression named '{expressionName}'");
|
||||
}
|
||||
else
|
||||
{
|
||||
_myExpressions.Add(expressionName, expression);
|
||||
}
|
||||
}
|
||||
|
||||
private ExpressionContext ParseAndLink(string expressionName, string expression)
|
||||
{
|
||||
IdentifierAnalyzer analyzer = Context.ParseIdentifiers(expression);
|
||||
|
||||
ExpressionContext context2 = _myContext.CloneInternal(true);
|
||||
this.LinkExpression(expressionName, context2, analyzer);
|
||||
|
||||
// Tell the expression not to clone the context since it's already been cloned
|
||||
context2.NoClone = true;
|
||||
|
||||
// Clear our context's variables
|
||||
_myContext.Variables.Clear();
|
||||
|
||||
return context2;
|
||||
}
|
||||
|
||||
private void LinkExpression(string expressionName, ExpressionContext context, IdentifierAnalyzer analyzer)
|
||||
{
|
||||
foreach (string identifier in analyzer.GetIdentifiers(context))
|
||||
{
|
||||
this.LinkIdentifier(identifier, expressionName, context);
|
||||
}
|
||||
}
|
||||
|
||||
private void LinkIdentifier(string identifier, string expressionName, ExpressionContext context)
|
||||
{
|
||||
IExpression child = null;
|
||||
|
||||
if (_myExpressions.TryGetValue(identifier, out child) == false)
|
||||
{
|
||||
string msg = $"Expression '{expressionName}' references unknown name '{identifier}'";
|
||||
throw new InvalidOperationException(msg);
|
||||
}
|
||||
|
||||
context.Variables.Add(identifier, child);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region "Methods - Public"
|
||||
|
||||
public void AddDynamic(string expressionName, string expression)
|
||||
{
|
||||
ExpressionContext linkedContext = this.ParseAndLink(expressionName, expression);
|
||||
IExpression e = linkedContext.CompileDynamic(expression);
|
||||
this.AddCompiledExpression(expressionName, e);
|
||||
}
|
||||
|
||||
public void AddGeneric<T>(string expressionName, string expression)
|
||||
{
|
||||
ExpressionContext linkedContext = this.ParseAndLink(expressionName, expression);
|
||||
IExpression e = linkedContext.CompileGeneric<T>(expression);
|
||||
this.AddCompiledExpression(expressionName, e);
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
_myExpressions.Clear();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region "Properties - Public"
|
||||
public IExpression this[string name]
|
||||
{
|
||||
get
|
||||
{
|
||||
IExpression e = null;
|
||||
_myExpressions.TryGetValue(name, out e);
|
||||
return e;
|
||||
}
|
||||
}
|
||||
|
||||
public ExpressionContext Context
|
||||
{
|
||||
get { return _myContext; }
|
||||
set { _myContext = value; }
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
|
||||
}
|
Reference in New Issue
Block a user