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
|
||||
}
|
||||
|
||||
}
|
||||
327
ExpressionElements/Arithmetic.cs
Normal file
327
ExpressionElements/Arithmetic.cs
Normal file
@@ -0,0 +1,327 @@
|
||||
using System.Diagnostics;
|
||||
using System.Reflection;
|
||||
using System.Reflection.Emit;
|
||||
using Flee.ExpressionElements.Base;
|
||||
using Flee.ExpressionElements.Base.Literals;
|
||||
using Flee.ExpressionElements.Literals.Integral;
|
||||
using Flee.InternalTypes;
|
||||
using Flee.PublicTypes;
|
||||
|
||||
namespace Flee.ExpressionElements
|
||||
{
|
||||
internal class ArithmeticElement : BinaryExpressionElement
|
||||
{
|
||||
private static MethodInfo _ourPowerMethodInfo;
|
||||
private static MethodInfo _ourStringConcatMethodInfo;
|
||||
private static MethodInfo _ourObjectConcatMethodInfo;
|
||||
private BinaryArithmeticOperation _myOperation;
|
||||
|
||||
public ArithmeticElement()
|
||||
{
|
||||
_ourPowerMethodInfo = typeof(Math).GetMethod("Pow", BindingFlags.Public | BindingFlags.Static);
|
||||
_ourStringConcatMethodInfo = typeof(string).GetMethod("Concat", new Type[] { typeof(string), typeof(string) }, null);
|
||||
_ourObjectConcatMethodInfo = typeof(string).GetMethod("Concat", new Type[] { typeof(object), typeof(object) }, null);
|
||||
}
|
||||
|
||||
protected override void GetOperation(object operation)
|
||||
{
|
||||
_myOperation = (BinaryArithmeticOperation)operation;
|
||||
}
|
||||
|
||||
protected override System.Type GetResultType(System.Type leftType, System.Type rightType)
|
||||
{
|
||||
Type binaryResultType = ImplicitConverter.GetBinaryResultType(leftType, rightType);
|
||||
MethodInfo overloadedMethod = this.GetOverloadedArithmeticOperator();
|
||||
|
||||
// Is an overloaded operator defined for our left and right children?
|
||||
if ((overloadedMethod != null))
|
||||
{
|
||||
// Yes, so use its return type
|
||||
return overloadedMethod.ReturnType;
|
||||
}
|
||||
else if ((binaryResultType != null))
|
||||
{
|
||||
// Operands are primitive types. Return computed result type unless we are doing a power operation
|
||||
if (_myOperation == BinaryArithmeticOperation.Power)
|
||||
{
|
||||
return this.GetPowerResultType(leftType, rightType, binaryResultType);
|
||||
}
|
||||
else
|
||||
{
|
||||
return binaryResultType;
|
||||
}
|
||||
}
|
||||
else if (this.IsEitherChildOfType(typeof(string)) == true & (_myOperation == BinaryArithmeticOperation.Add))
|
||||
{
|
||||
// String concatenation
|
||||
return typeof(string);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Invalid types
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private Type GetPowerResultType(Type leftType, Type rightType, Type binaryResultType)
|
||||
{
|
||||
if (this.IsOptimizablePower == true)
|
||||
{
|
||||
return leftType;
|
||||
}
|
||||
else
|
||||
{
|
||||
return typeof(double);
|
||||
}
|
||||
}
|
||||
|
||||
private MethodInfo GetOverloadedArithmeticOperator()
|
||||
{
|
||||
// Get the name of the operator
|
||||
string name = GetOverloadedOperatorFunctionName(_myOperation);
|
||||
return base.GetOverloadedBinaryOperator(name, _myOperation);
|
||||
}
|
||||
|
||||
private static string GetOverloadedOperatorFunctionName(BinaryArithmeticOperation op)
|
||||
{
|
||||
switch (op)
|
||||
{
|
||||
case BinaryArithmeticOperation.Add:
|
||||
return "Addition";
|
||||
case BinaryArithmeticOperation.Subtract:
|
||||
return "Subtraction";
|
||||
case BinaryArithmeticOperation.Multiply:
|
||||
return "Multiply";
|
||||
case BinaryArithmeticOperation.Divide:
|
||||
return "Division";
|
||||
case BinaryArithmeticOperation.Mod:
|
||||
return "Modulus";
|
||||
case BinaryArithmeticOperation.Power:
|
||||
return "Exponent";
|
||||
default:
|
||||
Debug.Assert(false, "unknown operator type");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public override void Emit(FleeILGenerator ilg, IServiceProvider services)
|
||||
{
|
||||
MethodInfo overloadedMethod = this.GetOverloadedArithmeticOperator();
|
||||
|
||||
if ((overloadedMethod != null))
|
||||
{
|
||||
// Emit a call to an overloaded operator
|
||||
this.EmitOverloadedOperatorCall(overloadedMethod, ilg, services);
|
||||
}
|
||||
else if (this.IsEitherChildOfType(typeof(string)) == true)
|
||||
{
|
||||
// One of our operands is a string so emit a concatenation
|
||||
this.EmitStringConcat(ilg, services);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Emit a regular arithmetic operation
|
||||
EmitArithmeticOperation(_myOperation, ilg, services);
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsUnsignedForArithmetic(Type t)
|
||||
{
|
||||
return object.ReferenceEquals(t, typeof(UInt32)) | object.ReferenceEquals(t, typeof(UInt64));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Emit an arithmetic operation with handling for unsigned and checked contexts
|
||||
/// </summary>
|
||||
/// <param name="op"></param>
|
||||
/// <param name="ilg"></param>
|
||||
/// <param name="services"></param>
|
||||
private void EmitArithmeticOperation(BinaryArithmeticOperation op, FleeILGenerator ilg, IServiceProvider services)
|
||||
{
|
||||
ExpressionOptions options = (ExpressionOptions)services.GetService(typeof(ExpressionOptions));
|
||||
bool unsigned = IsUnsignedForArithmetic(MyLeftChild.ResultType) & IsUnsignedForArithmetic(MyRightChild.ResultType);
|
||||
bool integral = Utility.IsIntegralType(MyLeftChild.ResultType) & Utility.IsIntegralType(MyRightChild.ResultType);
|
||||
bool emitOverflow = integral & options.Checked;
|
||||
|
||||
EmitChildWithConvert(MyLeftChild, this.ResultType, ilg, services);
|
||||
|
||||
if (this.IsOptimizablePower == false)
|
||||
{
|
||||
EmitChildWithConvert(MyRightChild, this.ResultType, ilg, services);
|
||||
}
|
||||
|
||||
switch (op)
|
||||
{
|
||||
case BinaryArithmeticOperation.Add:
|
||||
if (emitOverflow == true)
|
||||
{
|
||||
if (unsigned == true)
|
||||
{
|
||||
ilg.Emit(OpCodes.Add_Ovf_Un);
|
||||
}
|
||||
else
|
||||
{
|
||||
ilg.Emit(OpCodes.Add_Ovf);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ilg.Emit(OpCodes.Add);
|
||||
}
|
||||
break;
|
||||
case BinaryArithmeticOperation.Subtract:
|
||||
if (emitOverflow == true)
|
||||
{
|
||||
if (unsigned == true)
|
||||
{
|
||||
ilg.Emit(OpCodes.Sub_Ovf_Un);
|
||||
}
|
||||
else
|
||||
{
|
||||
ilg.Emit(OpCodes.Sub_Ovf);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ilg.Emit(OpCodes.Sub);
|
||||
}
|
||||
break;
|
||||
case BinaryArithmeticOperation.Multiply:
|
||||
this.EmitMultiply(ilg, emitOverflow, unsigned);
|
||||
break;
|
||||
case BinaryArithmeticOperation.Divide:
|
||||
if (unsigned == true)
|
||||
{
|
||||
ilg.Emit(OpCodes.Div_Un);
|
||||
}
|
||||
else
|
||||
{
|
||||
ilg.Emit(OpCodes.Div);
|
||||
}
|
||||
break;
|
||||
case BinaryArithmeticOperation.Mod:
|
||||
if (unsigned == true)
|
||||
{
|
||||
ilg.Emit(OpCodes.Rem_Un);
|
||||
}
|
||||
else
|
||||
{
|
||||
ilg.Emit(OpCodes.Rem);
|
||||
}
|
||||
break;
|
||||
case BinaryArithmeticOperation.Power:
|
||||
this.EmitPower(ilg, emitOverflow, unsigned);
|
||||
break;
|
||||
default:
|
||||
Debug.Fail("Unknown op type");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void EmitPower(FleeILGenerator ilg, bool emitOverflow, bool unsigned)
|
||||
{
|
||||
if (this.IsOptimizablePower == true)
|
||||
{
|
||||
this.EmitOptimizedPower(ilg, emitOverflow, unsigned);
|
||||
}
|
||||
else
|
||||
{
|
||||
ilg.Emit(OpCodes.Call, _ourPowerMethodInfo);
|
||||
}
|
||||
}
|
||||
|
||||
private void EmitOptimizedPower(FleeILGenerator ilg, bool emitOverflow, bool unsigned)
|
||||
{
|
||||
Int32LiteralElement right = (Int32LiteralElement)MyRightChild;
|
||||
|
||||
if (right.Value == 0)
|
||||
{
|
||||
ilg.Emit(OpCodes.Pop);
|
||||
IntegralLiteralElement.EmitLoad(1, ilg);
|
||||
ImplicitConverter.EmitImplicitNumericConvert(typeof(Int32), MyLeftChild.ResultType, ilg);
|
||||
return;
|
||||
}
|
||||
|
||||
if (right.Value == 1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Start at 1 since left operand has already been emited once
|
||||
for (int i = 1; i <= right.Value - 1; i++)
|
||||
{
|
||||
ilg.Emit(OpCodes.Dup);
|
||||
}
|
||||
|
||||
for (int i = 1; i <= right.Value - 1; i++)
|
||||
{
|
||||
this.EmitMultiply(ilg, emitOverflow, unsigned);
|
||||
}
|
||||
}
|
||||
|
||||
private void EmitMultiply(FleeILGenerator ilg, bool emitOverflow, bool unsigned)
|
||||
{
|
||||
if (emitOverflow == true)
|
||||
{
|
||||
if (unsigned == true)
|
||||
{
|
||||
ilg.Emit(OpCodes.Mul_Ovf_Un);
|
||||
}
|
||||
else
|
||||
{
|
||||
ilg.Emit(OpCodes.Mul_Ovf);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ilg.Emit(OpCodes.Mul);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Emit a string concatenation
|
||||
/// </summary>
|
||||
/// <param name="ilg"></param>
|
||||
/// <param name="services"></param>
|
||||
private void EmitStringConcat(FleeILGenerator ilg, IServiceProvider services)
|
||||
{
|
||||
Type argType = default(Type);
|
||||
System.Reflection.MethodInfo concatMethodInfo = default(System.Reflection.MethodInfo);
|
||||
|
||||
// Pick the most specific concat method
|
||||
if (this.AreBothChildrenOfType(typeof(string)) == true)
|
||||
{
|
||||
concatMethodInfo = _ourStringConcatMethodInfo;
|
||||
argType = typeof(string);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Assert(this.IsEitherChildOfType(typeof(string)), "one child must be a string");
|
||||
concatMethodInfo = _ourObjectConcatMethodInfo;
|
||||
argType = typeof(object);
|
||||
}
|
||||
|
||||
// Emit the operands and call the function
|
||||
MyLeftChild.Emit(ilg, services);
|
||||
ImplicitConverter.EmitImplicitConvert(MyLeftChild.ResultType, argType, ilg);
|
||||
MyRightChild.Emit(ilg, services);
|
||||
ImplicitConverter.EmitImplicitConvert(MyRightChild.ResultType, argType, ilg);
|
||||
ilg.Emit(OpCodes.Call, concatMethodInfo);
|
||||
}
|
||||
|
||||
private bool IsOptimizablePower
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_myOperation != BinaryArithmeticOperation.Power || !(MyRightChild is Int32LiteralElement))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Int32LiteralElement right = (Int32LiteralElement)MyRightChild;
|
||||
|
||||
return right?.Value >= 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
160
ExpressionElements/Base/Binary.cs
Normal file
160
ExpressionElements/Base/Binary.cs
Normal file
@@ -0,0 +1,160 @@
|
||||
using System.Collections;
|
||||
using System.Diagnostics;
|
||||
using System.Reflection.Emit;
|
||||
using System.Reflection;
|
||||
using Flee.InternalTypes;
|
||||
using Flee.PublicTypes;
|
||||
using Flee.Resources;
|
||||
|
||||
namespace Flee.ExpressionElements.Base
|
||||
{
|
||||
[Obsolete("Base class for expression elements that operate on two child elements")]
|
||||
internal abstract class BinaryExpressionElement : ExpressionElement
|
||||
{
|
||||
|
||||
protected ExpressionElement MyLeftChild;
|
||||
protected ExpressionElement MyRightChild;
|
||||
private Type _myResultType;
|
||||
|
||||
protected BinaryExpressionElement()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a list of binary elements into a binary tree
|
||||
/// </summary>
|
||||
/// <param name="childValues"></param>
|
||||
/// <param name="elementType"></param>
|
||||
/// <returns></returns>
|
||||
public static BinaryExpressionElement CreateElement(IList childValues, Type elementType)
|
||||
{
|
||||
BinaryExpressionElement firstElement = (BinaryExpressionElement)Activator.CreateInstance(elementType);
|
||||
firstElement.Configure((ExpressionElement)childValues[0], (ExpressionElement)childValues[2], childValues[1]);
|
||||
|
||||
BinaryExpressionElement lastElement = firstElement;
|
||||
|
||||
for (int i = 3; i <= childValues.Count - 1; i += 2)
|
||||
{
|
||||
BinaryExpressionElement element = (BinaryExpressionElement)Activator.CreateInstance(elementType);
|
||||
element.Configure(lastElement, (ExpressionElement)childValues[i + 1], childValues[i]);
|
||||
lastElement = element;
|
||||
}
|
||||
|
||||
return lastElement;
|
||||
}
|
||||
|
||||
protected abstract void GetOperation(object operation);
|
||||
|
||||
protected void ValidateInternal(object op)
|
||||
{
|
||||
_myResultType = this.GetResultType(MyLeftChild.ResultType, MyRightChild.ResultType);
|
||||
|
||||
if (_myResultType == null)
|
||||
{
|
||||
this.ThrowOperandTypeMismatch(op, MyLeftChild.ResultType, MyRightChild.ResultType);
|
||||
}
|
||||
}
|
||||
|
||||
protected MethodInfo GetOverloadedBinaryOperator(string name, object operation)
|
||||
{
|
||||
Type leftType = MyLeftChild.ResultType;
|
||||
Type rightType = MyRightChild.ResultType;
|
||||
BinaryOperatorBinder binder = new BinaryOperatorBinder(leftType, rightType);
|
||||
|
||||
// If both arguments are of the same type, pick either as the owner type
|
||||
if (object.ReferenceEquals(leftType, rightType))
|
||||
{
|
||||
return Utility.GetOverloadedOperator(name, leftType, binder, leftType, rightType);
|
||||
}
|
||||
|
||||
// Get the operator for both types
|
||||
MethodInfo leftMethod = default(MethodInfo);
|
||||
MethodInfo rightMethod = default(MethodInfo);
|
||||
leftMethod = Utility.GetOverloadedOperator(name, leftType, binder, leftType, rightType);
|
||||
rightMethod = Utility.GetOverloadedOperator(name, rightType, binder, leftType, rightType);
|
||||
|
||||
// Pick the right one
|
||||
if (leftMethod == null & rightMethod == null)
|
||||
{
|
||||
// No operator defined for either
|
||||
return null;
|
||||
}
|
||||
else if (leftMethod == null)
|
||||
{
|
||||
return rightMethod;
|
||||
}
|
||||
else if (rightMethod == null)
|
||||
{
|
||||
return leftMethod;
|
||||
}
|
||||
else if (object.ReferenceEquals(leftMethod, rightMethod))
|
||||
{
|
||||
// same operator for both (most likely defined in a common base class)
|
||||
return leftMethod;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Ambiguous call
|
||||
base.ThrowAmbiguousCallException(leftType, rightType, operation);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
protected void EmitOverloadedOperatorCall(MethodInfo method, FleeILGenerator ilg, IServiceProvider services)
|
||||
{
|
||||
ParameterInfo[] @params = method.GetParameters();
|
||||
ParameterInfo pLeft = @params[0];
|
||||
ParameterInfo pRight = @params[1];
|
||||
|
||||
EmitChildWithConvert(MyLeftChild, pLeft.ParameterType, ilg, services);
|
||||
EmitChildWithConvert(MyRightChild, pRight.ParameterType, ilg, services);
|
||||
ilg.Emit(OpCodes.Call, method);
|
||||
}
|
||||
|
||||
protected void ThrowOperandTypeMismatch(object operation, Type leftType, Type rightType)
|
||||
{
|
||||
base.ThrowCompileException(CompileErrorResourceKeys.OperationNotDefinedForTypes, CompileExceptionReason.TypeMismatch, operation, leftType.Name, rightType.Name);
|
||||
}
|
||||
|
||||
protected abstract Type GetResultType(Type leftType, Type rightType);
|
||||
|
||||
protected static void EmitChildWithConvert(ExpressionElement child, Type resultType, FleeILGenerator ilg, IServiceProvider services)
|
||||
{
|
||||
child.Emit(ilg, services);
|
||||
bool converted = ImplicitConverter.EmitImplicitConvert(child.ResultType, resultType, ilg);
|
||||
Debug.Assert(converted, "convert failed");
|
||||
}
|
||||
|
||||
protected bool AreBothChildrenOfType(Type target)
|
||||
{
|
||||
return IsChildOfType(MyLeftChild, target) & IsChildOfType(MyRightChild, target);
|
||||
}
|
||||
|
||||
protected bool IsEitherChildOfType(Type target)
|
||||
{
|
||||
return IsChildOfType(MyLeftChild, target) || IsChildOfType(MyRightChild, target);
|
||||
}
|
||||
|
||||
protected static bool IsChildOfType(ExpressionElement child, Type t)
|
||||
{
|
||||
return object.ReferenceEquals(child.ResultType, t);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the left and right operands, get the operation, and get the result type
|
||||
/// </summary>
|
||||
/// <param name="leftChild"></param>
|
||||
/// <param name="rightChild"></param>
|
||||
/// <param name="op"></param>
|
||||
private void Configure(ExpressionElement leftChild, ExpressionElement rightChild, object op)
|
||||
{
|
||||
MyLeftChild = leftChild;
|
||||
MyRightChild = rightChild;
|
||||
this.GetOperation(op);
|
||||
|
||||
this.ValidateInternal(op);
|
||||
}
|
||||
|
||||
public sealed override System.Type ResultType => _myResultType;
|
||||
}
|
||||
}
|
||||
55
ExpressionElements/Base/ExpressionElement.cs
Normal file
55
ExpressionElements/Base/ExpressionElement.cs
Normal file
@@ -0,0 +1,55 @@
|
||||
using System.Diagnostics;
|
||||
using Flee.InternalTypes;
|
||||
using Flee.PublicTypes;
|
||||
using Flee.Resources;
|
||||
|
||||
namespace Flee.ExpressionElements.Base
|
||||
{
|
||||
internal abstract class ExpressionElement
|
||||
{
|
||||
internal ExpressionElement()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// // All expression elements must be able to emit their IL
|
||||
/// </summary>
|
||||
/// <param name="ilg"></param>
|
||||
/// <param name="services"></param>
|
||||
public abstract void Emit(FleeILGenerator ilg, IServiceProvider services);
|
||||
/// <summary>
|
||||
/// All expression elements must expose the Type they evaluate to
|
||||
/// </summary>
|
||||
public abstract Type ResultType { get; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return this.Name;
|
||||
}
|
||||
|
||||
protected void ThrowCompileException(string messageKey, CompileExceptionReason reason, params object[] arguments)
|
||||
{
|
||||
string messageTemplate = FleeResourceManager.Instance.GetCompileErrorString(messageKey);
|
||||
string message = string.Format(messageTemplate, arguments);
|
||||
message = string.Concat(this.Name, ": ", message);
|
||||
throw new ExpressionCompileException(message, reason);
|
||||
}
|
||||
|
||||
protected void ThrowAmbiguousCallException(Type leftType, Type rightType, object operation)
|
||||
{
|
||||
this.ThrowCompileException(CompileErrorResourceKeys.AmbiguousOverloadedOperator, CompileExceptionReason.AmbiguousMatch, leftType.Name, rightType.Name, operation);
|
||||
}
|
||||
|
||||
|
||||
protected string Name
|
||||
{
|
||||
get
|
||||
{
|
||||
string key = this.GetType().Name;
|
||||
string value = FleeResourceManager.Instance.GetElementNameString(key);
|
||||
Debug.Assert(value != null, $"Element name for '{key}' not in resource file");
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
118
ExpressionElements/Base/Literals/Integral.cs
Normal file
118
ExpressionElements/Base/Literals/Integral.cs
Normal file
@@ -0,0 +1,118 @@
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using Flee.ExpressionElements.Literals.Integral;
|
||||
|
||||
|
||||
namespace Flee.ExpressionElements.Base.Literals
|
||||
{
|
||||
internal abstract class IntegralLiteralElement : LiteralElement
|
||||
{
|
||||
protected IntegralLiteralElement()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempt to find the first type of integer that a number can fit into
|
||||
/// </summary>
|
||||
/// <param name="image"></param>
|
||||
/// <param name="isHex"></param>
|
||||
/// <param name="negated"></param>
|
||||
/// <param name="services"></param>
|
||||
/// <returns></returns>
|
||||
public static LiteralElement Create(string image, bool isHex, bool negated, IServiceProvider services)
|
||||
{
|
||||
StringComparison comparison = StringComparison.OrdinalIgnoreCase;
|
||||
|
||||
if (isHex == false)
|
||||
{
|
||||
// Create a real element if required
|
||||
LiteralElement realElement = RealLiteralElement.CreateFromInteger(image, services);
|
||||
|
||||
if ((realElement != null))
|
||||
{
|
||||
return realElement;
|
||||
}
|
||||
}
|
||||
|
||||
bool hasUSuffix = image.EndsWith("u", comparison) & !image.EndsWith("lu", comparison);
|
||||
bool hasLSuffix = image.EndsWith("l", comparison) & !image.EndsWith("ul", comparison);
|
||||
bool hasUlSuffix = image.EndsWith("ul", comparison) | image.EndsWith("lu", comparison);
|
||||
bool hasSuffix = hasUSuffix | hasLSuffix | hasUlSuffix;
|
||||
|
||||
LiteralElement constant = default(LiteralElement);
|
||||
System.Globalization.NumberStyles numStyles = NumberStyles.Integer;
|
||||
|
||||
if (isHex == true)
|
||||
{
|
||||
numStyles = NumberStyles.AllowHexSpecifier;
|
||||
image = image.Remove(0, 2);
|
||||
}
|
||||
|
||||
if (hasSuffix == false)
|
||||
{
|
||||
// If the literal has no suffix, it has the first of these types in which its value can be represented: int, uint, long, ulong.
|
||||
constant = Int32LiteralElement.TryCreate(image, isHex, negated);
|
||||
|
||||
if ((constant != null))
|
||||
{
|
||||
return constant;
|
||||
}
|
||||
|
||||
constant = UInt32LiteralElement.TryCreate(image, numStyles);
|
||||
|
||||
if ((constant != null))
|
||||
{
|
||||
return constant;
|
||||
}
|
||||
|
||||
constant = Int64LiteralElement.TryCreate(image, isHex, negated);
|
||||
|
||||
if ((constant != null))
|
||||
{
|
||||
return constant;
|
||||
}
|
||||
|
||||
return new UInt64LiteralElement(image, numStyles);
|
||||
}
|
||||
else if (hasUSuffix == true)
|
||||
{
|
||||
image = image.Remove(image.Length - 1);
|
||||
// If the literal is suffixed by U or u, it has the first of these types in which its value can be represented: uint, ulong.
|
||||
|
||||
constant = UInt32LiteralElement.TryCreate(image, numStyles);
|
||||
|
||||
if ((constant != null))
|
||||
{
|
||||
return constant;
|
||||
}
|
||||
else
|
||||
{
|
||||
return new UInt64LiteralElement(image, numStyles);
|
||||
}
|
||||
}
|
||||
else if (hasLSuffix == true)
|
||||
{
|
||||
// If the literal is suffixed by L or l, it has the first of these types in which its value can be represented: long, ulong.
|
||||
image = image.Remove(image.Length - 1);
|
||||
|
||||
constant = Int64LiteralElement.TryCreate(image, isHex, negated);
|
||||
|
||||
if ((constant != null))
|
||||
{
|
||||
return constant;
|
||||
}
|
||||
else
|
||||
{
|
||||
return new UInt64LiteralElement(image, numStyles);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// If the literal is suffixed by UL, Ul, uL, ul, LU, Lu, lU, or lu, it is of type ulong.
|
||||
Debug.Assert(hasUlSuffix == true, "expecting ul suffix");
|
||||
image = image.Remove(image.Length - 2);
|
||||
return new UInt64LiteralElement(image, numStyles);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
107
ExpressionElements/Base/Literals/LiteralElement.cs
Normal file
107
ExpressionElements/Base/Literals/LiteralElement.cs
Normal file
@@ -0,0 +1,107 @@
|
||||
using System.Diagnostics;
|
||||
using System.Reflection.Emit;
|
||||
using Flee.InternalTypes;
|
||||
using Flee.PublicTypes;
|
||||
using Flee.Resources;
|
||||
|
||||
|
||||
namespace Flee.ExpressionElements.Base.Literals
|
||||
{
|
||||
internal abstract class LiteralElement : ExpressionElement
|
||||
{
|
||||
protected void OnParseOverflow(string image)
|
||||
{
|
||||
base.ThrowCompileException(CompileErrorResourceKeys.ValueNotRepresentableInType, CompileExceptionReason.ConstantOverflow, image, this.ResultType.Name);
|
||||
}
|
||||
|
||||
public static void EmitLoad(Int32 value, FleeILGenerator ilg)
|
||||
{
|
||||
if (value >= -1 & value <= 8)
|
||||
{
|
||||
EmitSuperShort(value, ilg);
|
||||
}
|
||||
else if (value >= sbyte.MinValue & value <= sbyte.MaxValue)
|
||||
{
|
||||
ilg.Emit(OpCodes.Ldc_I4_S, Convert.ToSByte(value));
|
||||
}
|
||||
else
|
||||
{
|
||||
ilg.Emit(OpCodes.Ldc_I4, value);
|
||||
}
|
||||
}
|
||||
|
||||
protected static void EmitLoad(Int64 value, FleeILGenerator ilg)
|
||||
{
|
||||
if (value >= Int32.MinValue & value <= Int32.MaxValue)
|
||||
{
|
||||
EmitLoad(Convert.ToInt32(value), ilg);
|
||||
ilg.Emit(OpCodes.Conv_I8);
|
||||
}
|
||||
else if (value >= 0 & value <= UInt32.MaxValue)
|
||||
{
|
||||
ilg.Emit(OpCodes.Ldc_I4, unchecked((int)Convert.ToUInt32(value)));
|
||||
ilg.Emit(OpCodes.Conv_U8);
|
||||
}
|
||||
else
|
||||
{
|
||||
ilg.Emit(OpCodes.Ldc_I8, value);
|
||||
}
|
||||
}
|
||||
|
||||
protected static void EmitLoad(bool value, FleeILGenerator ilg)
|
||||
{
|
||||
if (value == true)
|
||||
{
|
||||
ilg.Emit(OpCodes.Ldc_I4_1);
|
||||
}
|
||||
else
|
||||
{
|
||||
ilg.Emit(OpCodes.Ldc_I4_0);
|
||||
}
|
||||
}
|
||||
|
||||
private static void EmitSuperShort(Int32 value, FleeILGenerator ilg)
|
||||
{
|
||||
OpCode ldcOpcode = default(OpCode);
|
||||
|
||||
switch (value)
|
||||
{
|
||||
case 0:
|
||||
ldcOpcode = OpCodes.Ldc_I4_0;
|
||||
break;
|
||||
case 1:
|
||||
ldcOpcode = OpCodes.Ldc_I4_1;
|
||||
break;
|
||||
case 2:
|
||||
ldcOpcode = OpCodes.Ldc_I4_2;
|
||||
break;
|
||||
case 3:
|
||||
ldcOpcode = OpCodes.Ldc_I4_3;
|
||||
break;
|
||||
case 4:
|
||||
ldcOpcode = OpCodes.Ldc_I4_4;
|
||||
break;
|
||||
case 5:
|
||||
ldcOpcode = OpCodes.Ldc_I4_5;
|
||||
break;
|
||||
case 6:
|
||||
ldcOpcode = OpCodes.Ldc_I4_6;
|
||||
break;
|
||||
case 7:
|
||||
ldcOpcode = OpCodes.Ldc_I4_7;
|
||||
break;
|
||||
case 8:
|
||||
ldcOpcode = OpCodes.Ldc_I4_8;
|
||||
break;
|
||||
case -1:
|
||||
ldcOpcode = OpCodes.Ldc_I4_M1;
|
||||
break;
|
||||
default:
|
||||
Debug.Assert(false, "value out of range");
|
||||
break;
|
||||
}
|
||||
|
||||
ilg.Emit(ldcOpcode);
|
||||
}
|
||||
}
|
||||
}
|
||||
130
ExpressionElements/Base/Literals/Real.cs
Normal file
130
ExpressionElements/Base/Literals/Real.cs
Normal file
@@ -0,0 +1,130 @@
|
||||
using System.Diagnostics;
|
||||
using Flee.ExpressionElements.Literals.Real;
|
||||
using Flee.PublicTypes;
|
||||
|
||||
namespace Flee.ExpressionElements.Base.Literals
|
||||
{
|
||||
internal abstract class RealLiteralElement : LiteralElement
|
||||
{
|
||||
protected RealLiteralElement()
|
||||
{
|
||||
}
|
||||
|
||||
public static LiteralElement CreateFromInteger(string image, IServiceProvider services)
|
||||
{
|
||||
LiteralElement element = default(LiteralElement);
|
||||
|
||||
element = CreateSingle(image, services);
|
||||
|
||||
if ((element != null))
|
||||
{
|
||||
return element;
|
||||
}
|
||||
|
||||
element = CreateDecimal(image, services);
|
||||
|
||||
if ((element != null))
|
||||
{
|
||||
return element;
|
||||
}
|
||||
|
||||
ExpressionOptions options = (ExpressionOptions)services.GetService(typeof(ExpressionOptions));
|
||||
|
||||
// Convert to a double if option is set
|
||||
if (options.IntegersAsDoubles == true)
|
||||
{
|
||||
return DoubleLiteralElement.Parse(image, services);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static LiteralElement Create(string image, IServiceProvider services)
|
||||
{
|
||||
LiteralElement element = default(LiteralElement);
|
||||
|
||||
element = CreateSingle(image, services);
|
||||
|
||||
if ((element != null))
|
||||
{
|
||||
return element;
|
||||
}
|
||||
|
||||
element = CreateDecimal(image, services);
|
||||
|
||||
if ((element != null))
|
||||
{
|
||||
return element;
|
||||
}
|
||||
|
||||
element = CreateDouble(image, services);
|
||||
|
||||
if ((element != null))
|
||||
{
|
||||
return element;
|
||||
}
|
||||
|
||||
element = CreateImplicitReal(image, services);
|
||||
|
||||
return element;
|
||||
}
|
||||
|
||||
private static LiteralElement CreateImplicitReal(string image, IServiceProvider services)
|
||||
{
|
||||
ExpressionOptions options = (ExpressionOptions)services.GetService(typeof(ExpressionOptions));
|
||||
RealLiteralDataType realType = options.RealLiteralDataType;
|
||||
|
||||
switch (realType)
|
||||
{
|
||||
case RealLiteralDataType.Double:
|
||||
return DoubleLiteralElement.Parse(image, services);
|
||||
case RealLiteralDataType.Single:
|
||||
return SingleLiteralElement.Parse(image, services);
|
||||
case RealLiteralDataType.Decimal:
|
||||
return DecimalLiteralElement.Parse(image, services);
|
||||
default:
|
||||
Debug.Fail("Unknown value");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static DoubleLiteralElement CreateDouble(string image, IServiceProvider services)
|
||||
{
|
||||
if (image.EndsWith("d", StringComparison.OrdinalIgnoreCase) == true)
|
||||
{
|
||||
image = image.Remove(image.Length - 1);
|
||||
return DoubleLiteralElement.Parse(image, services);
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static SingleLiteralElement CreateSingle(string image, IServiceProvider services)
|
||||
{
|
||||
if (image.EndsWith("f", StringComparison.OrdinalIgnoreCase) == true)
|
||||
{
|
||||
image = image.Remove(image.Length - 1);
|
||||
return SingleLiteralElement.Parse(image, services);
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static DecimalLiteralElement CreateDecimal(string image, IServiceProvider services)
|
||||
{
|
||||
if (image.EndsWith("m", StringComparison.OrdinalIgnoreCase) == true)
|
||||
{
|
||||
image = image.Remove(image.Length - 1);
|
||||
return DecimalLiteralElement.Parse(image, services);
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
358
ExpressionElements/Base/Member.cs
Normal file
358
ExpressionElements/Base/Member.cs
Normal file
@@ -0,0 +1,358 @@
|
||||
using System.Diagnostics;
|
||||
using System.Reflection;
|
||||
using System.Reflection.Emit;
|
||||
using Flee.InternalTypes;
|
||||
using Flee.PublicTypes;
|
||||
using Flee.Resources;
|
||||
|
||||
namespace Flee.ExpressionElements.Base
|
||||
{
|
||||
internal abstract class MemberElement : ExpressionElement
|
||||
{
|
||||
protected string MyName;
|
||||
protected MemberElement MyPrevious;
|
||||
protected MemberElement MyNext;
|
||||
protected IServiceProvider MyServices;
|
||||
protected ExpressionOptions MyOptions;
|
||||
protected ExpressionContext MyContext;
|
||||
protected ImportBase MyImport;
|
||||
|
||||
public const BindingFlags BindFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static;
|
||||
|
||||
protected MemberElement()
|
||||
{
|
||||
}
|
||||
|
||||
public void Link(MemberElement nextElement)
|
||||
{
|
||||
MyNext = nextElement;
|
||||
if ((nextElement != null))
|
||||
{
|
||||
nextElement.MyPrevious = this;
|
||||
}
|
||||
}
|
||||
|
||||
public void Resolve(IServiceProvider services)
|
||||
{
|
||||
MyServices = services;
|
||||
MyOptions = (ExpressionOptions)services.GetService(typeof(ExpressionOptions));
|
||||
MyContext = (ExpressionContext)services.GetService(typeof(ExpressionContext));
|
||||
this.ResolveInternal();
|
||||
this.Validate();
|
||||
}
|
||||
|
||||
public void SetImport(ImportBase import)
|
||||
{
|
||||
MyImport = import;
|
||||
}
|
||||
|
||||
protected abstract void ResolveInternal();
|
||||
public abstract bool IsStatic { get; }
|
||||
public abstract bool IsExtensionMethod { get; }
|
||||
protected abstract bool IsPublic { get; }
|
||||
|
||||
protected virtual void Validate()
|
||||
{
|
||||
if (MyPrevious == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.IsStatic == true && this.SupportsStatic == false && IsExtensionMethod == false)
|
||||
{
|
||||
base.ThrowCompileException(CompileErrorResourceKeys.StaticMemberCannotBeAccessedWithInstanceReference, CompileExceptionReason.TypeMismatch, MyName);
|
||||
}
|
||||
else if (this.IsStatic == false && this.SupportsInstance == false)
|
||||
{
|
||||
base.ThrowCompileException(CompileErrorResourceKeys.ReferenceToNonSharedMemberRequiresObjectReference, CompileExceptionReason.TypeMismatch, MyName);
|
||||
}
|
||||
}
|
||||
|
||||
public override void Emit(FleeILGenerator ilg, IServiceProvider services)
|
||||
{
|
||||
if ((MyPrevious != null))
|
||||
{
|
||||
MyPrevious.Emit(ilg, services);
|
||||
}
|
||||
}
|
||||
|
||||
protected static void EmitLoadVariables(FleeILGenerator ilg)
|
||||
{
|
||||
ilg.Emit(OpCodes.Ldarg_2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles a call emit for static, instance methods of reference/value types
|
||||
/// </summary>
|
||||
/// <param name="mi"></param>
|
||||
/// <param name="ilg"></param>
|
||||
protected void EmitMethodCall(MethodInfo mi, FleeILGenerator ilg)
|
||||
{
|
||||
EmitMethodCall(this.ResultType, this.NextRequiresAddress, mi, ilg);
|
||||
}
|
||||
|
||||
protected static void EmitMethodCall(Type resultType, bool nextRequiresAddress, MethodInfo mi, FleeILGenerator ilg)
|
||||
{
|
||||
if (mi.GetType().IsValueType == false)
|
||||
{
|
||||
EmitReferenceTypeMethodCall(mi, ilg);
|
||||
}
|
||||
else
|
||||
{
|
||||
EmitValueTypeMethodCall(mi, ilg);
|
||||
}
|
||||
|
||||
if (resultType.IsValueType & nextRequiresAddress)
|
||||
{
|
||||
EmitValueTypeLoadAddress(ilg, resultType);
|
||||
}
|
||||
}
|
||||
|
||||
protected static bool IsGetTypeMethod(MethodInfo mi)
|
||||
{
|
||||
MethodInfo miGetType = typeof(object).GetMethod("gettype", BindingFlags.Instance | BindingFlags.Public | BindingFlags.IgnoreCase);
|
||||
return mi.MethodHandle.Equals(miGetType.MethodHandle);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Emit a function call for a value type
|
||||
/// </summary>
|
||||
/// <param name="mi"></param>
|
||||
/// <param name="ilg"></param>
|
||||
private static void EmitValueTypeMethodCall(MethodInfo mi, FleeILGenerator ilg)
|
||||
{
|
||||
if (mi.IsStatic == true)
|
||||
{
|
||||
ilg.Emit(OpCodes.Call, mi);
|
||||
}
|
||||
else if ((!object.ReferenceEquals(mi.DeclaringType, mi.ReflectedType)))
|
||||
{
|
||||
// Method is not defined on the value type
|
||||
|
||||
if (IsGetTypeMethod(mi) == true)
|
||||
{
|
||||
// Special GetType method which requires a box
|
||||
ilg.Emit(OpCodes.Box, mi.ReflectedType);
|
||||
ilg.Emit(OpCodes.Call, mi);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Equals, GetHashCode, and ToString methods on the base
|
||||
ilg.Emit(OpCodes.Constrained, mi.ReflectedType);
|
||||
ilg.Emit(OpCodes.Callvirt, mi);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Call value type's implementation
|
||||
ilg.Emit(OpCodes.Call, mi);
|
||||
}
|
||||
}
|
||||
|
||||
private static void EmitReferenceTypeMethodCall(MethodInfo mi, FleeILGenerator ilg)
|
||||
{
|
||||
if (mi.IsStatic == true)
|
||||
{
|
||||
ilg.Emit(OpCodes.Call, mi);
|
||||
}
|
||||
else
|
||||
{
|
||||
ilg.Emit(OpCodes.Callvirt, mi);
|
||||
}
|
||||
}
|
||||
|
||||
protected static void EmitValueTypeLoadAddress(FleeILGenerator ilg, Type targetType)
|
||||
{
|
||||
int index = ilg.GetTempLocalIndex(targetType);
|
||||
Utility.EmitStoreLocal(ilg, index);
|
||||
ilg.Emit(OpCodes.Ldloca_S, Convert.ToByte(index));
|
||||
}
|
||||
|
||||
protected void EmitLoadOwner(FleeILGenerator ilg)
|
||||
{
|
||||
ilg.Emit(OpCodes.Ldarg_0);
|
||||
|
||||
Type ownerType = MyOptions.OwnerType;
|
||||
|
||||
if (ownerType.IsValueType == false)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ilg.Emit(OpCodes.Unbox, ownerType);
|
||||
ilg.Emit(OpCodes.Ldobj, ownerType);
|
||||
|
||||
// Emit usual stuff for value types but use the owner type as the target
|
||||
if (this.RequiresAddress == true)
|
||||
{
|
||||
EmitValueTypeLoadAddress(ilg, ownerType);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determine if a field, property, or method is public
|
||||
/// </summary>
|
||||
/// <param name="member"></param>
|
||||
/// <returns></returns>
|
||||
private static bool IsMemberPublic(MemberInfo member)
|
||||
{
|
||||
FieldInfo fi = member as FieldInfo;
|
||||
|
||||
if ((fi != null))
|
||||
{
|
||||
return fi.IsPublic;
|
||||
}
|
||||
|
||||
PropertyInfo pi = member as PropertyInfo;
|
||||
|
||||
if ((pi != null))
|
||||
{
|
||||
MethodInfo pmi = pi.GetGetMethod(true);
|
||||
return pmi.IsPublic;
|
||||
}
|
||||
|
||||
MethodInfo mi = member as MethodInfo;
|
||||
|
||||
if ((mi != null))
|
||||
{
|
||||
return mi.IsPublic;
|
||||
}
|
||||
|
||||
Debug.Assert(false, "unknown member type");
|
||||
return false;
|
||||
}
|
||||
|
||||
protected MemberInfo[] GetAccessibleMembers(MemberInfo[] members)
|
||||
{
|
||||
List<MemberInfo> accessible = new List<MemberInfo>();
|
||||
|
||||
// Keep all members that are accessible
|
||||
foreach (MemberInfo mi in members)
|
||||
{
|
||||
if (this.IsMemberAccessible(mi) == true)
|
||||
{
|
||||
accessible.Add(mi);
|
||||
}
|
||||
}
|
||||
|
||||
return accessible.ToArray();
|
||||
}
|
||||
|
||||
protected static bool IsOwnerMemberAccessible(MemberInfo member, ExpressionOptions options)
|
||||
{
|
||||
bool accessAllowed = false;
|
||||
|
||||
// Get the allowed access defined in the options
|
||||
if (IsMemberPublic(member) == true)
|
||||
{
|
||||
accessAllowed = (options.OwnerMemberAccess & BindingFlags.Public) != 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
accessAllowed = (options.OwnerMemberAccess & BindingFlags.NonPublic) != 0;
|
||||
}
|
||||
|
||||
// See if the member has our access attribute defined
|
||||
ExpressionOwnerMemberAccessAttribute attr = (ExpressionOwnerMemberAccessAttribute)Attribute.GetCustomAttribute(member, typeof(ExpressionOwnerMemberAccessAttribute));
|
||||
|
||||
if (attr == null)
|
||||
{
|
||||
// No, so return the access level
|
||||
return accessAllowed;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Member has our access attribute defined; use its access value instead
|
||||
return attr.AllowAccess;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsMemberAccessible(MemberInfo member)
|
||||
{
|
||||
if (MyOptions.IsOwnerType(member.ReflectedType) == true)
|
||||
{
|
||||
return IsOwnerMemberAccessible(member, MyOptions);
|
||||
}
|
||||
else
|
||||
{
|
||||
return IsMemberPublic(member);
|
||||
}
|
||||
}
|
||||
|
||||
protected MemberInfo[] GetMembers(MemberTypes targets)
|
||||
{
|
||||
if (MyPrevious == null)
|
||||
{
|
||||
// Do we have a namespace?
|
||||
if (MyImport == null)
|
||||
{
|
||||
// Get all members in the default namespace
|
||||
return this.GetDefaultNamespaceMembers(MyName, targets);
|
||||
}
|
||||
else
|
||||
{
|
||||
return MyImport.FindMembers(MyName, targets);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// We are not the first element; find all members with our name on the type of the previous member
|
||||
// We are not the first element; find all members with our name on the type of the previous member
|
||||
var foundMembers = MyPrevious.TargetType.FindMembers(targets, BindFlags, MyOptions.MemberFilter, MyName);
|
||||
var importedMembers = MyContext.Imports.RootImport.FindMembers(MyName, targets);
|
||||
if (foundMembers.Length == 0) //If no members found search in root import
|
||||
return importedMembers;
|
||||
|
||||
MemberInfo[] allMembers = new MemberInfo[foundMembers.Length + importedMembers.Length];
|
||||
foundMembers.CopyTo(allMembers, 0);
|
||||
importedMembers.CopyTo(allMembers, foundMembers.Length);
|
||||
return allMembers;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Find members in the default namespace
|
||||
/// </summary>
|
||||
/// <param name="name"></param>
|
||||
/// <param name="memberType"></param>
|
||||
/// <returns></returns>
|
||||
protected MemberInfo[] GetDefaultNamespaceMembers(string name, MemberTypes memberType)
|
||||
{
|
||||
// Search the owner first
|
||||
MemberInfo[] members = MyContext.Imports.FindOwnerMembers(name, memberType);
|
||||
|
||||
// Keep only the accessible members
|
||||
members = this.GetAccessibleMembers(members);
|
||||
|
||||
//Also search imports
|
||||
var importedMembers = MyContext.Imports.RootImport.FindMembers(name, memberType);
|
||||
|
||||
//if no members, just return imports
|
||||
if (members.Length == 0)
|
||||
return importedMembers;
|
||||
|
||||
//combine members and imports
|
||||
MemberInfo[] allMembers = new MemberInfo[members.Length + importedMembers.Length];
|
||||
members.CopyTo(allMembers, 0);
|
||||
importedMembers.CopyTo(allMembers, members.Length);
|
||||
return allMembers;
|
||||
}
|
||||
|
||||
protected static bool IsElementPublic(MemberElement e)
|
||||
{
|
||||
return e.IsPublic;
|
||||
}
|
||||
|
||||
public string MemberName => MyName;
|
||||
|
||||
protected bool NextRequiresAddress => MyNext != null && MyNext.RequiresAddress;
|
||||
|
||||
protected virtual bool RequiresAddress => false;
|
||||
|
||||
protected virtual bool SupportsInstance => true;
|
||||
|
||||
protected virtual bool SupportsStatic => false;
|
||||
|
||||
public System.Type TargetType => this.ResultType;
|
||||
}
|
||||
}
|
||||
28
ExpressionElements/Base/Unary.cs
Normal file
28
ExpressionElements/Base/Unary.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using Flee.PublicTypes;
|
||||
using Flee.Resources;
|
||||
|
||||
namespace Flee.ExpressionElements.Base
|
||||
{
|
||||
internal abstract class UnaryElement : ExpressionElement
|
||||
{
|
||||
|
||||
protected ExpressionElement MyChild;
|
||||
|
||||
private Type _myResultType;
|
||||
public void SetChild(ExpressionElement child)
|
||||
{
|
||||
MyChild = child;
|
||||
_myResultType = this.GetResultType(child.ResultType);
|
||||
|
||||
if (_myResultType == null)
|
||||
{
|
||||
base.ThrowCompileException(CompileErrorResourceKeys.OperationNotDefinedForType, CompileExceptionReason.TypeMismatch, MyChild.ResultType.Name);
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract Type GetResultType(Type childType);
|
||||
|
||||
public override System.Type ResultType => _myResultType;
|
||||
}
|
||||
|
||||
}
|
||||
513
ExpressionElements/Cast.cs
Normal file
513
ExpressionElements/Cast.cs
Normal file
@@ -0,0 +1,513 @@
|
||||
using System.Diagnostics;
|
||||
using System.Reflection.Emit;
|
||||
using System.Reflection;
|
||||
using Flee.ExpressionElements.Base;
|
||||
using Flee.InternalTypes;
|
||||
using Flee.PublicTypes;
|
||||
using Flee.Resources;
|
||||
|
||||
namespace Flee.ExpressionElements
|
||||
{
|
||||
internal class CastElement : ExpressionElement
|
||||
{
|
||||
private readonly ExpressionElement _myCastExpression;
|
||||
private readonly Type _myDestType;
|
||||
public CastElement(ExpressionElement castExpression, string[] destTypeParts, bool isArray, IServiceProvider services)
|
||||
{
|
||||
_myCastExpression = castExpression;
|
||||
|
||||
_myDestType = GetDestType(destTypeParts, services);
|
||||
|
||||
if (_myDestType == null)
|
||||
{
|
||||
base.ThrowCompileException(CompileErrorResourceKeys.CouldNotResolveType, CompileExceptionReason.UndefinedName, GetDestTypeString(destTypeParts, isArray));
|
||||
}
|
||||
|
||||
if (isArray == true)
|
||||
{
|
||||
_myDestType = _myDestType.MakeArrayType();
|
||||
}
|
||||
|
||||
if (this.IsValidCast(_myCastExpression.ResultType, _myDestType) == false)
|
||||
{
|
||||
this.ThrowInvalidCastException();
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetDestTypeString(string[] parts, bool isArray)
|
||||
{
|
||||
string s = string.Join(".", parts);
|
||||
|
||||
if (isArray == true)
|
||||
{
|
||||
s = s + "[]";
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resolve the type we are casting to
|
||||
/// </summary>
|
||||
/// <param name="destTypeParts"></param>
|
||||
/// <param name="services"></param>
|
||||
/// <returns></returns>
|
||||
private static Type GetDestType(string[] destTypeParts, IServiceProvider services)
|
||||
{
|
||||
ExpressionContext context = (ExpressionContext)services.GetService(typeof(ExpressionContext));
|
||||
|
||||
Type t = null;
|
||||
|
||||
// Try to find a builtin type with the name
|
||||
if (destTypeParts.Length == 1)
|
||||
{
|
||||
t = ExpressionImports.GetBuiltinType(destTypeParts[0]);
|
||||
}
|
||||
|
||||
if ((t != null))
|
||||
{
|
||||
return t;
|
||||
}
|
||||
|
||||
// Try to find the type in an import
|
||||
t = context.Imports.FindType(destTypeParts);
|
||||
|
||||
if ((t != null))
|
||||
{
|
||||
return t;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private bool IsValidCast(Type sourceType, Type destType)
|
||||
{
|
||||
if (object.ReferenceEquals(sourceType, destType))
|
||||
{
|
||||
// Identity cast always succeeds
|
||||
return true;
|
||||
}
|
||||
else if (destType.IsAssignableFrom(sourceType) == true)
|
||||
{
|
||||
// Cast is already implicitly valid
|
||||
return true;
|
||||
}
|
||||
else if (ImplicitConverter.EmitImplicitConvert(sourceType, destType, null) == true)
|
||||
{
|
||||
// Cast is already implicitly valid
|
||||
return true;
|
||||
}
|
||||
else if (IsCastableNumericType(sourceType) & IsCastableNumericType(destType))
|
||||
{
|
||||
// Explicit cast of numeric types always succeeds
|
||||
return true;
|
||||
}
|
||||
else if (sourceType.IsEnum == true | destType.IsEnum == true)
|
||||
{
|
||||
return this.IsValidExplicitEnumCast(sourceType, destType);
|
||||
}
|
||||
else if ((this.GetExplictOverloadedOperator(sourceType, destType) != null))
|
||||
{
|
||||
// Overloaded explict cast exists
|
||||
return true;
|
||||
}
|
||||
|
||||
if (sourceType.IsValueType == true)
|
||||
{
|
||||
// If we get here then the cast always fails since we are either casting one value type to another
|
||||
// or a value type to an invalid reference type
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (destType.IsValueType == true)
|
||||
{
|
||||
// Reference type to value type
|
||||
// Can only succeed if the reference type is a base of the value type or
|
||||
// it is one of the interfaces the value type implements
|
||||
Type[] interfaces = destType.GetInterfaces();
|
||||
return IsBaseType(destType, sourceType) == true | System.Array.IndexOf(interfaces, sourceType) != -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Reference type to reference type
|
||||
return this.IsValidExplicitReferenceCast(sourceType, destType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private MethodInfo GetExplictOverloadedOperator(Type sourceType, Type destType)
|
||||
{
|
||||
ExplicitOperatorMethodBinder binder = new ExplicitOperatorMethodBinder(destType, sourceType);
|
||||
|
||||
// Look for an operator on the source type and dest types
|
||||
MethodInfo miSource = Utility.GetOverloadedOperator("Explicit", sourceType, binder, sourceType);
|
||||
MethodInfo miDest = Utility.GetOverloadedOperator("Explicit", destType, binder, sourceType);
|
||||
|
||||
if (miSource == null & miDest == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
else if (miSource == null)
|
||||
{
|
||||
return miDest;
|
||||
}
|
||||
else if (miDest == null)
|
||||
{
|
||||
return miSource;
|
||||
}
|
||||
else
|
||||
{
|
||||
base.ThrowAmbiguousCallException(sourceType, destType, "Explicit");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsValidExplicitEnumCast(Type sourceType, Type destType)
|
||||
{
|
||||
sourceType = GetUnderlyingEnumType(sourceType);
|
||||
destType = GetUnderlyingEnumType(destType);
|
||||
return this.IsValidCast(sourceType, destType);
|
||||
}
|
||||
|
||||
private bool IsValidExplicitReferenceCast(Type sourceType, Type destType)
|
||||
{
|
||||
Debug.Assert(sourceType.IsValueType == false & destType.IsValueType == false, "expecting reference types");
|
||||
|
||||
if (object.ReferenceEquals(sourceType, typeof(object)))
|
||||
{
|
||||
// From object to any other reference-type
|
||||
return true;
|
||||
}
|
||||
else if (sourceType.IsArray == true & destType.IsArray == true)
|
||||
{
|
||||
// From an array-type S with an element type SE to an array-type T with an element type TE,
|
||||
// provided all of the following are true:
|
||||
|
||||
// S and T have the same number of dimensions
|
||||
if (sourceType.GetArrayRank() != destType.GetArrayRank())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
Type SE = sourceType.GetElementType();
|
||||
Type TE = destType.GetElementType();
|
||||
|
||||
// Both SE and TE are reference-types
|
||||
if (SE.IsValueType == true | TE.IsValueType == true)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
// An explicit reference conversion exists from SE to TE
|
||||
return this.IsValidExplicitReferenceCast(SE, TE);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (sourceType.IsClass == true & destType.IsClass == true)
|
||||
{
|
||||
// From any class-type S to any class-type T, provided S is a base class of T
|
||||
return IsBaseType(destType, sourceType);
|
||||
}
|
||||
else if (sourceType.IsClass == true & destType.IsInterface == true)
|
||||
{
|
||||
// From any class-type S to any interface-type T, provided S is not sealed and provided S does not implement T
|
||||
return sourceType.IsSealed == false & ImplementsInterface(sourceType, destType) == false;
|
||||
}
|
||||
else if (sourceType.IsInterface == true & destType.IsClass == true)
|
||||
{
|
||||
// From any interface-type S to any class-type T, provided T is not sealed or provided T implements S.
|
||||
return destType.IsSealed == false | ImplementsInterface(destType, sourceType) == true;
|
||||
}
|
||||
else if (sourceType.IsInterface == true & destType.IsInterface == true)
|
||||
{
|
||||
// From any interface-type S to any interface-type T, provided S is not derived from T
|
||||
return ImplementsInterface(sourceType, destType) == false;
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Assert(false, "unknown explicit cast");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool IsBaseType(Type target, Type potentialBase)
|
||||
{
|
||||
Type current = target;
|
||||
while ((current != null))
|
||||
{
|
||||
if (object.ReferenceEquals(current, potentialBase))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
current = current.BaseType;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool ImplementsInterface(Type target, Type interfaceType)
|
||||
{
|
||||
Type[] interfaces = target.GetInterfaces();
|
||||
return System.Array.IndexOf(interfaces, interfaceType) != -1;
|
||||
}
|
||||
|
||||
private void ThrowInvalidCastException()
|
||||
{
|
||||
base.ThrowCompileException(CompileErrorResourceKeys.CannotConvertType, CompileExceptionReason.InvalidExplicitCast, _myCastExpression.ResultType.Name, _myDestType.Name);
|
||||
}
|
||||
|
||||
private static bool IsCastableNumericType(Type t)
|
||||
{
|
||||
return t.IsPrimitive == true & (!object.ReferenceEquals(t, typeof(bool)));
|
||||
}
|
||||
|
||||
private static Type GetUnderlyingEnumType(Type t)
|
||||
{
|
||||
if (t.IsEnum == true)
|
||||
{
|
||||
return System.Enum.GetUnderlyingType(t);
|
||||
}
|
||||
else
|
||||
{
|
||||
return t;
|
||||
}
|
||||
}
|
||||
|
||||
public override void Emit(FleeILGenerator ilg, IServiceProvider services)
|
||||
{
|
||||
_myCastExpression.Emit(ilg, services);
|
||||
|
||||
Type sourceType = _myCastExpression.ResultType;
|
||||
Type destType = _myDestType;
|
||||
|
||||
this.EmitCast(ilg, sourceType, destType, services);
|
||||
}
|
||||
|
||||
private void EmitCast(FleeILGenerator ilg, Type sourceType, Type destType, IServiceProvider services)
|
||||
{
|
||||
MethodInfo explicitOperator = this.GetExplictOverloadedOperator(sourceType, destType);
|
||||
|
||||
if (object.ReferenceEquals(sourceType, destType))
|
||||
{
|
||||
// Identity cast; do nothing
|
||||
return;
|
||||
}
|
||||
else if ((explicitOperator != null))
|
||||
{
|
||||
ilg.Emit(OpCodes.Call, explicitOperator);
|
||||
}
|
||||
else if (sourceType.IsEnum == true | destType.IsEnum == true)
|
||||
{
|
||||
this.EmitEnumCast(ilg, sourceType, destType, services);
|
||||
}
|
||||
else if (ImplicitConverter.EmitImplicitConvert(sourceType, destType, ilg) == true)
|
||||
{
|
||||
// Implicit numeric cast; do nothing
|
||||
return;
|
||||
}
|
||||
else if (IsCastableNumericType(sourceType) & IsCastableNumericType(destType))
|
||||
{
|
||||
// Explicit numeric cast
|
||||
EmitExplicitNumericCast(ilg, sourceType, destType, services);
|
||||
}
|
||||
else if (sourceType.IsValueType == true)
|
||||
{
|
||||
Debug.Assert(destType.IsValueType == false, "expecting reference type");
|
||||
ilg.Emit(OpCodes.Box, sourceType);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (destType.IsValueType == true)
|
||||
{
|
||||
// Reference type to value type
|
||||
ilg.Emit(OpCodes.Unbox_Any, destType);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Reference type to reference type
|
||||
if (destType.IsAssignableFrom(sourceType) == false)
|
||||
{
|
||||
// Only emit cast if it is an explicit cast
|
||||
ilg.Emit(OpCodes.Castclass, destType);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void EmitEnumCast(FleeILGenerator ilg, Type sourceType, Type destType, IServiceProvider services)
|
||||
{
|
||||
if (destType.IsValueType == false)
|
||||
{
|
||||
ilg.Emit(OpCodes.Box, sourceType);
|
||||
}
|
||||
else if (sourceType.IsValueType == false)
|
||||
{
|
||||
ilg.Emit(OpCodes.Unbox_Any, destType);
|
||||
}
|
||||
else
|
||||
{
|
||||
sourceType = GetUnderlyingEnumType(sourceType);
|
||||
destType = GetUnderlyingEnumType(destType);
|
||||
this.EmitCast(ilg, sourceType, destType, services);
|
||||
}
|
||||
}
|
||||
|
||||
private static void EmitExplicitNumericCast(FleeILGenerator ilg, Type sourceType, Type destType, IServiceProvider services)
|
||||
{
|
||||
TypeCode desttc = Type.GetTypeCode(destType);
|
||||
TypeCode sourcetc = Type.GetTypeCode(sourceType);
|
||||
bool unsigned = IsUnsignedType(sourceType);
|
||||
ExpressionOptions options = (ExpressionOptions)services.GetService(typeof(ExpressionOptions));
|
||||
bool @checked = options.Checked;
|
||||
OpCode op = OpCodes.Nop;
|
||||
|
||||
switch (desttc)
|
||||
{
|
||||
case TypeCode.SByte:
|
||||
if (unsigned == true & @checked == true)
|
||||
{
|
||||
op = OpCodes.Conv_Ovf_I1_Un;
|
||||
}
|
||||
else if (@checked == true)
|
||||
{
|
||||
op = OpCodes.Conv_Ovf_I1;
|
||||
}
|
||||
else
|
||||
{
|
||||
op = OpCodes.Conv_I1;
|
||||
}
|
||||
break;
|
||||
case TypeCode.Byte:
|
||||
if (unsigned == true & @checked == true)
|
||||
{
|
||||
op = OpCodes.Conv_Ovf_U1_Un;
|
||||
}
|
||||
else if (@checked == true)
|
||||
{
|
||||
op = OpCodes.Conv_Ovf_U1;
|
||||
}
|
||||
else
|
||||
{
|
||||
op = OpCodes.Conv_U1;
|
||||
}
|
||||
break;
|
||||
case TypeCode.Int16:
|
||||
if (unsigned == true & @checked == true)
|
||||
{
|
||||
op = OpCodes.Conv_Ovf_I2_Un;
|
||||
}
|
||||
else if (@checked == true)
|
||||
{
|
||||
op = OpCodes.Conv_Ovf_I2;
|
||||
}
|
||||
else
|
||||
{
|
||||
op = OpCodes.Conv_I2;
|
||||
}
|
||||
break;
|
||||
case TypeCode.UInt16:
|
||||
if (unsigned == true & @checked == true)
|
||||
{
|
||||
op = OpCodes.Conv_Ovf_U2_Un;
|
||||
}
|
||||
else if (@checked == true)
|
||||
{
|
||||
op = OpCodes.Conv_Ovf_U2;
|
||||
}
|
||||
else
|
||||
{
|
||||
op = OpCodes.Conv_U2;
|
||||
}
|
||||
break;
|
||||
case TypeCode.Int32:
|
||||
if (unsigned == true & @checked == true)
|
||||
{
|
||||
op = OpCodes.Conv_Ovf_I4_Un;
|
||||
}
|
||||
else if (@checked == true)
|
||||
{
|
||||
op = OpCodes.Conv_Ovf_I4;
|
||||
}
|
||||
else if (sourcetc != TypeCode.UInt32)
|
||||
{
|
||||
// Don't need to emit a convert for this case since, to the CLR, it is the same data type
|
||||
op = OpCodes.Conv_I4;
|
||||
}
|
||||
break;
|
||||
case TypeCode.UInt32:
|
||||
if (unsigned == true & @checked == true)
|
||||
{
|
||||
op = OpCodes.Conv_Ovf_U4_Un;
|
||||
}
|
||||
else if (@checked == true)
|
||||
{
|
||||
op = OpCodes.Conv_Ovf_U4;
|
||||
}
|
||||
else if (sourcetc != TypeCode.Int32)
|
||||
{
|
||||
op = OpCodes.Conv_U4;
|
||||
}
|
||||
break;
|
||||
case TypeCode.Int64:
|
||||
if (unsigned == true & @checked == true)
|
||||
{
|
||||
op = OpCodes.Conv_Ovf_I8_Un;
|
||||
}
|
||||
else if (@checked == true)
|
||||
{
|
||||
op = OpCodes.Conv_Ovf_I8;
|
||||
}
|
||||
else if (sourcetc != TypeCode.UInt64)
|
||||
{
|
||||
op = OpCodes.Conv_I8;
|
||||
}
|
||||
break;
|
||||
case TypeCode.UInt64:
|
||||
if (unsigned == true & @checked == true)
|
||||
{
|
||||
op = OpCodes.Conv_Ovf_U8_Un;
|
||||
}
|
||||
else if (@checked == true)
|
||||
{
|
||||
op = OpCodes.Conv_Ovf_U8;
|
||||
}
|
||||
else if (sourcetc != TypeCode.Int64)
|
||||
{
|
||||
op = OpCodes.Conv_U8;
|
||||
}
|
||||
break;
|
||||
case TypeCode.Single:
|
||||
op = OpCodes.Conv_R4;
|
||||
break;
|
||||
default:
|
||||
Debug.Assert(false, "Unknown cast dest type");
|
||||
break;
|
||||
}
|
||||
|
||||
if (op.Equals(OpCodes.Nop) == false)
|
||||
{
|
||||
ilg.Emit(op);
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsUnsignedType(Type t)
|
||||
{
|
||||
TypeCode tc = Type.GetTypeCode(t);
|
||||
switch (tc)
|
||||
{
|
||||
case TypeCode.Byte:
|
||||
case TypeCode.UInt16:
|
||||
case TypeCode.UInt32:
|
||||
case TypeCode.UInt64:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public override System.Type ResultType => _myDestType;
|
||||
}
|
||||
}
|
||||
279
ExpressionElements/Compare.cs
Normal file
279
ExpressionElements/Compare.cs
Normal file
@@ -0,0 +1,279 @@
|
||||
using System.Diagnostics;
|
||||
using System.Reflection.Emit;
|
||||
using System.Reflection;
|
||||
using Flee.ExpressionElements.Base;
|
||||
using Flee.ExpressionElements.Literals.Integral;
|
||||
|
||||
using Flee.InternalTypes;
|
||||
using Flee.PublicTypes;
|
||||
|
||||
|
||||
namespace Flee.ExpressionElements
|
||||
{
|
||||
internal class CompareElement : BinaryExpressionElement
|
||||
{
|
||||
private LogicalCompareOperation _myOperation;
|
||||
|
||||
public CompareElement()
|
||||
{
|
||||
}
|
||||
|
||||
public void Initialize(ExpressionElement leftChild, ExpressionElement rightChild, LogicalCompareOperation op)
|
||||
{
|
||||
MyLeftChild = leftChild;
|
||||
MyRightChild = rightChild;
|
||||
_myOperation = op;
|
||||
}
|
||||
|
||||
public void Validate()
|
||||
{
|
||||
this.ValidateInternal(_myOperation);
|
||||
}
|
||||
|
||||
protected override void GetOperation(object operation)
|
||||
{
|
||||
_myOperation = (LogicalCompareOperation)operation;
|
||||
}
|
||||
|
||||
protected override System.Type GetResultType(System.Type leftType, System.Type rightType)
|
||||
{
|
||||
Type binaryResultType = ImplicitConverter.GetBinaryResultType(leftType, rightType);
|
||||
MethodInfo overloadedOperator = this.GetOverloadedCompareOperator();
|
||||
bool isEqualityOp = IsOpTypeEqualOrNotEqual(_myOperation);
|
||||
|
||||
// Use our string equality instead of overloaded operator
|
||||
if (object.ReferenceEquals(leftType, typeof(string)) & object.ReferenceEquals(rightType, typeof(string)) & isEqualityOp == true)
|
||||
{
|
||||
// String equality
|
||||
return typeof(bool);
|
||||
}
|
||||
else if ((overloadedOperator != null))
|
||||
{
|
||||
return overloadedOperator.ReturnType;
|
||||
}
|
||||
else if ((binaryResultType != null))
|
||||
{
|
||||
// Comparison of numeric operands
|
||||
return typeof(bool);
|
||||
}
|
||||
else if (object.ReferenceEquals(leftType, typeof(bool)) & object.ReferenceEquals(rightType, typeof(bool)) & isEqualityOp == true)
|
||||
{
|
||||
// Boolean equality
|
||||
return typeof(bool);
|
||||
}
|
||||
else if (this.AreBothChildrenReferenceTypes() == true & isEqualityOp == true)
|
||||
{
|
||||
// Comparison of reference types
|
||||
return typeof(bool);
|
||||
}
|
||||
else if (this.AreBothChildrenSameEnum() == true)
|
||||
{
|
||||
return typeof(bool);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Invalid operands
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private MethodInfo GetOverloadedCompareOperator()
|
||||
{
|
||||
string name = GetCompareOperatorName(_myOperation);
|
||||
return base.GetOverloadedBinaryOperator(name, _myOperation);
|
||||
}
|
||||
|
||||
private static string GetCompareOperatorName(LogicalCompareOperation op)
|
||||
{
|
||||
switch (op)
|
||||
{
|
||||
case LogicalCompareOperation.Equal:
|
||||
return "Equality";
|
||||
case LogicalCompareOperation.NotEqual:
|
||||
return "Inequality";
|
||||
case LogicalCompareOperation.GreaterThan:
|
||||
return "GreaterThan";
|
||||
case LogicalCompareOperation.LessThan:
|
||||
return "LessThan";
|
||||
case LogicalCompareOperation.GreaterThanOrEqual:
|
||||
return "GreaterThanOrEqual";
|
||||
case LogicalCompareOperation.LessThanOrEqual:
|
||||
return "LessThanOrEqual";
|
||||
default:
|
||||
Debug.Assert(false, "unknown compare type");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public override void Emit(FleeILGenerator ilg, IServiceProvider services)
|
||||
{
|
||||
Type binaryResultType = ImplicitConverter.GetBinaryResultType(MyLeftChild.ResultType, MyRightChild.ResultType);
|
||||
MethodInfo overloadedOperator = this.GetOverloadedCompareOperator();
|
||||
|
||||
if (this.AreBothChildrenOfType(typeof(string)))
|
||||
{
|
||||
// String equality
|
||||
MyLeftChild.Emit(ilg, services);
|
||||
MyRightChild.Emit(ilg, services);
|
||||
EmitStringEquality(ilg, _myOperation, services);
|
||||
}
|
||||
else if ((overloadedOperator != null))
|
||||
{
|
||||
base.EmitOverloadedOperatorCall(overloadedOperator, ilg, services);
|
||||
}
|
||||
else if ((binaryResultType != null))
|
||||
{
|
||||
// Emit a compare of numeric operands
|
||||
EmitChildWithConvert(MyLeftChild, binaryResultType, ilg, services);
|
||||
EmitChildWithConvert(MyRightChild, binaryResultType, ilg, services);
|
||||
EmitCompareOperation(ilg, _myOperation);
|
||||
}
|
||||
else if (this.AreBothChildrenOfType(typeof(bool)))
|
||||
{
|
||||
// Boolean equality
|
||||
this.EmitRegular(ilg, services);
|
||||
}
|
||||
else if (this.AreBothChildrenReferenceTypes() == true)
|
||||
{
|
||||
// Reference equality
|
||||
this.EmitRegular(ilg, services);
|
||||
}
|
||||
else if (MyLeftChild.ResultType.IsEnum == true & MyRightChild.ResultType.IsEnum == true)
|
||||
{
|
||||
this.EmitRegular(ilg, services);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Fail("unknown operand types");
|
||||
}
|
||||
}
|
||||
|
||||
private void EmitRegular(FleeILGenerator ilg, IServiceProvider services)
|
||||
{
|
||||
MyLeftChild.Emit(ilg, services);
|
||||
MyRightChild.Emit(ilg, services);
|
||||
this.EmitCompareOperation(ilg, _myOperation);
|
||||
}
|
||||
|
||||
private static void EmitStringEquality(FleeILGenerator ilg, LogicalCompareOperation op, IServiceProvider services)
|
||||
{
|
||||
// Get the StringComparison from the options
|
||||
ExpressionOptions options = (ExpressionOptions)services.GetService(typeof(ExpressionOptions));
|
||||
Int32LiteralElement ic = new Int32LiteralElement((int)options.StringComparison);
|
||||
|
||||
ic.Emit(ilg, services);
|
||||
|
||||
// and emit the method call
|
||||
System.Reflection.MethodInfo mi = typeof(string).GetMethod("Equals", new Type[] { typeof(string), typeof(string), typeof(StringComparison) }, null);
|
||||
ilg.Emit(OpCodes.Call, mi);
|
||||
|
||||
if (op == LogicalCompareOperation.NotEqual)
|
||||
{
|
||||
ilg.Emit(OpCodes.Ldc_I4_0);
|
||||
ilg.Emit(OpCodes.Ceq);
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsOpTypeEqualOrNotEqual(LogicalCompareOperation op)
|
||||
{
|
||||
return op == LogicalCompareOperation.Equal | op == LogicalCompareOperation.NotEqual;
|
||||
}
|
||||
|
||||
private bool AreBothChildrenReferenceTypes()
|
||||
{
|
||||
return MyLeftChild.ResultType.IsValueType == false & MyRightChild.ResultType.IsValueType == false;
|
||||
}
|
||||
|
||||
private bool AreBothChildrenSameEnum()
|
||||
{
|
||||
return MyLeftChild.ResultType.IsEnum == true && object.ReferenceEquals(MyLeftChild.ResultType, MyRightChild.ResultType);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Emit the actual compare
|
||||
/// </summary>
|
||||
/// <param name="ilg"></param>
|
||||
/// <param name="op"></param>
|
||||
private void EmitCompareOperation(FleeILGenerator ilg, LogicalCompareOperation op)
|
||||
{
|
||||
OpCode ltOpcode = this.GetCompareGTLTOpcode(false);
|
||||
OpCode gtOpcode = this.GetCompareGTLTOpcode(true);
|
||||
|
||||
switch (op)
|
||||
{
|
||||
case LogicalCompareOperation.Equal:
|
||||
ilg.Emit(OpCodes.Ceq);
|
||||
break;
|
||||
case LogicalCompareOperation.LessThan:
|
||||
ilg.Emit(ltOpcode);
|
||||
break;
|
||||
case LogicalCompareOperation.GreaterThan:
|
||||
ilg.Emit(gtOpcode);
|
||||
break;
|
||||
case LogicalCompareOperation.NotEqual:
|
||||
ilg.Emit(OpCodes.Ceq);
|
||||
ilg.Emit(OpCodes.Ldc_I4_0);
|
||||
ilg.Emit(OpCodes.Ceq);
|
||||
break;
|
||||
case LogicalCompareOperation.LessThanOrEqual:
|
||||
ilg.Emit(gtOpcode);
|
||||
ilg.Emit(OpCodes.Ldc_I4_0);
|
||||
ilg.Emit(OpCodes.Ceq);
|
||||
break;
|
||||
case LogicalCompareOperation.GreaterThanOrEqual:
|
||||
ilg.Emit(ltOpcode);
|
||||
ilg.Emit(OpCodes.Ldc_I4_0);
|
||||
ilg.Emit(OpCodes.Ceq);
|
||||
break;
|
||||
default:
|
||||
Debug.Fail("Unknown op type");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the correct greater/less than opcode
|
||||
/// </summary>
|
||||
/// <param name="greaterThan"></param>
|
||||
/// <returns></returns>
|
||||
private OpCode GetCompareGTLTOpcode(bool greaterThan)
|
||||
{
|
||||
Type leftType = MyLeftChild.ResultType;
|
||||
|
||||
if (object.ReferenceEquals(leftType, MyRightChild.ResultType))
|
||||
{
|
||||
if (object.ReferenceEquals(leftType, typeof(UInt32)) | object.ReferenceEquals(leftType, typeof(UInt64)))
|
||||
{
|
||||
if (greaterThan == true)
|
||||
{
|
||||
return OpCodes.Cgt_Un;
|
||||
}
|
||||
else
|
||||
{
|
||||
return OpCodes.Clt_Un;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return GetCompareOpcode(greaterThan);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return GetCompareOpcode(greaterThan);
|
||||
}
|
||||
}
|
||||
|
||||
private static OpCode GetCompareOpcode(bool greaterThan)
|
||||
{
|
||||
if (greaterThan == true)
|
||||
{
|
||||
return OpCodes.Cgt;
|
||||
}
|
||||
else
|
||||
{
|
||||
return OpCodes.Clt;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
75
ExpressionElements/Conditional.cs
Normal file
75
ExpressionElements/Conditional.cs
Normal file
@@ -0,0 +1,75 @@
|
||||
using System.Reflection.Emit;
|
||||
using Flee.ExpressionElements.Base;
|
||||
using Flee.InternalTypes;
|
||||
using Flee.PublicTypes;
|
||||
using Flee.Resources;
|
||||
|
||||
namespace Flee.ExpressionElements
|
||||
{
|
||||
internal class ConditionalElement : ExpressionElement
|
||||
{
|
||||
private readonly ExpressionElement _myCondition;
|
||||
private readonly ExpressionElement _myWhenTrue;
|
||||
private readonly ExpressionElement _myWhenFalse;
|
||||
private readonly Type _myResultType;
|
||||
public ConditionalElement(ExpressionElement condition, ExpressionElement whenTrue, ExpressionElement whenFalse)
|
||||
{
|
||||
_myCondition = condition;
|
||||
_myWhenTrue = whenTrue;
|
||||
_myWhenFalse = whenFalse;
|
||||
|
||||
if ((!object.ReferenceEquals(_myCondition.ResultType, typeof(bool))))
|
||||
{
|
||||
base.ThrowCompileException(CompileErrorResourceKeys.FirstArgNotBoolean, CompileExceptionReason.TypeMismatch);
|
||||
}
|
||||
|
||||
// The result type is the type that is common to the true/false operands
|
||||
if (ImplicitConverter.EmitImplicitConvert(_myWhenFalse.ResultType, _myWhenTrue.ResultType, null) == true)
|
||||
{
|
||||
_myResultType = _myWhenTrue.ResultType;
|
||||
}
|
||||
else if (ImplicitConverter.EmitImplicitConvert(_myWhenTrue.ResultType, _myWhenFalse.ResultType, null) == true)
|
||||
{
|
||||
_myResultType = _myWhenFalse.ResultType;
|
||||
}
|
||||
else
|
||||
{
|
||||
base.ThrowCompileException(CompileErrorResourceKeys.NeitherArgIsConvertibleToTheOther, CompileExceptionReason.TypeMismatch, _myWhenTrue.ResultType.Name, _myWhenFalse.ResultType.Name);
|
||||
}
|
||||
}
|
||||
|
||||
public override void Emit(FleeILGenerator ilg, IServiceProvider services)
|
||||
{
|
||||
this.EmitConditional(ilg, services);
|
||||
}
|
||||
|
||||
private void EmitConditional(FleeILGenerator ilg, IServiceProvider services)
|
||||
{
|
||||
Label falseLabel = ilg.DefineLabel();
|
||||
Label endLabel = ilg.DefineLabel();
|
||||
|
||||
// Emit the condition
|
||||
_myCondition.Emit(ilg, services);
|
||||
|
||||
// On false go to the false operand
|
||||
ilg.EmitBranchFalse(falseLabel);
|
||||
|
||||
// Emit the true operand
|
||||
_myWhenTrue.Emit(ilg, services);
|
||||
ImplicitConverter.EmitImplicitConvert(_myWhenTrue.ResultType, _myResultType, ilg);
|
||||
|
||||
// Jump to end
|
||||
ilg.EmitBranch(endLabel);
|
||||
|
||||
ilg.MarkLabel(falseLabel);
|
||||
|
||||
// Emit the false operand
|
||||
_myWhenFalse.Emit(ilg, services);
|
||||
ImplicitConverter.EmitImplicitConvert(_myWhenFalse.ResultType, _myResultType, ilg);
|
||||
// Fall through to end
|
||||
ilg.MarkLabel(endLabel);
|
||||
}
|
||||
|
||||
public override System.Type ResultType => _myResultType;
|
||||
}
|
||||
}
|
||||
195
ExpressionElements/In.cs
Normal file
195
ExpressionElements/In.cs
Normal file
@@ -0,0 +1,195 @@
|
||||
using System.Collections;
|
||||
using System.Reflection.Emit;
|
||||
using System.Reflection;
|
||||
using Flee.ExpressionElements.Base;
|
||||
|
||||
using Flee.InternalTypes;
|
||||
using Flee.PublicTypes;
|
||||
using Flee.Resources;
|
||||
|
||||
|
||||
namespace Flee.ExpressionElements
|
||||
{
|
||||
internal class InElement : ExpressionElement
|
||||
{
|
||||
// Element we will search for
|
||||
private ExpressionElement MyOperand;
|
||||
// Elements we will compare against
|
||||
private List<ExpressionElement> MyArguments;
|
||||
// Collection to look in
|
||||
private ExpressionElement MyTargetCollectionElement;
|
||||
// Type of the collection
|
||||
|
||||
private Type MyTargetCollectionType;
|
||||
// Initialize for searching a list of values
|
||||
public InElement(ExpressionElement operand, IList listElements)
|
||||
{
|
||||
MyOperand = operand;
|
||||
|
||||
ExpressionElement[] arr = new ExpressionElement[listElements.Count];
|
||||
listElements.CopyTo(arr, 0);
|
||||
|
||||
MyArguments = new List<ExpressionElement>(arr);
|
||||
this.ResolveForListSearch();
|
||||
}
|
||||
|
||||
// Initialize for searching a collection
|
||||
public InElement(ExpressionElement operand, ExpressionElement targetCollection)
|
||||
{
|
||||
MyOperand = operand;
|
||||
MyTargetCollectionElement = targetCollection;
|
||||
this.ResolveForCollectionSearch();
|
||||
}
|
||||
|
||||
private void ResolveForListSearch()
|
||||
{
|
||||
CompareElement ce = new CompareElement();
|
||||
|
||||
// Validate that our operand is comparable to all elements in the list
|
||||
foreach (ExpressionElement argumentElement in MyArguments)
|
||||
{
|
||||
ce.Initialize(MyOperand, argumentElement, LogicalCompareOperation.Equal);
|
||||
ce.Validate();
|
||||
}
|
||||
}
|
||||
|
||||
private void ResolveForCollectionSearch()
|
||||
{
|
||||
// Try to find a collection type
|
||||
MyTargetCollectionType = this.GetTargetCollectionType();
|
||||
|
||||
if (MyTargetCollectionType == null)
|
||||
{
|
||||
base.ThrowCompileException(CompileErrorResourceKeys.SearchArgIsNotKnownCollectionType, CompileExceptionReason.TypeMismatch, MyTargetCollectionElement.ResultType.Name);
|
||||
}
|
||||
|
||||
// Validate that the operand type is compatible with the collection
|
||||
MethodInfo mi = this.GetCollectionContainsMethod();
|
||||
ParameterInfo p1 = mi.GetParameters()[0];
|
||||
|
||||
if (ImplicitConverter.EmitImplicitConvert(MyOperand.ResultType, p1.ParameterType, null) == false)
|
||||
{
|
||||
base.ThrowCompileException(CompileErrorResourceKeys.OperandNotConvertibleToCollectionType, CompileExceptionReason.TypeMismatch, MyOperand.ResultType.Name, p1.ParameterType.Name);
|
||||
}
|
||||
}
|
||||
|
||||
private Type GetTargetCollectionType()
|
||||
{
|
||||
Type collType = MyTargetCollectionElement.ResultType;
|
||||
|
||||
// Try to see if the collection is a generic ICollection or IDictionary
|
||||
Type[] interfaces = collType.GetInterfaces();
|
||||
|
||||
foreach (Type interfaceType in interfaces)
|
||||
{
|
||||
if (interfaceType.IsGenericType == false)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Type genericTypeDef = interfaceType.GetGenericTypeDefinition();
|
||||
|
||||
if (object.ReferenceEquals(genericTypeDef, typeof(ICollection<>)) | object.ReferenceEquals(genericTypeDef, typeof(IDictionary<,>)))
|
||||
{
|
||||
return interfaceType;
|
||||
}
|
||||
}
|
||||
|
||||
// Try to see if it is a regular IList or IDictionary
|
||||
if (typeof(IList<>).IsAssignableFrom(collType) == true)
|
||||
{
|
||||
return typeof(IList<>);
|
||||
}
|
||||
else if (typeof(IDictionary<,>).IsAssignableFrom(collType) == true)
|
||||
{
|
||||
return typeof(IDictionary<,>);
|
||||
}
|
||||
|
||||
// Not a known collection type
|
||||
return null;
|
||||
}
|
||||
|
||||
public override void Emit(FleeILGenerator ilg, IServiceProvider services)
|
||||
{
|
||||
if ((MyTargetCollectionType != null))
|
||||
{
|
||||
this.EmitCollectionIn(ilg, services);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Do the real emit
|
||||
this.EmitListIn(ilg, services);
|
||||
}
|
||||
}
|
||||
|
||||
private void EmitCollectionIn(FleeILGenerator ilg, IServiceProvider services)
|
||||
{
|
||||
// Get the contains method
|
||||
MethodInfo mi = this.GetCollectionContainsMethod();
|
||||
ParameterInfo p1 = mi.GetParameters()[0];
|
||||
|
||||
// Load the collection
|
||||
MyTargetCollectionElement.Emit(ilg, services);
|
||||
// Load the argument
|
||||
MyOperand.Emit(ilg, services);
|
||||
// Do an implicit convert if necessary
|
||||
ImplicitConverter.EmitImplicitConvert(MyOperand.ResultType, p1.ParameterType, ilg);
|
||||
// Call the contains method
|
||||
ilg.Emit(OpCodes.Callvirt, mi);
|
||||
}
|
||||
|
||||
private MethodInfo GetCollectionContainsMethod()
|
||||
{
|
||||
string methodName = "Contains";
|
||||
|
||||
if (MyTargetCollectionType.IsGenericType == true && object.ReferenceEquals(MyTargetCollectionType.GetGenericTypeDefinition(), typeof(IDictionary<,>)))
|
||||
{
|
||||
methodName = "ContainsKey";
|
||||
}
|
||||
|
||||
return MyTargetCollectionType.GetMethod(methodName, BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
|
||||
}
|
||||
|
||||
private void EmitListIn(FleeILGenerator ilg, IServiceProvider services)
|
||||
{
|
||||
CompareElement ce = new CompareElement();
|
||||
Label endLabel = ilg.DefineLabel();
|
||||
Label trueTerminal = ilg.DefineLabel();
|
||||
|
||||
// Cache the operand since we will be comparing against it a lot
|
||||
LocalBuilder lb = ilg.DeclareLocal(MyOperand.ResultType);
|
||||
int targetIndex = lb.LocalIndex;
|
||||
|
||||
MyOperand.Emit(ilg, services);
|
||||
Utility.EmitStoreLocal(ilg, targetIndex);
|
||||
|
||||
// Wrap our operand in a local shim
|
||||
LocalBasedElement targetShim = new LocalBasedElement(MyOperand, targetIndex);
|
||||
|
||||
// Emit the compares
|
||||
foreach (ExpressionElement argumentElement in MyArguments)
|
||||
{
|
||||
ce.Initialize(targetShim, argumentElement, LogicalCompareOperation.Equal);
|
||||
ce.Emit(ilg, services);
|
||||
|
||||
EmitBranchToTrueTerminal(ilg, trueTerminal);
|
||||
}
|
||||
|
||||
ilg.Emit(OpCodes.Ldc_I4_0);
|
||||
ilg.Emit(OpCodes.Br_S, endLabel);
|
||||
|
||||
ilg.MarkLabel(trueTerminal);
|
||||
|
||||
ilg.Emit(OpCodes.Ldc_I4_1);
|
||||
|
||||
ilg.MarkLabel(endLabel);
|
||||
}
|
||||
|
||||
private static void EmitBranchToTrueTerminal(FleeILGenerator ilg, Label trueTerminal)
|
||||
{
|
||||
ilg.EmitBranchTrue(trueTerminal);
|
||||
}
|
||||
|
||||
public override System.Type ResultType => typeof(bool);
|
||||
}
|
||||
}
|
||||
22
ExpressionElements/Literals/Boolean.cs
Normal file
22
ExpressionElements/Literals/Boolean.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using Flee.ExpressionElements.Base.Literals;
|
||||
|
||||
using Flee.InternalTypes;
|
||||
|
||||
namespace Flee.ExpressionElements.Literals
|
||||
{
|
||||
internal class BooleanLiteralElement : LiteralElement
|
||||
{
|
||||
private readonly bool _myValue;
|
||||
public BooleanLiteralElement(bool value)
|
||||
{
|
||||
_myValue = value;
|
||||
}
|
||||
|
||||
public override void Emit(FleeILGenerator ilg, IServiceProvider services)
|
||||
{
|
||||
EmitLoad(_myValue, ilg);
|
||||
}
|
||||
|
||||
public override System.Type ResultType => typeof(bool);
|
||||
}
|
||||
}
|
||||
24
ExpressionElements/Literals/Char.cs
Normal file
24
ExpressionElements/Literals/Char.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using Flee.ExpressionElements.Base.Literals;
|
||||
|
||||
using Flee.InternalTypes;
|
||||
|
||||
|
||||
namespace Flee.ExpressionElements.Literals
|
||||
{
|
||||
internal class CharLiteralElement : LiteralElement
|
||||
{
|
||||
private readonly char _myValue;
|
||||
public CharLiteralElement(char value)
|
||||
{
|
||||
_myValue = value;
|
||||
}
|
||||
|
||||
public override void Emit(FleeILGenerator ilg, IServiceProvider services)
|
||||
{
|
||||
int intValue = Convert.ToInt32(_myValue);
|
||||
EmitLoad(intValue, ilg);
|
||||
}
|
||||
|
||||
public override System.Type ResultType => typeof(char);
|
||||
}
|
||||
}
|
||||
42
ExpressionElements/Literals/DateTime.cs
Normal file
42
ExpressionElements/Literals/DateTime.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
using System.Reflection;
|
||||
using System.Reflection.Emit;
|
||||
using System.Globalization;
|
||||
using Flee.ExpressionElements.Base.Literals;
|
||||
|
||||
using Flee.InternalTypes;
|
||||
using Flee.PublicTypes;
|
||||
using Flee.Resources;
|
||||
|
||||
namespace Flee.ExpressionElements.Literals
|
||||
{
|
||||
internal class DateTimeLiteralElement : LiteralElement
|
||||
{
|
||||
private DateTime _myValue;
|
||||
public DateTimeLiteralElement(string image, ExpressionContext context)
|
||||
{
|
||||
ExpressionParserOptions options = context.ParserOptions;
|
||||
|
||||
if (DateTime.TryParseExact(image, options.DateTimeFormat, CultureInfo.InvariantCulture, DateTimeStyles.None, out _myValue) == false)
|
||||
{
|
||||
base.ThrowCompileException(CompileErrorResourceKeys.CannotParseType, CompileExceptionReason.InvalidFormat, typeof(DateTime).Name);
|
||||
}
|
||||
}
|
||||
|
||||
public override void Emit(FleeILGenerator ilg, IServiceProvider services)
|
||||
{
|
||||
int index = ilg.GetTempLocalIndex(typeof(DateTime));
|
||||
|
||||
Utility.EmitLoadLocalAddress(ilg, index);
|
||||
|
||||
LiteralElement.EmitLoad(_myValue.Ticks, ilg);
|
||||
|
||||
ConstructorInfo ci = typeof(DateTime).GetConstructor(new Type[] { typeof(long) });
|
||||
|
||||
ilg.Emit(OpCodes.Call, ci);
|
||||
|
||||
Utility.EmitLoadLocal(ilg, index);
|
||||
}
|
||||
|
||||
public override System.Type ResultType => typeof(DateTime);
|
||||
}
|
||||
}
|
||||
84
ExpressionElements/Literals/Integral/Int32.cs
Normal file
84
ExpressionElements/Literals/Integral/Int32.cs
Normal file
@@ -0,0 +1,84 @@
|
||||
using System.Globalization;
|
||||
using Flee.ExpressionElements.Base.Literals;
|
||||
|
||||
using Flee.InternalTypes;
|
||||
|
||||
|
||||
namespace Flee.ExpressionElements.Literals.Integral
|
||||
{
|
||||
internal class Int32LiteralElement : IntegralLiteralElement
|
||||
{
|
||||
private Int32 _myValue;
|
||||
private const string MinValue = "2147483648";
|
||||
private readonly bool _myIsMinValue;
|
||||
public Int32LiteralElement(Int32 value)
|
||||
{
|
||||
_myValue = value;
|
||||
}
|
||||
|
||||
private Int32LiteralElement()
|
||||
{
|
||||
_myIsMinValue = true;
|
||||
}
|
||||
|
||||
public static Int32LiteralElement TryCreate(string image, bool isHex, bool negated)
|
||||
{
|
||||
if (negated == true & image == MinValue)
|
||||
{
|
||||
return new Int32LiteralElement();
|
||||
}
|
||||
else if (isHex == true)
|
||||
{
|
||||
Int32 value = default(Int32);
|
||||
|
||||
// Since Int32.TryParse will succeed for a string like 0xFFFFFFFF we have to do some special handling
|
||||
if (Int32.TryParse(image, NumberStyles.AllowHexSpecifier, null, out value) == false)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
else if (value >= 0 & value <= Int32.MaxValue)
|
||||
{
|
||||
return new Int32LiteralElement(value);
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Int32 value = default(Int32);
|
||||
|
||||
if (Int32.TryParse(image,out value) == true)
|
||||
{
|
||||
return new Int32LiteralElement(value);
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Negate()
|
||||
{
|
||||
if (_myIsMinValue == true)
|
||||
{
|
||||
_myValue = Int32.MinValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
_myValue = -_myValue;
|
||||
}
|
||||
}
|
||||
|
||||
public override void Emit(FleeILGenerator ilg, IServiceProvider services)
|
||||
{
|
||||
EmitLoad(_myValue, ilg);
|
||||
}
|
||||
|
||||
public override System.Type ResultType => typeof(Int32);
|
||||
|
||||
public int Value => _myValue;
|
||||
}
|
||||
}
|
||||
82
ExpressionElements/Literals/Integral/Int64.cs
Normal file
82
ExpressionElements/Literals/Integral/Int64.cs
Normal file
@@ -0,0 +1,82 @@
|
||||
using System.Globalization;
|
||||
using Flee.ExpressionElements.Base.Literals;
|
||||
|
||||
using Flee.InternalTypes;
|
||||
|
||||
namespace Flee.ExpressionElements.Literals.Integral
|
||||
{
|
||||
internal class Int64LiteralElement : IntegralLiteralElement
|
||||
{
|
||||
|
||||
private Int64 _myValue;
|
||||
private const string MinValue = "9223372036854775808";
|
||||
|
||||
private readonly bool _myIsMinValue;
|
||||
public Int64LiteralElement(Int64 value)
|
||||
{
|
||||
_myValue = value;
|
||||
}
|
||||
|
||||
private Int64LiteralElement()
|
||||
{
|
||||
_myIsMinValue = true;
|
||||
}
|
||||
|
||||
public static Int64LiteralElement TryCreate(string image, bool isHex, bool negated)
|
||||
{
|
||||
if (negated == true & image == MinValue)
|
||||
{
|
||||
return new Int64LiteralElement();
|
||||
}
|
||||
else if (isHex == true)
|
||||
{
|
||||
Int64 value = default(Int64);
|
||||
|
||||
if (Int64.TryParse(image, NumberStyles.AllowHexSpecifier, null, out value) == false)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
else if (value >= 0 & value <= Int64.MaxValue)
|
||||
{
|
||||
return new Int64LiteralElement(value);
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Int64 value = default(Int64);
|
||||
|
||||
if (Int64.TryParse(image, out value) == true)
|
||||
{
|
||||
return new Int64LiteralElement(value);
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void Emit(FleeILGenerator ilg, IServiceProvider services)
|
||||
{
|
||||
EmitLoad(_myValue, ilg);
|
||||
}
|
||||
|
||||
public void Negate()
|
||||
{
|
||||
if (_myIsMinValue == true)
|
||||
{
|
||||
_myValue = Int64.MinValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
_myValue = -_myValue;
|
||||
}
|
||||
}
|
||||
|
||||
public override System.Type ResultType => typeof(Int64);
|
||||
}
|
||||
}
|
||||
34
ExpressionElements/Literals/Integral/UInt32.cs
Normal file
34
ExpressionElements/Literals/Integral/UInt32.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
using Flee.ExpressionElements.Base.Literals;
|
||||
using Flee.InternalTypes;
|
||||
|
||||
namespace Flee.ExpressionElements.Literals.Integral
|
||||
{
|
||||
internal class UInt32LiteralElement : IntegralLiteralElement
|
||||
{
|
||||
private readonly UInt32 _myValue;
|
||||
public UInt32LiteralElement(UInt32 value)
|
||||
{
|
||||
_myValue = value;
|
||||
}
|
||||
|
||||
public static UInt32LiteralElement TryCreate(string image, System.Globalization.NumberStyles ns)
|
||||
{
|
||||
UInt32 value = default(UInt32);
|
||||
if (UInt32.TryParse(image, ns, null, out value) == true)
|
||||
{
|
||||
return new UInt32LiteralElement(value);
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public override void Emit(FleeILGenerator ilg, IServiceProvider services)
|
||||
{
|
||||
EmitLoad(Convert.ToInt32(_myValue), ilg);
|
||||
}
|
||||
|
||||
public override System.Type ResultType => typeof(UInt32);
|
||||
}
|
||||
}
|
||||
34
ExpressionElements/Literals/Integral/UInt64.cs
Normal file
34
ExpressionElements/Literals/Integral/UInt64.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
using Flee.ExpressionElements.Base.Literals;
|
||||
using Flee.InternalTypes;
|
||||
|
||||
|
||||
namespace Flee.ExpressionElements.Literals.Integral
|
||||
{
|
||||
internal class UInt64LiteralElement : IntegralLiteralElement
|
||||
{
|
||||
private readonly UInt64 _myValue;
|
||||
public UInt64LiteralElement(string image, System.Globalization.NumberStyles ns)
|
||||
{
|
||||
try
|
||||
{
|
||||
_myValue = UInt64.Parse(image, ns);
|
||||
}
|
||||
catch (OverflowException ex)
|
||||
{
|
||||
base.OnParseOverflow(image);
|
||||
}
|
||||
}
|
||||
|
||||
public UInt64LiteralElement(UInt64 value)
|
||||
{
|
||||
_myValue = value;
|
||||
}
|
||||
|
||||
public override void Emit(FleeILGenerator ilg, IServiceProvider services)
|
||||
{
|
||||
EmitLoad(Convert.ToInt64(_myValue), ilg);
|
||||
}
|
||||
|
||||
public override System.Type ResultType => typeof(UInt64);
|
||||
}
|
||||
}
|
||||
16
ExpressionElements/Literals/Null.cs
Normal file
16
ExpressionElements/Literals/Null.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using System.Reflection.Emit;
|
||||
using Flee.ExpressionElements.Base.Literals;
|
||||
using Flee.InternalTypes;
|
||||
|
||||
namespace Flee.ExpressionElements.Literals
|
||||
{
|
||||
internal class NullLiteralElement : LiteralElement
|
||||
{
|
||||
public override void Emit(FleeILGenerator ilg, IServiceProvider services)
|
||||
{
|
||||
ilg.Emit(OpCodes.Ldnull);
|
||||
}
|
||||
|
||||
public override System.Type ResultType => typeof(Null);
|
||||
}
|
||||
}
|
||||
76
ExpressionElements/Literals/Real/Decimal.cs
Normal file
76
ExpressionElements/Literals/Real/Decimal.cs
Normal file
@@ -0,0 +1,76 @@
|
||||
using System.Reflection.Emit;
|
||||
using System.Reflection;
|
||||
using Flee.ExpressionElements.Base.Literals;
|
||||
using Flee.InternalTypes;
|
||||
using Flee.PublicTypes;
|
||||
|
||||
|
||||
namespace Flee.ExpressionElements.Literals.Real
|
||||
{
|
||||
internal class DecimalLiteralElement : RealLiteralElement
|
||||
{
|
||||
private static readonly ConstructorInfo OurConstructorInfo = GetConstructor();
|
||||
private readonly decimal _myValue;
|
||||
|
||||
private DecimalLiteralElement()
|
||||
{
|
||||
}
|
||||
|
||||
public DecimalLiteralElement(decimal value)
|
||||
{
|
||||
_myValue = value;
|
||||
}
|
||||
|
||||
private static ConstructorInfo GetConstructor()
|
||||
{
|
||||
Type[] types = {
|
||||
typeof(Int32),
|
||||
typeof(Int32),
|
||||
typeof(Int32),
|
||||
typeof(bool),
|
||||
typeof(byte)
|
||||
};
|
||||
return typeof(decimal).GetConstructor(BindingFlags.Instance | BindingFlags.Public, null, CallingConventions.Any, types, null);
|
||||
}
|
||||
|
||||
public static DecimalLiteralElement Parse(string image, IServiceProvider services)
|
||||
{
|
||||
ExpressionParserOptions options = (ExpressionParserOptions)services.GetService(typeof(ExpressionParserOptions));
|
||||
DecimalLiteralElement element = new DecimalLiteralElement();
|
||||
|
||||
try
|
||||
{
|
||||
decimal value = options.ParseDecimal(image);
|
||||
return new DecimalLiteralElement(value);
|
||||
}
|
||||
catch (OverflowException ex)
|
||||
{
|
||||
element.OnParseOverflow(image);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public override void Emit(FleeILGenerator ilg, IServiceProvider services)
|
||||
{
|
||||
int index = ilg.GetTempLocalIndex(typeof(decimal));
|
||||
Utility.EmitLoadLocalAddress(ilg, index);
|
||||
|
||||
int[] bits = decimal.GetBits(_myValue);
|
||||
EmitLoad(bits[0], ilg);
|
||||
EmitLoad(bits[1], ilg);
|
||||
EmitLoad(bits[2], ilg);
|
||||
|
||||
int flags = bits[3];
|
||||
|
||||
EmitLoad((flags >> 31) == -1, ilg);
|
||||
|
||||
EmitLoad(flags >> 16, ilg);
|
||||
|
||||
ilg.Emit(OpCodes.Call, OurConstructorInfo);
|
||||
|
||||
Utility.EmitLoadLocal(ilg, index);
|
||||
}
|
||||
|
||||
public override System.Type ResultType => typeof(decimal);
|
||||
}
|
||||
}
|
||||
46
ExpressionElements/Literals/Real/Double.cs
Normal file
46
ExpressionElements/Literals/Real/Double.cs
Normal file
@@ -0,0 +1,46 @@
|
||||
using System.Reflection.Emit;
|
||||
using Flee.ExpressionElements.Base.Literals;
|
||||
using Flee.InternalTypes;
|
||||
using Flee.PublicTypes;
|
||||
|
||||
|
||||
namespace Flee.ExpressionElements.Literals.Real
|
||||
{
|
||||
internal class DoubleLiteralElement : RealLiteralElement
|
||||
{
|
||||
private readonly double _myValue;
|
||||
|
||||
private DoubleLiteralElement()
|
||||
{
|
||||
}
|
||||
|
||||
public DoubleLiteralElement(double value)
|
||||
{
|
||||
_myValue = value;
|
||||
}
|
||||
|
||||
public static DoubleLiteralElement Parse(string image, IServiceProvider services)
|
||||
{
|
||||
ExpressionParserOptions options = (ExpressionParserOptions)services.GetService(typeof(ExpressionParserOptions));
|
||||
DoubleLiteralElement element = new DoubleLiteralElement();
|
||||
|
||||
try
|
||||
{
|
||||
double value = options.ParseDouble(image);
|
||||
return new DoubleLiteralElement(value);
|
||||
}
|
||||
catch (OverflowException ex)
|
||||
{
|
||||
element.OnParseOverflow(image);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public override void Emit(FleeILGenerator ilg, IServiceProvider services)
|
||||
{
|
||||
ilg.Emit(OpCodes.Ldc_R8, _myValue);
|
||||
}
|
||||
|
||||
public override System.Type ResultType => typeof(double);
|
||||
}
|
||||
}
|
||||
45
ExpressionElements/Literals/Real/Single.cs
Normal file
45
ExpressionElements/Literals/Real/Single.cs
Normal file
@@ -0,0 +1,45 @@
|
||||
using System.Reflection.Emit;
|
||||
using Flee.ExpressionElements.Base.Literals;
|
||||
using Flee.InternalTypes;
|
||||
using Flee.PublicTypes;
|
||||
|
||||
namespace Flee.ExpressionElements.Literals.Real
|
||||
{
|
||||
internal class SingleLiteralElement : RealLiteralElement
|
||||
{
|
||||
private readonly float _myValue;
|
||||
|
||||
private SingleLiteralElement()
|
||||
{
|
||||
}
|
||||
|
||||
public SingleLiteralElement(float value)
|
||||
{
|
||||
_myValue = value;
|
||||
}
|
||||
|
||||
public static SingleLiteralElement Parse(string image, IServiceProvider services)
|
||||
{
|
||||
ExpressionParserOptions options = (ExpressionParserOptions)services.GetService(typeof(ExpressionParserOptions));
|
||||
SingleLiteralElement element = new SingleLiteralElement();
|
||||
|
||||
try
|
||||
{
|
||||
float value = options.ParseSingle(image);
|
||||
return new SingleLiteralElement(value);
|
||||
}
|
||||
catch (OverflowException ex)
|
||||
{
|
||||
element.OnParseOverflow(image);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public override void Emit(FleeILGenerator ilg, IServiceProvider services)
|
||||
{
|
||||
ilg.Emit(OpCodes.Ldc_R4, _myValue);
|
||||
}
|
||||
|
||||
public override System.Type ResultType => typeof(float);
|
||||
}
|
||||
}
|
||||
23
ExpressionElements/Literals/String.cs
Normal file
23
ExpressionElements/Literals/String.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
using System.Reflection.Emit;
|
||||
using Flee.ExpressionElements.Base.Literals;
|
||||
using Flee.InternalTypes;
|
||||
|
||||
|
||||
namespace Flee.ExpressionElements.Literals
|
||||
{
|
||||
internal class StringLiteralElement : LiteralElement
|
||||
{
|
||||
private readonly string _myValue;
|
||||
public StringLiteralElement(string value)
|
||||
{
|
||||
_myValue = value;
|
||||
}
|
||||
|
||||
public override void Emit(FleeILGenerator ilg, IServiceProvider services)
|
||||
{
|
||||
ilg.Emit(OpCodes.Ldstr, _myValue);
|
||||
}
|
||||
|
||||
public override System.Type ResultType => typeof(string);
|
||||
}
|
||||
}
|
||||
38
ExpressionElements/Literals/TimeSpan.cs
Normal file
38
ExpressionElements/Literals/TimeSpan.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
using System.Reflection;
|
||||
using System.Reflection.Emit;
|
||||
using Flee.ExpressionElements.Base.Literals;
|
||||
using Flee.InternalTypes;
|
||||
using Flee.PublicTypes;
|
||||
using Flee.Resources;
|
||||
|
||||
namespace Flee.ExpressionElements.Literals
|
||||
{
|
||||
internal class TimeSpanLiteralElement : LiteralElement
|
||||
{
|
||||
private TimeSpan _myValue;
|
||||
public TimeSpanLiteralElement(string image)
|
||||
{
|
||||
if (TimeSpan.TryParse(image, out _myValue) == false)
|
||||
{
|
||||
base.ThrowCompileException(CompileErrorResourceKeys.CannotParseType, CompileExceptionReason.InvalidFormat, typeof(TimeSpan).Name);
|
||||
}
|
||||
}
|
||||
|
||||
public override void Emit(FleeILGenerator ilg, System.IServiceProvider services)
|
||||
{
|
||||
int index = ilg.GetTempLocalIndex(typeof(TimeSpan));
|
||||
|
||||
Utility.EmitLoadLocalAddress(ilg, index);
|
||||
|
||||
LiteralElement.EmitLoad(_myValue.Ticks, ilg);
|
||||
|
||||
ConstructorInfo ci = typeof(TimeSpan).GetConstructor(new Type[] { typeof(long) });
|
||||
|
||||
ilg.Emit(OpCodes.Call, ci);
|
||||
|
||||
Utility.EmitLoadLocal(ilg, index);
|
||||
}
|
||||
|
||||
public override System.Type ResultType => typeof(TimeSpan);
|
||||
}
|
||||
}
|
||||
348
ExpressionElements/LogicalBitwise/AndOr.cs
Normal file
348
ExpressionElements/LogicalBitwise/AndOr.cs
Normal file
@@ -0,0 +1,348 @@
|
||||
using System.Collections;
|
||||
using System.Diagnostics;
|
||||
using System.Reflection.Emit;
|
||||
using Flee.ExpressionElements.Base;
|
||||
using Flee.InternalTypes;
|
||||
|
||||
namespace Flee.ExpressionElements.LogicalBitwise
|
||||
{
|
||||
internal class AndOrElement : BinaryExpressionElement
|
||||
{
|
||||
private AndOrOperation _myOperation;
|
||||
private static readonly object OurTrueTerminalKey = new object();
|
||||
private static readonly object OurFalseTerminalKey = new object();
|
||||
private static readonly object OurEndLabelKey = new object();
|
||||
|
||||
public void New()
|
||||
{
|
||||
}
|
||||
|
||||
protected override void GetOperation(object operation)
|
||||
{
|
||||
_myOperation = (AndOrOperation)operation;
|
||||
}
|
||||
|
||||
protected override System.Type GetResultType(System.Type leftType, System.Type rightType)
|
||||
{
|
||||
Type bitwiseOpType = Utility.GetBitwiseOpType(leftType, rightType);
|
||||
if ((bitwiseOpType != null))
|
||||
{
|
||||
return bitwiseOpType;
|
||||
}
|
||||
else if (this.AreBothChildrenOfType(typeof(bool)))
|
||||
{
|
||||
return typeof(bool);
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public override void Emit(FleeILGenerator ilg, IServiceProvider services)
|
||||
{
|
||||
Type resultType = this.ResultType;
|
||||
|
||||
if (object.ReferenceEquals(resultType, typeof(bool)))
|
||||
{
|
||||
this.DoEmitLogical(ilg, services);
|
||||
}
|
||||
else
|
||||
{
|
||||
MyLeftChild.Emit(ilg, services);
|
||||
ImplicitConverter.EmitImplicitConvert(MyLeftChild.ResultType, resultType, ilg);
|
||||
MyRightChild.Emit(ilg, services);
|
||||
ImplicitConverter.EmitImplicitConvert(MyRightChild.ResultType, resultType, ilg);
|
||||
EmitBitwiseOperation(ilg, _myOperation);
|
||||
}
|
||||
}
|
||||
|
||||
private static void EmitBitwiseOperation(FleeILGenerator ilg, AndOrOperation op)
|
||||
{
|
||||
switch (op)
|
||||
{
|
||||
case AndOrOperation.And:
|
||||
ilg.Emit(OpCodes.And);
|
||||
break;
|
||||
case AndOrOperation.Or:
|
||||
ilg.Emit(OpCodes.Or);
|
||||
break;
|
||||
default:
|
||||
Debug.Fail("Unknown op type");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void DoEmitLogical(FleeILGenerator ilg, IServiceProvider services)
|
||||
{
|
||||
// We have to do a 'fake' emit so we can get the positions of the labels
|
||||
ShortCircuitInfo info = new ShortCircuitInfo();
|
||||
|
||||
// Do the real emit
|
||||
this.EmitLogical(ilg, info, services);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Emit a short-circuited logical operation sequence
|
||||
/// The idea: Store all the leaf operands in a stack with the leftmost at the top and rightmost at the bottom.
|
||||
/// For each operand, emit it and try to find an end point for when it short-circuits. This means we go up through
|
||||
/// the stack of operators (ignoring siblings) until we find a different operation (then emit a branch to its right operand)
|
||||
/// or we reach the root (emit a branch to a true/false).
|
||||
/// Repeat the process for all operands and then emit the true/false/last operand end cases.
|
||||
/// </summary>
|
||||
/// <param name="ilg"></param>
|
||||
/// <param name="info"></param>
|
||||
/// <param name="services"></param>
|
||||
private void EmitLogical(FleeILGenerator ilg, ShortCircuitInfo info, IServiceProvider services)
|
||||
{
|
||||
// We always have an end label
|
||||
Label endLabel = ilg.DefineLabel();
|
||||
|
||||
// Populate our data structures
|
||||
this.PopulateData(info);
|
||||
|
||||
// Emit the sequence
|
||||
EmitLogicalShortCircuit(ilg, info, services);
|
||||
|
||||
// Get the last operand
|
||||
ExpressionElement terminalOperand = (ExpressionElement)info.Operands.Pop();
|
||||
// Emit it
|
||||
EmitOperand(terminalOperand, info, ilg, services);
|
||||
|
||||
// only 1-3 opcodes, always a short branch
|
||||
ilg.EmitBranch(endLabel);
|
||||
|
||||
// Emit our true/false terminals
|
||||
EmitTerminals(info, ilg, endLabel);
|
||||
|
||||
// Mark the end
|
||||
ilg.MarkLabel(endLabel);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Emit a sequence of and/or expressions with short-circuiting
|
||||
/// </summary>
|
||||
/// <param name="ilg"></param>
|
||||
/// <param name="info"></param>
|
||||
/// <param name="services"></param>
|
||||
private static void EmitLogicalShortCircuit(FleeILGenerator ilg, ShortCircuitInfo info, IServiceProvider services)
|
||||
{
|
||||
while (info.Operators.Count != 0)
|
||||
{
|
||||
// Get the operator
|
||||
AndOrElement op = (AndOrElement)info.Operators.Pop();
|
||||
// Get the left operand
|
||||
ExpressionElement leftOperand = (ExpressionElement)info.Operands.Pop();
|
||||
|
||||
// Emit the left
|
||||
EmitOperand(leftOperand, info, ilg, services);
|
||||
|
||||
// Get the label for the short-circuit case
|
||||
Label l = GetShortCircuitLabel(op, info, ilg);
|
||||
// Emit the branch
|
||||
EmitBranch(op, ilg, l, info);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static void EmitBranch(AndOrElement op, FleeILGenerator ilg, Label target, ShortCircuitInfo info)
|
||||
{
|
||||
// Get the branch opcode
|
||||
if (op._myOperation == AndOrOperation.And)
|
||||
ilg.EmitBranchFalse(target);
|
||||
else
|
||||
ilg.EmitBranchTrue(target);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Get the label for a short-circuit
|
||||
/// </summary>
|
||||
/// <param name="current"></param>
|
||||
/// <param name="info"></param>
|
||||
/// <param name="ilg"></param>
|
||||
/// <returns></returns>
|
||||
private static Label GetShortCircuitLabel(AndOrElement current, ShortCircuitInfo info, FleeILGenerator ilg)
|
||||
{
|
||||
// We modify the given stacks so we need to clone them
|
||||
Stack cloneOperands = (Stack)info.Operands.Clone();
|
||||
Stack cloneOperators = (Stack)info.Operators.Clone();
|
||||
|
||||
// Pop all siblings
|
||||
current.PopRightChild(cloneOperands, cloneOperators);
|
||||
|
||||
// Go until we run out of operators
|
||||
while (cloneOperators.Count > 0)
|
||||
{
|
||||
// Get the top operator
|
||||
AndOrElement top = (AndOrElement)cloneOperators.Pop();
|
||||
|
||||
// Is is a different operation?
|
||||
if (top._myOperation != current._myOperation)
|
||||
{
|
||||
// Yes, so return a label to its right operand
|
||||
object nextOperand = cloneOperands.Pop();
|
||||
return GetLabel(nextOperand, ilg, info);
|
||||
}
|
||||
else
|
||||
{
|
||||
// No, so keep going up the stack
|
||||
top.PopRightChild(cloneOperands, cloneOperators);
|
||||
}
|
||||
}
|
||||
|
||||
// We've reached the end of the stack so return the label for the appropriate true/false terminal
|
||||
if (current._myOperation == AndOrOperation.And)
|
||||
{
|
||||
return GetLabel(OurFalseTerminalKey, ilg, info);
|
||||
}
|
||||
else
|
||||
{
|
||||
return GetLabel(OurTrueTerminalKey, ilg, info);
|
||||
}
|
||||
}
|
||||
|
||||
private void PopRightChild(Stack operands, Stack operators)
|
||||
{
|
||||
AndOrElement andOrChild = MyRightChild as AndOrElement;
|
||||
|
||||
// What kind of child do we have?
|
||||
if ((andOrChild != null))
|
||||
{
|
||||
// Another and/or expression so recurse
|
||||
andOrChild.Pop(operands, operators);
|
||||
}
|
||||
else
|
||||
{
|
||||
// A terminal so pop it off the operands stack
|
||||
operands.Pop();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Recursively pop operators and operands
|
||||
/// </summary>
|
||||
/// <param name="operands"></param>
|
||||
/// <param name="operators"></param>
|
||||
private void Pop(Stack operands, Stack operators)
|
||||
{
|
||||
operators.Pop();
|
||||
|
||||
AndOrElement andOrChild = MyLeftChild as AndOrElement;
|
||||
if (andOrChild == null)
|
||||
{
|
||||
operands.Pop();
|
||||
}
|
||||
else
|
||||
{
|
||||
andOrChild.Pop(operands, operators);
|
||||
}
|
||||
|
||||
andOrChild = MyRightChild as AndOrElement;
|
||||
|
||||
if (andOrChild == null)
|
||||
{
|
||||
operands.Pop();
|
||||
}
|
||||
else
|
||||
{
|
||||
andOrChild.Pop(operands, operators);
|
||||
}
|
||||
}
|
||||
|
||||
private static void EmitOperand(ExpressionElement operand, ShortCircuitInfo info, FleeILGenerator ilg, IServiceProvider services)
|
||||
{
|
||||
// Is this operand the target of a label?
|
||||
if (info.HasLabel(operand) == true)
|
||||
{
|
||||
// Yes, so mark it
|
||||
Label leftLabel = info.FindLabel(operand);
|
||||
ilg.MarkLabel(leftLabel);
|
||||
}
|
||||
|
||||
// Emit the operand
|
||||
operand.Emit(ilg, services);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Emit the end cases for a short-circuit
|
||||
/// </summary>
|
||||
/// <param name="info"></param>
|
||||
/// <param name="ilg"></param>
|
||||
/// <param name="endLabel"></param>
|
||||
private static void EmitTerminals(ShortCircuitInfo info, FleeILGenerator ilg, Label endLabel)
|
||||
{
|
||||
// Emit the false case if it was used
|
||||
if (info.HasLabel(OurFalseTerminalKey) == true)
|
||||
{
|
||||
Label falseLabel = info.FindLabel(OurFalseTerminalKey);
|
||||
|
||||
// Mark the label and note its position
|
||||
ilg.MarkLabel(falseLabel);
|
||||
|
||||
ilg.Emit(OpCodes.Ldc_I4_0);
|
||||
|
||||
// If we also have a true terminal, then skip over it
|
||||
if (info.HasLabel(OurTrueTerminalKey) == true)
|
||||
{
|
||||
// only 1-3 opcodes, always a short branch
|
||||
ilg.Emit(OpCodes.Br_S, endLabel);
|
||||
}
|
||||
}
|
||||
|
||||
// Emit the true case if it was used
|
||||
if (info.HasLabel(OurTrueTerminalKey) == true)
|
||||
{
|
||||
Label trueLabel = info.FindLabel(OurTrueTerminalKey);
|
||||
|
||||
// Mark the label and note its position
|
||||
ilg.MarkLabel(trueLabel);
|
||||
|
||||
ilg.Emit(OpCodes.Ldc_I4_1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static Label GetLabel(object key, FleeILGenerator ilg, ShortCircuitInfo info)
|
||||
{
|
||||
if (info.HasLabel(key))
|
||||
return info.FindLabel(key);
|
||||
return info.AddLabel(key, ilg.DefineLabel());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Visit the nodes of the tree (right then left) and populate some data structures
|
||||
/// </summary>
|
||||
/// <param name="info"></param>
|
||||
private void PopulateData(ShortCircuitInfo info)
|
||||
{
|
||||
// Is our right child a leaf or another And/Or expression?
|
||||
AndOrElement andOrChild = MyRightChild as AndOrElement;
|
||||
if (andOrChild == null)
|
||||
{
|
||||
// Leaf so push it on the stack
|
||||
info.Operands.Push(MyRightChild);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Another And/Or expression so recurse
|
||||
andOrChild.PopulateData(info);
|
||||
}
|
||||
|
||||
// Add ourselves as an operator
|
||||
info.Operators.Push(this);
|
||||
|
||||
// Do the same thing for the left child
|
||||
andOrChild = MyLeftChild as AndOrElement;
|
||||
|
||||
if (andOrChild == null)
|
||||
{
|
||||
info.Operands.Push(MyLeftChild);
|
||||
}
|
||||
else
|
||||
{
|
||||
andOrChild.PopulateData(info);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
46
ExpressionElements/LogicalBitwise/Not.cs
Normal file
46
ExpressionElements/LogicalBitwise/Not.cs
Normal file
@@ -0,0 +1,46 @@
|
||||
using System.Reflection.Emit;
|
||||
using Flee.ExpressionElements.Base;
|
||||
using Flee.InternalTypes;
|
||||
|
||||
|
||||
namespace Flee.ExpressionElements.LogicalBitwise
|
||||
{
|
||||
internal class NotElement : UnaryElement
|
||||
{
|
||||
public override void Emit(FleeILGenerator ilg, IServiceProvider services)
|
||||
{
|
||||
if (object.ReferenceEquals(MyChild.ResultType, typeof(bool)))
|
||||
{
|
||||
this.EmitLogical(ilg, services);
|
||||
}
|
||||
else
|
||||
{
|
||||
MyChild.Emit(ilg, services);
|
||||
ilg.Emit(OpCodes.Not);
|
||||
}
|
||||
}
|
||||
|
||||
private void EmitLogical(FleeILGenerator ilg, IServiceProvider services)
|
||||
{
|
||||
MyChild.Emit(ilg, services);
|
||||
ilg.Emit(OpCodes.Ldc_I4_0);
|
||||
ilg.Emit(OpCodes.Ceq);
|
||||
}
|
||||
|
||||
protected override System.Type GetResultType(System.Type childType)
|
||||
{
|
||||
if (object.ReferenceEquals(childType, typeof(bool)))
|
||||
{
|
||||
return typeof(bool);
|
||||
}
|
||||
else if (Utility.IsIntegralType(childType) == true)
|
||||
{
|
||||
return childType;
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
44
ExpressionElements/LogicalBitwise/Xor.cs
Normal file
44
ExpressionElements/LogicalBitwise/Xor.cs
Normal file
@@ -0,0 +1,44 @@
|
||||
using System.Reflection.Emit;
|
||||
using Flee.ExpressionElements.Base;
|
||||
using Flee.InternalTypes;
|
||||
|
||||
|
||||
namespace Flee.ExpressionElements.LogicalBitwise
|
||||
{
|
||||
internal class XorElement : BinaryExpressionElement
|
||||
{
|
||||
protected override System.Type GetResultType(System.Type leftType, System.Type rightType)
|
||||
{
|
||||
Type bitwiseType = Utility.GetBitwiseOpType(leftType, rightType);
|
||||
|
||||
if ((bitwiseType != null))
|
||||
{
|
||||
return bitwiseType;
|
||||
}
|
||||
else if (this.AreBothChildrenOfType(typeof(bool)) == true)
|
||||
{
|
||||
return typeof(bool);
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public override void Emit(FleeILGenerator ilg, IServiceProvider services)
|
||||
{
|
||||
Type resultType = this.ResultType;
|
||||
|
||||
MyLeftChild.Emit(ilg, services);
|
||||
ImplicitConverter.EmitImplicitConvert(MyLeftChild.ResultType, resultType, ilg);
|
||||
MyRightChild.Emit(ilg, services);
|
||||
ImplicitConverter.EmitImplicitConvert(MyRightChild.ResultType, resultType, ilg);
|
||||
ilg.Emit(OpCodes.Xor);
|
||||
}
|
||||
|
||||
|
||||
protected override void GetOperation(object operation)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
59
ExpressionElements/MemberElements/ArgumentList.cs
Normal file
59
ExpressionElements/MemberElements/ArgumentList.cs
Normal file
@@ -0,0 +1,59 @@
|
||||
using System.Collections;
|
||||
using Flee.ExpressionElements.Base;
|
||||
using Flee.InternalTypes;
|
||||
|
||||
namespace Flee.ExpressionElements.MemberElements
|
||||
{
|
||||
[Obsolete("Encapsulates an argument list")]
|
||||
internal class ArgumentList
|
||||
{
|
||||
private readonly IList<ExpressionElement> _myElements;
|
||||
public ArgumentList(ICollection elements)
|
||||
{
|
||||
ExpressionElement[] arr = new ExpressionElement[elements.Count];
|
||||
elements.CopyTo(arr, 0);
|
||||
_myElements = arr;
|
||||
}
|
||||
|
||||
private string[] GetArgumentTypeNames()
|
||||
{
|
||||
List<string> l = new List<string>();
|
||||
|
||||
foreach (ExpressionElement e in _myElements)
|
||||
{
|
||||
l.Add(e.ResultType.Name);
|
||||
}
|
||||
|
||||
return l.ToArray();
|
||||
}
|
||||
|
||||
public Type[] GetArgumentTypes()
|
||||
{
|
||||
List<Type> l = new List<Type>();
|
||||
|
||||
foreach (ExpressionElement e in _myElements)
|
||||
{
|
||||
l.Add(e.ResultType);
|
||||
}
|
||||
|
||||
return l.ToArray();
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
string[] typeNames = this.GetArgumentTypeNames();
|
||||
return Utility.FormatList(typeNames);
|
||||
}
|
||||
|
||||
public ExpressionElement[] ToArray()
|
||||
{
|
||||
ExpressionElement[] arr = new ExpressionElement[_myElements.Count];
|
||||
_myElements.CopyTo(arr, 0);
|
||||
return arr;
|
||||
}
|
||||
|
||||
public ExpressionElement this[int index] => _myElements[index];
|
||||
|
||||
public int Count => _myElements.Count;
|
||||
}
|
||||
}
|
||||
409
ExpressionElements/MemberElements/FunctionCall.cs
Normal file
409
ExpressionElements/MemberElements/FunctionCall.cs
Normal file
@@ -0,0 +1,409 @@
|
||||
using System.Diagnostics;
|
||||
using System.Reflection;
|
||||
using System.Reflection.Emit;
|
||||
using Flee.ExpressionElements.Base;
|
||||
using Flee.ExpressionElements.Base.Literals;
|
||||
using Flee.InternalTypes;
|
||||
using Flee.PublicTypes;
|
||||
using Flee.Resources;
|
||||
|
||||
namespace Flee.ExpressionElements.MemberElements
|
||||
{
|
||||
[Obsolete("Represents a function call")]
|
||||
internal class FunctionCallElement : MemberElement
|
||||
{
|
||||
private readonly ArgumentList _myArguments;
|
||||
private readonly ICollection<MethodInfo> _myMethods;
|
||||
private CustomMethodInfo _myTargetMethodInfo;
|
||||
|
||||
private Type _myOnDemandFunctionReturnType;
|
||||
public FunctionCallElement(string name, ArgumentList arguments)
|
||||
{
|
||||
this.MyName = name;
|
||||
_myArguments = arguments;
|
||||
}
|
||||
|
||||
internal FunctionCallElement(string name, ICollection<MethodInfo> methods, ArgumentList arguments)
|
||||
{
|
||||
MyName = name;
|
||||
_myArguments = arguments;
|
||||
_myMethods = methods;
|
||||
}
|
||||
|
||||
protected override void ResolveInternal()
|
||||
{
|
||||
// Get the types of our arguments
|
||||
Type[] argTypes = _myArguments.GetArgumentTypes();
|
||||
// Find all methods with our name on the type
|
||||
ICollection<MethodInfo> methods = _myMethods;
|
||||
|
||||
if (methods == null)
|
||||
{
|
||||
// Convert member info to method info
|
||||
MemberInfo[] arr = this.GetMembers(MemberTypes.Method);
|
||||
MethodInfo[] arr2 = new MethodInfo[arr.Length];
|
||||
Array.Copy(arr, arr2, arr.Length);
|
||||
methods = arr2;
|
||||
}
|
||||
|
||||
if (methods.Count > 0)
|
||||
{
|
||||
// More than one method exists with this name
|
||||
this.BindToMethod(methods, MyPrevious, argTypes);
|
||||
return;
|
||||
}
|
||||
|
||||
// No methods with this name exist; try to bind to an on-demand function
|
||||
_myOnDemandFunctionReturnType = MyContext.Variables.ResolveOnDemandFunction(MyName, argTypes);
|
||||
|
||||
if (_myOnDemandFunctionReturnType == null)
|
||||
{
|
||||
// Failed to bind to a function
|
||||
this.ThrowFunctionNotFoundException(MyPrevious);
|
||||
}
|
||||
}
|
||||
|
||||
private void ThrowFunctionNotFoundException(MemberElement previous)
|
||||
{
|
||||
if (previous == null)
|
||||
{
|
||||
base.ThrowCompileException(CompileErrorResourceKeys.UndefinedFunction, CompileExceptionReason.UndefinedName, MyName, _myArguments);
|
||||
}
|
||||
else
|
||||
{
|
||||
base.ThrowCompileException(CompileErrorResourceKeys.UndefinedFunctionOnType, CompileExceptionReason.UndefinedName, MyName, _myArguments, previous.TargetType.Name);
|
||||
}
|
||||
}
|
||||
|
||||
private void ThrowNoAccessibleMethodsException(MemberElement previous)
|
||||
{
|
||||
if (previous == null)
|
||||
{
|
||||
base.ThrowCompileException(CompileErrorResourceKeys.NoAccessibleMatches, CompileExceptionReason.AccessDenied, MyName, _myArguments);
|
||||
}
|
||||
else
|
||||
{
|
||||
base.ThrowCompileException(CompileErrorResourceKeys.NoAccessibleMatchesOnType, CompileExceptionReason.AccessDenied, MyName, _myArguments, previous.TargetType.Name);
|
||||
}
|
||||
}
|
||||
|
||||
private void ThrowAmbiguousMethodCallException()
|
||||
{
|
||||
base.ThrowCompileException(CompileErrorResourceKeys.AmbiguousCallOfFunction, CompileExceptionReason.AmbiguousMatch, MyName, _myArguments);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Try to find a match from a set of methods
|
||||
/// </summary>
|
||||
/// <param name="methods"></param>
|
||||
/// <param name="previous"></param>
|
||||
/// <param name="argTypes"></param>
|
||||
private void BindToMethod(ICollection<MethodInfo> methods, MemberElement previous, Type[] argTypes)
|
||||
{
|
||||
List<CustomMethodInfo> customInfos = new List<CustomMethodInfo>();
|
||||
|
||||
// Wrap the MethodInfos in our custom class
|
||||
foreach (MethodInfo mi in methods)
|
||||
{
|
||||
CustomMethodInfo cmi = new CustomMethodInfo(mi);
|
||||
customInfos.Add(cmi);
|
||||
}
|
||||
|
||||
// Discard any methods that cannot qualify as overloads
|
||||
CustomMethodInfo[] arr = customInfos.ToArray();
|
||||
customInfos.Clear();
|
||||
|
||||
foreach (CustomMethodInfo cmi in arr)
|
||||
{
|
||||
if (cmi.IsMatch(argTypes, MyPrevious, MyContext) == true)
|
||||
{
|
||||
customInfos.Add(cmi);
|
||||
}
|
||||
}
|
||||
|
||||
if (customInfos.Count == 0)
|
||||
{
|
||||
// We have no methods that can qualify as overloads; throw exception
|
||||
this.ThrowFunctionNotFoundException(previous);
|
||||
}
|
||||
else
|
||||
{
|
||||
// At least one method matches our criteria; do our custom overload resolution
|
||||
this.ResolveOverloads(customInfos.ToArray(), previous, argTypes);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Find the best match from a set of overloaded methods
|
||||
/// </summary>
|
||||
/// <param name="infos"></param>
|
||||
/// <param name="previous"></param>
|
||||
/// <param name="argTypes"></param>
|
||||
private void ResolveOverloads(CustomMethodInfo[] infos, MemberElement previous, Type[] argTypes)
|
||||
{
|
||||
// Compute a score for each candidate
|
||||
foreach (CustomMethodInfo cmi in infos)
|
||||
{
|
||||
cmi.ComputeScore(argTypes);
|
||||
}
|
||||
|
||||
// Sort array from best to worst matches
|
||||
Array.Sort<CustomMethodInfo>(infos);
|
||||
|
||||
// Discard any matches that aren't accessible
|
||||
infos = this.GetAccessibleInfos(infos);
|
||||
|
||||
// No accessible methods left
|
||||
if (infos.Length == 0)
|
||||
{
|
||||
this.ThrowNoAccessibleMethodsException(previous);
|
||||
}
|
||||
|
||||
// Handle case where we have more than one match with the same score
|
||||
this.DetectAmbiguousMatches(infos);
|
||||
|
||||
// If we get here, then there is only one best match
|
||||
_myTargetMethodInfo = infos[0];
|
||||
}
|
||||
|
||||
private CustomMethodInfo[] GetAccessibleInfos(CustomMethodInfo[] infos)
|
||||
{
|
||||
List<CustomMethodInfo> accessible = new List<CustomMethodInfo>();
|
||||
|
||||
foreach (CustomMethodInfo cmi in infos)
|
||||
{
|
||||
if (cmi.IsAccessible(this) == true)
|
||||
{
|
||||
accessible.Add(cmi);
|
||||
}
|
||||
}
|
||||
|
||||
return accessible.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle case where we have overloads with the same score
|
||||
/// </summary>
|
||||
/// <param name="infos"></param>
|
||||
private void DetectAmbiguousMatches(CustomMethodInfo[] infos)
|
||||
{
|
||||
List<CustomMethodInfo> sameScores = new List<CustomMethodInfo>();
|
||||
CustomMethodInfo first = infos[0];
|
||||
|
||||
// Find all matches with the same score as the best match
|
||||
foreach (CustomMethodInfo cmi in infos)
|
||||
{
|
||||
if (((IEquatable<CustomMethodInfo>)cmi).Equals(first) == true)
|
||||
{
|
||||
sameScores.Add(cmi);
|
||||
}
|
||||
}
|
||||
|
||||
// More than one accessible match with the same score exists
|
||||
if (sameScores.Count > 1)
|
||||
{
|
||||
this.ThrowAmbiguousMethodCallException();
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Validate()
|
||||
{
|
||||
base.Validate();
|
||||
|
||||
if ((_myOnDemandFunctionReturnType != null))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Any function reference in an expression must return a value
|
||||
if (object.ReferenceEquals(this.Method.ReturnType, typeof(void)))
|
||||
{
|
||||
base.ThrowCompileException(CompileErrorResourceKeys.FunctionHasNoReturnValue, CompileExceptionReason.FunctionHasNoReturnValue, MyName);
|
||||
}
|
||||
}
|
||||
|
||||
public override void Emit(FleeILGenerator ilg, IServiceProvider services)
|
||||
{
|
||||
base.Emit(ilg, services);
|
||||
|
||||
ExpressionElement[] elements = _myArguments.ToArray();
|
||||
|
||||
// If we are an on-demand function, then emit that and exit
|
||||
if ((_myOnDemandFunctionReturnType != null))
|
||||
{
|
||||
this.EmitOnDemandFunction(elements, ilg, services);
|
||||
return;
|
||||
}
|
||||
|
||||
bool isOwnerMember = MyOptions.IsOwnerType(this.Method.ReflectedType);
|
||||
|
||||
// Load the owner if required
|
||||
if (MyPrevious == null && isOwnerMember == true && this.IsStatic == false)
|
||||
{
|
||||
this.EmitLoadOwner(ilg);
|
||||
}
|
||||
|
||||
this.EmitFunctionCall(this.NextRequiresAddress, ilg, services);
|
||||
}
|
||||
|
||||
private void EmitOnDemandFunction(ExpressionElement[] elements, FleeILGenerator ilg, IServiceProvider services)
|
||||
{
|
||||
// Load the variable collection
|
||||
EmitLoadVariables(ilg);
|
||||
// Load the function name
|
||||
ilg.Emit(OpCodes.Ldstr, MyName);
|
||||
// Load the arguments array
|
||||
EmitElementArrayLoad(elements, typeof(object), ilg, services);
|
||||
|
||||
// Call the function to get the result
|
||||
MethodInfo mi = VariableCollection.GetFunctionInvokeMethod(_myOnDemandFunctionReturnType);
|
||||
|
||||
this.EmitMethodCall(mi, ilg);
|
||||
}
|
||||
|
||||
// Emit the arguments to a paramArray method call
|
||||
private void EmitParamArrayArguments(ParameterInfo[] parameters, ExpressionElement[] elements, FleeILGenerator ilg, IServiceProvider services)
|
||||
{
|
||||
// Get the fixed parameters
|
||||
ParameterInfo[] fixedParameters = new ParameterInfo[_myTargetMethodInfo.MyFixedArgTypes.Length];
|
||||
Array.Copy(parameters, fixedParameters, fixedParameters.Length);
|
||||
|
||||
// Get the corresponding fixed parameters
|
||||
ExpressionElement[] fixedElements = new ExpressionElement[_myTargetMethodInfo.MyFixedArgTypes.Length];
|
||||
Array.Copy(elements, fixedElements, fixedElements.Length);
|
||||
|
||||
// Emit the fixed arguments
|
||||
this.EmitRegularFunctionInternal(fixedParameters, fixedElements, ilg, services);
|
||||
|
||||
// Get the paramArray arguments
|
||||
ExpressionElement[] paramArrayElements = new ExpressionElement[elements.Length - fixedElements.Length];
|
||||
Array.Copy(elements, fixedElements.Length, paramArrayElements, 0, paramArrayElements.Length);
|
||||
|
||||
// Emit them into an array
|
||||
EmitElementArrayLoad(paramArrayElements, _myTargetMethodInfo.ParamArrayElementType, ilg, services);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Emit elements into an array
|
||||
/// </summary>
|
||||
/// <param name="elements"></param>
|
||||
/// <param name="arrayElementType"></param>
|
||||
/// <param name="ilg"></param>
|
||||
/// <param name="services"></param>
|
||||
private static void EmitElementArrayLoad(ExpressionElement[] elements, Type arrayElementType, FleeILGenerator ilg, IServiceProvider services)
|
||||
{
|
||||
// Load the array length
|
||||
LiteralElement.EmitLoad(elements.Length, ilg);
|
||||
|
||||
// Create the array
|
||||
ilg.Emit(OpCodes.Newarr, arrayElementType);
|
||||
|
||||
// Store the new array in a unique local and remember the index
|
||||
LocalBuilder local = ilg.DeclareLocal(arrayElementType.MakeArrayType());
|
||||
int arrayLocalIndex = local.LocalIndex;
|
||||
Utility.EmitStoreLocal(ilg, arrayLocalIndex);
|
||||
|
||||
for (int i = 0; i <= elements.Length - 1; i++)
|
||||
{
|
||||
// Load the array
|
||||
Utility.EmitLoadLocal(ilg, arrayLocalIndex);
|
||||
// Load the index
|
||||
LiteralElement.EmitLoad(i, ilg);
|
||||
// Emit the element (with any required conversions)
|
||||
ExpressionElement element = elements[i];
|
||||
element.Emit(ilg, services);
|
||||
ImplicitConverter.EmitImplicitConvert(element.ResultType, arrayElementType, ilg);
|
||||
// Store it into the array
|
||||
Utility.EmitArrayStore(ilg, arrayElementType);
|
||||
}
|
||||
|
||||
// Load the array
|
||||
Utility.EmitLoadLocal(ilg, arrayLocalIndex);
|
||||
}
|
||||
|
||||
public void EmitFunctionCall(bool nextRequiresAddress, FleeILGenerator ilg, IServiceProvider services)
|
||||
{
|
||||
ParameterInfo[] parameters = this.Method.GetParameters();
|
||||
ExpressionElement[] elements = _myArguments.ToArray();
|
||||
|
||||
// Emit either a regular or paramArray call
|
||||
if (_myTargetMethodInfo.IsParamArray == false)
|
||||
{
|
||||
if (_myTargetMethodInfo.IsExtensionMethod == false)
|
||||
this.EmitRegularFunctionInternal(parameters, elements, ilg, services);
|
||||
else
|
||||
this.EmitExtensionFunctionInternal(parameters, elements, ilg, services);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.EmitParamArrayArguments(parameters, elements, ilg, services);
|
||||
}
|
||||
|
||||
MemberElement.EmitMethodCall(this.ResultType, nextRequiresAddress, this.Method, ilg);
|
||||
}
|
||||
|
||||
private void EmitExtensionFunctionInternal(ParameterInfo[] parameters, ExpressionElement[] elements, FleeILGenerator ilg, IServiceProvider services)
|
||||
{
|
||||
Debug.Assert(parameters.Length == elements.Length + 1, "argument count mismatch");
|
||||
if (MyPrevious == null) this.EmitLoadOwner(ilg);
|
||||
//Emit each element and any required conversions to the actual parameter type
|
||||
for (int i = 1; i <= parameters.Length - 1; i++)
|
||||
{
|
||||
ExpressionElement element = elements[i - 1];
|
||||
ParameterInfo pi = parameters[i];
|
||||
element.Emit(ilg, services);
|
||||
bool success = ImplicitConverter.EmitImplicitConvert(element.ResultType, pi.ParameterType, ilg);
|
||||
Debug.Assert(success, "conversion failed");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Emit the arguments to a regular method call
|
||||
/// </summary>
|
||||
/// <param name="parameters"></param>
|
||||
/// <param name="elements"></param>
|
||||
/// <param name="ilg"></param>
|
||||
/// <param name="services"></param>
|
||||
private void EmitRegularFunctionInternal(ParameterInfo[] parameters, ExpressionElement[] elements, FleeILGenerator ilg, IServiceProvider services)
|
||||
{
|
||||
Debug.Assert(parameters.Length == elements.Length, "argument count mismatch");
|
||||
|
||||
// Emit each element and any required conversions to the actual parameter type
|
||||
for (int i = 0; i <= parameters.Length - 1; i++)
|
||||
{
|
||||
ExpressionElement element = elements[i];
|
||||
ParameterInfo pi = parameters[i];
|
||||
element.Emit(ilg, services);
|
||||
bool success = ImplicitConverter.EmitImplicitConvert(element.ResultType, pi.ParameterType, ilg);
|
||||
Debug.Assert(success, "conversion failed");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The method info we will be calling
|
||||
/// </summary>
|
||||
private MethodInfo Method => _myTargetMethodInfo.Target;
|
||||
|
||||
public override Type ResultType
|
||||
{
|
||||
get
|
||||
{
|
||||
if ((_myOnDemandFunctionReturnType != null))
|
||||
{
|
||||
return _myOnDemandFunctionReturnType;
|
||||
}
|
||||
else
|
||||
{
|
||||
return this.Method.ReturnType;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool RequiresAddress => !IsGetTypeMethod(this.Method);
|
||||
|
||||
protected override bool IsPublic => this.Method.IsPublic;
|
||||
|
||||
public override bool IsStatic => this.Method.IsStatic;
|
||||
public override bool IsExtensionMethod => this._myTargetMethodInfo.IsExtensionMethod;
|
||||
}
|
||||
}
|
||||
492
ExpressionElements/MemberElements/Identifier.cs
Normal file
492
ExpressionElements/MemberElements/Identifier.cs
Normal file
@@ -0,0 +1,492 @@
|
||||
using System.Diagnostics;
|
||||
using System.Reflection;
|
||||
using System.Reflection.Emit;
|
||||
using System.ComponentModel;
|
||||
using Flee.CalcEngine.PublicTypes;
|
||||
using Flee.ExpressionElements.Base;
|
||||
using Flee.ExpressionElements.Base.Literals;
|
||||
using Flee.ExpressionElements.Literals;
|
||||
using Flee.ExpressionElements.Literals.Integral;
|
||||
using Flee.ExpressionElements.Literals.Real;
|
||||
using Flee.InternalTypes;
|
||||
using Flee.PublicTypes;
|
||||
using Flee.Resources;
|
||||
|
||||
|
||||
namespace Flee.ExpressionElements.MemberElements
|
||||
{
|
||||
[Obsolete("Represents an identifier")]
|
||||
internal class IdentifierElement : MemberElement
|
||||
{
|
||||
private FieldInfo _myField;
|
||||
private PropertyInfo _myProperty;
|
||||
private PropertyDescriptor _myPropertyDescriptor;
|
||||
private Type _myVariableType;
|
||||
private Type _myCalcEngineReferenceType;
|
||||
public IdentifierElement(string name)
|
||||
{
|
||||
this.MyName = name;
|
||||
}
|
||||
|
||||
protected override void ResolveInternal()
|
||||
{
|
||||
// Try to bind to a field or property
|
||||
if (this.ResolveFieldProperty(MyPrevious) == true)
|
||||
{
|
||||
this.AddReferencedVariable(MyPrevious);
|
||||
return;
|
||||
}
|
||||
|
||||
// Try to find a variable with our name
|
||||
_myVariableType = MyContext.Variables.GetVariableTypeInternal(MyName);
|
||||
|
||||
// Variables are only usable as the first element
|
||||
if (MyPrevious == null && (_myVariableType != null))
|
||||
{
|
||||
this.AddReferencedVariable(MyPrevious);
|
||||
return;
|
||||
}
|
||||
|
||||
CalculationEngine ce = MyContext.CalculationEngine;
|
||||
|
||||
if ((ce != null))
|
||||
{
|
||||
ce.AddDependency(MyName, MyContext);
|
||||
_myCalcEngineReferenceType = ce.ResolveTailType(MyName);
|
||||
return;
|
||||
}
|
||||
|
||||
if (MyPrevious == null)
|
||||
{
|
||||
base.ThrowCompileException(CompileErrorResourceKeys.NoIdentifierWithName, CompileExceptionReason.UndefinedName, MyName);
|
||||
}
|
||||
else
|
||||
{
|
||||
base.ThrowCompileException(CompileErrorResourceKeys.NoIdentifierWithNameOnType, CompileExceptionReason.UndefinedName, MyName, MyPrevious.TargetType.Name);
|
||||
}
|
||||
}
|
||||
|
||||
private bool ResolveFieldProperty(MemberElement previous)
|
||||
{
|
||||
MemberInfo[] members = this.GetMembers(MemberTypes.Field | MemberTypes.Property);
|
||||
|
||||
// Keep only the ones which are accessible
|
||||
members = this.GetAccessibleMembers(members);
|
||||
|
||||
if (members.Length == 0)
|
||||
{
|
||||
// No accessible members; try to resolve a virtual property
|
||||
return this.ResolveVirtualProperty(previous);
|
||||
}
|
||||
else if (members.Length > 1)
|
||||
{
|
||||
// More than one accessible member
|
||||
if (previous == null)
|
||||
{
|
||||
base.ThrowCompileException(CompileErrorResourceKeys.IdentifierIsAmbiguous, CompileExceptionReason.AmbiguousMatch, MyName);
|
||||
}
|
||||
else
|
||||
{
|
||||
base.ThrowCompileException(CompileErrorResourceKeys.IdentifierIsAmbiguousOnType, CompileExceptionReason.AmbiguousMatch, MyName, previous.TargetType.Name);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Only one member; bind to it
|
||||
_myField = members[0] as FieldInfo;
|
||||
if ((_myField != null))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// Assume it must be a property
|
||||
_myProperty = (PropertyInfo)members[0];
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool ResolveVirtualProperty(MemberElement previous)
|
||||
{
|
||||
if (previous == null)
|
||||
{
|
||||
// We can't use virtual properties if we are the first element
|
||||
return false;
|
||||
}
|
||||
|
||||
PropertyDescriptorCollection coll = TypeDescriptor.GetProperties(previous.ResultType);
|
||||
_myPropertyDescriptor = coll.Find(MyName, true);
|
||||
return (_myPropertyDescriptor != null);
|
||||
}
|
||||
|
||||
private void AddReferencedVariable(MemberElement previous)
|
||||
{
|
||||
if ((previous != null))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if ((_myVariableType != null) || MyOptions.IsOwnerType(this.MemberOwnerType) == true)
|
||||
{
|
||||
ExpressionInfo info = (ExpressionInfo)MyServices.GetService(typeof(ExpressionInfo));
|
||||
info.AddReferencedVariable(MyName);
|
||||
}
|
||||
}
|
||||
|
||||
public override void Emit(FleeILGenerator ilg, IServiceProvider services)
|
||||
{
|
||||
base.Emit(ilg, services);
|
||||
|
||||
this.EmitFirst(ilg);
|
||||
|
||||
if ((_myCalcEngineReferenceType != null))
|
||||
{
|
||||
this.EmitReferenceLoad(ilg);
|
||||
}
|
||||
else if ((_myVariableType != null))
|
||||
{
|
||||
this.EmitVariableLoad(ilg);
|
||||
}
|
||||
else if ((_myField != null))
|
||||
{
|
||||
this.EmitFieldLoad(_myField, ilg, services);
|
||||
}
|
||||
else if ((_myPropertyDescriptor != null))
|
||||
{
|
||||
this.EmitVirtualPropertyLoad(ilg);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.EmitPropertyLoad(_myProperty, ilg);
|
||||
}
|
||||
}
|
||||
|
||||
private void EmitReferenceLoad(FleeILGenerator ilg)
|
||||
{
|
||||
ilg.Emit(OpCodes.Ldarg_1);
|
||||
MyContext.CalculationEngine.EmitLoad(MyName, ilg);
|
||||
}
|
||||
|
||||
private void EmitFirst(FleeILGenerator ilg)
|
||||
{
|
||||
if ((MyPrevious != null))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
bool isVariable = (_myVariableType != null);
|
||||
|
||||
if (isVariable == true)
|
||||
{
|
||||
// Load variables
|
||||
EmitLoadVariables(ilg);
|
||||
}
|
||||
else if (MyOptions.IsOwnerType(this.MemberOwnerType) == true & this.IsStatic == false)
|
||||
{
|
||||
this.EmitLoadOwner(ilg);
|
||||
}
|
||||
}
|
||||
|
||||
private void EmitVariableLoad(FleeILGenerator ilg)
|
||||
{
|
||||
MethodInfo mi = VariableCollection.GetVariableLoadMethod(_myVariableType);
|
||||
ilg.Emit(OpCodes.Ldstr, MyName);
|
||||
this.EmitMethodCall(mi, ilg);
|
||||
}
|
||||
|
||||
private void EmitFieldLoad(System.Reflection.FieldInfo fi, FleeILGenerator ilg, IServiceProvider services)
|
||||
{
|
||||
if (fi.IsLiteral == true)
|
||||
{
|
||||
EmitLiteral(fi, ilg, services);
|
||||
}
|
||||
else if (this.ResultType.IsValueType == true & this.NextRequiresAddress == true)
|
||||
{
|
||||
EmitLdfld(fi, true, ilg);
|
||||
}
|
||||
else
|
||||
{
|
||||
EmitLdfld(fi, false, ilg);
|
||||
}
|
||||
}
|
||||
|
||||
private static void EmitLdfld(System.Reflection.FieldInfo fi, bool indirect, FleeILGenerator ilg)
|
||||
{
|
||||
if (fi.IsStatic == true)
|
||||
{
|
||||
if (indirect == true)
|
||||
{
|
||||
ilg.Emit(OpCodes.Ldsflda, fi);
|
||||
}
|
||||
else
|
||||
{
|
||||
ilg.Emit(OpCodes.Ldsfld, fi);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (indirect == true)
|
||||
{
|
||||
ilg.Emit(OpCodes.Ldflda, fi);
|
||||
}
|
||||
else
|
||||
{
|
||||
ilg.Emit(OpCodes.Ldfld, fi);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Emit the load of a constant field. We can't emit a ldsfld/ldfld of a constant so we have to get its value
|
||||
/// and then emit a ldc.
|
||||
/// </summary>
|
||||
/// <param name="fi"></param>
|
||||
/// <param name="ilg"></param>
|
||||
/// <param name="services"></param>
|
||||
private static void EmitLiteral(System.Reflection.FieldInfo fi, FleeILGenerator ilg, IServiceProvider services)
|
||||
{
|
||||
object value = fi.GetValue(null);
|
||||
Type t = value.GetType();
|
||||
TypeCode code = Type.GetTypeCode(t);
|
||||
LiteralElement elem = default(LiteralElement);
|
||||
|
||||
switch (code)
|
||||
{
|
||||
case TypeCode.Char:
|
||||
case TypeCode.Byte:
|
||||
case TypeCode.SByte:
|
||||
case TypeCode.Int16:
|
||||
case TypeCode.UInt16:
|
||||
case TypeCode.Int32:
|
||||
elem = new Int32LiteralElement(System.Convert.ToInt32(value));
|
||||
break;
|
||||
case TypeCode.UInt32:
|
||||
elem = new UInt32LiteralElement((UInt32)value);
|
||||
break;
|
||||
case TypeCode.Int64:
|
||||
elem = new Int64LiteralElement((Int64)value);
|
||||
break;
|
||||
case TypeCode.UInt64:
|
||||
elem = new UInt64LiteralElement((UInt64)value);
|
||||
break;
|
||||
case TypeCode.Double:
|
||||
elem = new DoubleLiteralElement((double)value);
|
||||
break;
|
||||
case TypeCode.Single:
|
||||
elem = new SingleLiteralElement((float)value);
|
||||
break;
|
||||
case TypeCode.Boolean:
|
||||
elem = new BooleanLiteralElement((bool)value);
|
||||
break;
|
||||
case TypeCode.String:
|
||||
elem = new StringLiteralElement((string)value);
|
||||
break;
|
||||
default:
|
||||
elem = null;
|
||||
Debug.Fail("Unsupported constant type");
|
||||
break;
|
||||
}
|
||||
|
||||
elem.Emit(ilg, services);
|
||||
}
|
||||
|
||||
private void EmitPropertyLoad(System.Reflection.PropertyInfo pi, FleeILGenerator ilg)
|
||||
{
|
||||
System.Reflection.MethodInfo getter = pi.GetGetMethod(true);
|
||||
base.EmitMethodCall(getter, ilg);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Load a PropertyDescriptor based property
|
||||
/// </summary>
|
||||
/// <param name="ilg"></param>
|
||||
private void EmitVirtualPropertyLoad(FleeILGenerator ilg)
|
||||
{
|
||||
// The previous value is already on the top of the stack but we need it at the bottom
|
||||
|
||||
// Get a temporary local index
|
||||
int index = ilg.GetTempLocalIndex(MyPrevious.ResultType);
|
||||
|
||||
// Store the previous value there
|
||||
Utility.EmitStoreLocal(ilg, index);
|
||||
|
||||
// Load the variable collection
|
||||
EmitLoadVariables(ilg);
|
||||
// Load the property name
|
||||
ilg.Emit(OpCodes.Ldstr, MyName);
|
||||
|
||||
// Load the previous value and convert it to object
|
||||
Utility.EmitLoadLocal(ilg, index);
|
||||
ImplicitConverter.EmitImplicitConvert(MyPrevious.ResultType, typeof(object), ilg);
|
||||
|
||||
// Call the method to get the actual value
|
||||
MethodInfo mi = VariableCollection.GetVirtualPropertyLoadMethod(this.ResultType);
|
||||
this.EmitMethodCall(mi, ilg);
|
||||
}
|
||||
|
||||
private Type MemberOwnerType
|
||||
{
|
||||
get
|
||||
{
|
||||
if ((_myField != null))
|
||||
{
|
||||
return _myField.ReflectedType;
|
||||
}
|
||||
else if ((_myPropertyDescriptor != null))
|
||||
{
|
||||
return _myPropertyDescriptor.ComponentType;
|
||||
}
|
||||
else if ((_myProperty != null))
|
||||
{
|
||||
return _myProperty.ReflectedType;
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override System.Type ResultType
|
||||
{
|
||||
get
|
||||
{
|
||||
if ((_myCalcEngineReferenceType != null))
|
||||
{
|
||||
return _myCalcEngineReferenceType;
|
||||
}
|
||||
else if ((_myVariableType != null))
|
||||
{
|
||||
return _myVariableType;
|
||||
}
|
||||
else if ((_myPropertyDescriptor != null))
|
||||
{
|
||||
return _myPropertyDescriptor.PropertyType;
|
||||
}
|
||||
else if ((_myField != null))
|
||||
{
|
||||
return _myField.FieldType;
|
||||
}
|
||||
else
|
||||
{
|
||||
MethodInfo mi = _myProperty.GetGetMethod(true);
|
||||
return mi.ReturnType;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool RequiresAddress => _myPropertyDescriptor == null;
|
||||
|
||||
protected override bool IsPublic
|
||||
{
|
||||
get
|
||||
{
|
||||
if ((_myVariableType != null) | (_myCalcEngineReferenceType != null))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else if ((_myVariableType != null))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else if ((_myPropertyDescriptor != null))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else if ((_myField != null))
|
||||
{
|
||||
return _myField.IsPublic;
|
||||
}
|
||||
else
|
||||
{
|
||||
MethodInfo mi = _myProperty.GetGetMethod(true);
|
||||
return mi.IsPublic;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool SupportsStatic
|
||||
{
|
||||
get
|
||||
{
|
||||
if ((_myVariableType != null))
|
||||
{
|
||||
// Variables never support static
|
||||
return false;
|
||||
}
|
||||
else if ((_myPropertyDescriptor != null))
|
||||
{
|
||||
// Neither do virtual properties
|
||||
return false;
|
||||
}
|
||||
else if (MyOptions.IsOwnerType(this.MemberOwnerType) == true && MyPrevious == null)
|
||||
{
|
||||
// Owner members support static if we are the first element
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Support static if we are the first (ie: we are a static import)
|
||||
return MyPrevious == null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool SupportsInstance
|
||||
{
|
||||
get
|
||||
{
|
||||
if ((_myVariableType != null))
|
||||
{
|
||||
// Variables always support instance
|
||||
return true;
|
||||
}
|
||||
else if ((_myPropertyDescriptor != null))
|
||||
{
|
||||
// So do virtual properties
|
||||
return true;
|
||||
}
|
||||
else if (MyOptions.IsOwnerType(this.MemberOwnerType) == true && MyPrevious == null)
|
||||
{
|
||||
// Owner members support instance if we are the first element
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// We always support instance if we are not the first element
|
||||
return (MyPrevious != null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override bool IsStatic
|
||||
{
|
||||
get
|
||||
{
|
||||
if ((_myVariableType != null) | (_myCalcEngineReferenceType != null))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else if ((_myVariableType != null))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else if ((_myField != null))
|
||||
{
|
||||
return _myField.IsStatic;
|
||||
}
|
||||
else if ((_myPropertyDescriptor != null))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
MethodInfo mi = _myProperty.GetGetMethod(true);
|
||||
return mi.IsStatic;
|
||||
}
|
||||
}
|
||||
}
|
||||
public override bool IsExtensionMethod => false;
|
||||
}
|
||||
}
|
||||
181
ExpressionElements/MemberElements/Indexer.cs
Normal file
181
ExpressionElements/MemberElements/Indexer.cs
Normal file
@@ -0,0 +1,181 @@
|
||||
using System.Reflection;
|
||||
using System.Reflection.Emit;
|
||||
using Flee.ExpressionElements.Base;
|
||||
using Flee.InternalTypes;
|
||||
using Flee.PublicTypes;
|
||||
using Flee.Resources;
|
||||
|
||||
|
||||
namespace Flee.ExpressionElements.MemberElements
|
||||
{
|
||||
[Obsolete("Element representing an array index")]
|
||||
internal class IndexerElement : MemberElement
|
||||
{
|
||||
private ExpressionElement _myIndexerElement;
|
||||
|
||||
private readonly ArgumentList _myIndexerElements;
|
||||
public IndexerElement(ArgumentList indexer)
|
||||
{
|
||||
_myIndexerElements = indexer;
|
||||
}
|
||||
|
||||
protected override void ResolveInternal()
|
||||
{
|
||||
// Are we are indexing on an array?
|
||||
Type target = MyPrevious.TargetType;
|
||||
|
||||
// Yes, so setup for an array index
|
||||
if (target.IsArray == true)
|
||||
{
|
||||
this.SetupArrayIndexer();
|
||||
return;
|
||||
}
|
||||
|
||||
// Not an array, so try to find an indexer on the type
|
||||
if (this.FindIndexer(target) == false)
|
||||
{
|
||||
base.ThrowCompileException(CompileErrorResourceKeys.TypeNotArrayAndHasNoIndexerOfType, CompileExceptionReason.TypeMismatch, target.Name, _myIndexerElements);
|
||||
}
|
||||
}
|
||||
|
||||
private void SetupArrayIndexer()
|
||||
{
|
||||
_myIndexerElement = _myIndexerElements[0];
|
||||
|
||||
if (_myIndexerElements.Count > 1)
|
||||
{
|
||||
base.ThrowCompileException(CompileErrorResourceKeys.MultiArrayIndexNotSupported, CompileExceptionReason.TypeMismatch);
|
||||
}
|
||||
else if (ImplicitConverter.EmitImplicitConvert(_myIndexerElement.ResultType, typeof(Int32), null) == false)
|
||||
{
|
||||
base.ThrowCompileException(CompileErrorResourceKeys.ArrayIndexersMustBeOfType, CompileExceptionReason.TypeMismatch, typeof(Int32).Name);
|
||||
}
|
||||
}
|
||||
|
||||
private bool FindIndexer(Type targetType)
|
||||
{
|
||||
// Get the default members
|
||||
MemberInfo[] members = targetType.GetDefaultMembers();
|
||||
|
||||
List<MethodInfo> methods = new List<MethodInfo>();
|
||||
|
||||
// Use the first one that's valid for our indexer type
|
||||
foreach (MemberInfo mi in members)
|
||||
{
|
||||
PropertyInfo pi = mi as PropertyInfo;
|
||||
if ((pi != null))
|
||||
{
|
||||
methods.Add(pi.GetGetMethod(true));
|
||||
}
|
||||
}
|
||||
|
||||
FunctionCallElement func = new FunctionCallElement("Indexer", methods.ToArray(), _myIndexerElements);
|
||||
func.Resolve(MyServices);
|
||||
_myIndexerElement = func;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public override void Emit(FleeILGenerator ilg, IServiceProvider services)
|
||||
{
|
||||
base.Emit(ilg, services);
|
||||
|
||||
if (this.IsArray == true)
|
||||
{
|
||||
this.EmitArrayLoad(ilg, services);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.EmitIndexer(ilg, services);
|
||||
}
|
||||
}
|
||||
|
||||
private void EmitArrayLoad(FleeILGenerator ilg, IServiceProvider services)
|
||||
{
|
||||
_myIndexerElement.Emit(ilg, services);
|
||||
ImplicitConverter.EmitImplicitConvert(_myIndexerElement.ResultType, typeof(Int32), ilg);
|
||||
|
||||
Type elementType = this.ResultType;
|
||||
|
||||
if (elementType.IsValueType == false)
|
||||
{
|
||||
// Simple reference load
|
||||
ilg.Emit(OpCodes.Ldelem_Ref);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.EmitValueTypeArrayLoad(ilg, elementType);
|
||||
}
|
||||
}
|
||||
|
||||
private void EmitValueTypeArrayLoad(FleeILGenerator ilg, Type elementType)
|
||||
{
|
||||
if (this.NextRequiresAddress == true)
|
||||
{
|
||||
ilg.Emit(OpCodes.Ldelema, elementType);
|
||||
}
|
||||
else
|
||||
{
|
||||
Utility.EmitArrayLoad(ilg, elementType);
|
||||
}
|
||||
}
|
||||
|
||||
private void EmitIndexer(FleeILGenerator ilg, IServiceProvider services)
|
||||
{
|
||||
FunctionCallElement func = (FunctionCallElement)_myIndexerElement;
|
||||
func.EmitFunctionCall(this.NextRequiresAddress, ilg, services);
|
||||
}
|
||||
|
||||
private Type ArrayType
|
||||
{
|
||||
get
|
||||
{
|
||||
if (this.IsArray == true)
|
||||
{
|
||||
return MyPrevious.TargetType;
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsArray => MyPrevious.TargetType.IsArray;
|
||||
|
||||
protected override bool RequiresAddress => this.IsArray == false;
|
||||
|
||||
public override System.Type ResultType
|
||||
{
|
||||
get
|
||||
{
|
||||
if (this.IsArray == true)
|
||||
{
|
||||
return this.ArrayType.GetElementType();
|
||||
}
|
||||
else
|
||||
{
|
||||
return _myIndexerElement.ResultType;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool IsPublic
|
||||
{
|
||||
get
|
||||
{
|
||||
if (this.IsArray == true)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return IsElementPublic((MemberElement)_myIndexerElement);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override bool IsStatic => false;
|
||||
public override bool IsExtensionMethod => false;
|
||||
}
|
||||
}
|
||||
120
ExpressionElements/MemberElements/InvocationList.cs
Normal file
120
ExpressionElements/MemberElements/InvocationList.cs
Normal file
@@ -0,0 +1,120 @@
|
||||
using System.Collections;
|
||||
using Flee.ExpressionElements.Base;
|
||||
using Flee.InternalTypes;
|
||||
using Flee.PublicTypes;
|
||||
using Flee.Resources;
|
||||
|
||||
|
||||
namespace Flee.ExpressionElements.MemberElements
|
||||
{
|
||||
internal class InvocationListElement : ExpressionElement
|
||||
{
|
||||
private readonly MemberElement _myTail;
|
||||
public InvocationListElement(IList elements, IServiceProvider services)
|
||||
{
|
||||
this.HandleFirstElement(elements, services);
|
||||
LinkElements(elements);
|
||||
Resolve(elements, services);
|
||||
_myTail = (MemberElement)elements[elements.Count - 1];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Arrange elements as a linked list
|
||||
/// </summary>
|
||||
/// <param name="elements"></param>
|
||||
private static void LinkElements(IList elements)
|
||||
{
|
||||
for (int i = 0; i <= elements.Count - 1; i++)
|
||||
{
|
||||
MemberElement current = (MemberElement)elements[i];
|
||||
MemberElement nextElement = null;
|
||||
if (i + 1 < elements.Count)
|
||||
{
|
||||
nextElement = (MemberElement)elements[i + 1];
|
||||
}
|
||||
current.Link(nextElement);
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleFirstElement(IList elements, IServiceProvider services)
|
||||
{
|
||||
ExpressionElement first = (ExpressionElement)elements[0];
|
||||
|
||||
// If the first element is not a member element, then we assume it is an expression and replace it with the correct member element
|
||||
if (!(first is MemberElement))
|
||||
{
|
||||
ExpressionMemberElement actualFirst = new ExpressionMemberElement(first);
|
||||
elements[0] = actualFirst;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.ResolveNamespaces(elements, services);
|
||||
}
|
||||
}
|
||||
|
||||
private void ResolveNamespaces(IList elements, IServiceProvider services)
|
||||
{
|
||||
ExpressionContext context = (ExpressionContext)services.GetService(typeof(ExpressionContext));
|
||||
ImportBase currentImport = context.Imports.RootImport;
|
||||
|
||||
while (true)
|
||||
{
|
||||
string name = GetName(elements);
|
||||
|
||||
if (name == null)
|
||||
{
|
||||
break; // TODO: might not be correct. Was : Exit While
|
||||
}
|
||||
|
||||
ImportBase import = currentImport.FindImport(name);
|
||||
|
||||
if (import == null)
|
||||
{
|
||||
break; // TODO: might not be correct. Was : Exit While
|
||||
}
|
||||
|
||||
currentImport = import;
|
||||
elements.RemoveAt(0);
|
||||
|
||||
if (elements.Count > 0)
|
||||
{
|
||||
MemberElement newFirst = (MemberElement)elements[0];
|
||||
newFirst.SetImport(currentImport);
|
||||
}
|
||||
}
|
||||
|
||||
if (elements.Count == 0)
|
||||
{
|
||||
base.ThrowCompileException(CompileErrorResourceKeys.NamespaceCannotBeUsedAsType, CompileExceptionReason.TypeMismatch, currentImport.Name);
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetName(IList elements)
|
||||
{
|
||||
if (elements.Count == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// Is the first member a field/property element?
|
||||
var fpe = elements[0] as IdentifierElement;
|
||||
|
||||
return fpe?.MemberName;
|
||||
}
|
||||
|
||||
private static void Resolve(IList elements, IServiceProvider services)
|
||||
{
|
||||
foreach (MemberElement element in elements)
|
||||
{
|
||||
element.Resolve(services);
|
||||
}
|
||||
}
|
||||
|
||||
public override void Emit(FleeILGenerator ilg, IServiceProvider services)
|
||||
{
|
||||
_myTail.Emit(ilg, services);
|
||||
}
|
||||
|
||||
public override System.Type ResultType => _myTail.ResultType;
|
||||
}
|
||||
}
|
||||
38
ExpressionElements/MemberElements/Miscellaneous.cs
Normal file
38
ExpressionElements/MemberElements/Miscellaneous.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
using Flee.ExpressionElements.Base;
|
||||
using Flee.InternalTypes;
|
||||
|
||||
|
||||
namespace Flee.ExpressionElements.MemberElements
|
||||
{
|
||||
internal class ExpressionMemberElement : MemberElement
|
||||
{
|
||||
private readonly ExpressionElement _myElement;
|
||||
public ExpressionMemberElement(ExpressionElement element)
|
||||
{
|
||||
_myElement = element;
|
||||
}
|
||||
|
||||
protected override void ResolveInternal()
|
||||
{
|
||||
}
|
||||
|
||||
public override void Emit(FleeILGenerator ilg, IServiceProvider services)
|
||||
{
|
||||
base.Emit(ilg, services);
|
||||
_myElement.Emit(ilg, services);
|
||||
if (_myElement.ResultType.IsValueType == true)
|
||||
{
|
||||
EmitValueTypeLoadAddress(ilg, this.ResultType);
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool SupportsInstance => true;
|
||||
|
||||
protected override bool IsPublic => true;
|
||||
|
||||
public override bool IsStatic => false;
|
||||
public override bool IsExtensionMethod => false;
|
||||
|
||||
public override System.Type ResultType => _myElement.ResultType;
|
||||
}
|
||||
}
|
||||
56
ExpressionElements/Negate.cs
Normal file
56
ExpressionElements/Negate.cs
Normal file
@@ -0,0 +1,56 @@
|
||||
using System.Reflection.Emit;
|
||||
using System.Reflection;
|
||||
using Flee.ExpressionElements.Base;
|
||||
using Flee.InternalTypes;
|
||||
|
||||
namespace Flee.ExpressionElements
|
||||
{
|
||||
internal class NegateElement : UnaryElement
|
||||
{
|
||||
public NegateElement()
|
||||
{
|
||||
}
|
||||
|
||||
protected override System.Type GetResultType(System.Type childType)
|
||||
{
|
||||
TypeCode tc = Type.GetTypeCode(childType);
|
||||
|
||||
MethodInfo mi = Utility.GetSimpleOverloadedOperator("UnaryNegation", childType, null);
|
||||
if ((mi != null))
|
||||
{
|
||||
return mi.ReturnType;
|
||||
}
|
||||
|
||||
switch (tc)
|
||||
{
|
||||
case TypeCode.Single:
|
||||
case TypeCode.Double:
|
||||
case TypeCode.Int32:
|
||||
case TypeCode.Int64:
|
||||
return childType;
|
||||
case TypeCode.UInt32:
|
||||
return typeof(Int64);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public override void Emit(FleeILGenerator ilg, IServiceProvider services)
|
||||
{
|
||||
Type resultType = this.ResultType;
|
||||
MyChild.Emit(ilg, services);
|
||||
ImplicitConverter.EmitImplicitConvert(MyChild.ResultType, resultType, ilg);
|
||||
|
||||
MethodInfo mi = Utility.GetSimpleOverloadedOperator("UnaryNegation", resultType, null);
|
||||
|
||||
if (mi == null)
|
||||
{
|
||||
ilg.Emit(OpCodes.Neg);
|
||||
}
|
||||
else
|
||||
{
|
||||
ilg.Emit(OpCodes.Call, mi);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
45
ExpressionElements/Root.cs
Normal file
45
ExpressionElements/Root.cs
Normal file
@@ -0,0 +1,45 @@
|
||||
using System.Reflection.Emit;
|
||||
using Flee.ExpressionElements.Base;
|
||||
using Flee.InternalTypes;
|
||||
using Flee.PublicTypes;
|
||||
using Flee.Resources;
|
||||
|
||||
namespace Flee.ExpressionElements
|
||||
{
|
||||
internal class RootExpressionElement : ExpressionElement
|
||||
{
|
||||
private readonly ExpressionElement _myChild;
|
||||
private readonly Type _myResultType;
|
||||
public RootExpressionElement(ExpressionElement child, Type resultType)
|
||||
{
|
||||
_myChild = child;
|
||||
_myResultType = resultType;
|
||||
this.Validate();
|
||||
}
|
||||
|
||||
public override void Emit(FleeILGenerator ilg, IServiceProvider services)
|
||||
{
|
||||
_myChild.Emit(ilg, services);
|
||||
ImplicitConverter.EmitImplicitConvert(_myChild.ResultType, _myResultType, ilg);
|
||||
|
||||
ExpressionOptions options = (ExpressionOptions)services.GetService(typeof(ExpressionOptions));
|
||||
|
||||
if (options.IsGeneric == false)
|
||||
{
|
||||
ImplicitConverter.EmitImplicitConvert(_myResultType, typeof(object), ilg);
|
||||
}
|
||||
|
||||
ilg.Emit(OpCodes.Ret);
|
||||
}
|
||||
|
||||
private void Validate()
|
||||
{
|
||||
if (ImplicitConverter.EmitImplicitConvert(_myChild.ResultType, _myResultType, null) == false)
|
||||
{
|
||||
base.ThrowCompileException(CompileErrorResourceKeys.CannotConvertTypeToExpressionResult, CompileExceptionReason.TypeMismatch, _myChild.ResultType.Name, _myResultType.Name);
|
||||
}
|
||||
}
|
||||
|
||||
public override System.Type ResultType => typeof(object);
|
||||
}
|
||||
}
|
||||
136
ExpressionElements/Shift.cs
Normal file
136
ExpressionElements/Shift.cs
Normal file
@@ -0,0 +1,136 @@
|
||||
using System.Diagnostics;
|
||||
using System.Reflection.Emit;
|
||||
using Flee.ExpressionElements.Base;
|
||||
using Flee.InternalTypes;
|
||||
|
||||
|
||||
namespace Flee.ExpressionElements
|
||||
{
|
||||
internal class ShiftElement : BinaryExpressionElement
|
||||
{
|
||||
private ShiftOperation _myOperation;
|
||||
|
||||
public ShiftElement()
|
||||
{
|
||||
}
|
||||
|
||||
protected override System.Type GetResultType(System.Type leftType, System.Type rightType)
|
||||
{
|
||||
// Right argument (shift count) must be convertible to int32
|
||||
if (ImplicitConverter.EmitImplicitNumericConvert(rightType, typeof(Int32), null) == false)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// Left argument must be an integer type
|
||||
if (Utility.IsIntegralType(leftType) == false)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
TypeCode tc = Type.GetTypeCode(leftType);
|
||||
|
||||
switch (tc)
|
||||
{
|
||||
case TypeCode.Byte:
|
||||
case TypeCode.SByte:
|
||||
case TypeCode.Int16:
|
||||
case TypeCode.UInt16:
|
||||
case TypeCode.Int32:
|
||||
return typeof(Int32);
|
||||
case TypeCode.UInt32:
|
||||
return typeof(UInt32);
|
||||
case TypeCode.Int64:
|
||||
return typeof(Int64);
|
||||
case TypeCode.UInt64:
|
||||
return typeof(UInt64);
|
||||
default:
|
||||
Debug.Assert(false, "unknown left shift operand");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void GetOperation(object operation)
|
||||
{
|
||||
_myOperation = (ShiftOperation)operation;
|
||||
}
|
||||
|
||||
public override void Emit(FleeILGenerator ilg, IServiceProvider services)
|
||||
{
|
||||
MyLeftChild.Emit(ilg, services);
|
||||
this.EmitShiftCount(ilg, services);
|
||||
this.EmitShift(ilg);
|
||||
}
|
||||
|
||||
// If the shift count is greater than the number of bits in the number, the result is undefined.
|
||||
// So we play it safe and force the shift count to 32/64 bits by ANDing it with the appropriate mask.
|
||||
private void EmitShiftCount(FleeILGenerator ilg, IServiceProvider services)
|
||||
{
|
||||
MyRightChild.Emit(ilg, services);
|
||||
TypeCode tc = Type.GetTypeCode(MyLeftChild.ResultType);
|
||||
switch (tc)
|
||||
{
|
||||
case TypeCode.Byte:
|
||||
case TypeCode.SByte:
|
||||
case TypeCode.Int16:
|
||||
case TypeCode.UInt16:
|
||||
case TypeCode.Int32:
|
||||
case TypeCode.UInt32:
|
||||
ilg.Emit(OpCodes.Ldc_I4_S, Convert.ToSByte(0x1f));
|
||||
break;
|
||||
case TypeCode.Int64:
|
||||
case TypeCode.UInt64:
|
||||
ilg.Emit(OpCodes.Ldc_I4_S, Convert.ToSByte(0x3f));
|
||||
break;
|
||||
default:
|
||||
Debug.Assert(false, "unknown left shift operand");
|
||||
break;
|
||||
}
|
||||
|
||||
ilg.Emit(OpCodes.And);
|
||||
}
|
||||
|
||||
private void EmitShift(FleeILGenerator ilg)
|
||||
{
|
||||
TypeCode tc = Type.GetTypeCode(MyLeftChild.ResultType);
|
||||
OpCode op = default(OpCode);
|
||||
|
||||
switch (tc)
|
||||
{
|
||||
case TypeCode.Byte:
|
||||
case TypeCode.SByte:
|
||||
case TypeCode.Int16:
|
||||
case TypeCode.UInt16:
|
||||
case TypeCode.Int32:
|
||||
case TypeCode.Int64:
|
||||
// Signed operand, emit a left shift or arithmetic right shift
|
||||
if (_myOperation == ShiftOperation.LeftShift)
|
||||
{
|
||||
op = OpCodes.Shl;
|
||||
}
|
||||
else
|
||||
{
|
||||
op = OpCodes.Shr;
|
||||
}
|
||||
break;
|
||||
case TypeCode.UInt32:
|
||||
case TypeCode.UInt64:
|
||||
// Unsigned operand, emit left shift or logical right shift
|
||||
if (_myOperation == ShiftOperation.LeftShift)
|
||||
{
|
||||
op = OpCodes.Shl;
|
||||
}
|
||||
else
|
||||
{
|
||||
op = OpCodes.Shr_Un;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
Debug.Assert(false, "unknown left shift operand");
|
||||
break;
|
||||
}
|
||||
|
||||
ilg.Emit(op);
|
||||
}
|
||||
}
|
||||
}
|
||||
285
InternalTypes/BranchManager.cs
Normal file
285
InternalTypes/BranchManager.cs
Normal file
@@ -0,0 +1,285 @@
|
||||
using System.Reflection.Emit;
|
||||
|
||||
namespace Flee.InternalTypes
|
||||
{
|
||||
[Obsolete("Manages branch information and allows us to determine if we should emit a short or long branch")]
|
||||
internal class BranchManager
|
||||
{
|
||||
private readonly IList<BranchInfo> MyBranchInfos;
|
||||
|
||||
public BranchManager()
|
||||
{
|
||||
MyBranchInfos = new List<BranchInfo>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// check if any long branches exist
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public bool HasLongBranches()
|
||||
{
|
||||
foreach (BranchInfo bi in MyBranchInfos)
|
||||
{
|
||||
if (bi.ComputeIsLongBranch()) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determine whether to use short or long branches.
|
||||
/// This advances the ilg offset with No-op to adjust
|
||||
/// for the long branches needed.
|
||||
/// </summary>
|
||||
/// <remarks></remarks>
|
||||
public bool ComputeBranches()
|
||||
{
|
||||
//
|
||||
// we need to iterate in reverse order of the
|
||||
// starting location, as branch between our
|
||||
// branch could push our branch to a long branch.
|
||||
//
|
||||
for( var idx=MyBranchInfos.Count-1; idx >= 0; idx--)
|
||||
{
|
||||
var bi = MyBranchInfos[idx];
|
||||
|
||||
// count long branches between
|
||||
int longBranchesBetween = 0;
|
||||
for( var ii=idx+1; ii < MyBranchInfos.Count; ii++)
|
||||
{
|
||||
var bi2 = MyBranchInfos[ii];
|
||||
if (bi2.IsBetween(bi) && bi2.ComputeIsLongBranch())
|
||||
++longBranchesBetween;
|
||||
}
|
||||
|
||||
// Adjust the branch as necessary
|
||||
bi.AdjustForLongBranchesBetween(longBranchesBetween);
|
||||
}
|
||||
|
||||
int longBranchCount = 0;
|
||||
|
||||
// Adjust the start location of each branch
|
||||
foreach (BranchInfo bi in MyBranchInfos)
|
||||
{
|
||||
// Save the short/long branch type
|
||||
bi.BakeIsLongBranch();
|
||||
|
||||
// Adjust the start location as necessary
|
||||
bi.AdjustForLongBranches(longBranchCount);
|
||||
|
||||
// Keep a tally of the number of long branches
|
||||
longBranchCount += Convert.ToInt32(bi.IsLongBranch);
|
||||
}
|
||||
|
||||
return (longBranchCount > 0);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Determine if a branch from a point to a label will be long
|
||||
/// </summary>
|
||||
/// <param name="ilg"></param>
|
||||
/// <returns></returns>
|
||||
/// <remarks></remarks>
|
||||
public bool IsLongBranch(FleeILGenerator ilg)
|
||||
{
|
||||
ILLocation startLoc = new ILLocation(ilg.Length);
|
||||
|
||||
foreach (var bi in MyBranchInfos)
|
||||
{
|
||||
if (bi.Equals(startLoc))
|
||||
return bi.IsLongBranch;
|
||||
}
|
||||
|
||||
// we don't really know since this branch didn't exist.
|
||||
// we could throw an exceptio but
|
||||
// do a long branch to be safe.
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a branch from a location to a target label
|
||||
/// </summary>
|
||||
/// <param name="ilg"></param>
|
||||
/// <param name="target"></param>
|
||||
/// <remarks></remarks>
|
||||
public void AddBranch(FleeILGenerator ilg, Label target)
|
||||
{
|
||||
ILLocation startLoc = new ILLocation(ilg.Length);
|
||||
|
||||
BranchInfo bi = new BranchInfo(startLoc, target);
|
||||
// branches will be sorted in order
|
||||
MyBranchInfos.Add(bi);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Set the position for a label
|
||||
/// </summary>
|
||||
/// <param name="ilg"></param>
|
||||
/// <param name="target"></param>
|
||||
/// <remarks></remarks>
|
||||
public void MarkLabel(FleeILGenerator ilg, Label target)
|
||||
{
|
||||
int pos = ilg.Length;
|
||||
|
||||
foreach (BranchInfo bi in MyBranchInfos)
|
||||
{
|
||||
bi.Mark(target, pos);
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
string[] arr = new string[MyBranchInfos.Count];
|
||||
|
||||
for (int i = 0; i <= MyBranchInfos.Count - 1; i++)
|
||||
{
|
||||
arr[i] = MyBranchInfos[i].ToString();
|
||||
}
|
||||
|
||||
return string.Join(System.Environment.NewLine, arr);
|
||||
}
|
||||
}
|
||||
|
||||
[Obsolete("Represents a location in an IL stream")]
|
||||
internal class ILLocation : IEquatable<ILLocation>, IComparable<ILLocation>
|
||||
{
|
||||
private int _myPosition;
|
||||
|
||||
/// <summary>
|
||||
/// ' Long branch is 5 bytes; short branch is 2; so we adjust by the difference
|
||||
/// </summary>
|
||||
private const int LongBranchAdjust = 3;
|
||||
|
||||
/// <summary>
|
||||
/// Length of the Br_s opcode
|
||||
/// </summary>
|
||||
private const int BrSLength = 2;
|
||||
|
||||
public ILLocation()
|
||||
{
|
||||
}
|
||||
|
||||
public ILLocation(int position)
|
||||
{
|
||||
_myPosition = position;
|
||||
}
|
||||
|
||||
public void SetPosition(int position)
|
||||
{
|
||||
_myPosition = position;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adjust our position by a certain amount of long branches
|
||||
/// </summary>
|
||||
/// <param name="longBranchCount"></param>
|
||||
/// <remarks></remarks>
|
||||
public void AdjustForLongBranch(int longBranchCount)
|
||||
{
|
||||
_myPosition += longBranchCount * LongBranchAdjust;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determine if this branch is long
|
||||
/// </summary>
|
||||
/// <param name="target"></param>
|
||||
/// <returns></returns>
|
||||
/// <remarks></remarks>
|
||||
public bool IsLongBranch(ILLocation target)
|
||||
{
|
||||
// The branch offset is relative to the instruction *after* the branch so we add 2 (length of a br_s) to our position
|
||||
return Utility.IsLongBranch(_myPosition + BrSLength, target._myPosition);
|
||||
}
|
||||
|
||||
public bool Equals1(ILLocation other)
|
||||
{
|
||||
return _myPosition == other._myPosition;
|
||||
}
|
||||
bool System.IEquatable<ILLocation>.Equals(ILLocation other)
|
||||
{
|
||||
return Equals1(other);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return _myPosition.ToString("x");
|
||||
}
|
||||
|
||||
public int CompareTo(ILLocation other)
|
||||
{
|
||||
return _myPosition.CompareTo(other._myPosition);
|
||||
}
|
||||
}
|
||||
|
||||
[Obsolete("Represents a branch from a start location to an end location")]
|
||||
internal class BranchInfo
|
||||
{
|
||||
private readonly ILLocation _myStart;
|
||||
private readonly ILLocation _myEnd;
|
||||
private Label _myLabel;
|
||||
private bool _myIsLongBranch;
|
||||
|
||||
public BranchInfo(ILLocation startLocation, Label endLabel)
|
||||
{
|
||||
_myStart = startLocation;
|
||||
_myLabel = endLabel;
|
||||
_myEnd = new ILLocation();
|
||||
}
|
||||
|
||||
public void AdjustForLongBranches(int longBranchCount)
|
||||
{
|
||||
_myStart.AdjustForLongBranch(longBranchCount);
|
||||
// end not necessarily needed once we determine
|
||||
// if this is long, but keep it accurate anyway.
|
||||
_myEnd.AdjustForLongBranch(longBranchCount);
|
||||
}
|
||||
|
||||
public void BakeIsLongBranch()
|
||||
{
|
||||
_myIsLongBranch = this.ComputeIsLongBranch();
|
||||
}
|
||||
|
||||
public void AdjustForLongBranchesBetween(int betweenLongBranchCount)
|
||||
{
|
||||
_myEnd.AdjustForLongBranch(betweenLongBranchCount);
|
||||
}
|
||||
|
||||
public bool IsBetween(BranchInfo other)
|
||||
{
|
||||
return _myStart.CompareTo(other._myStart) > 0 && _myStart.CompareTo(other._myEnd) < 0;
|
||||
}
|
||||
|
||||
public bool ComputeIsLongBranch()
|
||||
{
|
||||
return _myStart.IsLongBranch(_myEnd);
|
||||
}
|
||||
|
||||
public void Mark(Label target, int position)
|
||||
{
|
||||
if (_myLabel.Equals(target) == true)
|
||||
{
|
||||
_myEnd.SetPosition(position);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// We only need to compare the start point. Can only have a single
|
||||
/// brach from the exact address, so if label doesn't match we have
|
||||
/// bigger problems.
|
||||
/// </summary>
|
||||
/// <param name="other"></param>
|
||||
/// <returns></returns>
|
||||
public bool Equals(ILLocation start)
|
||||
{
|
||||
return _myStart.Equals1(start);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{_myStart} -> {_myEnd} (L={_myStart.IsLongBranch(_myEnd)})";
|
||||
}
|
||||
|
||||
public bool IsLongBranch => _myIsLongBranch;
|
||||
}
|
||||
}
|
||||
215
InternalTypes/Expression.cs
Normal file
215
InternalTypes/Expression.cs
Normal file
@@ -0,0 +1,215 @@
|
||||
using System.ComponentModel.Design;
|
||||
using System.Reflection.Emit;
|
||||
using System.Reflection;
|
||||
using Flee.ExpressionElements;
|
||||
using Flee.ExpressionElements.Base;
|
||||
using Flee.PublicTypes;
|
||||
using Flee.Resources;
|
||||
using IDynamicExpression = Flee.PublicTypes.IDynamicExpression;
|
||||
|
||||
namespace Flee.InternalTypes
|
||||
{
|
||||
internal class Expression<T> : IExpression, IDynamicExpression, IGenericExpression<T>
|
||||
{
|
||||
private readonly string _myExpression;
|
||||
private ExpressionContext _myContext;
|
||||
private ExpressionOptions _myOptions;
|
||||
private readonly ExpressionInfo _myInfo;
|
||||
private ExpressionEvaluator<T> _myEvaluator;
|
||||
|
||||
private object _myOwner;
|
||||
private const string EmitAssemblyName = "FleeExpression";
|
||||
|
||||
private const string DynamicMethodName = "Flee Expression";
|
||||
public Expression(string expression, ExpressionContext context, bool isGeneric)
|
||||
{
|
||||
Utility.AssertNotNull(expression, "expression");
|
||||
_myExpression = expression;
|
||||
_myOwner = context.ExpressionOwner;
|
||||
|
||||
_myContext = context;
|
||||
|
||||
if (context.NoClone == false)
|
||||
{
|
||||
_myContext = context.CloneInternal(false);
|
||||
}
|
||||
|
||||
_myInfo = new ExpressionInfo();
|
||||
|
||||
this.SetupOptions(_myContext.Options, isGeneric);
|
||||
|
||||
_myContext.Imports.ImportOwner(_myOptions.OwnerType);
|
||||
|
||||
this.ValidateOwner(_myOwner);
|
||||
|
||||
this.Compile(expression, _myOptions);
|
||||
|
||||
_myContext.CalculationEngine?.FixTemporaryHead(this, _myContext, _myOptions.ResultType);
|
||||
}
|
||||
|
||||
private void SetupOptions(ExpressionOptions options, bool isGeneric)
|
||||
{
|
||||
// Make sure we clone the options
|
||||
_myOptions = options;
|
||||
_myOptions.IsGeneric = isGeneric;
|
||||
|
||||
if (isGeneric)
|
||||
{
|
||||
_myOptions.ResultType = typeof(T);
|
||||
}
|
||||
|
||||
_myOptions.SetOwnerType(_myOwner.GetType());
|
||||
}
|
||||
|
||||
private void Compile(string expression, ExpressionOptions options)
|
||||
{
|
||||
// Add the services that will be used by elements during the compile
|
||||
IServiceContainer services = new ServiceContainer();
|
||||
this.AddServices(services);
|
||||
|
||||
// Parse and get the root element of the parse tree
|
||||
ExpressionElement topElement = _myContext.Parse(expression, services);
|
||||
|
||||
if (options.ResultType == null)
|
||||
{
|
||||
options.ResultType = topElement.ResultType;
|
||||
}
|
||||
|
||||
RootExpressionElement rootElement = new RootExpressionElement(topElement, options.ResultType);
|
||||
|
||||
DynamicMethod dm = this.CreateDynamicMethod();
|
||||
|
||||
FleeILGenerator ilg = new FleeILGenerator(dm.GetILGenerator());
|
||||
|
||||
// Emit the IL
|
||||
rootElement.Emit(ilg, services);
|
||||
if (ilg.NeedsSecondPass())
|
||||
{
|
||||
// second pass required due to long branches.
|
||||
dm = this.CreateDynamicMethod();
|
||||
ilg.PrepareSecondPass(dm.GetILGenerator());
|
||||
rootElement.Emit(ilg, services);
|
||||
}
|
||||
|
||||
ilg.ValidateLength();
|
||||
|
||||
// Emit to an assembly if required
|
||||
if (options.EmitToAssembly == true)
|
||||
{
|
||||
EmitToAssembly(ilg, rootElement, services);
|
||||
}
|
||||
|
||||
Type delegateType = typeof(ExpressionEvaluator<>).MakeGenericType(typeof(T));
|
||||
_myEvaluator = (ExpressionEvaluator<T>)dm.CreateDelegate(delegateType);
|
||||
}
|
||||
|
||||
private DynamicMethod CreateDynamicMethod()
|
||||
{
|
||||
// Create the dynamic method
|
||||
Type[] parameterTypes = {
|
||||
typeof(object),
|
||||
typeof(ExpressionContext),
|
||||
typeof(VariableCollection)
|
||||
};
|
||||
DynamicMethod dm = default(DynamicMethod);
|
||||
|
||||
dm = new DynamicMethod(DynamicMethodName, typeof(T), parameterTypes, _myOptions.OwnerType);
|
||||
|
||||
return dm;
|
||||
}
|
||||
|
||||
private void AddServices(IServiceContainer dest)
|
||||
{
|
||||
dest.AddService(typeof(ExpressionOptions), _myOptions);
|
||||
dest.AddService(typeof(ExpressionParserOptions), _myContext.ParserOptions);
|
||||
dest.AddService(typeof(ExpressionContext), _myContext);
|
||||
dest.AddService(typeof(IExpression), this);
|
||||
dest.AddService(typeof(ExpressionInfo), _myInfo);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Emit to an assembly. We've already computed long branches at this point,
|
||||
/// so we emit as a second pass
|
||||
/// </summary>
|
||||
/// <param name="ilg"></param>
|
||||
/// <param name="rootElement"></param>
|
||||
/// <param name="services"></param>
|
||||
private static void EmitToAssembly(FleeILGenerator ilg, ExpressionElement rootElement, IServiceContainer services)
|
||||
{
|
||||
AssemblyName assemblyName = new AssemblyName(EmitAssemblyName);
|
||||
|
||||
string assemblyFileName = string.Format("{0}.dll", EmitAssemblyName);
|
||||
|
||||
AssemblyBuilder assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);
|
||||
ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule(assemblyFileName);
|
||||
|
||||
MethodBuilder mb = moduleBuilder.DefineGlobalMethod("Evaluate", MethodAttributes.Public | MethodAttributes.Static, typeof(T), new Type[] {
|
||||
typeof(object),typeof(ExpressionContext),typeof(VariableCollection)});
|
||||
// already emitted once for local use,
|
||||
ilg.PrepareSecondPass(mb.GetILGenerator());
|
||||
|
||||
rootElement.Emit(ilg, services);
|
||||
|
||||
moduleBuilder.CreateGlobalFunctions();
|
||||
//assemblyBuilder.Save(assemblyFileName);
|
||||
assemblyBuilder.CreateInstance(assemblyFileName);
|
||||
}
|
||||
|
||||
private void ValidateOwner(object owner)
|
||||
{
|
||||
Utility.AssertNotNull(owner, "owner");
|
||||
if (_myOptions.OwnerType.IsAssignableFrom(owner.GetType()) == false)
|
||||
{
|
||||
string msg = Utility.GetGeneralErrorMessage(GeneralErrorResourceKeys.NewOwnerTypeNotAssignableToCurrentOwner);
|
||||
throw new ArgumentException(msg);
|
||||
}
|
||||
}
|
||||
|
||||
public object Evaluate()
|
||||
{
|
||||
return _myEvaluator(_myOwner, _myContext, _myContext.Variables);
|
||||
}
|
||||
|
||||
public T EvaluateGeneric()
|
||||
{
|
||||
return _myEvaluator(_myOwner, _myContext, _myContext.Variables);
|
||||
}
|
||||
T IGenericExpression<T>.Evaluate()
|
||||
{
|
||||
return EvaluateGeneric();
|
||||
}
|
||||
|
||||
public IExpression Clone()
|
||||
{
|
||||
Expression<T> copy = (Expression<T>)this.MemberwiseClone();
|
||||
copy._myContext = _myContext.CloneInternal(true);
|
||||
copy._myOptions = copy._myContext.Options;
|
||||
return copy;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return _myExpression;
|
||||
}
|
||||
|
||||
internal Type ResultType => _myOptions.ResultType;
|
||||
|
||||
public string Text => _myExpression;
|
||||
|
||||
public ExpressionInfo Info1 => _myInfo;
|
||||
|
||||
ExpressionInfo IExpression.Info => Info1;
|
||||
|
||||
public object Owner
|
||||
{
|
||||
get { return _myOwner; }
|
||||
set
|
||||
{
|
||||
this.ValidateOwner(value);
|
||||
_myOwner = value;
|
||||
}
|
||||
}
|
||||
|
||||
public ExpressionContext Context => _myContext;
|
||||
}
|
||||
}
|
||||
269
InternalTypes/FleeILGenerator.cs
Normal file
269
InternalTypes/FleeILGenerator.cs
Normal file
@@ -0,0 +1,269 @@
|
||||
using System.Diagnostics;
|
||||
using System.Reflection;
|
||||
using System.Reflection.Emit;
|
||||
|
||||
namespace Flee.InternalTypes
|
||||
{
|
||||
internal class FleeILGenerator
|
||||
{
|
||||
private ILGenerator _myIlGenerator;
|
||||
private int _myLength;
|
||||
private int _myLabelCount;
|
||||
private readonly Dictionary<Type, LocalBuilder> _localBuilderTemp;
|
||||
private int _myPass;
|
||||
private int _brContext;
|
||||
private BranchManager _bm;
|
||||
|
||||
public FleeILGenerator(ILGenerator ilg)
|
||||
{
|
||||
_myIlGenerator = ilg;
|
||||
_localBuilderTemp = new Dictionary<Type, LocalBuilder>();
|
||||
_myLength = 0;
|
||||
_myPass = 1;
|
||||
_bm = new BranchManager();
|
||||
}
|
||||
|
||||
public int GetTempLocalIndex(Type localType)
|
||||
{
|
||||
LocalBuilder local = null;
|
||||
|
||||
if (_localBuilderTemp.TryGetValue(localType, out local) == false)
|
||||
{
|
||||
local = _myIlGenerator.DeclareLocal(localType);
|
||||
_localBuilderTemp.Add(localType, local);
|
||||
}
|
||||
|
||||
return local.LocalIndex;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// after first pass, check for long branches.
|
||||
/// If any, we need to generate again.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public bool NeedsSecondPass()
|
||||
{
|
||||
return _bm.HasLongBranches();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// need a new ILGenerator for 2nd pass. This can also
|
||||
/// get called for a 3rd pass when emitting to assembly.
|
||||
/// </summary>
|
||||
/// <param name="ilg"></param>
|
||||
public void PrepareSecondPass(ILGenerator ilg)
|
||||
{
|
||||
_bm.ComputeBranches();
|
||||
_localBuilderTemp.Clear();
|
||||
_myIlGenerator = ilg;
|
||||
_myLength = 0;
|
||||
_myPass++;
|
||||
}
|
||||
|
||||
public void Emit(OpCode op)
|
||||
{
|
||||
this.RecordOpcode(op);
|
||||
_myIlGenerator.Emit(op);
|
||||
}
|
||||
|
||||
public void Emit(OpCode op, Type arg)
|
||||
{
|
||||
this.RecordOpcode(op);
|
||||
_myIlGenerator.Emit(op, arg);
|
||||
}
|
||||
|
||||
public void Emit(OpCode op, ConstructorInfo arg)
|
||||
{
|
||||
this.RecordOpcode(op);
|
||||
_myIlGenerator.Emit(op, arg);
|
||||
}
|
||||
|
||||
public void Emit(OpCode op, MethodInfo arg)
|
||||
{
|
||||
this.RecordOpcode(op);
|
||||
_myIlGenerator.Emit(op, arg);
|
||||
}
|
||||
|
||||
public void Emit(OpCode op, FieldInfo arg)
|
||||
{
|
||||
this.RecordOpcode(op);
|
||||
_myIlGenerator.Emit(op, arg);
|
||||
}
|
||||
|
||||
public void Emit(OpCode op, byte arg)
|
||||
{
|
||||
this.RecordOpcode(op);
|
||||
_myIlGenerator.Emit(op, arg);
|
||||
}
|
||||
|
||||
public void Emit(OpCode op, sbyte arg)
|
||||
{
|
||||
this.RecordOpcode(op);
|
||||
_myIlGenerator.Emit(op, arg);
|
||||
}
|
||||
|
||||
public void Emit(OpCode op, short arg)
|
||||
{
|
||||
this.RecordOpcode(op);
|
||||
_myIlGenerator.Emit(op, arg);
|
||||
}
|
||||
|
||||
public void Emit(OpCode op, int arg)
|
||||
{
|
||||
this.RecordOpcode(op);
|
||||
_myIlGenerator.Emit(op, arg);
|
||||
}
|
||||
|
||||
public void Emit(OpCode op, long arg)
|
||||
{
|
||||
this.RecordOpcode(op);
|
||||
_myIlGenerator.Emit(op, arg);
|
||||
}
|
||||
|
||||
public void Emit(OpCode op, float arg)
|
||||
{
|
||||
this.RecordOpcode(op);
|
||||
_myIlGenerator.Emit(op, arg);
|
||||
}
|
||||
|
||||
public void Emit(OpCode op, double arg)
|
||||
{
|
||||
this.RecordOpcode(op);
|
||||
_myIlGenerator.Emit(op, arg);
|
||||
}
|
||||
|
||||
public void Emit(OpCode op, string arg)
|
||||
{
|
||||
this.RecordOpcode(op);
|
||||
_myIlGenerator.Emit(op, arg);
|
||||
}
|
||||
|
||||
public void Emit(OpCode op, Label arg)
|
||||
{
|
||||
this.RecordOpcode(op);
|
||||
_myIlGenerator.Emit(op, arg);
|
||||
}
|
||||
|
||||
public void EmitBranch(Label arg)
|
||||
{
|
||||
if (_myPass == 1)
|
||||
{
|
||||
_bm.AddBranch(this, arg);
|
||||
Emit(OpCodes.Br_S, arg);
|
||||
}
|
||||
else if (_bm.IsLongBranch(this) == false)
|
||||
{
|
||||
Emit(OpCodes.Br_S, arg);
|
||||
}
|
||||
else
|
||||
{
|
||||
Emit(OpCodes.Br, arg);
|
||||
}
|
||||
}
|
||||
|
||||
public void EmitBranchFalse(Label arg)
|
||||
{
|
||||
if (_myPass == 1)
|
||||
{
|
||||
_bm.AddBranch(this, arg);
|
||||
Emit(OpCodes.Brfalse_S, arg);
|
||||
}
|
||||
else if (_bm.IsLongBranch(this) == false)
|
||||
{
|
||||
Emit(OpCodes.Brfalse_S, arg);
|
||||
}
|
||||
else
|
||||
{
|
||||
Emit(OpCodes.Brfalse, arg);
|
||||
}
|
||||
}
|
||||
|
||||
public void EmitBranchTrue(Label arg)
|
||||
{
|
||||
if (_myPass == 1)
|
||||
{
|
||||
_bm.AddBranch(this, arg);
|
||||
Emit(OpCodes.Brtrue_S, arg);
|
||||
}
|
||||
else if (_bm.IsLongBranch(this) == false)
|
||||
{
|
||||
Emit(OpCodes.Brtrue_S, arg);
|
||||
}
|
||||
else
|
||||
{
|
||||
Emit(OpCodes.Brtrue, arg);
|
||||
}
|
||||
}
|
||||
|
||||
public void MarkLabel(Label lbl)
|
||||
{
|
||||
_myIlGenerator.MarkLabel(lbl);
|
||||
_bm.MarkLabel(this, lbl);
|
||||
}
|
||||
|
||||
|
||||
public Label DefineLabel()
|
||||
{
|
||||
_myLabelCount += 1;
|
||||
var label = _myIlGenerator.DefineLabel();
|
||||
return label;
|
||||
}
|
||||
|
||||
|
||||
public LocalBuilder DeclareLocal(Type localType)
|
||||
{
|
||||
return _myIlGenerator.DeclareLocal(localType);
|
||||
}
|
||||
|
||||
private void RecordOpcode(OpCode op)
|
||||
{
|
||||
//Trace.WriteLine(String.Format("{0:x}: {1}", MyLength, op.Name))
|
||||
int operandLength = GetOpcodeOperandSize(op.OperandType);
|
||||
_myLength += op.Size + operandLength;
|
||||
}
|
||||
|
||||
private static int GetOpcodeOperandSize(OperandType operand)
|
||||
{
|
||||
switch (operand)
|
||||
{
|
||||
case OperandType.InlineNone:
|
||||
return 0;
|
||||
case OperandType.ShortInlineBrTarget:
|
||||
case OperandType.ShortInlineI:
|
||||
case OperandType.ShortInlineVar:
|
||||
return 1;
|
||||
case OperandType.InlineVar:
|
||||
return 2;
|
||||
case OperandType.InlineBrTarget:
|
||||
case OperandType.InlineField:
|
||||
case OperandType.InlineI:
|
||||
case OperandType.InlineMethod:
|
||||
case OperandType.InlineSig:
|
||||
case OperandType.InlineString:
|
||||
case OperandType.InlineTok:
|
||||
case OperandType.InlineType:
|
||||
case OperandType.ShortInlineR:
|
||||
return 4;
|
||||
case OperandType.InlineI8:
|
||||
case OperandType.InlineR:
|
||||
return 8;
|
||||
default:
|
||||
Debug.Fail("Unknown operand type");
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
[Conditional("DEBUG")]
|
||||
public void ValidateLength()
|
||||
{
|
||||
Debug.Assert(this.Length == this.ILGeneratorLength, "ILGenerator length mismatch");
|
||||
}
|
||||
|
||||
public int Length => _myLength;
|
||||
|
||||
public int LabelCount => _myLabelCount;
|
||||
|
||||
private int ILGeneratorLength => Utility.GetILGeneratorLength(_myIlGenerator);
|
||||
}
|
||||
}
|
||||
573
InternalTypes/ImplicitConversions.cs
Normal file
573
InternalTypes/ImplicitConversions.cs
Normal file
@@ -0,0 +1,573 @@
|
||||
using System.Diagnostics;
|
||||
using System.Reflection.Emit;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Flee.InternalTypes
|
||||
{
|
||||
internal class ImplicitConverter
|
||||
{
|
||||
/// <summary>
|
||||
/// Table of results for binary operations using primitives
|
||||
/// </summary>
|
||||
private static readonly Type[,] OurBinaryResultTable;
|
||||
|
||||
/// <summary>
|
||||
/// Primitive types we support
|
||||
/// </summary>
|
||||
private static readonly Type[] OurBinaryTypes;
|
||||
static ImplicitConverter()
|
||||
{
|
||||
// Create a table with all the primitive types
|
||||
Type[] types = {
|
||||
typeof(char),
|
||||
typeof(byte),
|
||||
typeof(sbyte),
|
||||
typeof(Int16),
|
||||
typeof(UInt16),
|
||||
typeof(Int32),
|
||||
typeof(UInt32),
|
||||
typeof(Int64),
|
||||
typeof(UInt64),
|
||||
typeof(float),
|
||||
typeof(double)
|
||||
};
|
||||
OurBinaryTypes = types;
|
||||
Type[,] table = new Type[types.Length, types.Length];
|
||||
OurBinaryResultTable = table;
|
||||
FillIdentities(types, table);
|
||||
|
||||
// Fill the table
|
||||
AddEntry(typeof(UInt32), typeof(UInt64), typeof(UInt64));
|
||||
AddEntry(typeof(Int32), typeof(Int64), typeof(Int64));
|
||||
AddEntry(typeof(UInt32), typeof(Int64), typeof(Int64));
|
||||
AddEntry(typeof(Int32), typeof(UInt32), typeof(Int64));
|
||||
AddEntry(typeof(UInt32), typeof(float), typeof(float));
|
||||
AddEntry(typeof(UInt32), typeof(double), typeof(double));
|
||||
AddEntry(typeof(Int32), typeof(float), typeof(float));
|
||||
AddEntry(typeof(Int32), typeof(double), typeof(double));
|
||||
AddEntry(typeof(Int64), typeof(float), typeof(float));
|
||||
AddEntry(typeof(Int64), typeof(double), typeof(double));
|
||||
AddEntry(typeof(UInt64), typeof(float), typeof(float));
|
||||
AddEntry(typeof(UInt64), typeof(double), typeof(double));
|
||||
AddEntry(typeof(float), typeof(double), typeof(double));
|
||||
|
||||
// Byte
|
||||
AddEntry(typeof(byte), typeof(byte), typeof(Int32));
|
||||
AddEntry(typeof(byte), typeof(sbyte), typeof(Int32));
|
||||
AddEntry(typeof(byte), typeof(Int16), typeof(Int32));
|
||||
AddEntry(typeof(byte), typeof(UInt16), typeof(Int32));
|
||||
AddEntry(typeof(byte), typeof(Int32), typeof(Int32));
|
||||
AddEntry(typeof(byte), typeof(UInt32), typeof(UInt32));
|
||||
AddEntry(typeof(byte), typeof(Int64), typeof(Int64));
|
||||
AddEntry(typeof(byte), typeof(UInt64), typeof(UInt64));
|
||||
AddEntry(typeof(byte), typeof(float), typeof(float));
|
||||
AddEntry(typeof(byte), typeof(double), typeof(double));
|
||||
|
||||
// SByte
|
||||
AddEntry(typeof(sbyte), typeof(sbyte), typeof(Int32));
|
||||
AddEntry(typeof(sbyte), typeof(Int16), typeof(Int32));
|
||||
AddEntry(typeof(sbyte), typeof(UInt16), typeof(Int32));
|
||||
AddEntry(typeof(sbyte), typeof(Int32), typeof(Int32));
|
||||
AddEntry(typeof(sbyte), typeof(UInt32), typeof(long));
|
||||
AddEntry(typeof(sbyte), typeof(Int64), typeof(Int64));
|
||||
//invalid -- AddEntry(GetType(SByte), GetType(UInt64), GetType(UInt64))
|
||||
AddEntry(typeof(sbyte), typeof(float), typeof(float));
|
||||
AddEntry(typeof(sbyte), typeof(double), typeof(double));
|
||||
|
||||
// int16
|
||||
AddEntry(typeof(Int16), typeof(Int16), typeof(Int32));
|
||||
AddEntry(typeof(Int16), typeof(UInt16), typeof(Int32));
|
||||
AddEntry(typeof(Int16), typeof(Int32), typeof(Int32));
|
||||
AddEntry(typeof(Int16), typeof(UInt32), typeof(long));
|
||||
AddEntry(typeof(Int16), typeof(Int64), typeof(Int64));
|
||||
//invalid -- AddEntry(GetType(Int16), GetType(UInt64), GetType(UInt64))
|
||||
AddEntry(typeof(Int16), typeof(float), typeof(float));
|
||||
AddEntry(typeof(Int16), typeof(double), typeof(double));
|
||||
|
||||
// Uint16
|
||||
AddEntry(typeof(UInt16), typeof(UInt16), typeof(Int32));
|
||||
AddEntry(typeof(UInt16), typeof(Int16), typeof(Int32));
|
||||
AddEntry(typeof(UInt16), typeof(Int32), typeof(Int32));
|
||||
AddEntry(typeof(UInt16), typeof(UInt32), typeof(UInt32));
|
||||
AddEntry(typeof(UInt16), typeof(Int64), typeof(Int64));
|
||||
AddEntry(typeof(UInt16), typeof(UInt64), typeof(UInt64));
|
||||
AddEntry(typeof(UInt16), typeof(float), typeof(float));
|
||||
AddEntry(typeof(UInt16), typeof(double), typeof(double));
|
||||
|
||||
// Char
|
||||
AddEntry(typeof(char), typeof(char), typeof(Int32));
|
||||
AddEntry(typeof(char), typeof(UInt16), typeof(UInt16));
|
||||
AddEntry(typeof(char), typeof(Int32), typeof(Int32));
|
||||
AddEntry(typeof(char), typeof(UInt32), typeof(UInt32));
|
||||
AddEntry(typeof(char), typeof(Int64), typeof(Int64));
|
||||
AddEntry(typeof(char), typeof(UInt64), typeof(UInt64));
|
||||
AddEntry(typeof(char), typeof(float), typeof(float));
|
||||
AddEntry(typeof(char), typeof(double), typeof(double));
|
||||
}
|
||||
|
||||
private ImplicitConverter()
|
||||
{
|
||||
}
|
||||
|
||||
private static void FillIdentities(Type[] typeList, Type[,] table)
|
||||
{
|
||||
for (int i = 0; i <= typeList.Length - 1; i++)
|
||||
{
|
||||
Type t = typeList[i];
|
||||
table[i, i] = t;
|
||||
}
|
||||
}
|
||||
|
||||
private static void AddEntry(Type t1, Type t2, Type result)
|
||||
{
|
||||
int index1 = GetTypeIndex(t1);
|
||||
int index2 = GetTypeIndex(t2);
|
||||
OurBinaryResultTable[index1, index2] = result;
|
||||
OurBinaryResultTable[index2, index1] = result;
|
||||
}
|
||||
|
||||
private static int GetTypeIndex(Type t)
|
||||
{
|
||||
return System.Array.IndexOf(OurBinaryTypes, t);
|
||||
}
|
||||
|
||||
public static bool EmitImplicitConvert(Type sourceType, Type destType, FleeILGenerator ilg)
|
||||
{
|
||||
if (object.ReferenceEquals(sourceType, destType))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else if (EmitOverloadedImplicitConvert(sourceType, destType, ilg) == true)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else if (ImplicitConvertToReferenceType(sourceType, destType, ilg) == true)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return ImplicitConvertToValueType(sourceType, destType, ilg);
|
||||
}
|
||||
}
|
||||
|
||||
private static bool EmitOverloadedImplicitConvert(Type sourceType, Type destType, FleeILGenerator ilg)
|
||||
{
|
||||
// Look for an implicit operator on the destination type
|
||||
MethodInfo mi = Utility.GetSimpleOverloadedOperator("Implicit", sourceType, destType);
|
||||
|
||||
if (mi == null)
|
||||
{
|
||||
// No match
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((ilg != null))
|
||||
{
|
||||
ilg.Emit(OpCodes.Call, mi);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool ImplicitConvertToReferenceType(Type sourceType, Type destType, FleeILGenerator ilg)
|
||||
{
|
||||
if (destType.IsValueType == true)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (object.ReferenceEquals(sourceType, typeof(Null)))
|
||||
{
|
||||
// Null is always convertible to a reference type
|
||||
return true;
|
||||
}
|
||||
|
||||
if (destType.IsAssignableFrom(sourceType) == false)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (sourceType.IsValueType == true)
|
||||
{
|
||||
if ((ilg != null))
|
||||
{
|
||||
ilg.Emit(OpCodes.Box, sourceType);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool ImplicitConvertToValueType(Type sourceType, Type destType, FleeILGenerator ilg)
|
||||
{
|
||||
// We only handle value types
|
||||
if (sourceType.IsValueType == false & destType.IsValueType == false)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// No implicit conversion to enum. Have to do this check here since calling GetTypeCode on an enum will return the typecode
|
||||
// of the underlying type which screws us up.
|
||||
if (sourceType.IsEnum == true | destType.IsEnum == true)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return EmitImplicitNumericConvert(sourceType, destType, ilg);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///Emit an implicit conversion (if the ilg is not null) and returns a value that determines whether the implicit conversion
|
||||
/// succeeded
|
||||
/// </summary>
|
||||
/// <param name="sourceType"></param>
|
||||
/// <param name="destType"></param>
|
||||
/// <param name="ilg"></param>
|
||||
/// <returns></returns>
|
||||
public static bool EmitImplicitNumericConvert(Type sourceType, Type destType, FleeILGenerator ilg)
|
||||
{
|
||||
TypeCode sourceTypeCode = Type.GetTypeCode(sourceType);
|
||||
TypeCode destTypeCode = Type.GetTypeCode(destType);
|
||||
|
||||
switch (destTypeCode)
|
||||
{
|
||||
case TypeCode.Int16:
|
||||
return ImplicitConvertToInt16(sourceTypeCode, ilg);
|
||||
case TypeCode.UInt16:
|
||||
return ImplicitConvertToUInt16(sourceTypeCode, ilg);
|
||||
case TypeCode.Int32:
|
||||
return ImplicitConvertToInt32(sourceTypeCode, ilg);
|
||||
case TypeCode.UInt32:
|
||||
return ImplicitConvertToUInt32(sourceTypeCode, ilg);
|
||||
case TypeCode.Double:
|
||||
return ImplicitConvertToDouble(sourceTypeCode, ilg);
|
||||
case TypeCode.Single:
|
||||
return ImplicitConvertToSingle(sourceTypeCode, ilg);
|
||||
case TypeCode.Int64:
|
||||
return ImplicitConvertToInt64(sourceTypeCode, ilg);
|
||||
case TypeCode.UInt64:
|
||||
return ImplicitConvertToUInt64(sourceTypeCode, ilg);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static bool ImplicitConvertToInt16(TypeCode sourceTypeCode, FleeILGenerator ilg)
|
||||
{
|
||||
switch (sourceTypeCode)
|
||||
{
|
||||
case TypeCode.Byte:
|
||||
case TypeCode.SByte:
|
||||
case TypeCode.Int16:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool ImplicitConvertToUInt16(TypeCode sourceTypeCode, FleeILGenerator ilg)
|
||||
{
|
||||
switch (sourceTypeCode)
|
||||
{
|
||||
case TypeCode.Char:
|
||||
case TypeCode.Byte:
|
||||
case TypeCode.UInt16:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool ImplicitConvertToInt32(TypeCode sourceTypeCode, FleeILGenerator ilg)
|
||||
{
|
||||
switch (sourceTypeCode)
|
||||
{
|
||||
case TypeCode.Char:
|
||||
case TypeCode.Byte:
|
||||
case TypeCode.SByte:
|
||||
case TypeCode.Int16:
|
||||
case TypeCode.UInt16:
|
||||
case TypeCode.Int32:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool ImplicitConvertToUInt32(TypeCode sourceTypeCode, FleeILGenerator ilg)
|
||||
{
|
||||
switch (sourceTypeCode)
|
||||
{
|
||||
case TypeCode.Char:
|
||||
case TypeCode.Byte:
|
||||
case TypeCode.SByte:
|
||||
case TypeCode.Int16:
|
||||
case TypeCode.UInt16:
|
||||
case TypeCode.UInt32:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool ImplicitConvertToDouble(TypeCode sourceTypeCode, FleeILGenerator ilg)
|
||||
{
|
||||
switch (sourceTypeCode)
|
||||
{
|
||||
case TypeCode.Char:
|
||||
case TypeCode.SByte:
|
||||
case TypeCode.Byte:
|
||||
case TypeCode.Int16:
|
||||
case TypeCode.UInt16:
|
||||
case TypeCode.Int32:
|
||||
case TypeCode.Single:
|
||||
case TypeCode.Int64:
|
||||
EmitConvert(ilg, OpCodes.Conv_R8);
|
||||
break;
|
||||
case TypeCode.UInt32:
|
||||
case TypeCode.UInt64:
|
||||
EmitConvert(ilg, OpCodes.Conv_R_Un);
|
||||
EmitConvert(ilg, OpCodes.Conv_R8);
|
||||
break;
|
||||
case TypeCode.Double:
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool ImplicitConvertToSingle(TypeCode sourceTypeCode, FleeILGenerator ilg)
|
||||
{
|
||||
switch (sourceTypeCode)
|
||||
{
|
||||
case TypeCode.Char:
|
||||
case TypeCode.Byte:
|
||||
case TypeCode.SByte:
|
||||
case TypeCode.Int16:
|
||||
case TypeCode.UInt16:
|
||||
case TypeCode.Int32:
|
||||
case TypeCode.Int64:
|
||||
EmitConvert(ilg, OpCodes.Conv_R4);
|
||||
break;
|
||||
case TypeCode.UInt32:
|
||||
case TypeCode.UInt64:
|
||||
EmitConvert(ilg, OpCodes.Conv_R_Un);
|
||||
EmitConvert(ilg, OpCodes.Conv_R4);
|
||||
break;
|
||||
case TypeCode.Single:
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool ImplicitConvertToInt64(TypeCode sourceTypeCode, FleeILGenerator ilg)
|
||||
{
|
||||
switch (sourceTypeCode)
|
||||
{
|
||||
case TypeCode.SByte:
|
||||
case TypeCode.Int16:
|
||||
case TypeCode.Int32:
|
||||
EmitConvert(ilg, OpCodes.Conv_I8);
|
||||
break;
|
||||
case TypeCode.Char:
|
||||
case TypeCode.Byte:
|
||||
case TypeCode.UInt16:
|
||||
case TypeCode.UInt32:
|
||||
EmitConvert(ilg, OpCodes.Conv_U8);
|
||||
break;
|
||||
case TypeCode.Int64:
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool ImplicitConvertToUInt64(TypeCode sourceTypeCode, FleeILGenerator ilg)
|
||||
{
|
||||
switch (sourceTypeCode)
|
||||
{
|
||||
case TypeCode.Char:
|
||||
case TypeCode.Byte:
|
||||
case TypeCode.UInt16:
|
||||
case TypeCode.UInt32:
|
||||
EmitConvert(ilg, OpCodes.Conv_U8);
|
||||
break;
|
||||
case TypeCode.UInt64:
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void EmitConvert(FleeILGenerator ilg, OpCode convertOpcode)
|
||||
{
|
||||
if ((ilg != null))
|
||||
{
|
||||
ilg.Emit(convertOpcode);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the result type for a binary operation
|
||||
/// </summary>
|
||||
/// <param name="t1"></param>
|
||||
/// <param name="t2"></param>
|
||||
/// <returns></returns>
|
||||
public static Type GetBinaryResultType(Type t1, Type t2)
|
||||
{
|
||||
int index1 = GetTypeIndex(t1);
|
||||
int index2 = GetTypeIndex(t2);
|
||||
|
||||
if (index1 == -1 | index2 == -1)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
else
|
||||
{
|
||||
return OurBinaryResultTable[index1, index2];
|
||||
}
|
||||
}
|
||||
|
||||
public static int GetImplicitConvertScore(Type sourceType, Type destType)
|
||||
{
|
||||
if (object.ReferenceEquals(sourceType, destType))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (object.ReferenceEquals(sourceType, typeof(Null)))
|
||||
{
|
||||
return GetInverseDistanceToObject(destType);
|
||||
}
|
||||
|
||||
if (Utility.GetSimpleOverloadedOperator("Implicit", sourceType, destType) != null)
|
||||
{
|
||||
// Implicit operator conversion, score it at 1 so it's just above the minimum
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (sourceType.IsValueType == true)
|
||||
{
|
||||
if (destType.IsValueType == true)
|
||||
{
|
||||
// Value type -> value type
|
||||
int sourceScore = GetValueTypeImplicitConvertScore(sourceType);
|
||||
int destScore = GetValueTypeImplicitConvertScore(destType);
|
||||
|
||||
return destScore - sourceScore;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Value type -> reference type
|
||||
return GetReferenceTypeImplicitConvertScore(sourceType, destType);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (destType.IsValueType == true)
|
||||
{
|
||||
// Reference type -> value type
|
||||
// Reference types can never be implicitly converted to value types
|
||||
Debug.Fail("No implicit conversion from reference type to value type");
|
||||
}
|
||||
else
|
||||
{
|
||||
// Reference type -> reference type
|
||||
return GetReferenceTypeImplicitConvertScore(sourceType, destType);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static int GetValueTypeImplicitConvertScore(Type t)
|
||||
{
|
||||
TypeCode tc = Type.GetTypeCode(t);
|
||||
|
||||
switch (tc)
|
||||
{
|
||||
case TypeCode.Byte:
|
||||
return 1;
|
||||
case TypeCode.SByte:
|
||||
return 2;
|
||||
case TypeCode.Char:
|
||||
return 3;
|
||||
case TypeCode.Int16:
|
||||
return 4;
|
||||
case TypeCode.UInt16:
|
||||
return 5;
|
||||
case TypeCode.Int32:
|
||||
return 6;
|
||||
case TypeCode.UInt32:
|
||||
return 7;
|
||||
case TypeCode.Int64:
|
||||
return 8;
|
||||
case TypeCode.UInt64:
|
||||
return 9;
|
||||
case TypeCode.Single:
|
||||
return 10;
|
||||
case TypeCode.Double:
|
||||
return 11;
|
||||
case TypeCode.Decimal:
|
||||
return 11;
|
||||
case TypeCode.Boolean:
|
||||
return 12;
|
||||
case TypeCode.DateTime:
|
||||
return 13;
|
||||
default:
|
||||
Debug.Assert(false, "unknown value type");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
private static int GetReferenceTypeImplicitConvertScore(Type sourceType, Type destType)
|
||||
{
|
||||
if (destType.IsInterface == true)
|
||||
{
|
||||
return 100;
|
||||
}
|
||||
else
|
||||
{
|
||||
return GetInheritanceDistance(sourceType, destType);
|
||||
}
|
||||
}
|
||||
|
||||
private static int GetInheritanceDistance(Type sourceType, Type destType)
|
||||
{
|
||||
int count = 0;
|
||||
Type current = sourceType;
|
||||
|
||||
while ((!object.ReferenceEquals(current, destType)))
|
||||
{
|
||||
count += 1;
|
||||
current = current.BaseType;
|
||||
}
|
||||
|
||||
return count * 1000;
|
||||
}
|
||||
|
||||
private static int GetInverseDistanceToObject(Type t)
|
||||
{
|
||||
int score = 1000;
|
||||
Type current = t.BaseType;
|
||||
|
||||
while ((current != null))
|
||||
{
|
||||
score -= 100;
|
||||
current = current.BaseType;
|
||||
}
|
||||
|
||||
return score;
|
||||
}
|
||||
}
|
||||
}
|
||||
562
InternalTypes/Miscellaneous.cs
Normal file
562
InternalTypes/Miscellaneous.cs
Normal file
@@ -0,0 +1,562 @@
|
||||
using System.Collections;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.Reflection;
|
||||
using System.Reflection.Emit;
|
||||
using Flee.ExpressionElements.Base;
|
||||
using Flee.PublicTypes;
|
||||
|
||||
namespace Flee.InternalTypes
|
||||
{
|
||||
internal enum BinaryArithmeticOperation
|
||||
{
|
||||
Add,
|
||||
Subtract,
|
||||
Multiply,
|
||||
Divide,
|
||||
Mod,
|
||||
Power
|
||||
}
|
||||
|
||||
internal enum LogicalCompareOperation
|
||||
{
|
||||
LessThan,
|
||||
GreaterThan,
|
||||
Equal,
|
||||
NotEqual,
|
||||
LessThanOrEqual,
|
||||
GreaterThanOrEqual
|
||||
}
|
||||
|
||||
internal enum AndOrOperation
|
||||
{
|
||||
And,
|
||||
Or
|
||||
}
|
||||
|
||||
internal enum ShiftOperation
|
||||
{
|
||||
LeftShift,
|
||||
RightShift
|
||||
}
|
||||
|
||||
internal delegate T ExpressionEvaluator<T>(object owner, ExpressionContext context, VariableCollection variables);
|
||||
|
||||
internal abstract class CustomBinder : Binder
|
||||
{
|
||||
|
||||
public override System.Reflection.FieldInfo BindToField(System.Reflection.BindingFlags bindingAttr, System.Reflection.FieldInfo[] match, object value, System.Globalization.CultureInfo culture)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public System.Reflection.MethodBase BindToMethod(System.Reflection.BindingFlags bindingAttr, System.Reflection.MethodBase[] match, ref object[] args, System.Reflection.ParameterModifier[] modifiers, System.Globalization.CultureInfo culture, string[] names, ref object state)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public override object ChangeType(object value, System.Type type, System.Globalization.CultureInfo culture)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
public override void ReorderArgumentArray(ref object[] args, object state)
|
||||
{
|
||||
}
|
||||
|
||||
public override System.Reflection.PropertyInfo SelectProperty(System.Reflection.BindingFlags bindingAttr, System.Reflection.PropertyInfo[] match, System.Type returnType, System.Type[] indexes, System.Reflection.ParameterModifier[] modifiers)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
internal class ExplicitOperatorMethodBinder : CustomBinder
|
||||
{
|
||||
private readonly Type _myReturnType;
|
||||
private readonly Type _myArgType;
|
||||
private CustomBinder _customBinderImplementation;
|
||||
|
||||
public ExplicitOperatorMethodBinder(Type returnType, Type argType)
|
||||
{
|
||||
_myReturnType = returnType;
|
||||
_myArgType = argType;
|
||||
}
|
||||
|
||||
public override MethodBase BindToMethod(BindingFlags bindingAttr, MethodBase[] match, ref object[] args, ParameterModifier[] modifiers,
|
||||
CultureInfo culture, string[] names, out object state)
|
||||
{
|
||||
return _customBinderImplementation.BindToMethod(bindingAttr, match, ref args, modifiers, culture, names, out state);
|
||||
}
|
||||
|
||||
public override System.Reflection.MethodBase SelectMethod(System.Reflection.BindingFlags bindingAttr, System.Reflection.MethodBase[] match, System.Type[] types, System.Reflection.ParameterModifier[] modifiers)
|
||||
{
|
||||
foreach (MethodInfo mi in match)
|
||||
{
|
||||
ParameterInfo[] parameters = mi.GetParameters();
|
||||
ParameterInfo firstParameter = parameters[0];
|
||||
if (object.ReferenceEquals(firstParameter.ParameterType, _myArgType) & object.ReferenceEquals(mi.ReturnType, _myReturnType))
|
||||
{
|
||||
return mi;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
internal class BinaryOperatorBinder : CustomBinder
|
||||
{
|
||||
|
||||
private readonly Type _myLeftType;
|
||||
private readonly Type _myRightType;
|
||||
private CustomBinder _customBinderImplementation;
|
||||
|
||||
public BinaryOperatorBinder(Type leftType, Type rightType)
|
||||
{
|
||||
_myLeftType = leftType;
|
||||
_myRightType = rightType;
|
||||
}
|
||||
|
||||
public override MethodBase BindToMethod(BindingFlags bindingAttr, MethodBase[] match, ref object[] args, ParameterModifier[] modifiers,
|
||||
CultureInfo culture, string[] names, out object state)
|
||||
{
|
||||
return _customBinderImplementation.BindToMethod(bindingAttr, match, ref args, modifiers, culture, names, out state);
|
||||
}
|
||||
|
||||
public override System.Reflection.MethodBase SelectMethod(System.Reflection.BindingFlags bindingAttr, System.Reflection.MethodBase[] match, System.Type[] types, System.Reflection.ParameterModifier[] modifiers)
|
||||
{
|
||||
foreach (MethodInfo mi in match)
|
||||
{
|
||||
ParameterInfo[] parameters = mi.GetParameters();
|
||||
bool leftValid = ImplicitConverter.EmitImplicitConvert(_myLeftType, parameters[0].ParameterType, null);
|
||||
bool rightValid = ImplicitConverter.EmitImplicitConvert(_myRightType, parameters[1].ParameterType, null);
|
||||
|
||||
if (leftValid == true & rightValid == true)
|
||||
{
|
||||
return mi;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
internal class Null
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
internal class DefaultExpressionOwner
|
||||
{
|
||||
|
||||
|
||||
private static readonly DefaultExpressionOwner OurInstance = new DefaultExpressionOwner();
|
||||
|
||||
private DefaultExpressionOwner()
|
||||
{
|
||||
}
|
||||
|
||||
public static object Instance => OurInstance;
|
||||
}
|
||||
|
||||
[Obsolete("Helper class to resolve overloads")]
|
||||
internal class CustomMethodInfo : IComparable<CustomMethodInfo>, IEquatable<CustomMethodInfo>
|
||||
{
|
||||
/// <summary>
|
||||
/// Method we are wrapping
|
||||
/// </summary>
|
||||
private readonly MethodInfo _myTarget;
|
||||
/// <summary>
|
||||
/// The rating of how close the method matches the given arguments (0 is best)
|
||||
/// </summary>
|
||||
private float _myScore;
|
||||
public bool IsParamArray;
|
||||
public Type[] MyFixedArgTypes;
|
||||
public Type[] MyParamArrayArgTypes;
|
||||
public bool IsExtensionMethod;
|
||||
public Type ParamArrayElementType;
|
||||
public CustomMethodInfo(MethodInfo target)
|
||||
{
|
||||
_myTarget = target;
|
||||
}
|
||||
|
||||
public void ComputeScore(Type[] argTypes)
|
||||
{
|
||||
ParameterInfo[] @params = _myTarget.GetParameters();
|
||||
|
||||
if (@params.Length == 0)
|
||||
{
|
||||
_myScore = 0.0F;
|
||||
}
|
||||
else if (@params.Length == 1 && argTypes.Length == 0)//extension method without parameter support -> prefer members
|
||||
{
|
||||
_myScore = 0.1F;
|
||||
}
|
||||
else if (IsParamArray == true)
|
||||
{
|
||||
_myScore = this.ComputeScoreForParamArray(@params, argTypes);
|
||||
}
|
||||
else if (IsExtensionMethod == true)
|
||||
{
|
||||
_myScore = this.ComputeScoreExtensionMethodInternal(@params, argTypes);
|
||||
}
|
||||
else
|
||||
{
|
||||
_myScore = this.ComputeScoreInternal(@params, argTypes);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compute a score showing how close our method matches the given argument types (for extension methods)
|
||||
/// </summary>
|
||||
/// <param name="parameters"></param>
|
||||
/// <param name="argTypes"></param>
|
||||
/// <returns></returns>
|
||||
private float ComputeScoreExtensionMethodInternal(ParameterInfo[] parameters, Type[] argTypes)
|
||||
{
|
||||
Debug.Assert(parameters.Length == argTypes.Length + 1);
|
||||
int sum = 0;
|
||||
|
||||
for (int i = 0; i <= argTypes.Length - 1; i++)
|
||||
{
|
||||
sum += ImplicitConverter.GetImplicitConvertScore(argTypes[i], parameters[i + 1].ParameterType);
|
||||
}
|
||||
|
||||
return sum;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compute a score showing how close our method matches the given argument types
|
||||
/// </summary>
|
||||
/// <param name="parameters"></param>
|
||||
/// <param name="argTypes"></param>
|
||||
/// <returns></returns>
|
||||
private float ComputeScoreInternal(ParameterInfo[] parameters, Type[] argTypes)
|
||||
{
|
||||
// Our score is the average of the scores of each parameter. The lower the score, the better the match.
|
||||
int sum = ComputeSum(parameters, argTypes);
|
||||
|
||||
return (float)sum / (float)argTypes.Length;
|
||||
}
|
||||
|
||||
private static int ComputeSum(ParameterInfo[] parameters, Type[] argTypes)
|
||||
{
|
||||
Debug.Assert(parameters.Length == argTypes.Length);
|
||||
int sum = 0;
|
||||
|
||||
for (int i = 0; i <= parameters.Length - 1; i++)
|
||||
{
|
||||
sum += ImplicitConverter.GetImplicitConvertScore(argTypes[i], parameters[i].ParameterType);
|
||||
}
|
||||
|
||||
return sum;
|
||||
}
|
||||
|
||||
private float ComputeScoreForParamArray(ParameterInfo[] parameters, Type[] argTypes)
|
||||
{
|
||||
ParameterInfo paramArrayParameter = parameters[parameters.Length - 1];
|
||||
int fixedParameterCount = paramArrayParameter.Position;
|
||||
|
||||
ParameterInfo[] fixedParameters = new ParameterInfo[fixedParameterCount];
|
||||
|
||||
System.Array.Copy(parameters, fixedParameters, fixedParameterCount);
|
||||
|
||||
int fixedSum = ComputeSum(fixedParameters, MyFixedArgTypes);
|
||||
|
||||
Type paramArrayElementType = paramArrayParameter.ParameterType.GetElementType();
|
||||
|
||||
int paramArraySum = 0;
|
||||
|
||||
foreach (Type argType in MyParamArrayArgTypes)
|
||||
{
|
||||
paramArraySum += ImplicitConverter.GetImplicitConvertScore(argType, paramArrayElementType);
|
||||
}
|
||||
|
||||
float score = 0;
|
||||
|
||||
if (argTypes.Length > 0)
|
||||
{
|
||||
score = (fixedSum + paramArraySum) / argTypes.Length;
|
||||
}
|
||||
else
|
||||
{
|
||||
score = 0;
|
||||
}
|
||||
|
||||
// The param array score gets a slight penalty so that it scores worse than direct matches
|
||||
return score + 1;
|
||||
}
|
||||
|
||||
public bool IsAccessible(MemberElement owner)
|
||||
{
|
||||
return owner.IsMemberAccessible(_myTarget);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Is the given MethodInfo usable as an overload?
|
||||
/// </summary>
|
||||
/// <param name="argTypes"></param>
|
||||
/// <returns></returns>
|
||||
public bool IsMatch(Type[] argTypes, MemberElement previous, ExpressionContext context)
|
||||
{
|
||||
ParameterInfo[] parameters = _myTarget.GetParameters();
|
||||
|
||||
// If there are no parameters and no arguments were passed, then we are a match.
|
||||
if (parameters.Length == 0 & argTypes.Length == 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// If there are no parameters but there are arguments, we cannot be a match
|
||||
if (parameters.Length == 0 & argTypes.Length > 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Is the last parameter a paramArray?
|
||||
ParameterInfo lastParam = parameters[parameters.Length - 1];
|
||||
|
||||
if (lastParam.IsDefined(typeof(ParamArrayAttribute), false) == false)
|
||||
{
|
||||
//Extension method support
|
||||
if (parameters.Length == argTypes.Length + 1)
|
||||
{
|
||||
IsExtensionMethod = true;
|
||||
return AreValidExtensionMethodArgumentsForParameters(argTypes, parameters, previous, context);
|
||||
}
|
||||
if ((parameters.Length != argTypes.Length))
|
||||
{
|
||||
// Not a paramArray and parameter and argument counts don't match
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Regular method call, do the test
|
||||
return AreValidArgumentsForParameters(argTypes, parameters);
|
||||
}
|
||||
}
|
||||
|
||||
// At this point, we are dealing with a paramArray call
|
||||
|
||||
// If the parameter and argument counts are equal and there is an implicit conversion from one to the other, we are a match.
|
||||
if (parameters.Length == argTypes.Length && AreValidArgumentsForParameters(argTypes, parameters) == true)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else if (this.IsParamArrayMatch(argTypes, parameters, lastParam) == true)
|
||||
{
|
||||
IsParamArray = true;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsParamArrayMatch(Type[] argTypes, ParameterInfo[] parameters, ParameterInfo paramArrayParameter)
|
||||
{
|
||||
// Get the count of arguments before the paramArray parameter
|
||||
int fixedParameterCount = paramArrayParameter.Position;
|
||||
Type[] fixedArgTypes = new Type[fixedParameterCount];
|
||||
ParameterInfo[] fixedParameters = new ParameterInfo[fixedParameterCount];
|
||||
|
||||
// Get the argument types and parameters before the paramArray
|
||||
System.Array.Copy(argTypes, fixedArgTypes, fixedParameterCount);
|
||||
System.Array.Copy(parameters, fixedParameters, fixedParameterCount);
|
||||
|
||||
// If the fixed arguments don't match, we are not a match
|
||||
if (AreValidArgumentsForParameters(fixedArgTypes, fixedParameters) == false)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get the type of the paramArray
|
||||
ParamArrayElementType = paramArrayParameter.ParameterType.GetElementType();
|
||||
|
||||
// Get the types of the arguments passed to the paramArray
|
||||
Type[] paramArrayArgTypes = new Type[argTypes.Length - fixedParameterCount];
|
||||
System.Array.Copy(argTypes, fixedParameterCount, paramArrayArgTypes, 0, paramArrayArgTypes.Length);
|
||||
|
||||
// Check each argument
|
||||
foreach (Type argType in paramArrayArgTypes)
|
||||
{
|
||||
if (ImplicitConverter.EmitImplicitConvert(argType, ParamArrayElementType, null) == false)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
MyFixedArgTypes = fixedArgTypes;
|
||||
MyParamArrayArgTypes = paramArrayArgTypes;
|
||||
|
||||
// They all match, so we are a match
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool AreValidExtensionMethodArgumentsForParameters(Type[] argTypes, ParameterInfo[] parameters, MemberElement previous, ExpressionContext context)
|
||||
{
|
||||
Debug.Assert(argTypes.Length + 1 == parameters.Length);
|
||||
|
||||
if (previous != null)
|
||||
{
|
||||
if (ImplicitConverter.EmitImplicitConvert(previous.ResultType, parameters[0].ParameterType, null) == false)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (context.ExpressionOwner != null)
|
||||
{
|
||||
if (ImplicitConverter.EmitImplicitConvert(context.ExpressionOwner.GetType(), parameters[0].ParameterType, null) == false)
|
||||
return false;
|
||||
}
|
||||
else
|
||||
return false;
|
||||
|
||||
//Match if every given argument is implicitly convertible to the method's corresponding parameter
|
||||
for (int i = 0; i <= argTypes.Length - 1; i++)
|
||||
{
|
||||
if (ImplicitConverter.EmitImplicitConvert(argTypes[i], parameters[i + 1].ParameterType, null) == false)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool AreValidArgumentsForParameters(Type[] argTypes, ParameterInfo[] parameters)
|
||||
{
|
||||
Debug.Assert(argTypes.Length == parameters.Length);
|
||||
// Match if every given argument is implicitly convertible to the method's corresponding parameter
|
||||
for (int i = 0; i <= argTypes.Length - 1; i++)
|
||||
{
|
||||
if (ImplicitConverter.EmitImplicitConvert(argTypes[i], parameters[i].ParameterType, null) == false)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public int CompareTo(CustomMethodInfo other)
|
||||
{
|
||||
return _myScore.CompareTo(other._myScore);
|
||||
}
|
||||
|
||||
private bool Equals1(CustomMethodInfo other)
|
||||
{
|
||||
return _myScore == other._myScore;
|
||||
}
|
||||
bool System.IEquatable<CustomMethodInfo>.Equals(CustomMethodInfo other)
|
||||
{
|
||||
return Equals1(other);
|
||||
}
|
||||
|
||||
public MethodInfo Target => _myTarget;
|
||||
}
|
||||
|
||||
internal class ShortCircuitInfo
|
||||
{
|
||||
|
||||
public Stack Operands;
|
||||
public Stack Operators;
|
||||
private Dictionary<object, Label> Labels;
|
||||
|
||||
public ShortCircuitInfo()
|
||||
{
|
||||
this.Operands = new Stack();
|
||||
this.Operators = new Stack();
|
||||
this.Labels = new Dictionary<object, Label>();
|
||||
}
|
||||
|
||||
public void ClearTempState()
|
||||
{
|
||||
this.Operands.Clear();
|
||||
this.Operators.Clear();
|
||||
}
|
||||
|
||||
public Label AddLabel(object key, Label lbl)
|
||||
{
|
||||
Labels.Add(key, lbl);
|
||||
return lbl;
|
||||
}
|
||||
|
||||
public bool HasLabel(object key)
|
||||
{
|
||||
return Labels.ContainsKey(key);
|
||||
}
|
||||
|
||||
public Label FindLabel(object key)
|
||||
{
|
||||
return Labels[key];
|
||||
}
|
||||
}
|
||||
|
||||
[Obsolete("Wraps an expression element so that it is loaded from a local slot")]
|
||||
internal class LocalBasedElement : ExpressionElement
|
||||
{
|
||||
private readonly int _myIndex;
|
||||
|
||||
private readonly ExpressionElement _myTarget;
|
||||
public LocalBasedElement(ExpressionElement target, int index)
|
||||
{
|
||||
_myTarget = target;
|
||||
_myIndex = index;
|
||||
}
|
||||
|
||||
public override void Emit(FleeILGenerator ilg, IServiceProvider services)
|
||||
{
|
||||
Utility.EmitLoadLocal(ilg, _myIndex);
|
||||
}
|
||||
|
||||
public override System.Type ResultType => _myTarget.ResultType;
|
||||
}
|
||||
|
||||
[Obsolete("Helper class for storing strongly-typed properties")]
|
||||
internal class PropertyDictionary
|
||||
{
|
||||
private readonly Dictionary<string, object> _myProperties;
|
||||
public PropertyDictionary()
|
||||
{
|
||||
_myProperties = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
public PropertyDictionary Clone()
|
||||
{
|
||||
PropertyDictionary copy = new PropertyDictionary();
|
||||
|
||||
foreach (KeyValuePair<string, object> pair in _myProperties)
|
||||
{
|
||||
copy.SetValue(pair.Key, pair.Value);
|
||||
}
|
||||
|
||||
return copy;
|
||||
}
|
||||
|
||||
public T GetValue<T>(string name)
|
||||
{
|
||||
object value = default(T);
|
||||
if (_myProperties.TryGetValue(name, out value) == false)
|
||||
{
|
||||
Debug.Fail($"Unknown property '{name}'");
|
||||
}
|
||||
return (T)value;
|
||||
}
|
||||
|
||||
public void SetToDefault<T>(string name)
|
||||
{
|
||||
T value = default(T);
|
||||
this.SetValue(name, value);
|
||||
}
|
||||
|
||||
public void SetValue(string name, object value)
|
||||
{
|
||||
_myProperties[name] = value;
|
||||
}
|
||||
|
||||
public bool Contains(string name)
|
||||
{
|
||||
return _myProperties.ContainsKey(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
350
InternalTypes/Utility.cs
Normal file
350
InternalTypes/Utility.cs
Normal file
@@ -0,0 +1,350 @@
|
||||
using System.Collections;
|
||||
using System.Diagnostics;
|
||||
using System.Reflection;
|
||||
using System.Reflection.Emit;
|
||||
using Flee.Resources;
|
||||
|
||||
namespace Flee.InternalTypes
|
||||
{
|
||||
[Obsolete("Holds various shared utility methods")]
|
||||
internal class Utility
|
||||
{
|
||||
private Utility()
|
||||
{
|
||||
}
|
||||
|
||||
public static void AssertNotNull(object o, string paramName)
|
||||
{
|
||||
if (o == null)
|
||||
{
|
||||
throw new ArgumentNullException(paramName);
|
||||
}
|
||||
}
|
||||
|
||||
public static void EmitStoreLocal(FleeILGenerator ilg, int index)
|
||||
{
|
||||
if (index >= 0 & index <= 3)
|
||||
{
|
||||
switch (index)
|
||||
{
|
||||
case 0:
|
||||
ilg.Emit(OpCodes.Stloc_0);
|
||||
break;
|
||||
case 1:
|
||||
ilg.Emit(OpCodes.Stloc_1);
|
||||
break;
|
||||
case 2:
|
||||
ilg.Emit(OpCodes.Stloc_2);
|
||||
break;
|
||||
case 3:
|
||||
ilg.Emit(OpCodes.Stloc_3);
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (index < 256)
|
||||
{
|
||||
ilg.Emit(OpCodes.Stloc_S, Convert.ToByte(index));
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Assert(index < 65535, "local index too large");
|
||||
ilg.Emit(OpCodes.Stloc, unchecked((short)Convert.ToUInt16(index)));
|
||||
}
|
||||
}
|
||||
|
||||
public static void EmitLoadLocal(FleeILGenerator ilg, int index)
|
||||
{
|
||||
Debug.Assert(index >= 0, "Invalid index");
|
||||
|
||||
if (index >= 0 & index <= 3)
|
||||
{
|
||||
switch (index)
|
||||
{
|
||||
case 0:
|
||||
ilg.Emit(OpCodes.Ldloc_0);
|
||||
break;
|
||||
case 1:
|
||||
ilg.Emit(OpCodes.Ldloc_1);
|
||||
break;
|
||||
case 2:
|
||||
ilg.Emit(OpCodes.Ldloc_2);
|
||||
break;
|
||||
case 3:
|
||||
ilg.Emit(OpCodes.Ldloc_3);
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (index < 256)
|
||||
{
|
||||
ilg.Emit(OpCodes.Ldloc_S, Convert.ToByte(index));
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Assert(index < 65535, "local index too large");
|
||||
ilg.Emit(OpCodes.Ldloc, unchecked((short)Convert.ToUInt16(index)));
|
||||
}
|
||||
}
|
||||
|
||||
public static void EmitLoadLocalAddress(FleeILGenerator ilg, int index)
|
||||
{
|
||||
Debug.Assert(index >= 0, "Invalid index");
|
||||
|
||||
if (index <= byte.MaxValue)
|
||||
{
|
||||
ilg.Emit(OpCodes.Ldloca_S, Convert.ToByte(index));
|
||||
}
|
||||
else
|
||||
{
|
||||
ilg.Emit(OpCodes.Ldloca, index);
|
||||
}
|
||||
}
|
||||
|
||||
public static void EmitArrayLoad(FleeILGenerator ilg, Type elementType)
|
||||
{
|
||||
TypeCode tc = Type.GetTypeCode(elementType);
|
||||
|
||||
switch (tc)
|
||||
{
|
||||
case TypeCode.Byte:
|
||||
ilg.Emit(OpCodes.Ldelem_U1);
|
||||
break;
|
||||
case TypeCode.SByte:
|
||||
case TypeCode.Boolean:
|
||||
ilg.Emit(OpCodes.Ldelem_I1);
|
||||
break;
|
||||
case TypeCode.Int16:
|
||||
ilg.Emit(OpCodes.Ldelem_I2);
|
||||
break;
|
||||
case TypeCode.UInt16:
|
||||
ilg.Emit(OpCodes.Ldelem_U2);
|
||||
break;
|
||||
case TypeCode.Int32:
|
||||
ilg.Emit(OpCodes.Ldelem_I4);
|
||||
break;
|
||||
case TypeCode.UInt32:
|
||||
ilg.Emit(OpCodes.Ldelem_U4);
|
||||
break;
|
||||
case TypeCode.Int64:
|
||||
case TypeCode.UInt64:
|
||||
ilg.Emit(OpCodes.Ldelem_I8);
|
||||
break;
|
||||
case TypeCode.Single:
|
||||
ilg.Emit(OpCodes.Ldelem_R4);
|
||||
break;
|
||||
case TypeCode.Double:
|
||||
ilg.Emit(OpCodes.Ldelem_R8);
|
||||
break;
|
||||
case TypeCode.Object:
|
||||
case TypeCode.String:
|
||||
ilg.Emit(OpCodes.Ldelem_Ref);
|
||||
break;
|
||||
default:
|
||||
// Must be a non-primitive value type
|
||||
ilg.Emit(OpCodes.Ldelema, elementType);
|
||||
ilg.Emit(OpCodes.Ldobj, elementType);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
public static void EmitArrayStore(FleeILGenerator ilg, Type elementType)
|
||||
{
|
||||
TypeCode tc = Type.GetTypeCode(elementType);
|
||||
|
||||
switch (tc)
|
||||
{
|
||||
case TypeCode.Byte:
|
||||
case TypeCode.SByte:
|
||||
case TypeCode.Boolean:
|
||||
ilg.Emit(OpCodes.Stelem_I1);
|
||||
break;
|
||||
case TypeCode.Int16:
|
||||
case TypeCode.UInt16:
|
||||
ilg.Emit(OpCodes.Stelem_I2);
|
||||
break;
|
||||
case TypeCode.Int32:
|
||||
case TypeCode.UInt32:
|
||||
ilg.Emit(OpCodes.Stelem_I4);
|
||||
break;
|
||||
case TypeCode.Int64:
|
||||
case TypeCode.UInt64:
|
||||
ilg.Emit(OpCodes.Stelem_I8);
|
||||
break;
|
||||
case TypeCode.Single:
|
||||
ilg.Emit(OpCodes.Stelem_R4);
|
||||
break;
|
||||
case TypeCode.Double:
|
||||
ilg.Emit(OpCodes.Stelem_R8);
|
||||
break;
|
||||
case TypeCode.Object:
|
||||
case TypeCode.String:
|
||||
ilg.Emit(OpCodes.Stelem_Ref);
|
||||
break;
|
||||
default:
|
||||
// Must be a non-primitive value type
|
||||
ilg.Emit(OpCodes.Stelem, elementType);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static bool IsIntegralType(Type t)
|
||||
{
|
||||
TypeCode tc = Type.GetTypeCode(t);
|
||||
switch (tc)
|
||||
{
|
||||
case TypeCode.Byte:
|
||||
case TypeCode.SByte:
|
||||
case TypeCode.Int16:
|
||||
case TypeCode.UInt16:
|
||||
case TypeCode.Int32:
|
||||
case TypeCode.UInt32:
|
||||
case TypeCode.Int64:
|
||||
case TypeCode.UInt64:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static Type GetBitwiseOpType(Type leftType, Type rightType)
|
||||
{
|
||||
if (IsIntegralType(leftType) == false || IsIntegralType(rightType) == false)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
else
|
||||
{
|
||||
return ImplicitConverter.GetBinaryResultType(leftType, rightType);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Find a simple (unary) overloaded operator
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the operator</param>
|
||||
/// <param name="sourceType">The type to convert from</param>
|
||||
/// <param name="destType">The type to convert to (can be null if it's not known beforehand)</param>
|
||||
/// <returns>The operator's method or null of no match is found</returns>
|
||||
public static MethodInfo GetSimpleOverloadedOperator(string name, Type sourceType, Type destType)
|
||||
{
|
||||
Hashtable data = new Hashtable();
|
||||
data.Add("Name", string.Concat("op_", name));
|
||||
data.Add("sourceType", sourceType);
|
||||
data.Add("destType", destType);
|
||||
|
||||
const BindingFlags flags = BindingFlags.Public | BindingFlags.Static;
|
||||
|
||||
// Look on the source type and its ancestors
|
||||
MemberInfo[] members = new MemberInfo[0];
|
||||
do
|
||||
{
|
||||
members = sourceType.FindMembers(MemberTypes.Method, flags, SimpleOverloadedOperatorFilter, data);
|
||||
} while (members.Length == 0 && (sourceType = sourceType.BaseType) != null);
|
||||
|
||||
if (members.Length == 0 && destType != null)
|
||||
{
|
||||
// Look on the dest type and its ancestors
|
||||
do
|
||||
{
|
||||
members = destType.FindMembers(MemberTypes.Method, flags, SimpleOverloadedOperatorFilter, data);
|
||||
} while (members.Length == 0 && (destType = destType.BaseType) != null);
|
||||
}
|
||||
|
||||
Debug.Assert(members.Length < 2, "Multiple overloaded operators found");
|
||||
|
||||
if (members.Length == 0)
|
||||
{
|
||||
// No match
|
||||
return null;
|
||||
}
|
||||
else
|
||||
{
|
||||
return (MethodInfo)members[0];
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Matches simple overloaded operators
|
||||
/// </summary>
|
||||
/// <param name="member"></param>
|
||||
/// <param name="value"></param>
|
||||
/// <returns></returns>
|
||||
/// <remarks></remarks>
|
||||
private static bool SimpleOverloadedOperatorFilter(MemberInfo member, object value)
|
||||
{
|
||||
IDictionary data = (IDictionary)value;
|
||||
MethodInfo method = (MethodInfo)member;
|
||||
|
||||
bool nameMatch = method.IsSpecialName == true && method.Name.Equals((string)data["Name"], StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
if (nameMatch == false)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// destination type might not be known
|
||||
Type destType = (Type)data["destType"];
|
||||
|
||||
if (destType != null)
|
||||
{
|
||||
bool returnTypeMatch = object.ReferenceEquals(destType, method.ReturnType);
|
||||
|
||||
if (returnTypeMatch == false)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
ParameterInfo[] parameters = method.GetParameters();
|
||||
bool argumentMatch = parameters.Length > 0 && parameters[0].ParameterType.IsAssignableFrom((Type)data["sourceType"]);
|
||||
|
||||
return argumentMatch;
|
||||
}
|
||||
|
||||
public static MethodInfo GetOverloadedOperator(string name, Type sourceType, Binder binder, params Type[] argumentTypes)
|
||||
{
|
||||
name = string.Concat("op_", name);
|
||||
MethodInfo mi = null;
|
||||
do
|
||||
{
|
||||
mi = sourceType.GetMethod(name, BindingFlags.Public | BindingFlags.Static, binder, CallingConventions.Any, argumentTypes, null);
|
||||
if (mi != null && mi.IsSpecialName == true)
|
||||
{
|
||||
return mi;
|
||||
}
|
||||
} while ((sourceType = sourceType.BaseType) != null);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static int GetILGeneratorLength(ILGenerator ilg)
|
||||
{
|
||||
System.Reflection.FieldInfo fi = typeof(ILGenerator).GetField("m_length", BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
return (int)fi.GetValue(ilg);
|
||||
}
|
||||
|
||||
public static bool IsLongBranch(int startPosition, int endPosition)
|
||||
{
|
||||
return (endPosition - startPosition) > sbyte.MaxValue;
|
||||
}
|
||||
|
||||
public static string FormatList(string[] items)
|
||||
{
|
||||
string separator = System.Globalization.CultureInfo.CurrentCulture.TextInfo.ListSeparator + " ";
|
||||
return string.Join(separator, items);
|
||||
}
|
||||
|
||||
public static string GetGeneralErrorMessage(string key, params object[] args)
|
||||
{
|
||||
string msg = FleeResourceManager.Instance.GetGeneralErrorString(key);
|
||||
return string.Format(msg, args);
|
||||
}
|
||||
|
||||
public static string GetCompileErrorMessage(string key, params object[] args)
|
||||
{
|
||||
string msg = FleeResourceManager.Instance.GetCompileErrorString(key);
|
||||
return string.Format(msg, args);
|
||||
}
|
||||
}
|
||||
}
|
||||
100
InternalTypes/VariableTypes.cs
Normal file
100
InternalTypes/VariableTypes.cs
Normal file
@@ -0,0 +1,100 @@
|
||||
using Flee.PublicTypes;
|
||||
|
||||
namespace Flee.InternalTypes
|
||||
{
|
||||
internal interface IVariable
|
||||
{
|
||||
IVariable Clone();
|
||||
Type VariableType { get; }
|
||||
object ValueAsObject { get; set; }
|
||||
}
|
||||
|
||||
internal interface IGenericVariable<T>
|
||||
{
|
||||
object GetValue();
|
||||
}
|
||||
|
||||
internal class DynamicExpressionVariable<T> : IVariable, IGenericVariable<T>
|
||||
{
|
||||
private IDynamicExpression _myExpression;
|
||||
public IVariable Clone()
|
||||
{
|
||||
DynamicExpressionVariable<T> copy = new DynamicExpressionVariable<T>();
|
||||
copy._myExpression = _myExpression;
|
||||
return copy;
|
||||
}
|
||||
|
||||
public object GetValue()
|
||||
{
|
||||
return (T)_myExpression.Evaluate();
|
||||
}
|
||||
|
||||
public object ValueAsObject
|
||||
{
|
||||
get { return _myExpression; }
|
||||
set { _myExpression = value as IDynamicExpression; }
|
||||
}
|
||||
|
||||
public System.Type VariableType => _myExpression.Context.Options.ResultType;
|
||||
}
|
||||
|
||||
internal class GenericExpressionVariable<T> : IVariable, IGenericVariable<T>
|
||||
{
|
||||
private IGenericExpression<T> _myExpression;
|
||||
public IVariable Clone()
|
||||
{
|
||||
GenericExpressionVariable<T> copy = new GenericExpressionVariable<T>();
|
||||
copy._myExpression = _myExpression;
|
||||
return copy;
|
||||
}
|
||||
|
||||
public object GetValue()
|
||||
{
|
||||
return _myExpression.Evaluate();
|
||||
}
|
||||
|
||||
public object ValueAsObject
|
||||
{
|
||||
get { return _myExpression; }
|
||||
set { _myExpression = (IGenericExpression<T>)value; }
|
||||
}
|
||||
|
||||
public System.Type VariableType => _myExpression.Context.Options.ResultType;
|
||||
}
|
||||
|
||||
internal class GenericVariable<T> : IVariable, IGenericVariable<T>
|
||||
{
|
||||
|
||||
|
||||
public object MyValue;
|
||||
public IVariable Clone()
|
||||
{
|
||||
GenericVariable<T> copy = new GenericVariable<T> { MyValue = MyValue };
|
||||
return copy;
|
||||
}
|
||||
|
||||
public object GetValue()
|
||||
{
|
||||
return MyValue;
|
||||
}
|
||||
|
||||
public System.Type VariableType => typeof(T);
|
||||
|
||||
public object ValueAsObject
|
||||
{
|
||||
get { return MyValue; }
|
||||
set
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
MyValue = default(T);
|
||||
}
|
||||
else
|
||||
{
|
||||
MyValue = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
60
Parsing/AlternativeElement.cs
Normal file
60
Parsing/AlternativeElement.cs
Normal file
@@ -0,0 +1,60 @@
|
||||
namespace Flee.Parsing
|
||||
{
|
||||
/**
|
||||
* A regular expression alternative element. This element matches
|
||||
* the longest alternative element.
|
||||
*/
|
||||
internal class AlternativeElement : Element
|
||||
{
|
||||
private readonly Element _elem1;
|
||||
private readonly Element _elem2;
|
||||
|
||||
public AlternativeElement(Element first, Element second)
|
||||
{
|
||||
_elem1 = first;
|
||||
_elem2 = second;
|
||||
}
|
||||
|
||||
public override object Clone()
|
||||
{
|
||||
return new AlternativeElement(_elem1, _elem2);
|
||||
}
|
||||
|
||||
public override int Match(Matcher m,
|
||||
ReaderBuffer buffer,
|
||||
int start,
|
||||
int skip)
|
||||
{
|
||||
int length = 0;
|
||||
int length1 = -1;
|
||||
int length2 = -1;
|
||||
int skip1 = 0;
|
||||
int skip2 = 0;
|
||||
|
||||
while (length >= 0 && skip1 + skip2 <= skip)
|
||||
{
|
||||
length1 = _elem1.Match(m, buffer, start, skip1);
|
||||
length2 = _elem2.Match(m, buffer, start, skip2);
|
||||
if (length1 >= length2)
|
||||
{
|
||||
length = length1;
|
||||
skip1++;
|
||||
}
|
||||
else
|
||||
{
|
||||
length = length2;
|
||||
skip2++;
|
||||
}
|
||||
}
|
||||
return length;
|
||||
}
|
||||
|
||||
public override void PrintTo(TextWriter output, string indent)
|
||||
{
|
||||
output.WriteLine(indent + "Alternative 1");
|
||||
_elem1.PrintTo(output, indent + " ");
|
||||
output.WriteLine(indent + "Alternative 2");
|
||||
_elem2.PrintTo(output, indent + " ");
|
||||
}
|
||||
}
|
||||
}
|
||||
240
Parsing/Analyzer.cs
Normal file
240
Parsing/Analyzer.cs
Normal file
@@ -0,0 +1,240 @@
|
||||
using System.Collections;
|
||||
|
||||
namespace Flee.Parsing
|
||||
{
|
||||
[Obsolete("Creates a new parse tree analyzer.")]
|
||||
internal class Analyzer
|
||||
{
|
||||
public Analyzer()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets this analyzer when the parser is reset for another
|
||||
///input stream.The default implementation of this method does
|
||||
/// nothing.
|
||||
/// </summary>
|
||||
public virtual void Reset()
|
||||
{
|
||||
// Default implementation does nothing
|
||||
}
|
||||
|
||||
public Node Analyze(Node node)
|
||||
{
|
||||
ParserLogException log = new ParserLogException();
|
||||
|
||||
node = Analyze(node, log);
|
||||
if (log.Count > 0)
|
||||
{
|
||||
throw log;
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
private Node Analyze(Node node, ParserLogException log)
|
||||
{
|
||||
var errorCount = log.Count;
|
||||
if (node is Production)
|
||||
{
|
||||
var prod = (Production)node;
|
||||
prod = NewProduction(prod.Pattern);
|
||||
try
|
||||
{
|
||||
Enter(prod);
|
||||
}
|
||||
catch (ParseException e)
|
||||
{
|
||||
log.AddError(e);
|
||||
}
|
||||
for (int i = 0; i < node.Count; i++)
|
||||
{
|
||||
try
|
||||
{
|
||||
Child(prod, Analyze(node[i], log));
|
||||
}
|
||||
catch (ParseException e)
|
||||
{
|
||||
log.AddError(e);
|
||||
}
|
||||
}
|
||||
try
|
||||
{
|
||||
return Exit(prod);
|
||||
}
|
||||
catch (ParseException e)
|
||||
{
|
||||
if (errorCount == log.Count)
|
||||
{
|
||||
log.AddError(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
node.Values.Clear();
|
||||
try
|
||||
{
|
||||
Enter(node);
|
||||
}
|
||||
catch (ParseException e)
|
||||
{
|
||||
log.AddError(e);
|
||||
}
|
||||
try
|
||||
{
|
||||
return Exit(node);
|
||||
}
|
||||
catch (ParseException e)
|
||||
{
|
||||
if (errorCount == log.Count)
|
||||
{
|
||||
log.AddError(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public virtual Production NewProduction(ProductionPattern pattern)
|
||||
{
|
||||
return new Production(pattern);
|
||||
}
|
||||
|
||||
public virtual void Enter(Node node)
|
||||
{
|
||||
}
|
||||
|
||||
public virtual Node Exit(Node node)
|
||||
{
|
||||
return node;
|
||||
}
|
||||
|
||||
public virtual void Child(Production node, Node child)
|
||||
{
|
||||
node.AddChild(child);
|
||||
}
|
||||
|
||||
protected Node GetChildAt(Node node, int pos)
|
||||
{
|
||||
if (node == null)
|
||||
{
|
||||
throw new ParseException(
|
||||
ParseException.ErrorType.INTERNAL,
|
||||
"attempt to read 'null' parse tree node",
|
||||
-1,
|
||||
-1);
|
||||
}
|
||||
var child = node[pos];
|
||||
if (child == null)
|
||||
{
|
||||
throw new ParseException(
|
||||
ParseException.ErrorType.INTERNAL,
|
||||
"node '" + node.Name + "' has no child at " +
|
||||
"position " + pos,
|
||||
node.StartLine,
|
||||
node.StartColumn);
|
||||
}
|
||||
return child;
|
||||
}
|
||||
|
||||
protected Node GetChildWithId(Node node, int id)
|
||||
{
|
||||
if (node == null)
|
||||
{
|
||||
throw new ParseException(
|
||||
ParseException.ErrorType.INTERNAL,
|
||||
"attempt to read 'null' parse tree node",
|
||||
-1,
|
||||
-1);
|
||||
}
|
||||
for (int i = 0; i < node.Count; i++)
|
||||
{
|
||||
var child = node[i];
|
||||
if (child != null && child.Id == id)
|
||||
{
|
||||
return child;
|
||||
}
|
||||
}
|
||||
throw new ParseException(
|
||||
ParseException.ErrorType.INTERNAL,
|
||||
"node '" + node.Name + "' has no child with id " + id,
|
||||
node.StartLine,
|
||||
node.StartColumn);
|
||||
}
|
||||
|
||||
protected object GetValue(Node node, int pos)
|
||||
{
|
||||
if (node == null)
|
||||
{
|
||||
throw new ParseException(
|
||||
ParseException.ErrorType.INTERNAL,
|
||||
"attempt to read 'null' parse tree node",
|
||||
-1,
|
||||
-1);
|
||||
}
|
||||
var value = node.Values[pos];
|
||||
if (value == null)
|
||||
{
|
||||
throw new ParseException(
|
||||
ParseException.ErrorType.INTERNAL,
|
||||
"node '" + node.Name + "' has no value at " +
|
||||
"position " + pos,
|
||||
node.StartLine,
|
||||
node.StartColumn);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
protected int GetIntValue(Node node, int pos)
|
||||
{
|
||||
var value = GetValue(node, pos);
|
||||
if (value is int)
|
||||
{
|
||||
return (int)value;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ParseException(
|
||||
ParseException.ErrorType.INTERNAL,
|
||||
"node '" + node.Name + "' has no integer value " +
|
||||
"at position " + pos,
|
||||
node.StartLine,
|
||||
node.StartColumn);
|
||||
}
|
||||
}
|
||||
|
||||
protected string GetStringValue(Node node, int pos)
|
||||
{
|
||||
var value = GetValue(node, pos);
|
||||
if (value is string)
|
||||
{
|
||||
return (string)value;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ParseException(
|
||||
ParseException.ErrorType.INTERNAL,
|
||||
"node '" + node.Name + "' has no string value " +
|
||||
"at position " + pos,
|
||||
node.StartLine,
|
||||
node.StartColumn);
|
||||
}
|
||||
}
|
||||
|
||||
protected ArrayList GetChildValues(Node node)
|
||||
{
|
||||
ArrayList result = new ArrayList();
|
||||
|
||||
for (int i = 0; i < node.Count; i++)
|
||||
{
|
||||
var child = node[i];
|
||||
var values = child.Values;
|
||||
if (values != null)
|
||||
{
|
||||
result.AddRange(values);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
111
Parsing/Automaton.cs
Normal file
111
Parsing/Automaton.cs
Normal file
@@ -0,0 +1,111 @@
|
||||
namespace Flee.Parsing
|
||||
{
|
||||
internal class Automaton
|
||||
{
|
||||
private object _value;
|
||||
private readonly AutomatonTree _tree = new AutomatonTree();
|
||||
|
||||
public Automaton()
|
||||
{
|
||||
}
|
||||
|
||||
public void AddMatch(string str, bool caseInsensitive, object value)
|
||||
{
|
||||
if (str.Length == 0)
|
||||
{
|
||||
this._value = value;
|
||||
}
|
||||
else
|
||||
{
|
||||
var state = _tree.Find(str[0], caseInsensitive);
|
||||
if (state == null)
|
||||
{
|
||||
state = new Automaton();
|
||||
state.AddMatch(str.Substring(1), caseInsensitive, value);
|
||||
_tree.Add(str[0], caseInsensitive, state);
|
||||
}
|
||||
else
|
||||
{
|
||||
state.AddMatch(str.Substring(1), caseInsensitive, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public object MatchFrom(LookAheadReader input, int pos, bool caseInsensitive)
|
||||
{
|
||||
|
||||
object result = null;
|
||||
Automaton state = null;
|
||||
int c = 0;
|
||||
|
||||
c = input.Peek(pos);
|
||||
if (_tree != null && c >= 0)
|
||||
{
|
||||
state = _tree.Find(Convert.ToChar(c), caseInsensitive);
|
||||
if (state != null)
|
||||
{
|
||||
result = state.MatchFrom(input, pos + 1, caseInsensitive);
|
||||
}
|
||||
}
|
||||
return result ?? _value;
|
||||
}
|
||||
}
|
||||
|
||||
// * An automaton state transition tree. This class contains a
|
||||
// * binary search tree for the automaton transitions from one state
|
||||
// * to another. All transitions are linked to a single character.
|
||||
internal class AutomatonTree
|
||||
{
|
||||
private char _value;
|
||||
private Automaton _state;
|
||||
private AutomatonTree _left;
|
||||
private AutomatonTree _right;
|
||||
|
||||
public AutomatonTree()
|
||||
{
|
||||
}
|
||||
|
||||
public Automaton Find(char c, bool lowerCase)
|
||||
{
|
||||
if (lowerCase)
|
||||
{
|
||||
c = Char.ToLower(c);
|
||||
}
|
||||
if (_value == (char)0 || _value == c)
|
||||
{
|
||||
return _state;
|
||||
}
|
||||
else if (_value > c)
|
||||
{
|
||||
return _left.Find(c, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
return _right.Find(c, false);
|
||||
}
|
||||
}
|
||||
|
||||
public void Add(char c, bool lowerCase, Automaton state)
|
||||
{
|
||||
if (lowerCase)
|
||||
{
|
||||
c = Char.ToLower(c);
|
||||
}
|
||||
if (_value == (char)0)
|
||||
{
|
||||
this._value = c;
|
||||
this._state = state;
|
||||
this._left = new AutomatonTree();
|
||||
this._right = new AutomatonTree();
|
||||
}
|
||||
else if (_value > c)
|
||||
{
|
||||
_left.Add(c, false, state);
|
||||
}
|
||||
else
|
||||
{
|
||||
_right.Add(c, false, state);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
267
Parsing/CharacterSetElement.cs
Normal file
267
Parsing/CharacterSetElement.cs
Normal file
@@ -0,0 +1,267 @@
|
||||
using System.Collections;
|
||||
using System.Text;
|
||||
|
||||
namespace Flee.Parsing
|
||||
{
|
||||
/**
|
||||
* A regular expression character set element. This element
|
||||
* matches a single character inside (or outside) a character set.
|
||||
* The character set is user defined and may contain ranges of
|
||||
* characters. The set may also be inverted, meaning that only
|
||||
* characters not inside the set will be considered to match.
|
||||
*/
|
||||
internal class CharacterSetElement : Element
|
||||
{
|
||||
public static CharacterSetElement Dot = new CharacterSetElement(false);
|
||||
public static CharacterSetElement Digit = new CharacterSetElement(false);
|
||||
public static CharacterSetElement NonDigit = new CharacterSetElement(true);
|
||||
public static CharacterSetElement Whitespace = new CharacterSetElement(false);
|
||||
public static CharacterSetElement NonWhitespace = new CharacterSetElement(true);
|
||||
public static CharacterSetElement Word = new CharacterSetElement(false);
|
||||
public static CharacterSetElement NonWord = new CharacterSetElement(true);
|
||||
private readonly bool _inverted;
|
||||
private readonly ArrayList _contents = new ArrayList();
|
||||
|
||||
public CharacterSetElement(bool inverted)
|
||||
{
|
||||
this._inverted = inverted;
|
||||
}
|
||||
|
||||
public void AddCharacter(char c)
|
||||
{
|
||||
_contents.Add(c);
|
||||
}
|
||||
|
||||
public void AddCharacters(string str)
|
||||
{
|
||||
for (int i = 0; i < str.Length; i++)
|
||||
{
|
||||
AddCharacter(str[i]);
|
||||
}
|
||||
}
|
||||
|
||||
public void AddCharacters(StringElement elem)
|
||||
{
|
||||
AddCharacters(elem.GetString());
|
||||
}
|
||||
|
||||
public void AddRange(char min, char max)
|
||||
{
|
||||
_contents.Add(new Range(min, max));
|
||||
}
|
||||
|
||||
public void AddCharacterSet(CharacterSetElement elem)
|
||||
{
|
||||
_contents.Add(elem);
|
||||
}
|
||||
|
||||
public override object Clone()
|
||||
{
|
||||
return this;
|
||||
}
|
||||
|
||||
public override int Match(Matcher m,
|
||||
ReaderBuffer buffer,
|
||||
int start,
|
||||
int skip)
|
||||
{
|
||||
|
||||
int c;
|
||||
|
||||
if (skip != 0)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
c = buffer.Peek(start);
|
||||
if (c < 0)
|
||||
{
|
||||
m.SetReadEndOfString();
|
||||
return -1;
|
||||
}
|
||||
if (m.IsCaseInsensitive())
|
||||
{
|
||||
c = (int)Char.ToLower((char)c);
|
||||
}
|
||||
return InSet((char)c) ? 1 : -1;
|
||||
}
|
||||
|
||||
private bool InSet(char c)
|
||||
{
|
||||
if (this == Dot)
|
||||
{
|
||||
return InDotSet(c);
|
||||
}
|
||||
else if (this == Digit || this == NonDigit)
|
||||
{
|
||||
return InDigitSet(c) != _inverted;
|
||||
}
|
||||
else if (this == Whitespace || this == NonWhitespace)
|
||||
{
|
||||
return InWhitespaceSet(c) != _inverted;
|
||||
}
|
||||
else if (this == Word || this == NonWord)
|
||||
{
|
||||
return InWordSet(c) != _inverted;
|
||||
}
|
||||
else
|
||||
{
|
||||
return InUserSet(c) != _inverted;
|
||||
}
|
||||
}
|
||||
|
||||
private bool InDotSet(char c)
|
||||
{
|
||||
switch (c)
|
||||
{
|
||||
case '\n':
|
||||
case '\r':
|
||||
case '\u0085':
|
||||
case '\u2028':
|
||||
case '\u2029':
|
||||
return false;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private bool InDigitSet(char c)
|
||||
{
|
||||
return '0' <= c && c <= '9';
|
||||
}
|
||||
|
||||
private bool InWhitespaceSet(char c)
|
||||
{
|
||||
switch (c)
|
||||
{
|
||||
case ' ':
|
||||
case '\t':
|
||||
case '\n':
|
||||
case '\f':
|
||||
case '\r':
|
||||
case (char)11:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private bool InWordSet(char c)
|
||||
{
|
||||
return ('a' <= c && c <= 'z')
|
||||
|| ('A' <= c && c <= 'Z')
|
||||
|| ('0' <= c && c <= '9')
|
||||
|| c == '_';
|
||||
}
|
||||
|
||||
private bool InUserSet(char value)
|
||||
{
|
||||
for (int i = 0; i < _contents.Count; i++)
|
||||
{
|
||||
var obj = _contents[i];
|
||||
if (obj is char)
|
||||
{
|
||||
var c = (char)obj;
|
||||
if (c == value)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if (obj is Range)
|
||||
{
|
||||
var r = (Range)obj;
|
||||
if (r.Inside(value))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if (obj is CharacterSetElement)
|
||||
{
|
||||
var e = (CharacterSetElement)obj;
|
||||
if (e.InSet(value))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public override void PrintTo(TextWriter output, string indent)
|
||||
{
|
||||
output.WriteLine(indent + ToString());
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
// Handle predefined character sets
|
||||
if (this == Dot)
|
||||
{
|
||||
return ".";
|
||||
}
|
||||
else if (this == Digit)
|
||||
{
|
||||
return "\\d";
|
||||
}
|
||||
else if (this == NonDigit)
|
||||
{
|
||||
return "\\D";
|
||||
}
|
||||
else if (this == Whitespace)
|
||||
{
|
||||
return "\\s";
|
||||
}
|
||||
else if (this == NonWhitespace)
|
||||
{
|
||||
return "\\S";
|
||||
}
|
||||
else if (this == Word)
|
||||
{
|
||||
return "\\w";
|
||||
}
|
||||
else if (this == NonWord)
|
||||
{
|
||||
return "\\W";
|
||||
}
|
||||
|
||||
// Handle user-defined character sets
|
||||
var buffer = new StringBuilder();
|
||||
if (_inverted)
|
||||
{
|
||||
buffer.Append("^[");
|
||||
}
|
||||
else
|
||||
{
|
||||
buffer.Append("[");
|
||||
}
|
||||
for (int i = 0; i < _contents.Count; i++)
|
||||
{
|
||||
buffer.Append(_contents[i]);
|
||||
}
|
||||
buffer.Append("]");
|
||||
|
||||
return buffer.ToString();
|
||||
}
|
||||
|
||||
private class Range
|
||||
{
|
||||
private readonly char _min;
|
||||
private readonly char _max;
|
||||
|
||||
public Range(char min, char max)
|
||||
{
|
||||
this._min = min;
|
||||
this._max = max;
|
||||
}
|
||||
|
||||
public bool Inside(char c)
|
||||
{
|
||||
return _min <= c && c <= _max;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return _min + "-" + _max;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
58
Parsing/CombineElement.cs
Normal file
58
Parsing/CombineElement.cs
Normal file
@@ -0,0 +1,58 @@
|
||||
namespace Flee.Parsing
|
||||
{
|
||||
internal class CombineElement : Element
|
||||
{
|
||||
private readonly Element _elem1;
|
||||
private readonly Element _elem2;
|
||||
|
||||
public CombineElement(Element first, Element second)
|
||||
{
|
||||
_elem1 = first;
|
||||
_elem2 = second;
|
||||
}
|
||||
|
||||
public override object Clone()
|
||||
{
|
||||
return new CombineElement(_elem1, _elem2);
|
||||
}
|
||||
|
||||
public override int Match(Matcher m,
|
||||
ReaderBuffer buffer,
|
||||
int start,
|
||||
int skip)
|
||||
{
|
||||
int length1 = -1;
|
||||
int length2 = 0;
|
||||
int skip1 = 0;
|
||||
int skip2 = 0;
|
||||
|
||||
while (skip >= 0)
|
||||
{
|
||||
length1 = _elem1.Match(m, buffer, start, skip1);
|
||||
if (length1 < 0)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
length2 = _elem2.Match(m, buffer, start + length1, skip2);
|
||||
if (length2 < 0)
|
||||
{
|
||||
skip1++;
|
||||
skip2 = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
skip2++;
|
||||
skip--;
|
||||
}
|
||||
}
|
||||
|
||||
return length1 + length2;
|
||||
}
|
||||
|
||||
public override void PrintTo(TextWriter output, string indent)
|
||||
{
|
||||
_elem1.PrintTo(output, indent);
|
||||
_elem2.PrintTo(output, indent);
|
||||
}
|
||||
}
|
||||
}
|
||||
596
Parsing/CustomExpressionAnalyzer.cs
Normal file
596
Parsing/CustomExpressionAnalyzer.cs
Normal file
@@ -0,0 +1,596 @@
|
||||
using System.Collections;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.Text.RegularExpressions;
|
||||
using Flee.ExpressionElements;
|
||||
using Flee.ExpressionElements.Base;
|
||||
using Flee.ExpressionElements.Base.Literals;
|
||||
using Flee.ExpressionElements.Literals;
|
||||
using Flee.ExpressionElements.Literals.Integral;
|
||||
using Flee.ExpressionElements.LogicalBitwise;
|
||||
using Flee.ExpressionElements.MemberElements;
|
||||
using Flee.InternalTypes;
|
||||
using Flee.PublicTypes;
|
||||
|
||||
namespace Flee.Parsing
|
||||
{
|
||||
internal class FleeExpressionAnalyzer : ExpressionAnalyzer
|
||||
{
|
||||
|
||||
private IServiceProvider _myServices;
|
||||
private readonly Regex _myUnicodeEscapeRegex;
|
||||
private readonly Regex _myRegularEscapeRegex;
|
||||
|
||||
private bool _myInUnaryNegate;
|
||||
internal FleeExpressionAnalyzer()
|
||||
{
|
||||
_myUnicodeEscapeRegex = new Regex("\\\\u[0-9a-f]{4}", RegexOptions.IgnoreCase);
|
||||
_myRegularEscapeRegex = new Regex("\\\\[\\\\\"'trn]", RegexOptions.IgnoreCase);
|
||||
}
|
||||
|
||||
public void SetServices(IServiceProvider services)
|
||||
{
|
||||
_myServices = services;
|
||||
}
|
||||
|
||||
public override void Reset()
|
||||
{
|
||||
_myServices = null;
|
||||
}
|
||||
|
||||
public override Node ExitExpression(Production node)
|
||||
{
|
||||
this.AddFirstChildValue(node);
|
||||
return node;
|
||||
}
|
||||
|
||||
public override Node ExitExpressionGroup(Production node)
|
||||
{
|
||||
node.AddValues(this.GetChildValues(node));
|
||||
return node;
|
||||
}
|
||||
|
||||
public override Node ExitXorExpression(Production node)
|
||||
{
|
||||
this.AddBinaryOp(node, typeof(XorElement));
|
||||
return node;
|
||||
}
|
||||
|
||||
public override Node ExitOrExpression(Production node)
|
||||
{
|
||||
this.AddBinaryOp(node, typeof(AndOrElement));
|
||||
return node;
|
||||
}
|
||||
|
||||
public override Node ExitAndExpression(Production node)
|
||||
{
|
||||
this.AddBinaryOp(node, typeof(AndOrElement));
|
||||
return node;
|
||||
}
|
||||
|
||||
public override Node ExitNotExpression(Production node)
|
||||
{
|
||||
this.AddUnaryOp(node, typeof(NotElement));
|
||||
return node;
|
||||
}
|
||||
|
||||
public override Node ExitCompareExpression(Production node)
|
||||
{
|
||||
this.AddBinaryOp(node, typeof(CompareElement));
|
||||
return node;
|
||||
}
|
||||
|
||||
public override Node ExitShiftExpression(Production node)
|
||||
{
|
||||
this.AddBinaryOp(node, typeof(ShiftElement));
|
||||
return node;
|
||||
}
|
||||
|
||||
public override Node ExitAdditiveExpression(Production node)
|
||||
{
|
||||
this.AddBinaryOp(node, typeof(ArithmeticElement));
|
||||
return node;
|
||||
}
|
||||
|
||||
public override Node ExitMultiplicativeExpression(Production node)
|
||||
{
|
||||
this.AddBinaryOp(node, typeof(ArithmeticElement));
|
||||
return node;
|
||||
}
|
||||
|
||||
public override Node ExitPowerExpression(Production node)
|
||||
{
|
||||
this.AddBinaryOp(node, typeof(ArithmeticElement));
|
||||
return node;
|
||||
}
|
||||
|
||||
// Try to fold a negated constant int32. We have to do this so that parsing int32.MinValue will work
|
||||
public override Node ExitNegateExpression(Production node)
|
||||
{
|
||||
IList childValues = this.GetChildValues(node);
|
||||
|
||||
// Get last child
|
||||
ExpressionElement childElement = (ExpressionElement)childValues[childValues.Count - 1];
|
||||
|
||||
// Is it an signed integer constant?
|
||||
if (object.ReferenceEquals(childElement.GetType(), typeof(Int32LiteralElement)) & childValues.Count == 2)
|
||||
{
|
||||
((Int32LiteralElement)childElement).Negate();
|
||||
// Add it directly instead of the negate element since it will already be negated
|
||||
node.AddValue(childElement);
|
||||
}
|
||||
else if (object.ReferenceEquals(childElement.GetType(), typeof(Int64LiteralElement)) & childValues.Count == 2)
|
||||
{
|
||||
((Int64LiteralElement)childElement).Negate();
|
||||
// Add it directly instead of the negate element since it will already be negated
|
||||
node.AddValue(childElement);
|
||||
}
|
||||
else
|
||||
{
|
||||
// No so just add a regular negate
|
||||
this.AddUnaryOp(node, typeof(NegateElement));
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
public override Node ExitMemberExpression(Production node)
|
||||
{
|
||||
IList childValues = this.GetChildValues(node);
|
||||
object first = childValues[0];
|
||||
|
||||
if (childValues.Count == 1 && !(first is MemberElement))
|
||||
{
|
||||
node.AddValue(first);
|
||||
}
|
||||
else
|
||||
{
|
||||
InvocationListElement list = new InvocationListElement(childValues, _myServices);
|
||||
node.AddValue(list);
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
public override Node ExitIndexExpression(Production node)
|
||||
{
|
||||
IList childValues = this.GetChildValues(node);
|
||||
ArgumentList args = new ArgumentList(childValues);
|
||||
IndexerElement e = new IndexerElement(args);
|
||||
node.AddValue(e);
|
||||
return node;
|
||||
}
|
||||
|
||||
public override Node ExitMemberAccessExpression(Production node)
|
||||
{
|
||||
node.AddValue(node.GetChildAt(1).GetValue(0));
|
||||
return node;
|
||||
}
|
||||
|
||||
public override Node ExitSpecialFunctionExpression(Production node)
|
||||
{
|
||||
this.AddFirstChildValue(node);
|
||||
return node;
|
||||
}
|
||||
|
||||
public override Node ExitIfExpression(Production node)
|
||||
{
|
||||
IList childValues = this.GetChildValues(node);
|
||||
ConditionalElement op = new ConditionalElement((ExpressionElement)childValues[0], (ExpressionElement)childValues[1], (ExpressionElement)childValues[2]);
|
||||
node.AddValue(op);
|
||||
return node;
|
||||
}
|
||||
|
||||
public override Node ExitInExpression(Production node)
|
||||
{
|
||||
IList childValues = this.GetChildValues(node);
|
||||
|
||||
if (childValues.Count == 1)
|
||||
{
|
||||
this.AddFirstChildValue(node);
|
||||
return node;
|
||||
}
|
||||
|
||||
ExpressionElement operand = (ExpressionElement)childValues[0];
|
||||
childValues.RemoveAt(0);
|
||||
|
||||
object second = childValues[0];
|
||||
InElement op = default(InElement);
|
||||
|
||||
if ((second) is IList)
|
||||
{
|
||||
op = new InElement(operand, (IList)second);
|
||||
}
|
||||
else
|
||||
{
|
||||
InvocationListElement il = new InvocationListElement(childValues, _myServices);
|
||||
op = new InElement(operand, il);
|
||||
}
|
||||
|
||||
node.AddValue(op);
|
||||
return node;
|
||||
}
|
||||
|
||||
public override Node ExitInTargetExpression(Production node)
|
||||
{
|
||||
this.AddFirstChildValue(node);
|
||||
return node;
|
||||
}
|
||||
|
||||
public override Node ExitInListTargetExpression(Production node)
|
||||
{
|
||||
IList childValues = this.GetChildValues(node);
|
||||
node.AddValue(childValues);
|
||||
return node;
|
||||
}
|
||||
|
||||
public override Node ExitCastExpression(Production node)
|
||||
{
|
||||
IList childValues = this.GetChildValues(node);
|
||||
string[] destTypeParts = (string[])childValues[1];
|
||||
bool isArray = (bool)childValues[2];
|
||||
CastElement op = new CastElement((ExpressionElement)childValues[0], destTypeParts, isArray, _myServices);
|
||||
node.AddValue(op);
|
||||
return node;
|
||||
}
|
||||
|
||||
public override Node ExitCastTypeExpression(Production node)
|
||||
{
|
||||
IList childValues = this.GetChildValues(node);
|
||||
List<string> parts = new List<string>();
|
||||
|
||||
foreach (string part in childValues)
|
||||
{
|
||||
parts.Add(part);
|
||||
}
|
||||
|
||||
bool isArray = false;
|
||||
|
||||
if (parts[parts.Count - 1] == "[]")
|
||||
{
|
||||
isArray = true;
|
||||
parts.RemoveAt(parts.Count - 1);
|
||||
}
|
||||
|
||||
node.AddValue(parts.ToArray());
|
||||
node.AddValue(isArray);
|
||||
return node;
|
||||
}
|
||||
|
||||
public override Node ExitMemberFunctionExpression(Production node)
|
||||
{
|
||||
this.AddFirstChildValue(node);
|
||||
return node;
|
||||
}
|
||||
|
||||
public override Node ExitFieldPropertyExpression(Production node)
|
||||
{
|
||||
//string name = ((Token)node.GetChildAt(0))?.Image;
|
||||
string name = node.GetChildAt(0).GetValue(0).ToString();
|
||||
IdentifierElement elem = new IdentifierElement(name);
|
||||
node.AddValue(elem);
|
||||
return node;
|
||||
}
|
||||
|
||||
public override Node ExitFunctionCallExpression(Production node)
|
||||
{
|
||||
IList childValues = this.GetChildValues(node);
|
||||
string name = (string)childValues[0];
|
||||
childValues.RemoveAt(0);
|
||||
ArgumentList args = new ArgumentList(childValues);
|
||||
FunctionCallElement funcCall = new FunctionCallElement(name, args);
|
||||
node.AddValue(funcCall);
|
||||
return node;
|
||||
}
|
||||
|
||||
public override Node ExitArgumentList(Production node)
|
||||
{
|
||||
IList childValues = this.GetChildValues(node);
|
||||
node.AddValues((ArrayList)childValues);
|
||||
return node;
|
||||
}
|
||||
|
||||
public override Node ExitBasicExpression(Production node)
|
||||
{
|
||||
this.AddFirstChildValue(node);
|
||||
return node;
|
||||
}
|
||||
|
||||
public override Node ExitLiteralExpression(Production node)
|
||||
{
|
||||
this.AddFirstChildValue(node);
|
||||
return node;
|
||||
}
|
||||
|
||||
private void AddFirstChildValue(Production node)
|
||||
{
|
||||
node.AddValue(this.GetChildAt(node, 0).Values[0]);
|
||||
}
|
||||
|
||||
private void AddUnaryOp(Production node, Type elementType)
|
||||
{
|
||||
IList childValues = this.GetChildValues(node);
|
||||
|
||||
if (childValues.Count == 2)
|
||||
{
|
||||
UnaryElement element = (UnaryElement)Activator.CreateInstance(elementType);
|
||||
element.SetChild((ExpressionElement)childValues[1]);
|
||||
node.AddValue(element);
|
||||
}
|
||||
else
|
||||
{
|
||||
node.AddValue(childValues[0]);
|
||||
}
|
||||
}
|
||||
|
||||
private void AddBinaryOp(Production node, Type elementType)
|
||||
{
|
||||
IList childValues = this.GetChildValues(node);
|
||||
|
||||
if (childValues.Count > 1)
|
||||
{
|
||||
BinaryExpressionElement e = BinaryExpressionElement.CreateElement(childValues, elementType);
|
||||
node.AddValue(e);
|
||||
}
|
||||
else if (childValues.Count == 1)
|
||||
{
|
||||
node.AddValue(childValues[0]);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Assert(false, "wrong number of chilren");
|
||||
}
|
||||
}
|
||||
|
||||
public override Node ExitReal(Token node)
|
||||
{
|
||||
string image = node.Image;
|
||||
LiteralElement element = RealLiteralElement.Create(image, _myServices);
|
||||
|
||||
node.AddValue(element);
|
||||
return node;
|
||||
}
|
||||
|
||||
public override Node ExitInteger(Token node)
|
||||
{
|
||||
LiteralElement element = IntegralLiteralElement.Create(node.Image, false, _myInUnaryNegate, _myServices);
|
||||
node.AddValue(element);
|
||||
return node;
|
||||
}
|
||||
|
||||
public override Node ExitHexliteral(Token node)
|
||||
{
|
||||
LiteralElement element = IntegralLiteralElement.Create(node.Image, true, _myInUnaryNegate, _myServices);
|
||||
node.AddValue(element);
|
||||
return node;
|
||||
}
|
||||
|
||||
public override Node ExitBooleanLiteralExpression(Production node)
|
||||
{
|
||||
this.AddFirstChildValue(node);
|
||||
return node;
|
||||
}
|
||||
|
||||
public override Node ExitTrue(Token node)
|
||||
{
|
||||
node.AddValue(new BooleanLiteralElement(true));
|
||||
return node;
|
||||
}
|
||||
|
||||
public override Node ExitFalse(Token node)
|
||||
{
|
||||
node.AddValue(new BooleanLiteralElement(false));
|
||||
return node;
|
||||
}
|
||||
|
||||
public override Node ExitStringLiteral(Token node)
|
||||
{
|
||||
string s = this.DoEscapes(node.Image);
|
||||
StringLiteralElement element = new StringLiteralElement(s);
|
||||
node.AddValue(element);
|
||||
return node;
|
||||
}
|
||||
|
||||
public override Node ExitCharLiteral(Token node)
|
||||
{
|
||||
string s = this.DoEscapes(node.Image);
|
||||
node.AddValue(new CharLiteralElement(s[0]));
|
||||
return node;
|
||||
}
|
||||
|
||||
public override Node ExitDatetime(Token node)
|
||||
{
|
||||
ExpressionContext context = (ExpressionContext)_myServices.GetService(typeof(ExpressionContext));
|
||||
string image = node.Image.Substring(1, node.Image.Length - 2);
|
||||
DateTimeLiteralElement element = new DateTimeLiteralElement(image, context);
|
||||
node.AddValue(element);
|
||||
return node;
|
||||
}
|
||||
|
||||
public override Node ExitTimespan(Token node)
|
||||
{
|
||||
string image = node.Image.Substring(2, node.Image.Length - 3);
|
||||
TimeSpanLiteralElement element = new TimeSpanLiteralElement(image);
|
||||
node.AddValue(element);
|
||||
return node;
|
||||
}
|
||||
|
||||
private string DoEscapes(string image)
|
||||
{
|
||||
// Remove outer quotes
|
||||
image = image.Substring(1, image.Length - 2);
|
||||
image = _myUnicodeEscapeRegex.Replace(image, UnicodeEscapeMatcher);
|
||||
image = _myRegularEscapeRegex.Replace(image, RegularEscapeMatcher);
|
||||
return image;
|
||||
}
|
||||
|
||||
private string RegularEscapeMatcher(Match m)
|
||||
{
|
||||
string s = m.Value;
|
||||
// Remove leading \
|
||||
s = s.Remove(0, 1);
|
||||
|
||||
switch (s)
|
||||
{
|
||||
case "\\":
|
||||
case "\"":
|
||||
case "'":
|
||||
return s;
|
||||
case "t":
|
||||
case "T":
|
||||
return Convert.ToChar(9).ToString();
|
||||
case "n":
|
||||
case "N":
|
||||
return Convert.ToChar(10).ToString();
|
||||
case "r":
|
||||
case "R":
|
||||
return Convert.ToChar(13).ToString();
|
||||
default:
|
||||
Debug.Assert(false, "Unrecognized escape sequence");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private string UnicodeEscapeMatcher(Match m)
|
||||
{
|
||||
string s = m.Value;
|
||||
// Remove \u
|
||||
s = s.Remove(0, 2);
|
||||
int code = int.Parse(s, NumberStyles.AllowHexSpecifier);
|
||||
char c = Convert.ToChar(code);
|
||||
return c.ToString();
|
||||
}
|
||||
|
||||
public override Node ExitIdentifier(Token node)
|
||||
{
|
||||
node.AddValue(node.Image);
|
||||
return node;
|
||||
}
|
||||
|
||||
public override Node ExitNullLiteral(Token node)
|
||||
{
|
||||
node.AddValue(new NullLiteralElement());
|
||||
return node;
|
||||
}
|
||||
|
||||
public override Node ExitArrayBraces(Token node)
|
||||
{
|
||||
node.AddValue("[]");
|
||||
return node;
|
||||
}
|
||||
|
||||
public override Node ExitAdd(Token node)
|
||||
{
|
||||
node.AddValue(BinaryArithmeticOperation.Add);
|
||||
return node;
|
||||
}
|
||||
|
||||
public override Node ExitSub(Token node)
|
||||
{
|
||||
node.AddValue(BinaryArithmeticOperation.Subtract);
|
||||
return node;
|
||||
}
|
||||
|
||||
public override Node ExitMul(Token node)
|
||||
{
|
||||
node.AddValue(BinaryArithmeticOperation.Multiply);
|
||||
return node;
|
||||
}
|
||||
|
||||
public override Node ExitDiv(Token node)
|
||||
{
|
||||
node.AddValue(BinaryArithmeticOperation.Divide);
|
||||
return node;
|
||||
}
|
||||
|
||||
public override Node ExitMod(Token node)
|
||||
{
|
||||
node.AddValue(BinaryArithmeticOperation.Mod);
|
||||
return node;
|
||||
}
|
||||
|
||||
public override Node ExitPower(Token node)
|
||||
{
|
||||
node.AddValue(BinaryArithmeticOperation.Power);
|
||||
return node;
|
||||
}
|
||||
|
||||
public override Node ExitEq(Token node)
|
||||
{
|
||||
node.AddValue(LogicalCompareOperation.Equal);
|
||||
return node;
|
||||
}
|
||||
|
||||
public override Node ExitNe(Token node)
|
||||
{
|
||||
node.AddValue(LogicalCompareOperation.NotEqual);
|
||||
return node;
|
||||
}
|
||||
|
||||
public override Node ExitLt(Token node)
|
||||
{
|
||||
node.AddValue(LogicalCompareOperation.LessThan);
|
||||
return node;
|
||||
}
|
||||
|
||||
public override Node ExitGt(Token node)
|
||||
{
|
||||
node.AddValue(LogicalCompareOperation.GreaterThan);
|
||||
return node;
|
||||
}
|
||||
|
||||
public override Node ExitLte(Token node)
|
||||
{
|
||||
node.AddValue(LogicalCompareOperation.LessThanOrEqual);
|
||||
return node;
|
||||
}
|
||||
|
||||
public override Node ExitGte(Token node)
|
||||
{
|
||||
node.AddValue(LogicalCompareOperation.GreaterThanOrEqual);
|
||||
return node;
|
||||
}
|
||||
|
||||
public override Node ExitAnd(Token node)
|
||||
{
|
||||
node.AddValue(AndOrOperation.And);
|
||||
return node;
|
||||
}
|
||||
|
||||
public override Node ExitOr(Token node)
|
||||
{
|
||||
node.AddValue(AndOrOperation.Or);
|
||||
return node;
|
||||
}
|
||||
|
||||
public override Node ExitXor(Token node)
|
||||
{
|
||||
node.AddValue("Xor");
|
||||
return node;
|
||||
}
|
||||
|
||||
public override Node ExitNot(Token node)
|
||||
{
|
||||
node.AddValue(string.Empty);
|
||||
return node;
|
||||
}
|
||||
|
||||
public override Node ExitLeftShift(Token node)
|
||||
{
|
||||
node.AddValue(ShiftOperation.LeftShift);
|
||||
return node;
|
||||
}
|
||||
|
||||
public override Node ExitRightShift(Token node)
|
||||
{
|
||||
node.AddValue(ShiftOperation.RightShift);
|
||||
return node;
|
||||
}
|
||||
|
||||
public override void Child(Production node, Node child)
|
||||
{
|
||||
base.Child(node, child);
|
||||
_myInUnaryNegate = node.Id == (int)ExpressionConstants.NEGATE_EXPRESSION & child.Id == (int)ExpressionConstants.SUB;
|
||||
}
|
||||
}
|
||||
}
|
||||
49
Parsing/CustomTokenPatterns.cs
Normal file
49
Parsing/CustomTokenPatterns.cs
Normal file
@@ -0,0 +1,49 @@
|
||||
using Flee.PublicTypes;
|
||||
|
||||
namespace Flee.Parsing
|
||||
{
|
||||
internal abstract class CustomTokenPattern : TokenPattern
|
||||
{
|
||||
protected CustomTokenPattern(int id, string name, PatternType type, string pattern) : base(id, name, type, pattern)
|
||||
{
|
||||
}
|
||||
|
||||
public void Initialize(int id, string name, PatternType type, string pattern, ExpressionContext context)
|
||||
{
|
||||
this.ComputeToken(id, name, type, pattern, context);
|
||||
}
|
||||
|
||||
protected abstract void ComputeToken(int id, string name, PatternType type, string pattern, ExpressionContext context);
|
||||
}
|
||||
|
||||
internal class RealPattern : CustomTokenPattern
|
||||
{
|
||||
public RealPattern(int id, string name, PatternType type, string pattern) : base(id, name, type, pattern)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void ComputeToken(int id, string name, PatternType type, string pattern, ExpressionContext context)
|
||||
{
|
||||
ExpressionParserOptions options = context.ParserOptions;
|
||||
|
||||
char digitsBeforePattern = (options.RequireDigitsBeforeDecimalPoint ? '+' : '*');
|
||||
|
||||
pattern = string.Format(pattern, digitsBeforePattern, options.DecimalSeparator);
|
||||
|
||||
this.SetData(id, name, type, pattern);
|
||||
}
|
||||
}
|
||||
|
||||
internal class ArgumentSeparatorPattern : CustomTokenPattern
|
||||
{
|
||||
public ArgumentSeparatorPattern(int id, string name, PatternType type, string pattern) : base(id, name, type, pattern)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void ComputeToken(int id, string name, PatternType type, string pattern, ExpressionContext context)
|
||||
{
|
||||
ExpressionParserOptions options = context.ParserOptions;
|
||||
this.SetData(id, name, type, options.FunctionArgumentSeparator.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
19
Parsing/Element.cs
Normal file
19
Parsing/Element.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
namespace Flee.Parsing
|
||||
{
|
||||
/**
|
||||
* A regular expression element. This is the common base class for
|
||||
* all regular expression elements, i.e. the parts of the regular
|
||||
* expression.
|
||||
*/
|
||||
internal abstract class Element : ICloneable
|
||||
{
|
||||
public abstract object Clone();
|
||||
|
||||
public abstract int Match(Matcher m,
|
||||
ReaderBuffer buffer,
|
||||
int start,
|
||||
int skip);
|
||||
|
||||
public abstract void PrintTo(TextWriter output, string indent);
|
||||
}
|
||||
}
|
||||
133
Parsing/Expression.grammar
Normal file
133
Parsing/Expression.grammar
Normal file
@@ -0,0 +1,133 @@
|
||||
%header%
|
||||
|
||||
DESCRIPTION = "A general expression grammar"
|
||||
AUTHOR = "Eugene Ciloci"
|
||||
VERSION = "1.0"
|
||||
DATE = "May 2007"
|
||||
|
||||
GRAMMARTYPE = "LL"
|
||||
CASESENSITIVE = "False"
|
||||
|
||||
LICENSE = "This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public License
|
||||
as published by the Free Software Foundation; either version 2.1
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free
|
||||
Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
|
||||
MA 02111-1307, USA.
|
||||
"
|
||||
|
||||
COPYRIGHT = "Copyright (c) 2007 Eugene Ciloci"
|
||||
|
||||
%tokens%
|
||||
ADD = "+"
|
||||
SUB = "-"
|
||||
MUL = "*"
|
||||
DIV = "/"
|
||||
POWER = "^"
|
||||
MOD = "%"
|
||||
LEFT_PAREN = "("
|
||||
RIGHT_PAREN = ")"
|
||||
LEFT_BRACE = "["
|
||||
RIGHT_BRACE = "]"
|
||||
EQ = "="
|
||||
LT = "<"
|
||||
GT = ">"
|
||||
LTE = "<="
|
||||
GTE = ">="
|
||||
NE = "<>"
|
||||
AND = "AND"
|
||||
OR = "OR"
|
||||
XOR = "XOR"
|
||||
NOT = "NOT"
|
||||
IN = "in"
|
||||
DOT = "."
|
||||
ARGUMENT_SEPARATOR = ","
|
||||
ARRAY_BRACES = "[]"
|
||||
LEFT_SHIFT = "<<"
|
||||
RIGHT_SHIFT = ">>"
|
||||
WHITESPACE = <<\s+>> %ignore%
|
||||
|
||||
// Primitives
|
||||
INTEGER = <<\d+(u|l|ul|lu)?>>
|
||||
REAL = <<\d*\.\d+([e][+-]\d{1,3})?f?>>
|
||||
STRING_LITERAL = <<"([^"\r\n\\]|\\u[0-9a-f]{4}|\\[\\"'trn])*">>
|
||||
CHAR_LITERAL = <<'([^'\r\n\\]|\\u[0-9a-f]{4}|\\[\\"'trn])'>>
|
||||
TRUE = "True"
|
||||
FALSE = "False"
|
||||
IDENTIFIER = <<[a-z_]\w*>>
|
||||
HEX_LITERAL = <<0x[0-9a-f]+(u|l|ul|lu)?>>
|
||||
NULL_LITERAL = "null"
|
||||
TIMESPAN = <<##(\d+\.)?\d{2}:\d{2}(:\d{2}(\.\d{1,7})?)?#>>
|
||||
DATETIME = <<#[^#]+#>>
|
||||
|
||||
// Special Functions
|
||||
IF = "if"
|
||||
CAST = "cast"
|
||||
|
||||
%productions%
|
||||
|
||||
Expression = XorExpression;
|
||||
|
||||
XorExpression = OrExpression {XOR OrExpression};
|
||||
|
||||
OrExpression = AndExpression {OR AndExpression};
|
||||
|
||||
AndExpression = NotExpression {AND NotExpression};
|
||||
|
||||
NotExpression = NOT? InExpression;
|
||||
|
||||
InExpression = CompareExpression [IN InTargetExpression];
|
||||
|
||||
InTargetExpression = FieldPropertyExpression | InListTargetExpression;
|
||||
|
||||
InListTargetExpression = "(" ArgumentList ")";
|
||||
|
||||
CompareExpression = ShiftExpression {("=" | ">" | "<" | ">=" | "<=" | "<>") ShiftExpression};
|
||||
|
||||
ShiftExpression = AdditiveExpression {("<<" | ">>") AdditiveExpression};
|
||||
|
||||
AdditiveExpression = MultiplicativeExpression {("+" | "-") MultiplicativeExpression};
|
||||
|
||||
MultiplicativeExpression = PowerExpression {("*" | "/" | "%") PowerExpression};
|
||||
|
||||
PowerExpression = NegateExpression {"^" NegateExpression};
|
||||
|
||||
NegateExpression = "-"? MemberExpression;
|
||||
|
||||
MemberExpression = BasicExpression {MemberAccessExpression | IndexExpression};
|
||||
|
||||
MemberAccessExpression = "." MemberFunctionExpression;
|
||||
|
||||
BasicExpression = LiteralExpression | ExpressionGroup | MemberFunctionExpression | SpecialFunctionExpression;
|
||||
|
||||
MemberFunctionExpression = FieldPropertyExpression | FunctionCallExpression;
|
||||
|
||||
FieldPropertyExpression = IDENTIFIER;
|
||||
|
||||
SpecialFunctionExpression = IfExpression | CastExpression;
|
||||
|
||||
IfExpression = IF "(" Expression "," Expression "," Expression ")";
|
||||
|
||||
CastExpression = CAST "(" Expression "," CastTypeExpression ")";
|
||||
|
||||
CastTypeExpression = IDENTIFIER {"." IDENTIFIER} ARRAY_BRACES?;
|
||||
|
||||
IndexExpression = "[" ArgumentList "]";
|
||||
|
||||
FunctionCallExpression = IDENTIFIER "(" ArgumentList? ")";
|
||||
|
||||
ArgumentList = Expression {"," Expression};
|
||||
|
||||
LiteralExpression = INTEGER | REAL | STRING_LITERAL | BooleanLiteralExpression | HEX_LITERAL | CHAR_LITERAL | NULL_LITERAL | DATETIME | TIMESPAN;
|
||||
|
||||
BooleanLiteralExpression = TRUE | FALSE;
|
||||
|
||||
ExpressionGroup = "(" Expression ")";
|
||||
1395
Parsing/ExpressionAnalyzer.cs
Normal file
1395
Parsing/ExpressionAnalyzer.cs
Normal file
File diff suppressed because it is too large
Load Diff
78
Parsing/ExpressionConstants.cs
Normal file
78
Parsing/ExpressionConstants.cs
Normal file
@@ -0,0 +1,78 @@
|
||||
namespace Flee.Parsing
|
||||
{
|
||||
///<remarks>
|
||||
/// An enumeration with token and production node
|
||||
///constants.</remarks>
|
||||
internal enum ExpressionConstants
|
||||
{
|
||||
ADD = 1001,
|
||||
SUB = 1002,
|
||||
MUL = 1003,
|
||||
DIV = 1004,
|
||||
POWER = 1005,
|
||||
MOD = 1006,
|
||||
LEFT_PAREN = 1007,
|
||||
RIGHT_PAREN = 1008,
|
||||
LEFT_BRACE = 1009,
|
||||
RIGHT_BRACE = 1010,
|
||||
EQ = 1011,
|
||||
LT = 1012,
|
||||
GT = 1013,
|
||||
LTE = 1014,
|
||||
GTE = 1015,
|
||||
NE = 1016,
|
||||
AND = 1017,
|
||||
OR = 1018,
|
||||
XOR = 1019,
|
||||
NOT = 1020,
|
||||
IN = 1021,
|
||||
DOT = 1022,
|
||||
ARGUMENT_SEPARATOR = 1023,
|
||||
ARRAY_BRACES = 1024,
|
||||
LEFT_SHIFT = 1025,
|
||||
RIGHT_SHIFT = 1026,
|
||||
WHITESPACE = 1027,
|
||||
INTEGER = 1028,
|
||||
REAL = 1029,
|
||||
STRING_LITERAL = 1030,
|
||||
CHAR_LITERAL = 1031,
|
||||
TRUE = 1032,
|
||||
FALSE = 1033,
|
||||
NULL_LITERAL = 1034,
|
||||
HEX_LITERAL = 1035,
|
||||
IDENTIFIER = 1036,
|
||||
TIMESPAN = 1037,
|
||||
DATETIME = 1038,
|
||||
IF = 1039,
|
||||
CAST = 1040,
|
||||
EXPRESSION = 2001,
|
||||
XOR_EXPRESSION = 2002,
|
||||
OR_EXPRESSION = 2003,
|
||||
AND_EXPRESSION = 2004,
|
||||
NOT_EXPRESSION = 2005,
|
||||
IN_EXPRESSION = 2006,
|
||||
IN_TARGET_EXPRESSION = 2007,
|
||||
IN_LIST_TARGET_EXPRESSION = 2008,
|
||||
COMPARE_EXPRESSION = 2009,
|
||||
SHIFT_EXPRESSION = 2010,
|
||||
ADDITIVE_EXPRESSION = 2011,
|
||||
MULTIPLICATIVE_EXPRESSION = 2012,
|
||||
POWER_EXPRESSION = 2013,
|
||||
NEGATE_EXPRESSION = 2014,
|
||||
MEMBER_EXPRESSION = 2015,
|
||||
MEMBER_ACCESS_EXPRESSION = 2016,
|
||||
BASIC_EXPRESSION = 2017,
|
||||
MEMBER_FUNCTION_EXPRESSION = 2018,
|
||||
FIELD_PROPERTY_EXPRESSION = 2019,
|
||||
SPECIAL_FUNCTION_EXPRESSION = 2020,
|
||||
IF_EXPRESSION = 2021,
|
||||
CAST_EXPRESSION = 2022,
|
||||
CAST_TYPE_EXPRESSION = 2023,
|
||||
INDEX_EXPRESSION = 2024,
|
||||
FUNCTION_CALL_EXPRESSION = 2025,
|
||||
ARGUMENT_LIST = 2026,
|
||||
LITERAL_EXPRESSION = 2027,
|
||||
BOOLEAN_LITERAL_EXPRESSION = 2028,
|
||||
EXPRESSION_GROUP = 2029
|
||||
}
|
||||
}
|
||||
460
Parsing/ExpressionParser.cs
Normal file
460
Parsing/ExpressionParser.cs
Normal file
@@ -0,0 +1,460 @@
|
||||
using Flee.PublicTypes;
|
||||
|
||||
namespace Flee.Parsing
|
||||
{
|
||||
/// <summary>
|
||||
/// A token stream parser.
|
||||
/// </summary>
|
||||
internal class ExpressionParser : StackParser
|
||||
{
|
||||
private enum SynteticPatterns
|
||||
{
|
||||
SUBPRODUCTION_1 = 3001,
|
||||
SUBPRODUCTION_2 = 3002,
|
||||
SUBPRODUCTION_3 = 3003,
|
||||
SUBPRODUCTION_4 = 3004,
|
||||
SUBPRODUCTION_5 = 3005,
|
||||
SUBPRODUCTION_6 = 3006,
|
||||
SUBPRODUCTION_7 = 3007,
|
||||
SUBPRODUCTION_8 = 3008,
|
||||
SUBPRODUCTION_9 = 3009,
|
||||
SUBPRODUCTION_10 = 3010,
|
||||
SUBPRODUCTION_11 = 3011,
|
||||
SUBPRODUCTION_12 = 3012,
|
||||
SUBPRODUCTION_13 = 3013,
|
||||
SUBPRODUCTION_14 = 3014,
|
||||
SUBPRODUCTION_15 = 3015,
|
||||
SUBPRODUCTION_16 = 3016
|
||||
}
|
||||
|
||||
public ExpressionParser(TextReader input, Analyzer analyzer, ExpressionContext context) : base(new ExpressionTokenizer(input, context), analyzer)
|
||||
{
|
||||
CreatePatterns();
|
||||
}
|
||||
|
||||
public ExpressionParser(TextReader input) : base(new ExpressionTokenizer(input))
|
||||
{
|
||||
CreatePatterns();
|
||||
}
|
||||
|
||||
public ExpressionParser(TextReader input, Analyzer analyzer) : base(new ExpressionTokenizer(input), analyzer)
|
||||
{
|
||||
CreatePatterns();
|
||||
}
|
||||
|
||||
private void CreatePatterns()
|
||||
{
|
||||
ProductionPattern pattern = default(ProductionPattern);
|
||||
ProductionPatternAlternative alt = default(ProductionPatternAlternative);
|
||||
|
||||
pattern = new ProductionPattern(Convert.ToInt32(ExpressionConstants.EXPRESSION), "Expression");
|
||||
alt = new ProductionPatternAlternative();
|
||||
alt.AddProduction(Convert.ToInt32(ExpressionConstants.XOR_EXPRESSION), 1, 1);
|
||||
pattern.AddAlternative(alt);
|
||||
AddPattern(pattern);
|
||||
|
||||
pattern = new ProductionPattern(Convert.ToInt32(ExpressionConstants.XOR_EXPRESSION), "XorExpression");
|
||||
alt = new ProductionPatternAlternative();
|
||||
alt.AddProduction(Convert.ToInt32(ExpressionConstants.OR_EXPRESSION), 1, 1);
|
||||
alt.AddProduction(Convert.ToInt32(SynteticPatterns.SUBPRODUCTION_1), 0, -1);
|
||||
pattern.AddAlternative(alt);
|
||||
AddPattern(pattern);
|
||||
|
||||
pattern = new ProductionPattern(Convert.ToInt32(ExpressionConstants.OR_EXPRESSION), "OrExpression");
|
||||
alt = new ProductionPatternAlternative();
|
||||
alt.AddProduction(Convert.ToInt32(ExpressionConstants.AND_EXPRESSION), 1, 1);
|
||||
alt.AddProduction(Convert.ToInt32(SynteticPatterns.SUBPRODUCTION_2), 0, -1);
|
||||
pattern.AddAlternative(alt);
|
||||
AddPattern(pattern);
|
||||
|
||||
pattern = new ProductionPattern(Convert.ToInt32(ExpressionConstants.AND_EXPRESSION), "AndExpression");
|
||||
alt = new ProductionPatternAlternative();
|
||||
alt.AddProduction(Convert.ToInt32(ExpressionConstants.NOT_EXPRESSION), 1, 1);
|
||||
alt.AddProduction(Convert.ToInt32(SynteticPatterns.SUBPRODUCTION_3), 0, -1);
|
||||
pattern.AddAlternative(alt);
|
||||
AddPattern(pattern);
|
||||
|
||||
pattern = new ProductionPattern(Convert.ToInt32(ExpressionConstants.NOT_EXPRESSION), "NotExpression");
|
||||
alt = new ProductionPatternAlternative();
|
||||
alt.AddToken(Convert.ToInt32(ExpressionConstants.NOT), 0, 1);
|
||||
alt.AddProduction(Convert.ToInt32(ExpressionConstants.IN_EXPRESSION), 1, 1);
|
||||
pattern.AddAlternative(alt);
|
||||
AddPattern(pattern);
|
||||
|
||||
pattern = new ProductionPattern(Convert.ToInt32(ExpressionConstants.IN_EXPRESSION), "InExpression");
|
||||
alt = new ProductionPatternAlternative();
|
||||
alt.AddProduction(Convert.ToInt32(ExpressionConstants.COMPARE_EXPRESSION), 1, 1);
|
||||
alt.AddProduction(Convert.ToInt32(SynteticPatterns.SUBPRODUCTION_4), 0, 1);
|
||||
pattern.AddAlternative(alt);
|
||||
AddPattern(pattern);
|
||||
|
||||
pattern = new ProductionPattern(Convert.ToInt32(ExpressionConstants.IN_TARGET_EXPRESSION), "InTargetExpression");
|
||||
alt = new ProductionPatternAlternative();
|
||||
alt.AddProduction(Convert.ToInt32(ExpressionConstants.FIELD_PROPERTY_EXPRESSION), 1, 1);
|
||||
pattern.AddAlternative(alt);
|
||||
alt = new ProductionPatternAlternative();
|
||||
alt.AddProduction(Convert.ToInt32(ExpressionConstants.IN_LIST_TARGET_EXPRESSION), 1, 1);
|
||||
pattern.AddAlternative(alt);
|
||||
AddPattern(pattern);
|
||||
|
||||
pattern = new ProductionPattern(Convert.ToInt32(ExpressionConstants.IN_LIST_TARGET_EXPRESSION), "InListTargetExpression");
|
||||
alt = new ProductionPatternAlternative();
|
||||
alt.AddToken(Convert.ToInt32(ExpressionConstants.LEFT_PAREN), 1, 1);
|
||||
alt.AddProduction(Convert.ToInt32(ExpressionConstants.ARGUMENT_LIST), 1, 1);
|
||||
alt.AddToken(Convert.ToInt32(ExpressionConstants.RIGHT_PAREN), 1, 1);
|
||||
pattern.AddAlternative(alt);
|
||||
AddPattern(pattern);
|
||||
|
||||
pattern = new ProductionPattern(Convert.ToInt32(ExpressionConstants.COMPARE_EXPRESSION), "CompareExpression");
|
||||
alt = new ProductionPatternAlternative();
|
||||
alt.AddProduction(Convert.ToInt32(ExpressionConstants.SHIFT_EXPRESSION), 1, 1);
|
||||
alt.AddProduction(Convert.ToInt32(SynteticPatterns.SUBPRODUCTION_6), 0, -1);
|
||||
pattern.AddAlternative(alt);
|
||||
AddPattern(pattern);
|
||||
|
||||
pattern = new ProductionPattern(Convert.ToInt32(ExpressionConstants.SHIFT_EXPRESSION), "ShiftExpression");
|
||||
alt = new ProductionPatternAlternative();
|
||||
alt.AddProduction(Convert.ToInt32(ExpressionConstants.ADDITIVE_EXPRESSION), 1, 1);
|
||||
alt.AddProduction(Convert.ToInt32(SynteticPatterns.SUBPRODUCTION_8), 0, -1);
|
||||
pattern.AddAlternative(alt);
|
||||
AddPattern(pattern);
|
||||
|
||||
pattern = new ProductionPattern(Convert.ToInt32(ExpressionConstants.ADDITIVE_EXPRESSION), "AdditiveExpression");
|
||||
alt = new ProductionPatternAlternative();
|
||||
alt.AddProduction(Convert.ToInt32(ExpressionConstants.MULTIPLICATIVE_EXPRESSION), 1, 1);
|
||||
alt.AddProduction(Convert.ToInt32(SynteticPatterns.SUBPRODUCTION_10), 0, -1);
|
||||
pattern.AddAlternative(alt);
|
||||
AddPattern(pattern);
|
||||
|
||||
pattern = new ProductionPattern(Convert.ToInt32(ExpressionConstants.MULTIPLICATIVE_EXPRESSION), "MultiplicativeExpression");
|
||||
alt = new ProductionPatternAlternative();
|
||||
alt.AddProduction(Convert.ToInt32(ExpressionConstants.POWER_EXPRESSION), 1, 1);
|
||||
alt.AddProduction(Convert.ToInt32(SynteticPatterns.SUBPRODUCTION_12), 0, -1);
|
||||
pattern.AddAlternative(alt);
|
||||
AddPattern(pattern);
|
||||
|
||||
pattern = new ProductionPattern(Convert.ToInt32(ExpressionConstants.POWER_EXPRESSION), "PowerExpression");
|
||||
alt = new ProductionPatternAlternative();
|
||||
alt.AddProduction(Convert.ToInt32(ExpressionConstants.NEGATE_EXPRESSION), 1, 1);
|
||||
alt.AddProduction(Convert.ToInt32(SynteticPatterns.SUBPRODUCTION_13), 0, -1);
|
||||
pattern.AddAlternative(alt);
|
||||
AddPattern(pattern);
|
||||
|
||||
pattern = new ProductionPattern(Convert.ToInt32(ExpressionConstants.NEGATE_EXPRESSION), "NegateExpression");
|
||||
alt = new ProductionPatternAlternative();
|
||||
alt.AddToken(Convert.ToInt32(ExpressionConstants.SUB), 0, 1);
|
||||
alt.AddProduction(Convert.ToInt32(ExpressionConstants.MEMBER_EXPRESSION), 1, 1);
|
||||
pattern.AddAlternative(alt);
|
||||
AddPattern(pattern);
|
||||
|
||||
pattern = new ProductionPattern(Convert.ToInt32(ExpressionConstants.MEMBER_EXPRESSION), "MemberExpression");
|
||||
alt = new ProductionPatternAlternative();
|
||||
alt.AddProduction(Convert.ToInt32(ExpressionConstants.BASIC_EXPRESSION), 1, 1);
|
||||
alt.AddProduction(Convert.ToInt32(SynteticPatterns.SUBPRODUCTION_14), 0, -1);
|
||||
pattern.AddAlternative(alt);
|
||||
AddPattern(pattern);
|
||||
|
||||
pattern = new ProductionPattern(Convert.ToInt32(ExpressionConstants.MEMBER_ACCESS_EXPRESSION), "MemberAccessExpression");
|
||||
alt = new ProductionPatternAlternative();
|
||||
alt.AddToken(Convert.ToInt32(ExpressionConstants.DOT), 1, 1);
|
||||
alt.AddProduction(Convert.ToInt32(ExpressionConstants.MEMBER_FUNCTION_EXPRESSION), 1, 1);
|
||||
pattern.AddAlternative(alt);
|
||||
AddPattern(pattern);
|
||||
|
||||
pattern = new ProductionPattern(Convert.ToInt32(ExpressionConstants.BASIC_EXPRESSION), "BasicExpression");
|
||||
alt = new ProductionPatternAlternative();
|
||||
alt.AddProduction(Convert.ToInt32(ExpressionConstants.LITERAL_EXPRESSION), 1, 1);
|
||||
pattern.AddAlternative(alt);
|
||||
alt = new ProductionPatternAlternative();
|
||||
alt.AddProduction(Convert.ToInt32(ExpressionConstants.EXPRESSION_GROUP), 1, 1);
|
||||
pattern.AddAlternative(alt);
|
||||
alt = new ProductionPatternAlternative();
|
||||
alt.AddProduction(Convert.ToInt32(ExpressionConstants.MEMBER_FUNCTION_EXPRESSION), 1, 1);
|
||||
pattern.AddAlternative(alt);
|
||||
alt = new ProductionPatternAlternative();
|
||||
alt.AddProduction(Convert.ToInt32(ExpressionConstants.SPECIAL_FUNCTION_EXPRESSION), 1, 1);
|
||||
pattern.AddAlternative(alt);
|
||||
AddPattern(pattern);
|
||||
|
||||
pattern = new ProductionPattern(Convert.ToInt32(ExpressionConstants.MEMBER_FUNCTION_EXPRESSION), "MemberFunctionExpression");
|
||||
alt = new ProductionPatternAlternative();
|
||||
alt.AddProduction(Convert.ToInt32(ExpressionConstants.FIELD_PROPERTY_EXPRESSION), 1, 1);
|
||||
pattern.AddAlternative(alt);
|
||||
alt = new ProductionPatternAlternative();
|
||||
alt.AddProduction(Convert.ToInt32(ExpressionConstants.FUNCTION_CALL_EXPRESSION), 1, 1);
|
||||
pattern.AddAlternative(alt);
|
||||
AddPattern(pattern);
|
||||
|
||||
pattern = new ProductionPattern(Convert.ToInt32(ExpressionConstants.FIELD_PROPERTY_EXPRESSION), "FieldPropertyExpression");
|
||||
alt = new ProductionPatternAlternative();
|
||||
alt.AddToken(Convert.ToInt32(ExpressionConstants.IDENTIFIER), 1, 1);
|
||||
pattern.AddAlternative(alt);
|
||||
AddPattern(pattern);
|
||||
|
||||
pattern = new ProductionPattern(Convert.ToInt32(ExpressionConstants.SPECIAL_FUNCTION_EXPRESSION), "SpecialFunctionExpression");
|
||||
alt = new ProductionPatternAlternative();
|
||||
alt.AddProduction(Convert.ToInt32(ExpressionConstants.IF_EXPRESSION), 1, 1);
|
||||
pattern.AddAlternative(alt);
|
||||
alt = new ProductionPatternAlternative();
|
||||
alt.AddProduction(Convert.ToInt32(ExpressionConstants.CAST_EXPRESSION), 1, 1);
|
||||
pattern.AddAlternative(alt);
|
||||
AddPattern(pattern);
|
||||
|
||||
pattern = new ProductionPattern(Convert.ToInt32(ExpressionConstants.IF_EXPRESSION), "IfExpression");
|
||||
alt = new ProductionPatternAlternative();
|
||||
alt.AddToken(Convert.ToInt32(ExpressionConstants.IF), 1, 1);
|
||||
alt.AddToken(Convert.ToInt32(ExpressionConstants.LEFT_PAREN), 1, 1);
|
||||
alt.AddProduction(Convert.ToInt32(ExpressionConstants.EXPRESSION), 1, 1);
|
||||
alt.AddToken(Convert.ToInt32(ExpressionConstants.ARGUMENT_SEPARATOR), 1, 1);
|
||||
alt.AddProduction(Convert.ToInt32(ExpressionConstants.EXPRESSION), 1, 1);
|
||||
alt.AddToken(Convert.ToInt32(ExpressionConstants.ARGUMENT_SEPARATOR), 1, 1);
|
||||
alt.AddProduction(Convert.ToInt32(ExpressionConstants.EXPRESSION), 1, 1);
|
||||
alt.AddToken(Convert.ToInt32(ExpressionConstants.RIGHT_PAREN), 1, 1);
|
||||
pattern.AddAlternative(alt);
|
||||
AddPattern(pattern);
|
||||
|
||||
pattern = new ProductionPattern(Convert.ToInt32(ExpressionConstants.CAST_EXPRESSION), "CastExpression");
|
||||
alt = new ProductionPatternAlternative();
|
||||
alt.AddToken(Convert.ToInt32(ExpressionConstants.CAST), 1, 1);
|
||||
alt.AddToken(Convert.ToInt32(ExpressionConstants.LEFT_PAREN), 1, 1);
|
||||
alt.AddProduction(Convert.ToInt32(ExpressionConstants.EXPRESSION), 1, 1);
|
||||
alt.AddToken(Convert.ToInt32(ExpressionConstants.ARGUMENT_SEPARATOR), 1, 1);
|
||||
alt.AddProduction(Convert.ToInt32(ExpressionConstants.CAST_TYPE_EXPRESSION), 1, 1);
|
||||
alt.AddToken(Convert.ToInt32(ExpressionConstants.RIGHT_PAREN), 1, 1);
|
||||
pattern.AddAlternative(alt);
|
||||
AddPattern(pattern);
|
||||
|
||||
pattern = new ProductionPattern(Convert.ToInt32(ExpressionConstants.CAST_TYPE_EXPRESSION), "CastTypeExpression");
|
||||
alt = new ProductionPatternAlternative();
|
||||
alt.AddToken(Convert.ToInt32(ExpressionConstants.IDENTIFIER), 1, 1);
|
||||
alt.AddProduction(Convert.ToInt32(SynteticPatterns.SUBPRODUCTION_15), 0, -1);
|
||||
alt.AddToken(Convert.ToInt32(ExpressionConstants.ARRAY_BRACES), 0, 1);
|
||||
pattern.AddAlternative(alt);
|
||||
AddPattern(pattern);
|
||||
|
||||
pattern = new ProductionPattern(Convert.ToInt32(ExpressionConstants.INDEX_EXPRESSION), "IndexExpression");
|
||||
alt = new ProductionPatternAlternative();
|
||||
alt.AddToken(Convert.ToInt32(ExpressionConstants.LEFT_BRACE), 1, 1);
|
||||
alt.AddProduction(Convert.ToInt32(ExpressionConstants.ARGUMENT_LIST), 1, 1);
|
||||
alt.AddToken(Convert.ToInt32(ExpressionConstants.RIGHT_BRACE), 1, 1);
|
||||
pattern.AddAlternative(alt);
|
||||
AddPattern(pattern);
|
||||
|
||||
pattern = new ProductionPattern(Convert.ToInt32(ExpressionConstants.FUNCTION_CALL_EXPRESSION), "FunctionCallExpression");
|
||||
alt = new ProductionPatternAlternative();
|
||||
alt.AddToken(Convert.ToInt32(ExpressionConstants.IDENTIFIER), 1, 1);
|
||||
alt.AddToken(Convert.ToInt32(ExpressionConstants.LEFT_PAREN), 1, 1);
|
||||
alt.AddProduction(Convert.ToInt32(ExpressionConstants.ARGUMENT_LIST), 0, 1);
|
||||
alt.AddToken(Convert.ToInt32(ExpressionConstants.RIGHT_PAREN), 1, 1);
|
||||
pattern.AddAlternative(alt);
|
||||
AddPattern(pattern);
|
||||
|
||||
pattern = new ProductionPattern(Convert.ToInt32(ExpressionConstants.ARGUMENT_LIST), "ArgumentList");
|
||||
alt = new ProductionPatternAlternative();
|
||||
alt.AddProduction(Convert.ToInt32(ExpressionConstants.EXPRESSION), 1, 1);
|
||||
alt.AddProduction(Convert.ToInt32(SynteticPatterns.SUBPRODUCTION_16), 0, -1);
|
||||
pattern.AddAlternative(alt);
|
||||
AddPattern(pattern);
|
||||
|
||||
pattern = new ProductionPattern(Convert.ToInt32(ExpressionConstants.LITERAL_EXPRESSION), "LiteralExpression");
|
||||
alt = new ProductionPatternAlternative();
|
||||
alt.AddToken(Convert.ToInt32(ExpressionConstants.INTEGER), 1, 1);
|
||||
pattern.AddAlternative(alt);
|
||||
alt = new ProductionPatternAlternative();
|
||||
alt.AddToken(Convert.ToInt32(ExpressionConstants.REAL), 1, 1);
|
||||
pattern.AddAlternative(alt);
|
||||
alt = new ProductionPatternAlternative();
|
||||
alt.AddToken(Convert.ToInt32(ExpressionConstants.STRING_LITERAL), 1, 1);
|
||||
pattern.AddAlternative(alt);
|
||||
alt = new ProductionPatternAlternative();
|
||||
alt.AddProduction(Convert.ToInt32(ExpressionConstants.BOOLEAN_LITERAL_EXPRESSION), 1, 1);
|
||||
pattern.AddAlternative(alt);
|
||||
alt = new ProductionPatternAlternative();
|
||||
alt.AddToken(Convert.ToInt32(ExpressionConstants.HEX_LITERAL), 1, 1);
|
||||
pattern.AddAlternative(alt);
|
||||
alt = new ProductionPatternAlternative();
|
||||
alt.AddToken(Convert.ToInt32(ExpressionConstants.CHAR_LITERAL), 1, 1);
|
||||
pattern.AddAlternative(alt);
|
||||
alt = new ProductionPatternAlternative();
|
||||
alt.AddToken(Convert.ToInt32(ExpressionConstants.NULL_LITERAL), 1, 1);
|
||||
pattern.AddAlternative(alt);
|
||||
alt = new ProductionPatternAlternative();
|
||||
alt.AddToken(Convert.ToInt32(ExpressionConstants.DATETIME), 1, 1);
|
||||
pattern.AddAlternative(alt);
|
||||
alt = new ProductionPatternAlternative();
|
||||
alt.AddToken(Convert.ToInt32(ExpressionConstants.TIMESPAN), 1, 1);
|
||||
pattern.AddAlternative(alt);
|
||||
AddPattern(pattern);
|
||||
|
||||
pattern = new ProductionPattern(Convert.ToInt32(ExpressionConstants.BOOLEAN_LITERAL_EXPRESSION), "BooleanLiteralExpression");
|
||||
alt = new ProductionPatternAlternative();
|
||||
alt.AddToken(Convert.ToInt32(ExpressionConstants.TRUE), 1, 1);
|
||||
pattern.AddAlternative(alt);
|
||||
alt = new ProductionPatternAlternative();
|
||||
alt.AddToken(Convert.ToInt32(ExpressionConstants.FALSE), 1, 1);
|
||||
pattern.AddAlternative(alt);
|
||||
AddPattern(pattern);
|
||||
|
||||
pattern = new ProductionPattern(Convert.ToInt32(ExpressionConstants.EXPRESSION_GROUP), "ExpressionGroup");
|
||||
alt = new ProductionPatternAlternative();
|
||||
alt.AddToken(Convert.ToInt32(ExpressionConstants.LEFT_PAREN), 1, 1);
|
||||
alt.AddProduction(Convert.ToInt32(ExpressionConstants.EXPRESSION), 1, 1);
|
||||
alt.AddToken(Convert.ToInt32(ExpressionConstants.RIGHT_PAREN), 1, 1);
|
||||
pattern.AddAlternative(alt);
|
||||
AddPattern(pattern);
|
||||
|
||||
pattern = new ProductionPattern(Convert.ToInt32(SynteticPatterns.SUBPRODUCTION_1), "Subproduction1");
|
||||
pattern.Synthetic = true;
|
||||
alt = new ProductionPatternAlternative();
|
||||
alt.AddToken(Convert.ToInt32(ExpressionConstants.XOR), 1, 1);
|
||||
alt.AddProduction(Convert.ToInt32(ExpressionConstants.OR_EXPRESSION), 1, 1);
|
||||
pattern.AddAlternative(alt);
|
||||
AddPattern(pattern);
|
||||
|
||||
pattern = new ProductionPattern(Convert.ToInt32(SynteticPatterns.SUBPRODUCTION_2), "Subproduction2");
|
||||
pattern.Synthetic = true;
|
||||
alt = new ProductionPatternAlternative();
|
||||
alt.AddToken(Convert.ToInt32(ExpressionConstants.OR), 1, 1);
|
||||
alt.AddProduction(Convert.ToInt32(ExpressionConstants.AND_EXPRESSION), 1, 1);
|
||||
pattern.AddAlternative(alt);
|
||||
AddPattern(pattern);
|
||||
|
||||
pattern = new ProductionPattern(Convert.ToInt32(SynteticPatterns.SUBPRODUCTION_3), "Subproduction3");
|
||||
pattern.Synthetic = true;
|
||||
alt = new ProductionPatternAlternative();
|
||||
alt.AddToken(Convert.ToInt32(ExpressionConstants.AND), 1, 1);
|
||||
alt.AddProduction(Convert.ToInt32(ExpressionConstants.NOT_EXPRESSION), 1, 1);
|
||||
pattern.AddAlternative(alt);
|
||||
AddPattern(pattern);
|
||||
|
||||
pattern = new ProductionPattern(Convert.ToInt32(SynteticPatterns.SUBPRODUCTION_4), "Subproduction4");
|
||||
pattern.Synthetic = true;
|
||||
alt = new ProductionPatternAlternative();
|
||||
alt.AddToken(Convert.ToInt32(ExpressionConstants.IN), 1, 1);
|
||||
alt.AddProduction(Convert.ToInt32(ExpressionConstants.IN_TARGET_EXPRESSION), 1, 1);
|
||||
pattern.AddAlternative(alt);
|
||||
AddPattern(pattern);
|
||||
|
||||
pattern = new ProductionPattern(Convert.ToInt32(SynteticPatterns.SUBPRODUCTION_5), "Subproduction5");
|
||||
pattern.Synthetic = true;
|
||||
alt = new ProductionPatternAlternative();
|
||||
alt.AddToken(Convert.ToInt32(ExpressionConstants.EQ), 1, 1);
|
||||
pattern.AddAlternative(alt);
|
||||
alt = new ProductionPatternAlternative();
|
||||
alt.AddToken(Convert.ToInt32(ExpressionConstants.GT), 1, 1);
|
||||
pattern.AddAlternative(alt);
|
||||
alt = new ProductionPatternAlternative();
|
||||
alt.AddToken(Convert.ToInt32(ExpressionConstants.LT), 1, 1);
|
||||
pattern.AddAlternative(alt);
|
||||
alt = new ProductionPatternAlternative();
|
||||
alt.AddToken(Convert.ToInt32(ExpressionConstants.GTE), 1, 1);
|
||||
pattern.AddAlternative(alt);
|
||||
alt = new ProductionPatternAlternative();
|
||||
alt.AddToken(Convert.ToInt32(ExpressionConstants.LTE), 1, 1);
|
||||
pattern.AddAlternative(alt);
|
||||
alt = new ProductionPatternAlternative();
|
||||
alt.AddToken(Convert.ToInt32(ExpressionConstants.NE), 1, 1);
|
||||
pattern.AddAlternative(alt);
|
||||
AddPattern(pattern);
|
||||
|
||||
pattern = new ProductionPattern(Convert.ToInt32(SynteticPatterns.SUBPRODUCTION_6), "Subproduction6");
|
||||
pattern.Synthetic = true;
|
||||
alt = new ProductionPatternAlternative();
|
||||
alt.AddProduction(Convert.ToInt32(SynteticPatterns.SUBPRODUCTION_5), 1, 1);
|
||||
alt.AddProduction(Convert.ToInt32(ExpressionConstants.SHIFT_EXPRESSION), 1, 1);
|
||||
pattern.AddAlternative(alt);
|
||||
AddPattern(pattern);
|
||||
|
||||
pattern = new ProductionPattern(Convert.ToInt32(SynteticPatterns.SUBPRODUCTION_7), "Subproduction7");
|
||||
pattern.Synthetic = true;
|
||||
alt = new ProductionPatternAlternative();
|
||||
alt.AddToken(Convert.ToInt32(ExpressionConstants.LEFT_SHIFT), 1, 1);
|
||||
pattern.AddAlternative(alt);
|
||||
alt = new ProductionPatternAlternative();
|
||||
alt.AddToken(Convert.ToInt32(ExpressionConstants.RIGHT_SHIFT), 1, 1);
|
||||
pattern.AddAlternative(alt);
|
||||
AddPattern(pattern);
|
||||
|
||||
pattern = new ProductionPattern(Convert.ToInt32(SynteticPatterns.SUBPRODUCTION_8), "Subproduction8");
|
||||
pattern.Synthetic = true;
|
||||
alt = new ProductionPatternAlternative();
|
||||
alt.AddProduction(Convert.ToInt32(SynteticPatterns.SUBPRODUCTION_7), 1, 1);
|
||||
alt.AddProduction(Convert.ToInt32(ExpressionConstants.ADDITIVE_EXPRESSION), 1, 1);
|
||||
pattern.AddAlternative(alt);
|
||||
AddPattern(pattern);
|
||||
|
||||
pattern = new ProductionPattern(Convert.ToInt32(SynteticPatterns.SUBPRODUCTION_9), "Subproduction9");
|
||||
pattern.Synthetic = true;
|
||||
alt = new ProductionPatternAlternative();
|
||||
alt.AddToken(Convert.ToInt32(ExpressionConstants.ADD), 1, 1);
|
||||
pattern.AddAlternative(alt);
|
||||
alt = new ProductionPatternAlternative();
|
||||
alt.AddToken(Convert.ToInt32(ExpressionConstants.SUB), 1, 1);
|
||||
pattern.AddAlternative(alt);
|
||||
AddPattern(pattern);
|
||||
|
||||
pattern = new ProductionPattern(Convert.ToInt32(SynteticPatterns.SUBPRODUCTION_10), "Subproduction10");
|
||||
pattern.Synthetic = true;
|
||||
alt = new ProductionPatternAlternative();
|
||||
alt.AddProduction(Convert.ToInt32(SynteticPatterns.SUBPRODUCTION_9), 1, 1);
|
||||
alt.AddProduction(Convert.ToInt32(ExpressionConstants.MULTIPLICATIVE_EXPRESSION), 1, 1);
|
||||
pattern.AddAlternative(alt);
|
||||
AddPattern(pattern);
|
||||
|
||||
pattern = new ProductionPattern(Convert.ToInt32(SynteticPatterns.SUBPRODUCTION_11), "Subproduction11");
|
||||
pattern.Synthetic = true;
|
||||
alt = new ProductionPatternAlternative();
|
||||
alt.AddToken(Convert.ToInt32(ExpressionConstants.MUL), 1, 1);
|
||||
pattern.AddAlternative(alt);
|
||||
alt = new ProductionPatternAlternative();
|
||||
alt.AddToken(Convert.ToInt32(ExpressionConstants.DIV), 1, 1);
|
||||
pattern.AddAlternative(alt);
|
||||
alt = new ProductionPatternAlternative();
|
||||
alt.AddToken(Convert.ToInt32(ExpressionConstants.MOD), 1, 1);
|
||||
pattern.AddAlternative(alt);
|
||||
AddPattern(pattern);
|
||||
|
||||
pattern = new ProductionPattern(Convert.ToInt32(SynteticPatterns.SUBPRODUCTION_12), "Subproduction12");
|
||||
pattern.Synthetic = true;
|
||||
alt = new ProductionPatternAlternative();
|
||||
alt.AddProduction(Convert.ToInt32(SynteticPatterns.SUBPRODUCTION_11), 1, 1);
|
||||
alt.AddProduction(Convert.ToInt32(ExpressionConstants.POWER_EXPRESSION), 1, 1);
|
||||
pattern.AddAlternative(alt);
|
||||
AddPattern(pattern);
|
||||
|
||||
pattern = new ProductionPattern(Convert.ToInt32(SynteticPatterns.SUBPRODUCTION_13), "Subproduction13");
|
||||
pattern.Synthetic = true;
|
||||
alt = new ProductionPatternAlternative();
|
||||
alt.AddToken(Convert.ToInt32(ExpressionConstants.POWER), 1, 1);
|
||||
alt.AddProduction(Convert.ToInt32(ExpressionConstants.NEGATE_EXPRESSION), 1, 1);
|
||||
pattern.AddAlternative(alt);
|
||||
AddPattern(pattern);
|
||||
|
||||
pattern = new ProductionPattern(Convert.ToInt32(SynteticPatterns.SUBPRODUCTION_14), "Subproduction14");
|
||||
pattern.Synthetic = true;
|
||||
alt = new ProductionPatternAlternative();
|
||||
alt.AddProduction(Convert.ToInt32(ExpressionConstants.MEMBER_ACCESS_EXPRESSION), 1, 1);
|
||||
pattern.AddAlternative(alt);
|
||||
alt = new ProductionPatternAlternative();
|
||||
alt.AddProduction(Convert.ToInt32(ExpressionConstants.INDEX_EXPRESSION), 1, 1);
|
||||
pattern.AddAlternative(alt);
|
||||
AddPattern(pattern);
|
||||
|
||||
pattern = new ProductionPattern(Convert.ToInt32(SynteticPatterns.SUBPRODUCTION_15), "Subproduction15");
|
||||
pattern.Synthetic = true;
|
||||
alt = new ProductionPatternAlternative();
|
||||
alt.AddToken(Convert.ToInt32(ExpressionConstants.DOT), 1, 1);
|
||||
alt.AddToken(Convert.ToInt32(ExpressionConstants.IDENTIFIER), 1, 1);
|
||||
pattern.AddAlternative(alt);
|
||||
AddPattern(pattern);
|
||||
|
||||
pattern = new ProductionPattern(Convert.ToInt32(SynteticPatterns.SUBPRODUCTION_16), "Subproduction16");
|
||||
pattern.Synthetic = true;
|
||||
alt = new ProductionPatternAlternative();
|
||||
alt.AddToken(Convert.ToInt32(ExpressionConstants.ARGUMENT_SEPARATOR), 1, 1);
|
||||
alt.AddProduction(Convert.ToInt32(ExpressionConstants.EXPRESSION), 1, 1);
|
||||
pattern.AddAlternative(alt);
|
||||
AddPattern(pattern);
|
||||
}
|
||||
}
|
||||
}
|
||||
153
Parsing/ExpressionTokenizer.cs
Normal file
153
Parsing/ExpressionTokenizer.cs
Normal file
@@ -0,0 +1,153 @@
|
||||
using Flee.PublicTypes;
|
||||
|
||||
|
||||
namespace Flee.Parsing
|
||||
{
|
||||
/// <summary>
|
||||
/// A character stream tokenizer.
|
||||
/// </summary>
|
||||
internal class ExpressionTokenizer : Tokenizer
|
||||
{
|
||||
private readonly ExpressionContext _myContext;
|
||||
|
||||
public ExpressionTokenizer(TextReader input, ExpressionContext context) : base(input, true)
|
||||
{
|
||||
_myContext = context;
|
||||
CreatePatterns();
|
||||
}
|
||||
|
||||
public ExpressionTokenizer(TextReader input) : base(input, true)
|
||||
{
|
||||
CreatePatterns();
|
||||
}
|
||||
|
||||
private void CreatePatterns()
|
||||
{
|
||||
TokenPattern pattern = default(TokenPattern);
|
||||
CustomTokenPattern customPattern = default(CustomTokenPattern);
|
||||
|
||||
pattern = new TokenPattern(Convert.ToInt32(ExpressionConstants.ADD), "ADD", TokenPattern.PatternType.STRING, "+");
|
||||
AddPattern(pattern);
|
||||
|
||||
pattern = new TokenPattern(Convert.ToInt32(ExpressionConstants.SUB), "SUB", TokenPattern.PatternType.STRING, "-");
|
||||
AddPattern(pattern);
|
||||
|
||||
pattern = new TokenPattern(Convert.ToInt32(ExpressionConstants.MUL), "MUL", TokenPattern.PatternType.STRING, "*");
|
||||
AddPattern(pattern);
|
||||
|
||||
pattern = new TokenPattern(Convert.ToInt32(ExpressionConstants.DIV), "DIV", TokenPattern.PatternType.STRING, "/");
|
||||
AddPattern(pattern);
|
||||
|
||||
pattern = new TokenPattern(Convert.ToInt32(ExpressionConstants.POWER), "POWER", TokenPattern.PatternType.STRING, "^");
|
||||
AddPattern(pattern);
|
||||
|
||||
pattern = new TokenPattern(Convert.ToInt32(ExpressionConstants.MOD), "MOD", TokenPattern.PatternType.STRING, "%");
|
||||
AddPattern(pattern);
|
||||
|
||||
pattern = new TokenPattern(Convert.ToInt32(ExpressionConstants.LEFT_PAREN), "LEFT_PAREN", TokenPattern.PatternType.STRING, "(");
|
||||
AddPattern(pattern);
|
||||
|
||||
pattern = new TokenPattern(Convert.ToInt32(ExpressionConstants.RIGHT_PAREN), "RIGHT_PAREN", TokenPattern.PatternType.STRING, ")");
|
||||
AddPattern(pattern);
|
||||
|
||||
pattern = new TokenPattern(Convert.ToInt32(ExpressionConstants.LEFT_BRACE), "LEFT_BRACE", TokenPattern.PatternType.STRING, "[");
|
||||
AddPattern(pattern);
|
||||
|
||||
pattern = new TokenPattern(Convert.ToInt32(ExpressionConstants.RIGHT_BRACE), "RIGHT_BRACE", TokenPattern.PatternType.STRING, "]");
|
||||
AddPattern(pattern);
|
||||
|
||||
pattern = new TokenPattern(Convert.ToInt32(ExpressionConstants.EQ), "EQ", TokenPattern.PatternType.STRING, "=");
|
||||
AddPattern(pattern);
|
||||
|
||||
pattern = new TokenPattern(Convert.ToInt32(ExpressionConstants.LT), "LT", TokenPattern.PatternType.STRING, "<");
|
||||
AddPattern(pattern);
|
||||
|
||||
pattern = new TokenPattern(Convert.ToInt32(ExpressionConstants.GT), "GT", TokenPattern.PatternType.STRING, ">");
|
||||
AddPattern(pattern);
|
||||
|
||||
pattern = new TokenPattern(Convert.ToInt32(ExpressionConstants.LTE), "LTE", TokenPattern.PatternType.STRING, "<=");
|
||||
AddPattern(pattern);
|
||||
|
||||
pattern = new TokenPattern(Convert.ToInt32(ExpressionConstants.GTE), "GTE", TokenPattern.PatternType.STRING, ">=");
|
||||
AddPattern(pattern);
|
||||
|
||||
pattern = new TokenPattern(Convert.ToInt32(ExpressionConstants.NE), "NE", TokenPattern.PatternType.STRING, "<>");
|
||||
AddPattern(pattern);
|
||||
|
||||
pattern = new TokenPattern(Convert.ToInt32(ExpressionConstants.AND), "AND", TokenPattern.PatternType.STRING, "AND");
|
||||
AddPattern(pattern);
|
||||
|
||||
pattern = new TokenPattern(Convert.ToInt32(ExpressionConstants.OR), "OR", TokenPattern.PatternType.STRING, "OR");
|
||||
AddPattern(pattern);
|
||||
|
||||
pattern = new TokenPattern(Convert.ToInt32(ExpressionConstants.XOR), "XOR", TokenPattern.PatternType.STRING, "XOR");
|
||||
AddPattern(pattern);
|
||||
|
||||
pattern = new TokenPattern(Convert.ToInt32(ExpressionConstants.NOT), "NOT", TokenPattern.PatternType.STRING, "NOT");
|
||||
AddPattern(pattern);
|
||||
|
||||
pattern = new TokenPattern(Convert.ToInt32(ExpressionConstants.IN), "IN", TokenPattern.PatternType.STRING, "in");
|
||||
AddPattern(pattern);
|
||||
|
||||
pattern = new TokenPattern(Convert.ToInt32(ExpressionConstants.DOT), "DOT", TokenPattern.PatternType.STRING, ".");
|
||||
AddPattern(pattern);
|
||||
|
||||
customPattern = new ArgumentSeparatorPattern(Convert.ToInt32(ExpressionConstants.ARGUMENT_SEPARATOR), "ARGUMENT_SEPARATOR", TokenPattern.PatternType.STRING, ",");
|
||||
customPattern.Initialize(Convert.ToInt32(ExpressionConstants.ARGUMENT_SEPARATOR), "ARGUMENT_SEPARATOR", TokenPattern.PatternType.STRING, ",", _myContext);
|
||||
AddPattern(customPattern);
|
||||
|
||||
pattern = new TokenPattern(Convert.ToInt32(ExpressionConstants.ARRAY_BRACES), "ARRAY_BRACES", TokenPattern.PatternType.STRING, "[]");
|
||||
AddPattern(pattern);
|
||||
|
||||
pattern = new TokenPattern(Convert.ToInt32(ExpressionConstants.LEFT_SHIFT), "LEFT_SHIFT", TokenPattern.PatternType.STRING, "<<");
|
||||
AddPattern(pattern);
|
||||
|
||||
pattern = new TokenPattern(Convert.ToInt32(ExpressionConstants.RIGHT_SHIFT), "RIGHT_SHIFT", TokenPattern.PatternType.STRING, ">>");
|
||||
AddPattern(pattern);
|
||||
|
||||
pattern = new TokenPattern(Convert.ToInt32(ExpressionConstants.WHITESPACE), "WHITESPACE", TokenPattern.PatternType.REGEXP, "\\s+");
|
||||
pattern.Ignore = true;
|
||||
AddPattern(pattern);
|
||||
|
||||
pattern = new TokenPattern(Convert.ToInt32(ExpressionConstants.INTEGER), "INTEGER", TokenPattern.PatternType.REGEXP, "\\d+(u|l|ul|lu|f|m)?");
|
||||
AddPattern(pattern);
|
||||
|
||||
customPattern = new RealPattern(Convert.ToInt32(ExpressionConstants.REAL), "REAL", TokenPattern.PatternType.REGEXP, "\\d{0}\\{1}\\d+([e][+-]\\d{{1,3}})?(d|f|m)?");
|
||||
customPattern.Initialize(Convert.ToInt32(ExpressionConstants.REAL), "REAL", TokenPattern.PatternType.REGEXP, "\\d{0}\\{1}\\d+([e][+-]\\d{{1,3}})?(d|f|m)?", _myContext);
|
||||
AddPattern(customPattern, false);
|
||||
|
||||
pattern = new TokenPattern(Convert.ToInt32(ExpressionConstants.STRING_LITERAL), "STRING_LITERAL", TokenPattern.PatternType.REGEXP, "\"([^\"\\r\\n\\\\]|\\\\u[0-9a-f]{4}|\\\\[\\\\\"'trn])*\"");
|
||||
AddPattern(pattern, false);
|
||||
|
||||
pattern = new TokenPattern(Convert.ToInt32(ExpressionConstants.CHAR_LITERAL), "CHAR_LITERAL", TokenPattern.PatternType.REGEXP, "'([^'\\r\\n\\\\]|\\\\u[0-9a-f]{4}|\\\\[\\\\\"'trn])'");
|
||||
AddPattern(pattern, false);
|
||||
|
||||
pattern = new TokenPattern(Convert.ToInt32(ExpressionConstants.TRUE), "TRUE", TokenPattern.PatternType.STRING, "True");
|
||||
AddPattern(pattern);
|
||||
|
||||
pattern = new TokenPattern(Convert.ToInt32(ExpressionConstants.FALSE), "FALSE", TokenPattern.PatternType.STRING, "False");
|
||||
AddPattern(pattern);
|
||||
|
||||
pattern = new TokenPattern(Convert.ToInt32(ExpressionConstants.IDENTIFIER), "IDENTIFIER", TokenPattern.PatternType.REGEXP, "[a-z_]\\w*");
|
||||
AddPattern(pattern);
|
||||
|
||||
pattern = new TokenPattern(Convert.ToInt32(ExpressionConstants.HEX_LITERAL), "HEX_LITERAL", TokenPattern.PatternType.REGEXP, "0x[0-9a-f]+(u|l|ul|lu)?");
|
||||
AddPattern(pattern);
|
||||
|
||||
pattern = new TokenPattern(Convert.ToInt32(ExpressionConstants.NULL_LITERAL), "NULL_LITERAL", TokenPattern.PatternType.STRING, "null");
|
||||
AddPattern(pattern);
|
||||
|
||||
pattern = new TokenPattern(Convert.ToInt32(ExpressionConstants.TIMESPAN), "TIMESPAN", TokenPattern.PatternType.REGEXP, "##(\\d+\\.)?\\d{2}:\\d{2}(:\\d{2}(\\.\\d{1,7})?)?#");
|
||||
AddPattern(pattern, false);
|
||||
|
||||
pattern = new TokenPattern(Convert.ToInt32(ExpressionConstants.DATETIME), "DATETIME", TokenPattern.PatternType.REGEXP, "#[^#]+#");
|
||||
AddPattern(pattern);
|
||||
|
||||
pattern = new TokenPattern(Convert.ToInt32(ExpressionConstants.IF), "IF", TokenPattern.PatternType.STRING, "if");
|
||||
AddPattern(pattern);
|
||||
|
||||
pattern = new TokenPattern(Convert.ToInt32(ExpressionConstants.CAST), "CAST", TokenPattern.PatternType.STRING, "cast");
|
||||
AddPattern(pattern);
|
||||
}
|
||||
}
|
||||
}
|
||||
226
Parsing/LookAheadReader.cs
Normal file
226
Parsing/LookAheadReader.cs
Normal file
@@ -0,0 +1,226 @@
|
||||
namespace Flee.Parsing
|
||||
{
|
||||
// * A look-ahead character stream reader. This class provides the
|
||||
// * functionalities of a buffered line-number reader, but with the
|
||||
// * additional possibility of peeking an unlimited number of
|
||||
// * characters ahead. When looking further and further ahead in the
|
||||
// * character stream, the buffer is continously enlarged to contain
|
||||
// * all the required characters from the current position an
|
||||
// * onwards. This means that looking more characters ahead requires
|
||||
// * more memory, and thus becomes unviable in the end.
|
||||
internal class LookAheadReader : TextReader
|
||||
{
|
||||
private const int StreamBlockSize = 4096;
|
||||
private const int BufferBlockSize = 1024;
|
||||
private char[] _buffer = new char[StreamBlockSize];
|
||||
private int _pos;
|
||||
private int _length;
|
||||
private TextReader _input = null;
|
||||
private int _line = 1;
|
||||
private int _column = 1;
|
||||
|
||||
public LookAheadReader(TextReader input) : base()
|
||||
{
|
||||
this._input = input;
|
||||
}
|
||||
|
||||
public int LineNumber => _line;
|
||||
|
||||
public int ColumnNumber => _column;
|
||||
|
||||
public override int Read()
|
||||
{
|
||||
ReadAhead(1);
|
||||
if (_pos >= _length)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
UpdateLineColumnNumbers(1);
|
||||
return Convert.ToInt32(_buffer[System.Math.Max(System.Threading.Interlocked.Increment(ref _pos), _pos - 1)]);
|
||||
}
|
||||
}
|
||||
|
||||
public override int Read(char[] cbuf, int off, int len)
|
||||
{
|
||||
ReadAhead(len);
|
||||
if (_pos >= _length)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
var count = _length - _pos;
|
||||
if (count > len)
|
||||
{
|
||||
count = len;
|
||||
}
|
||||
UpdateLineColumnNumbers(count);
|
||||
Array.Copy(_buffer, _pos, cbuf, off, count);
|
||||
_pos += count;
|
||||
return count;
|
||||
}
|
||||
}
|
||||
|
||||
public string ReadString(int len)
|
||||
{
|
||||
ReadAhead(len);
|
||||
if (_pos >= _length)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
else
|
||||
{
|
||||
var count = _length - _pos;
|
||||
if (count > len)
|
||||
{
|
||||
count = len;
|
||||
}
|
||||
UpdateLineColumnNumbers(count);
|
||||
var result = new string(_buffer, _pos, count);
|
||||
_pos += count;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
public override int Peek()
|
||||
{
|
||||
return Peek(0);
|
||||
}
|
||||
|
||||
public int Peek(int off)
|
||||
{
|
||||
ReadAhead(off + 1);
|
||||
if (_pos + off >= _length)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
return Convert.ToInt32(_buffer[_pos + off]);
|
||||
}
|
||||
}
|
||||
|
||||
public string PeekString(int off, int len)
|
||||
{
|
||||
ReadAhead(off + len + 1);
|
||||
if (_pos + off >= _length)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
else
|
||||
{
|
||||
var count = _length - (_pos + off);
|
||||
if (count > len)
|
||||
{
|
||||
count = len;
|
||||
}
|
||||
return new string(_buffer, _pos + off, count);
|
||||
}
|
||||
}
|
||||
|
||||
public override void Close()
|
||||
{
|
||||
_buffer = null;
|
||||
_pos = 0;
|
||||
_length = 0;
|
||||
if (_input != null)
|
||||
{
|
||||
_input.Close();
|
||||
_input = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void ReadAhead(int offset)
|
||||
{
|
||||
int size = 0;
|
||||
int readSize = 0;
|
||||
|
||||
// Check for end of stream or already read characters
|
||||
if (_input == null || _pos + offset < _length)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove old characters from buffer
|
||||
if (_pos > BufferBlockSize)
|
||||
{
|
||||
Array.Copy(_buffer, _pos, _buffer, 0, _length - _pos);
|
||||
_length -= _pos;
|
||||
_pos = 0;
|
||||
}
|
||||
|
||||
// Calculate number of characters to read
|
||||
size = _pos + offset - _length + 1;
|
||||
if (size % StreamBlockSize != 0)
|
||||
{
|
||||
size = (size / StreamBlockSize) * StreamBlockSize;
|
||||
size += StreamBlockSize;
|
||||
}
|
||||
EnsureBufferCapacity(_length + size);
|
||||
|
||||
// Read characters
|
||||
try
|
||||
{
|
||||
readSize = _input.Read(_buffer, _length, size);
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
_input = null;
|
||||
throw;
|
||||
}
|
||||
|
||||
// Append characters to buffer
|
||||
if (readSize > 0)
|
||||
{
|
||||
_length += readSize;
|
||||
}
|
||||
if (readSize < size)
|
||||
{
|
||||
try
|
||||
{
|
||||
_input.Close();
|
||||
}
|
||||
finally
|
||||
{
|
||||
_input = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void EnsureBufferCapacity(int size)
|
||||
{
|
||||
char[] newbuf = null;
|
||||
|
||||
if (_buffer.Length >= size)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (size % BufferBlockSize != 0)
|
||||
{
|
||||
size = (size / BufferBlockSize) * BufferBlockSize;
|
||||
size += BufferBlockSize;
|
||||
}
|
||||
newbuf = new char[size];
|
||||
Array.Copy(_buffer, 0, newbuf, 0, _length);
|
||||
_buffer = newbuf;
|
||||
}
|
||||
|
||||
private void UpdateLineColumnNumbers(int offset)
|
||||
{
|
||||
for (int i = 0; i <= offset - 1; i++)
|
||||
{
|
||||
if (_buffer.Contains(_buffer[_pos + i]))
|
||||
{
|
||||
_line += 1;
|
||||
_column = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
_column += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
589
Parsing/LookAheadSet.cs
Normal file
589
Parsing/LookAheadSet.cs
Normal file
@@ -0,0 +1,589 @@
|
||||
using System.Collections;
|
||||
using System.Text;
|
||||
|
||||
namespace Flee.Parsing
|
||||
{
|
||||
/*
|
||||
* A token look-ahead set. This class contains a set of token id
|
||||
* sequences. All sequences in the set are limited in length, so
|
||||
* that no single sequence is longer than a maximum value. This
|
||||
* class also filters out duplicates. Each token sequence also
|
||||
* contains a repeat flag, allowing the look-ahead set to contain
|
||||
* information about possible infinite repetitions of certain
|
||||
* sequences. That information is important when conflicts arise
|
||||
* between two look-ahead sets, as such a conflict cannot be
|
||||
* resolved if the conflicting sequences can be repeated (would
|
||||
* cause infinite loop).
|
||||
*/
|
||||
internal class LookAheadSet
|
||||
{
|
||||
private readonly ArrayList _elements = new ArrayList();
|
||||
private readonly int _maxLength;
|
||||
|
||||
public LookAheadSet(int maxLength)
|
||||
{
|
||||
this._maxLength = maxLength;
|
||||
}
|
||||
|
||||
public LookAheadSet(int maxLength, LookAheadSet set)
|
||||
: this(maxLength)
|
||||
{
|
||||
|
||||
AddAll(set);
|
||||
}
|
||||
|
||||
public int Size()
|
||||
{
|
||||
return _elements.Count;
|
||||
}
|
||||
|
||||
public int GetMinLength()
|
||||
{
|
||||
int min = -1;
|
||||
|
||||
for (int i = 0; i < _elements.Count; i++)
|
||||
{
|
||||
var seq = (Sequence)_elements[i];
|
||||
if (min < 0 || seq.Length() < min)
|
||||
{
|
||||
min = seq.Length();
|
||||
}
|
||||
}
|
||||
return (min < 0) ? 0 : min;
|
||||
}
|
||||
|
||||
public int GetMaxLength()
|
||||
{
|
||||
int max = 0;
|
||||
for (int i = 0; i < _elements.Count; i++)
|
||||
{
|
||||
var seq = (Sequence)_elements[i];
|
||||
if (seq.Length() > max)
|
||||
{
|
||||
max = seq.Length();
|
||||
}
|
||||
}
|
||||
return max;
|
||||
}
|
||||
|
||||
public int[] GetInitialTokens()
|
||||
{
|
||||
ArrayList list = new ArrayList();
|
||||
int i;
|
||||
for (i = 0; i < _elements.Count; i++)
|
||||
{
|
||||
var token = ((Sequence)_elements[i]).GetToken(0);
|
||||
if (token != null && !list.Contains(token))
|
||||
{
|
||||
list.Add(token);
|
||||
}
|
||||
}
|
||||
var result = new int[list.Count];
|
||||
for (i = 0; i < list.Count; i++)
|
||||
{
|
||||
result[i] = (int)list[i];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public bool IsRepetitive()
|
||||
{
|
||||
for (int i = 0; i < _elements.Count; i++)
|
||||
{
|
||||
var seq = (Sequence)_elements[i];
|
||||
if (seq.IsRepetitive())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool IsNext(Parser parser)
|
||||
{
|
||||
for (int i = 0; i < _elements.Count; i++)
|
||||
{
|
||||
var seq = (Sequence)_elements[i];
|
||||
if (seq.IsNext(parser))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool IsNext(Parser parser, int length)
|
||||
{
|
||||
for (int i = 0; i < _elements.Count; i++)
|
||||
{
|
||||
var seq = (Sequence)_elements[i];
|
||||
if (seq.IsNext(parser, length))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool IsOverlap(LookAheadSet set)
|
||||
{
|
||||
for (int i = 0; i < _elements.Count; i++)
|
||||
{
|
||||
if (set.IsOverlap((Sequence)_elements[i]))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool IsOverlap(Sequence seq)
|
||||
{
|
||||
for (int i = 0; i < _elements.Count; i++)
|
||||
{
|
||||
var elem = (Sequence)_elements[i];
|
||||
if (seq.StartsWith(elem) || elem.StartsWith(seq))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool Contains(Sequence elem)
|
||||
{
|
||||
return FindSequence(elem) != null;
|
||||
}
|
||||
|
||||
public bool Intersects(LookAheadSet set)
|
||||
{
|
||||
for (int i = 0; i < _elements.Count; i++)
|
||||
{
|
||||
if (set.Contains((Sequence)_elements[i]))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private Sequence FindSequence(Sequence elem)
|
||||
{
|
||||
for (int i = 0; i < _elements.Count; i++)
|
||||
{
|
||||
if (_elements[i].Equals(elem))
|
||||
{
|
||||
return (Sequence)_elements[i];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void Add(Sequence seq)
|
||||
{
|
||||
if (seq.Length() > _maxLength)
|
||||
{
|
||||
seq = new Sequence(_maxLength, seq);
|
||||
}
|
||||
if (!Contains(seq))
|
||||
{
|
||||
_elements.Add(seq);
|
||||
}
|
||||
}
|
||||
|
||||
public void Add(int token)
|
||||
{
|
||||
Add(new Sequence(false, token));
|
||||
}
|
||||
|
||||
public void AddAll(LookAheadSet set)
|
||||
{
|
||||
for (int i = 0; i < set._elements.Count; i++)
|
||||
{
|
||||
Add((Sequence)set._elements[i]);
|
||||
}
|
||||
}
|
||||
|
||||
public void AddEmpty()
|
||||
{
|
||||
Add(new Sequence());
|
||||
}
|
||||
|
||||
private void Remove(Sequence seq)
|
||||
{
|
||||
_elements.Remove(seq);
|
||||
}
|
||||
|
||||
public void RemoveAll(LookAheadSet set)
|
||||
{
|
||||
for (int i = 0; i < set._elements.Count; i++)
|
||||
{
|
||||
Remove((Sequence)set._elements[i]);
|
||||
}
|
||||
}
|
||||
|
||||
public LookAheadSet CreateNextSet(int token)
|
||||
{
|
||||
LookAheadSet result = new LookAheadSet(_maxLength - 1);
|
||||
for (int i = 0; i < _elements.Count; i++)
|
||||
{
|
||||
var seq = (Sequence)_elements[i];
|
||||
var value = seq.GetToken(0);
|
||||
if (value != null && token == (int)value)
|
||||
{
|
||||
result.Add(seq.Subsequence(1));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public LookAheadSet CreateIntersection(LookAheadSet set)
|
||||
{
|
||||
LookAheadSet result = new LookAheadSet(_maxLength);
|
||||
for (int i = 0; i < _elements.Count; i++)
|
||||
{
|
||||
var seq1 = (Sequence)_elements[i];
|
||||
var seq2 = set.FindSequence(seq1);
|
||||
if (seq2 != null && seq1.IsRepetitive())
|
||||
{
|
||||
result.Add(seq2);
|
||||
}
|
||||
else if (seq2 != null)
|
||||
{
|
||||
result.Add(seq1);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public LookAheadSet CreateCombination(LookAheadSet set)
|
||||
{
|
||||
LookAheadSet result = new LookAheadSet(_maxLength);
|
||||
|
||||
// Handle special cases
|
||||
if (this.Size() <= 0)
|
||||
{
|
||||
return set;
|
||||
}
|
||||
else if (set.Size() <= 0)
|
||||
{
|
||||
return this;
|
||||
}
|
||||
|
||||
// Create combinations
|
||||
for (int i = 0; i < _elements.Count; i++)
|
||||
{
|
||||
var first = (Sequence)_elements[i];
|
||||
if (first.Length() >= _maxLength)
|
||||
{
|
||||
result.Add(first);
|
||||
}
|
||||
else if (first.Length() <= 0)
|
||||
{
|
||||
result.AddAll(set);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int j = 0; j < set._elements.Count; j++)
|
||||
{
|
||||
var second = (Sequence)set._elements[j];
|
||||
result.Add(first.Concat(_maxLength, second));
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public LookAheadSet CreateOverlaps(LookAheadSet set)
|
||||
{
|
||||
LookAheadSet result = new LookAheadSet(_maxLength);
|
||||
|
||||
for (int i = 0; i < _elements.Count; i++)
|
||||
{
|
||||
var seq = (Sequence)_elements[i];
|
||||
if (set.IsOverlap(seq))
|
||||
{
|
||||
result.Add(seq);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public LookAheadSet CreateFilter(LookAheadSet set)
|
||||
{
|
||||
LookAheadSet result = new LookAheadSet(_maxLength);
|
||||
|
||||
// Handle special cases
|
||||
if (this.Size() <= 0 || set.Size() <= 0)
|
||||
{
|
||||
return this;
|
||||
}
|
||||
|
||||
// Create combinations
|
||||
for (int i = 0; i < _elements.Count; i++)
|
||||
{
|
||||
var first = (Sequence)_elements[i];
|
||||
for (int j = 0; j < set._elements.Count; j++)
|
||||
{
|
||||
var second = (Sequence)set._elements[j];
|
||||
if (first.StartsWith(second))
|
||||
{
|
||||
result.Add(first.Subsequence(second.Length()));
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public LookAheadSet CreateRepetitive()
|
||||
{
|
||||
LookAheadSet result = new LookAheadSet(_maxLength);
|
||||
|
||||
for (int i = 0; i < _elements.Count; i++)
|
||||
{
|
||||
var seq = (Sequence)_elements[i];
|
||||
if (seq.IsRepetitive())
|
||||
{
|
||||
result.Add(seq);
|
||||
}
|
||||
else
|
||||
{
|
||||
result.Add(new Sequence(true, seq));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return ToString(null);
|
||||
}
|
||||
|
||||
public string ToString(Tokenizer tokenizer)
|
||||
{
|
||||
StringBuilder buffer = new StringBuilder();
|
||||
|
||||
buffer.Append("{");
|
||||
for (int i = 0; i < _elements.Count; i++)
|
||||
{
|
||||
var seq = (Sequence)_elements[i];
|
||||
buffer.Append("\n ");
|
||||
buffer.Append(seq.ToString(tokenizer));
|
||||
}
|
||||
buffer.Append("\n}");
|
||||
return buffer.ToString();
|
||||
}
|
||||
|
||||
private class Sequence
|
||||
{
|
||||
private bool _repeat;
|
||||
private readonly ArrayList _tokens;
|
||||
|
||||
public Sequence()
|
||||
{
|
||||
this._repeat = false;
|
||||
this._tokens = new ArrayList(0);
|
||||
}
|
||||
|
||||
public Sequence(bool repeat, int token)
|
||||
{
|
||||
_repeat = false;
|
||||
_tokens = new ArrayList(1);
|
||||
_tokens.Add(token);
|
||||
}
|
||||
|
||||
public Sequence(int length, Sequence seq)
|
||||
{
|
||||
this._repeat = seq._repeat;
|
||||
this._tokens = new ArrayList(length);
|
||||
if (seq.Length() < length)
|
||||
{
|
||||
length = seq.Length();
|
||||
}
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
_tokens.Add(seq._tokens[i]);
|
||||
}
|
||||
}
|
||||
|
||||
public Sequence(bool repeat, Sequence seq)
|
||||
{
|
||||
this._repeat = repeat;
|
||||
this._tokens = seq._tokens;
|
||||
}
|
||||
|
||||
public int Length()
|
||||
{
|
||||
return _tokens.Count;
|
||||
}
|
||||
|
||||
public object GetToken(int pos)
|
||||
{
|
||||
if (pos >= 0 && pos < _tokens.Count)
|
||||
{
|
||||
return _tokens[pos];
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (obj is Sequence)
|
||||
{
|
||||
return Equals((Sequence)obj);
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public bool Equals(Sequence seq)
|
||||
{
|
||||
if (_tokens.Count != seq._tokens.Count)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
for (int i = 0; i < _tokens.Count; i++)
|
||||
{
|
||||
if (!_tokens[i].Equals(seq._tokens[i]))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return _tokens.Count.GetHashCode();
|
||||
}
|
||||
|
||||
public bool StartsWith(Sequence seq)
|
||||
{
|
||||
if (Length() < seq.Length())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
for (int i = 0; i < seq._tokens.Count; i++)
|
||||
{
|
||||
if (!_tokens[i].Equals(seq._tokens[i]))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool IsRepetitive()
|
||||
{
|
||||
return _repeat;
|
||||
}
|
||||
|
||||
public bool IsNext(Parser parser)
|
||||
{
|
||||
for (int i = 0; i < _tokens.Count; i++)
|
||||
{
|
||||
var id = (int)_tokens[i];
|
||||
var token = parser.PeekToken(i);
|
||||
if (token == null || token.Id != id)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool IsNext(Parser parser, int length)
|
||||
{
|
||||
if (length > _tokens.Count)
|
||||
{
|
||||
length = _tokens.Count;
|
||||
}
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
var id = (int)_tokens[i];
|
||||
var token = parser.PeekToken(i);
|
||||
if (token == null || token.Id != id)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return ToString(null);
|
||||
}
|
||||
|
||||
public string ToString(Tokenizer tokenizer)
|
||||
{
|
||||
StringBuilder buffer = new StringBuilder();
|
||||
|
||||
if (tokenizer == null)
|
||||
{
|
||||
buffer.Append(_tokens.ToString());
|
||||
}
|
||||
else
|
||||
{
|
||||
buffer.Append("[");
|
||||
for (int i = 0; i < _tokens.Count; i++)
|
||||
{
|
||||
var id = (int)_tokens[i];
|
||||
var str = tokenizer.GetPatternDescription(id);
|
||||
if (i > 0)
|
||||
{
|
||||
buffer.Append(" ");
|
||||
}
|
||||
buffer.Append(str);
|
||||
}
|
||||
buffer.Append("]");
|
||||
}
|
||||
if (_repeat)
|
||||
{
|
||||
buffer.Append(" *");
|
||||
}
|
||||
return buffer.ToString();
|
||||
}
|
||||
|
||||
public Sequence Concat(int length, Sequence seq)
|
||||
{
|
||||
Sequence res = new Sequence(length, this);
|
||||
|
||||
if (seq._repeat)
|
||||
{
|
||||
res._repeat = true;
|
||||
}
|
||||
length -= this.Length();
|
||||
if (length > seq.Length())
|
||||
{
|
||||
res._tokens.AddRange(seq._tokens);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
res._tokens.Add(seq._tokens[i]);
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
public Sequence Subsequence(int start)
|
||||
{
|
||||
Sequence res = new Sequence(Length(), this);
|
||||
|
||||
while (start > 0 && res._tokens.Count > 0)
|
||||
{
|
||||
res._tokens.RemoveAt(0);
|
||||
start--;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
107
Parsing/Matcher.cs
Normal file
107
Parsing/Matcher.cs
Normal file
@@ -0,0 +1,107 @@
|
||||
namespace Flee.Parsing
|
||||
{
|
||||
/**
|
||||
* A regular expression string matcher. This class handles the
|
||||
* matching of a specific string with a specific regular
|
||||
* expression. It contains state information about the matching
|
||||
* process, as for example the position of the latest match, and a
|
||||
* number of flags that were set. This class is not thread-safe.
|
||||
*/
|
||||
internal class Matcher
|
||||
{
|
||||
private readonly Element _element;
|
||||
private ReaderBuffer _buffer;
|
||||
private readonly bool _ignoreCase;
|
||||
private int _start;
|
||||
private int _length;
|
||||
private bool _endOfString;
|
||||
|
||||
internal Matcher(Element e, ReaderBuffer buffer, bool ignoreCase)
|
||||
{
|
||||
this._element = e;
|
||||
this._buffer = buffer;
|
||||
this._ignoreCase = ignoreCase;
|
||||
this._start = 0;
|
||||
Reset();
|
||||
}
|
||||
|
||||
public bool IsCaseInsensitive()
|
||||
{
|
||||
return _ignoreCase;
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
_length = -1;
|
||||
_endOfString = false;
|
||||
}
|
||||
|
||||
public void Reset(string str)
|
||||
{
|
||||
Reset(new ReaderBuffer(new StringReader(str)));
|
||||
}
|
||||
|
||||
public void Reset(ReaderBuffer buffer)
|
||||
{
|
||||
this._buffer = buffer;
|
||||
Reset();
|
||||
}
|
||||
|
||||
public int Start()
|
||||
{
|
||||
return _start;
|
||||
}
|
||||
|
||||
public int End()
|
||||
{
|
||||
if (_length > 0)
|
||||
{
|
||||
return _start + _length;
|
||||
}
|
||||
else
|
||||
{
|
||||
return _start;
|
||||
}
|
||||
}
|
||||
|
||||
public int Length()
|
||||
{
|
||||
return _length;
|
||||
}
|
||||
|
||||
public bool HasReadEndOfString()
|
||||
{
|
||||
return _endOfString;
|
||||
}
|
||||
|
||||
public bool MatchFromBeginning()
|
||||
{
|
||||
return MatchFrom(0);
|
||||
}
|
||||
|
||||
public bool MatchFrom(int pos)
|
||||
{
|
||||
Reset();
|
||||
_start = pos;
|
||||
_length = _element.Match(this, _buffer, _start, 0);
|
||||
return _length >= 0;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
if (_length <= 0)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
else
|
||||
{
|
||||
return _buffer.Substring(_buffer.Position, _length);
|
||||
}
|
||||
}
|
||||
|
||||
internal void SetReadEndOfString()
|
||||
{
|
||||
_endOfString = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
240
Parsing/Node.cs
Normal file
240
Parsing/Node.cs
Normal file
@@ -0,0 +1,240 @@
|
||||
using System.Collections;
|
||||
|
||||
namespace Flee.Parsing
|
||||
{
|
||||
|
||||
/**
|
||||
* An abstract parse tree node. This class is inherited by all
|
||||
* nodes in the parse tree, i.e. by the token and production
|
||||
* classes.
|
||||
*/
|
||||
internal abstract class Node
|
||||
{
|
||||
private Node _parent;
|
||||
private ArrayList _values;
|
||||
|
||||
internal virtual bool IsHidden()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public abstract int Id
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
public virtual int GetId()
|
||||
{
|
||||
return Id;
|
||||
}
|
||||
|
||||
public abstract string Name
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
public virtual string GetName()
|
||||
{
|
||||
return Name;
|
||||
}
|
||||
|
||||
public virtual int StartLine
|
||||
{
|
||||
get
|
||||
{
|
||||
for (int i = 0; i < Count; i++)
|
||||
{
|
||||
var line = this[i].StartLine;
|
||||
if (line >= 0)
|
||||
{
|
||||
return line;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
public virtual int GetStartLine()
|
||||
{
|
||||
return StartLine;
|
||||
}
|
||||
|
||||
public virtual int StartColumn
|
||||
{
|
||||
get
|
||||
{
|
||||
for (int i = 0; i < Count; i++)
|
||||
{
|
||||
var col = this[i].StartColumn;
|
||||
if (col >= 0)
|
||||
{
|
||||
return col;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
public virtual int GetStartColumn()
|
||||
{
|
||||
return StartColumn;
|
||||
}
|
||||
|
||||
public virtual int EndLine
|
||||
{
|
||||
get
|
||||
{
|
||||
for (int i = Count - 1; i >= 0; i--)
|
||||
{
|
||||
var line = this[i].EndLine;
|
||||
if (line >= 0)
|
||||
{
|
||||
return line;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
public virtual int GetEndLine()
|
||||
{
|
||||
return EndLine;
|
||||
}
|
||||
|
||||
public virtual int EndColumn
|
||||
{
|
||||
get
|
||||
{
|
||||
int col;
|
||||
|
||||
for (int i = Count - 1; i >= 0; i--)
|
||||
{
|
||||
col = this[i].EndColumn;
|
||||
if (col >= 0)
|
||||
{
|
||||
return col;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
public virtual int GetEndColumn()
|
||||
{
|
||||
return EndColumn;
|
||||
}
|
||||
|
||||
public Node Parent => _parent;
|
||||
|
||||
public Node GetParent()
|
||||
{
|
||||
return Parent;
|
||||
}
|
||||
|
||||
internal void SetParent(Node parent)
|
||||
{
|
||||
this._parent = parent;
|
||||
}
|
||||
|
||||
public virtual int Count => 0;
|
||||
|
||||
public virtual int GetChildCount()
|
||||
{
|
||||
return Count;
|
||||
}
|
||||
|
||||
public int GetDescendantCount()
|
||||
{
|
||||
int count = 0;
|
||||
|
||||
for (int i = 0; i < Count; i++)
|
||||
{
|
||||
count += 1 + this[i].GetDescendantCount();
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
public virtual Node this[int index] => null;
|
||||
|
||||
public virtual Node GetChildAt(int index)
|
||||
{
|
||||
return this[index];
|
||||
}
|
||||
|
||||
public ArrayList Values
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_values == null)
|
||||
{
|
||||
_values = new ArrayList();
|
||||
}
|
||||
return _values;
|
||||
}
|
||||
set
|
||||
{
|
||||
this._values = value;
|
||||
}
|
||||
}
|
||||
|
||||
public int GetValueCount()
|
||||
{
|
||||
if (_values == null)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
return _values.Count;
|
||||
}
|
||||
}
|
||||
|
||||
public object GetValue(int pos)
|
||||
{
|
||||
return Values[pos];
|
||||
}
|
||||
|
||||
public ArrayList GetAllValues()
|
||||
{
|
||||
return _values;
|
||||
}
|
||||
|
||||
|
||||
public void AddValue(object value)
|
||||
{
|
||||
if (value != null)
|
||||
{
|
||||
Values.Add(value);
|
||||
}
|
||||
}
|
||||
|
||||
public void AddValues(ArrayList values)
|
||||
{
|
||||
if (values != null)
|
||||
{
|
||||
Values.AddRange(values);
|
||||
}
|
||||
}
|
||||
|
||||
public void RemoveAllValues()
|
||||
{
|
||||
_values = null;
|
||||
}
|
||||
|
||||
public void PrintTo(TextWriter output)
|
||||
{
|
||||
PrintTo(output, "");
|
||||
output.Flush();
|
||||
}
|
||||
|
||||
private void PrintTo(TextWriter output, string indent)
|
||||
{
|
||||
output.WriteLine(indent + ToString());
|
||||
indent = indent + " ";
|
||||
for (int i = 0; i < Count; i++)
|
||||
{
|
||||
this[i].PrintTo(output, indent);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
250
Parsing/ParseException.cs
Normal file
250
Parsing/ParseException.cs
Normal file
@@ -0,0 +1,250 @@
|
||||
using System.Collections;
|
||||
using System.Text;
|
||||
|
||||
namespace Flee.Parsing
|
||||
{
|
||||
/**
|
||||
* A parse exception.
|
||||
*/
|
||||
public class ParseException : Exception
|
||||
{
|
||||
public enum ErrorType
|
||||
{
|
||||
|
||||
/**
|
||||
* The internal error type is only used to signal an error
|
||||
* that is a result of a bug in the parser or tokenizer
|
||||
* code.
|
||||
*/
|
||||
INTERNAL,
|
||||
|
||||
/**
|
||||
* The I/O error type is used for stream I/O errors.
|
||||
*/
|
||||
IO,
|
||||
|
||||
/**
|
||||
* The unexpected end of file error type is used when end
|
||||
* of file is encountered instead of a valid token.
|
||||
*/
|
||||
UNEXPECTED_EOF,
|
||||
|
||||
/**
|
||||
* The unexpected character error type is used when a
|
||||
* character is read that isn't handled by one of the
|
||||
* token patterns.
|
||||
*/
|
||||
UNEXPECTED_CHAR,
|
||||
|
||||
/**
|
||||
* The unexpected token error type is used when another
|
||||
* token than the expected one is encountered.
|
||||
*/
|
||||
UNEXPECTED_TOKEN,
|
||||
|
||||
/**
|
||||
* The invalid token error type is used when a token
|
||||
* pattern with an error message is matched. The
|
||||
* additional information provided should contain the
|
||||
* error message.
|
||||
*/
|
||||
INVALID_TOKEN,
|
||||
|
||||
/**
|
||||
* The analysis error type is used when an error is
|
||||
* encountered in the analysis. The additional information
|
||||
* provided should contain the error message.
|
||||
*/
|
||||
ANALYSIS
|
||||
}
|
||||
|
||||
private readonly ErrorType _type;
|
||||
private readonly string _info;
|
||||
private readonly ArrayList _details;
|
||||
private readonly int _line;
|
||||
private readonly int _column;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new parse exception.
|
||||
/// </summary>
|
||||
/// <param name="type"></param>
|
||||
/// <param name="info"></param>
|
||||
/// <param name="line"></param>
|
||||
/// <param name="column"></param>
|
||||
public ParseException(ErrorType type,
|
||||
string info,
|
||||
int line,
|
||||
int column)
|
||||
: this(type, info, null, line, column)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new parse exception. This constructor is only
|
||||
/// used to supply the detailed information array, which is
|
||||
/// only used for expected token errors. The list then contains
|
||||
/// descriptions of the expected tokens.
|
||||
/// </summary>
|
||||
/// <param name="type"></param>
|
||||
/// <param name="info"></param>
|
||||
/// <param name="details"></param>
|
||||
/// <param name="line"></param>
|
||||
/// <param name="column"></param>
|
||||
public ParseException(ErrorType type,
|
||||
string info,
|
||||
ArrayList details,
|
||||
int line,
|
||||
int column)
|
||||
{
|
||||
|
||||
this._type = type;
|
||||
this._info = info;
|
||||
this._details = details;
|
||||
this._line = line;
|
||||
this._column = column;
|
||||
}
|
||||
|
||||
|
||||
public ErrorType Type => _type;
|
||||
|
||||
public ErrorType GetErrorType()
|
||||
{
|
||||
return Type;
|
||||
}
|
||||
|
||||
public string Info => _info;
|
||||
|
||||
public string GetInfo()
|
||||
{
|
||||
return Info;
|
||||
}
|
||||
|
||||
public ArrayList Details => new ArrayList(_details);
|
||||
|
||||
public ArrayList GetDetails()
|
||||
{
|
||||
return Details;
|
||||
}
|
||||
|
||||
public int Line => _line;
|
||||
|
||||
public int GetLine()
|
||||
{
|
||||
return Line;
|
||||
}
|
||||
|
||||
public int Column => _column;
|
||||
|
||||
public int GetColumn()
|
||||
{
|
||||
return _column;
|
||||
}
|
||||
|
||||
public override string Message
|
||||
{
|
||||
get
|
||||
{
|
||||
StringBuilder buffer = new StringBuilder();
|
||||
|
||||
// Add error description
|
||||
buffer.Append(ErrorMessage);
|
||||
|
||||
// Add line and column
|
||||
if (_line > 0 && _column > 0)
|
||||
{
|
||||
buffer.Append(", on line: ");
|
||||
buffer.Append(_line);
|
||||
buffer.Append(" column: ");
|
||||
buffer.Append(_column);
|
||||
}
|
||||
|
||||
return buffer.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
public string GetMessage()
|
||||
{
|
||||
return Message;
|
||||
}
|
||||
|
||||
public string ErrorMessage
|
||||
{
|
||||
get
|
||||
{
|
||||
StringBuilder buffer = new StringBuilder();
|
||||
|
||||
// Add type and info
|
||||
switch (_type)
|
||||
{
|
||||
case ErrorType.IO:
|
||||
buffer.Append("I/O error: ");
|
||||
buffer.Append(_info);
|
||||
break;
|
||||
case ErrorType.UNEXPECTED_EOF:
|
||||
buffer.Append("unexpected end of file");
|
||||
break;
|
||||
case ErrorType.UNEXPECTED_CHAR:
|
||||
buffer.Append("unexpected character '");
|
||||
buffer.Append(_info);
|
||||
buffer.Append("'");
|
||||
break;
|
||||
case ErrorType.UNEXPECTED_TOKEN:
|
||||
buffer.Append("unexpected token ");
|
||||
buffer.Append(_info);
|
||||
if (_details != null)
|
||||
{
|
||||
buffer.Append(", expected ");
|
||||
if (_details.Count > 1)
|
||||
{
|
||||
buffer.Append("one of ");
|
||||
}
|
||||
buffer.Append(GetMessageDetails());
|
||||
}
|
||||
break;
|
||||
case ErrorType.INVALID_TOKEN:
|
||||
buffer.Append(_info);
|
||||
break;
|
||||
case ErrorType.ANALYSIS:
|
||||
buffer.Append(_info);
|
||||
break;
|
||||
default:
|
||||
buffer.Append("internal error");
|
||||
if (_info != null)
|
||||
{
|
||||
buffer.Append(": ");
|
||||
buffer.Append(_info);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return buffer.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
public string GetErrorMessage()
|
||||
{
|
||||
return ErrorMessage;
|
||||
}
|
||||
|
||||
private string GetMessageDetails()
|
||||
{
|
||||
StringBuilder buffer = new StringBuilder();
|
||||
|
||||
for (int i = 0; i < _details.Count; i++)
|
||||
{
|
||||
if (i > 0)
|
||||
{
|
||||
buffer.Append(", ");
|
||||
if (i + 1 == _details.Count)
|
||||
{
|
||||
buffer.Append("or ");
|
||||
}
|
||||
}
|
||||
buffer.Append(_details[i]);
|
||||
}
|
||||
|
||||
return buffer.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
492
Parsing/Parser.cs
Normal file
492
Parsing/Parser.cs
Normal file
@@ -0,0 +1,492 @@
|
||||
using System.Collections;
|
||||
using System.Text;
|
||||
|
||||
namespace Flee.Parsing
|
||||
{
|
||||
|
||||
[Obsolete(" A base parser class. This class provides the standard parser interface, as well as token handling.")]
|
||||
internal abstract class Parser
|
||||
{
|
||||
private bool _initialized;
|
||||
private readonly Tokenizer _tokenizer;
|
||||
private Analyzer _analyzer;
|
||||
private readonly ArrayList _patterns = new ArrayList();
|
||||
private readonly Hashtable _patternIds = new Hashtable();
|
||||
private readonly ArrayList _tokens = new ArrayList();
|
||||
private ParserLogException _errorLog = new ParserLogException();
|
||||
private int _errorRecovery = -1;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new parser.
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
internal Parser(TextReader input) : this(input, null)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new parser.
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <param name="analyzer"></param>
|
||||
internal Parser(TextReader input, Analyzer analyzer)
|
||||
{
|
||||
_tokenizer = NewTokenizer(input);
|
||||
this._analyzer = analyzer ?? NewAnalyzer();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new parser.
|
||||
*
|
||||
* @param tokenizer the tokenizer to use
|
||||
*/
|
||||
internal Parser(Tokenizer tokenizer) : this(tokenizer, null)
|
||||
{
|
||||
}
|
||||
|
||||
internal Parser(Tokenizer tokenizer, Analyzer analyzer)
|
||||
{
|
||||
this._tokenizer = tokenizer;
|
||||
this._analyzer = analyzer ?? NewAnalyzer();
|
||||
}
|
||||
|
||||
protected virtual Tokenizer NewTokenizer(TextReader input)
|
||||
{
|
||||
// TODO: This method should really be abstract, but it isn't in this
|
||||
// version due to backwards compatibility requirements.
|
||||
return new Tokenizer(input);
|
||||
}
|
||||
|
||||
protected virtual Analyzer NewAnalyzer()
|
||||
{
|
||||
// TODO: This method should really be abstract, but it isn't in this
|
||||
// version due to backwards compatibility requirements.
|
||||
return new Analyzer();
|
||||
}
|
||||
|
||||
public Tokenizer Tokenizer => _tokenizer;
|
||||
|
||||
public Analyzer Analyzer => _analyzer;
|
||||
|
||||
public Tokenizer GetTokenizer()
|
||||
{
|
||||
return Tokenizer;
|
||||
}
|
||||
|
||||
public Analyzer GetAnalyzer()
|
||||
{
|
||||
return Analyzer;
|
||||
}
|
||||
|
||||
internal void SetInitialized(bool initialized)
|
||||
{
|
||||
_initialized = initialized;
|
||||
}
|
||||
|
||||
public virtual void AddPattern(ProductionPattern pattern)
|
||||
{
|
||||
if (pattern.Count <= 0)
|
||||
{
|
||||
throw new ParserCreationException(
|
||||
ParserCreationException.ErrorType.INVALID_PRODUCTION,
|
||||
pattern.Name,
|
||||
"no production alternatives are present (must have at " +
|
||||
"least one)");
|
||||
}
|
||||
if (_patternIds.ContainsKey(pattern.Id))
|
||||
{
|
||||
throw new ParserCreationException(
|
||||
ParserCreationException.ErrorType.INVALID_PRODUCTION,
|
||||
pattern.Name,
|
||||
"another pattern with the same id (" + pattern.Id +
|
||||
") has already been added");
|
||||
}
|
||||
_patterns.Add(pattern);
|
||||
_patternIds.Add(pattern.Id, pattern);
|
||||
SetInitialized(false);
|
||||
}
|
||||
|
||||
public virtual void Prepare()
|
||||
{
|
||||
if (_patterns.Count <= 0)
|
||||
{
|
||||
throw new ParserCreationException(
|
||||
ParserCreationException.ErrorType.INVALID_PARSER,
|
||||
"no production patterns have been added");
|
||||
}
|
||||
for (int i = 0; i < _patterns.Count; i++)
|
||||
{
|
||||
CheckPattern((ProductionPattern)_patterns[i]);
|
||||
}
|
||||
SetInitialized(true);
|
||||
}
|
||||
|
||||
private void CheckPattern(ProductionPattern pattern)
|
||||
{
|
||||
for (int i = 0; i < pattern.Count; i++)
|
||||
{
|
||||
CheckAlternative(pattern.Name, pattern[i]);
|
||||
}
|
||||
}
|
||||
|
||||
private void CheckAlternative(string name,
|
||||
ProductionPatternAlternative alt)
|
||||
{
|
||||
|
||||
for (int i = 0; i < alt.Count; i++)
|
||||
{
|
||||
CheckElement(name, alt[i]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void CheckElement(string name,
|
||||
ProductionPatternElement elem)
|
||||
{
|
||||
|
||||
if (elem.IsProduction() && GetPattern(elem.Id) == null)
|
||||
{
|
||||
throw new ParserCreationException(
|
||||
ParserCreationException.ErrorType.INVALID_PRODUCTION,
|
||||
name,
|
||||
"an undefined production pattern id (" + elem.Id +
|
||||
") is referenced");
|
||||
}
|
||||
}
|
||||
|
||||
public void Reset(TextReader input)
|
||||
{
|
||||
this._tokenizer.Reset(input);
|
||||
this._analyzer.Reset();
|
||||
}
|
||||
|
||||
public void Reset(TextReader input, Analyzer analyzer)
|
||||
{
|
||||
this._tokenizer.Reset(input);
|
||||
this._analyzer = analyzer;
|
||||
}
|
||||
|
||||
public Node Parse()
|
||||
{
|
||||
Node root = null;
|
||||
|
||||
// Initialize parser
|
||||
if (!_initialized)
|
||||
{
|
||||
Prepare();
|
||||
}
|
||||
this._tokens.Clear();
|
||||
this._errorLog = new ParserLogException();
|
||||
this._errorRecovery = -1;
|
||||
|
||||
// Parse input
|
||||
try
|
||||
{
|
||||
root = ParseStart();
|
||||
}
|
||||
catch (ParseException e)
|
||||
{
|
||||
AddError(e, true);
|
||||
}
|
||||
|
||||
// Check for errors
|
||||
if (_errorLog.Count > 0)
|
||||
{
|
||||
throw _errorLog;
|
||||
}
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
protected abstract Node ParseStart();
|
||||
|
||||
protected virtual Production NewProduction(ProductionPattern pattern)
|
||||
{
|
||||
return _analyzer.NewProduction(pattern);
|
||||
}
|
||||
|
||||
internal void AddError(ParseException e, bool recovery)
|
||||
{
|
||||
if (_errorRecovery <= 0)
|
||||
{
|
||||
_errorLog.AddError(e);
|
||||
}
|
||||
if (recovery)
|
||||
{
|
||||
_errorRecovery = 3;
|
||||
}
|
||||
}
|
||||
|
||||
internal ProductionPattern GetPattern(int id)
|
||||
{
|
||||
return (ProductionPattern)_patternIds[id];
|
||||
}
|
||||
|
||||
internal ProductionPattern GetStartPattern()
|
||||
{
|
||||
if (_patterns.Count <= 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
else
|
||||
{
|
||||
return (ProductionPattern)_patterns[0];
|
||||
}
|
||||
}
|
||||
|
||||
internal ICollection GetPatterns()
|
||||
{
|
||||
return _patterns;
|
||||
}
|
||||
|
||||
internal void EnterNode(Node node)
|
||||
{
|
||||
if (!node.IsHidden() && _errorRecovery < 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
_analyzer.Enter(node);
|
||||
}
|
||||
catch (ParseException e)
|
||||
{
|
||||
AddError(e, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal Node ExitNode(Node node)
|
||||
{
|
||||
if (!node.IsHidden() && _errorRecovery < 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
return _analyzer.Exit(node);
|
||||
}
|
||||
catch (ParseException e)
|
||||
{
|
||||
AddError(e, false);
|
||||
}
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
internal void AddNode(Production node, Node child)
|
||||
{
|
||||
if (_errorRecovery >= 0)
|
||||
{
|
||||
// Do nothing
|
||||
}
|
||||
else if (node.IsHidden())
|
||||
{
|
||||
node.AddChild(child);
|
||||
}
|
||||
else if (child != null && child.IsHidden())
|
||||
{
|
||||
for (int i = 0; i < child.Count; i++)
|
||||
{
|
||||
AddNode(node, child[i]);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
_analyzer.Child(node, child);
|
||||
}
|
||||
catch (ParseException e)
|
||||
{
|
||||
AddError(e, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal Token NextToken()
|
||||
{
|
||||
Token token = PeekToken(0);
|
||||
|
||||
if (token != null)
|
||||
{
|
||||
_tokens.RemoveAt(0);
|
||||
return token;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ParseException(
|
||||
ParseException.ErrorType.UNEXPECTED_EOF,
|
||||
null,
|
||||
_tokenizer.GetCurrentLine(),
|
||||
_tokenizer.GetCurrentColumn());
|
||||
}
|
||||
}
|
||||
|
||||
internal Token NextToken(int id)
|
||||
{
|
||||
Token token = NextToken();
|
||||
|
||||
if (token.Id == id)
|
||||
{
|
||||
if (_errorRecovery > 0)
|
||||
{
|
||||
_errorRecovery--;
|
||||
}
|
||||
return token;
|
||||
}
|
||||
else
|
||||
{
|
||||
var list = new ArrayList(1) {_tokenizer.GetPatternDescription(id)};
|
||||
throw new ParseException(
|
||||
ParseException.ErrorType.UNEXPECTED_TOKEN,
|
||||
token.ToShortString(),
|
||||
list,
|
||||
token.StartLine,
|
||||
token.StartColumn);
|
||||
}
|
||||
}
|
||||
|
||||
internal Token PeekToken(int steps)
|
||||
{
|
||||
while (steps >= _tokens.Count)
|
||||
{
|
||||
try
|
||||
{
|
||||
var token = _tokenizer.Next();
|
||||
if (token == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
else
|
||||
{
|
||||
_tokens.Add(token);
|
||||
}
|
||||
}
|
||||
catch (ParseException e)
|
||||
{
|
||||
AddError(e, true);
|
||||
}
|
||||
}
|
||||
return (Token)_tokens[steps];
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
StringBuilder buffer = new StringBuilder();
|
||||
|
||||
for (int i = 0; i < _patterns.Count; i++)
|
||||
{
|
||||
buffer.Append(ToString((ProductionPattern)_patterns[i]));
|
||||
buffer.Append("\n");
|
||||
}
|
||||
return buffer.ToString();
|
||||
}
|
||||
|
||||
private string ToString(ProductionPattern prod)
|
||||
{
|
||||
StringBuilder buffer = new StringBuilder();
|
||||
StringBuilder indent = new StringBuilder();
|
||||
int i;
|
||||
|
||||
buffer.Append(prod.Name);
|
||||
buffer.Append(" (");
|
||||
buffer.Append(prod.Id);
|
||||
buffer.Append(") ");
|
||||
for (i = 0; i < buffer.Length; i++)
|
||||
{
|
||||
indent.Append(" ");
|
||||
}
|
||||
buffer.Append("= ");
|
||||
indent.Append("| ");
|
||||
for (i = 0; i < prod.Count; i++)
|
||||
{
|
||||
if (i > 0)
|
||||
{
|
||||
buffer.Append(indent);
|
||||
}
|
||||
buffer.Append(ToString(prod[i]));
|
||||
buffer.Append("\n");
|
||||
}
|
||||
for (i = 0; i < prod.Count; i++)
|
||||
{
|
||||
var set = prod[i].LookAhead;
|
||||
if (set.GetMaxLength() > 1)
|
||||
{
|
||||
buffer.Append("Using ");
|
||||
buffer.Append(set.GetMaxLength());
|
||||
buffer.Append(" token look-ahead for alternative ");
|
||||
buffer.Append(i + 1);
|
||||
buffer.Append(": ");
|
||||
buffer.Append(set.ToString(_tokenizer));
|
||||
buffer.Append("\n");
|
||||
}
|
||||
}
|
||||
return buffer.ToString();
|
||||
}
|
||||
|
||||
private string ToString(ProductionPatternAlternative alt)
|
||||
{
|
||||
StringBuilder buffer = new StringBuilder();
|
||||
|
||||
for (int i = 0; i < alt.Count; i++)
|
||||
{
|
||||
if (i > 0)
|
||||
{
|
||||
buffer.Append(" ");
|
||||
}
|
||||
buffer.Append(ToString(alt[i]));
|
||||
}
|
||||
return buffer.ToString();
|
||||
}
|
||||
|
||||
private string ToString(ProductionPatternElement elem)
|
||||
{
|
||||
StringBuilder buffer = new StringBuilder();
|
||||
int min = elem.MinCount;
|
||||
int max = elem.MaxCount;
|
||||
|
||||
if (min == 0 && max == 1)
|
||||
{
|
||||
buffer.Append("[");
|
||||
}
|
||||
if (elem.IsToken())
|
||||
{
|
||||
buffer.Append(GetTokenDescription(elem.Id));
|
||||
}
|
||||
else
|
||||
{
|
||||
buffer.Append(GetPattern(elem.Id).Name);
|
||||
}
|
||||
if (min == 0 && max == 1)
|
||||
{
|
||||
buffer.Append("]");
|
||||
}
|
||||
else if (min == 0 && max == Int32.MaxValue)
|
||||
{
|
||||
buffer.Append("*");
|
||||
}
|
||||
else if (min == 1 && max == Int32.MaxValue)
|
||||
{
|
||||
buffer.Append("+");
|
||||
}
|
||||
else if (min != 1 || max != 1)
|
||||
{
|
||||
buffer.Append("{");
|
||||
buffer.Append(min);
|
||||
buffer.Append(",");
|
||||
buffer.Append(max);
|
||||
buffer.Append("}");
|
||||
}
|
||||
return buffer.ToString();
|
||||
}
|
||||
|
||||
internal string GetTokenDescription(int token)
|
||||
{
|
||||
if (_tokenizer == null)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
else
|
||||
{
|
||||
return _tokenizer.GetPatternDescription(token);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
216
Parsing/ParserCreationException.cs
Normal file
216
Parsing/ParserCreationException.cs
Normal file
@@ -0,0 +1,216 @@
|
||||
using System.Collections;
|
||||
using System.Text;
|
||||
|
||||
namespace Flee.Parsing
|
||||
{
|
||||
/**
|
||||
* A parser creation exception. This exception is used for signalling
|
||||
* an error in the token or production patterns, making it impossible
|
||||
* to create a working parser or tokenizer.
|
||||
*/
|
||||
internal class ParserCreationException : Exception
|
||||
{
|
||||
|
||||
/**
|
||||
* The error type enumeration.
|
||||
*/
|
||||
public enum ErrorType
|
||||
{
|
||||
|
||||
/**
|
||||
* The internal error type is only used to signal an
|
||||
* error that is a result of a bug in the parser or
|
||||
* tokenizer code.
|
||||
*/
|
||||
INTERNAL,
|
||||
|
||||
/**
|
||||
* The invalid parser error type is used when the parser
|
||||
* as such is invalid. This error is typically caused by
|
||||
* using a parser without any patterns.
|
||||
*/
|
||||
INVALID_PARSER,
|
||||
|
||||
/**
|
||||
* The invalid token error type is used when a token
|
||||
* pattern is erroneous. This error is typically caused
|
||||
* by an invalid pattern type or an erroneous regular
|
||||
* expression.
|
||||
*/
|
||||
INVALID_TOKEN,
|
||||
|
||||
/**
|
||||
* The invalid production error type is used when a
|
||||
* production pattern is erroneous. This error is
|
||||
* typically caused by referencing undeclared productions,
|
||||
* or violating some other production pattern constraint.
|
||||
*/
|
||||
INVALID_PRODUCTION,
|
||||
|
||||
/**
|
||||
* The infinite loop error type is used when an infinite
|
||||
* loop has been detected in the grammar. One of the
|
||||
* productions in the loop will be reported.
|
||||
*/
|
||||
INFINITE_LOOP,
|
||||
|
||||
/**
|
||||
* The inherent ambiguity error type is used when the set
|
||||
* of production patterns (i.e. the grammar) contains
|
||||
* ambiguities that cannot be resolved.
|
||||
*/
|
||||
INHERENT_AMBIGUITY
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
private readonly ErrorType _type;
|
||||
private readonly string _name;
|
||||
private readonly string _info;
|
||||
private readonly ArrayList _details;
|
||||
|
||||
public ParserCreationException(ErrorType type,
|
||||
String info)
|
||||
: this(type, null, info)
|
||||
{
|
||||
}
|
||||
|
||||
public ParserCreationException(ErrorType type,
|
||||
String name,
|
||||
String info)
|
||||
: this(type, name, info, null)
|
||||
{
|
||||
}
|
||||
|
||||
public ParserCreationException(ErrorType type,
|
||||
String name,
|
||||
String info,
|
||||
ArrayList details)
|
||||
{
|
||||
|
||||
this._type = type;
|
||||
this._name = name;
|
||||
this._info = info;
|
||||
this._details = details;
|
||||
}
|
||||
|
||||
public ErrorType Type => _type;
|
||||
|
||||
public ErrorType GetErrorType()
|
||||
{
|
||||
return Type;
|
||||
}
|
||||
|
||||
public string Name => _name;
|
||||
|
||||
public string GetName()
|
||||
{
|
||||
return Name;
|
||||
}
|
||||
|
||||
public string Info => _info;
|
||||
|
||||
public string GetInfo()
|
||||
{
|
||||
return Info;
|
||||
}
|
||||
|
||||
public string Details
|
||||
{
|
||||
get
|
||||
{
|
||||
StringBuilder buffer = new StringBuilder();
|
||||
|
||||
if (_details == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
for (int i = 0; i < _details.Count; i++)
|
||||
{
|
||||
if (i > 0)
|
||||
{
|
||||
buffer.Append(", ");
|
||||
if (i + 1 == _details.Count)
|
||||
{
|
||||
buffer.Append("and ");
|
||||
}
|
||||
}
|
||||
buffer.Append(_details[i]);
|
||||
}
|
||||
|
||||
return buffer.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
public string GetDetails()
|
||||
{
|
||||
return Details;
|
||||
}
|
||||
|
||||
public override string Message
|
||||
{
|
||||
get
|
||||
{
|
||||
StringBuilder buffer = new StringBuilder();
|
||||
|
||||
switch (_type)
|
||||
{
|
||||
case ErrorType.INVALID_PARSER:
|
||||
buffer.Append("parser is invalid, as ");
|
||||
buffer.Append(_info);
|
||||
break;
|
||||
case ErrorType.INVALID_TOKEN:
|
||||
buffer.Append("token '");
|
||||
buffer.Append(_name);
|
||||
buffer.Append("' is invalid, as ");
|
||||
buffer.Append(_info);
|
||||
break;
|
||||
case ErrorType.INVALID_PRODUCTION:
|
||||
buffer.Append("production '");
|
||||
buffer.Append(_name);
|
||||
buffer.Append("' is invalid, as ");
|
||||
buffer.Append(_info);
|
||||
break;
|
||||
case ErrorType.INFINITE_LOOP:
|
||||
buffer.Append("infinite loop found in production pattern '");
|
||||
buffer.Append(_name);
|
||||
buffer.Append("'");
|
||||
break;
|
||||
case ErrorType.INHERENT_AMBIGUITY:
|
||||
buffer.Append("inherent ambiguity in production '");
|
||||
buffer.Append(_name);
|
||||
buffer.Append("'");
|
||||
if (_info != null)
|
||||
{
|
||||
buffer.Append(" ");
|
||||
buffer.Append(_info);
|
||||
}
|
||||
if (_details != null)
|
||||
{
|
||||
buffer.Append(" starting with ");
|
||||
if (_details.Count > 1)
|
||||
{
|
||||
buffer.Append("tokens ");
|
||||
}
|
||||
else
|
||||
{
|
||||
buffer.Append("token ");
|
||||
}
|
||||
buffer.Append(Details);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
buffer.Append("internal error");
|
||||
break;
|
||||
}
|
||||
return buffer.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
public string GetMessage()
|
||||
{
|
||||
return Message;
|
||||
}
|
||||
}
|
||||
}
|
||||
55
Parsing/ParserLogException.cs
Normal file
55
Parsing/ParserLogException.cs
Normal file
@@ -0,0 +1,55 @@
|
||||
using System.Collections;
|
||||
using System.Text;
|
||||
|
||||
namespace Flee.Parsing
|
||||
{
|
||||
internal class ParserLogException : Exception
|
||||
{
|
||||
private readonly ArrayList _errors = new ArrayList();
|
||||
public ParserLogException()
|
||||
{
|
||||
}
|
||||
public override string Message
|
||||
{
|
||||
get
|
||||
{
|
||||
StringBuilder buffer = new StringBuilder();
|
||||
|
||||
for (int i = 0; i < Count; i++)
|
||||
{
|
||||
if (i > 0)
|
||||
{
|
||||
buffer.Append("\n");
|
||||
}
|
||||
buffer.Append(this[i].Message);
|
||||
}
|
||||
return buffer.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
public int Count => _errors.Count;
|
||||
|
||||
|
||||
public int GetErrorCount()
|
||||
{
|
||||
return Count;
|
||||
}
|
||||
|
||||
public ParseException this[int index] => (ParseException)_errors[index];
|
||||
|
||||
public ParseException GetError(int index)
|
||||
{
|
||||
return this[index];
|
||||
}
|
||||
|
||||
public void AddError(ParseException e)
|
||||
{
|
||||
_errors.Add(e);
|
||||
}
|
||||
|
||||
public string GetMessage()
|
||||
{
|
||||
return Message;
|
||||
}
|
||||
}
|
||||
}
|
||||
70
Parsing/Production.cs
Normal file
70
Parsing/Production.cs
Normal file
@@ -0,0 +1,70 @@
|
||||
using System.Collections;
|
||||
|
||||
namespace Flee.Parsing
|
||||
{
|
||||
|
||||
/**
|
||||
* A production node. This class represents a grammar production
|
||||
* (i.e. a list of child nodes) in a parse tree. The productions
|
||||
* are created by a parser, that adds children a according to a
|
||||
* set of production patterns (i.e. grammar rules).
|
||||
*/
|
||||
internal class Production : Node
|
||||
{
|
||||
private readonly ProductionPattern _pattern;
|
||||
private readonly ArrayList _children;
|
||||
|
||||
public Production(ProductionPattern pattern)
|
||||
{
|
||||
this._pattern = pattern;
|
||||
this._children = new ArrayList();
|
||||
}
|
||||
|
||||
public override int Id => _pattern.Id;
|
||||
|
||||
public override string Name => _pattern.Name;
|
||||
|
||||
public override int Count => _children.Count;
|
||||
|
||||
public override Node this[int index]
|
||||
{
|
||||
get
|
||||
{
|
||||
if (index < 0 || index >= _children.Count)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
else
|
||||
{
|
||||
return (Node)_children[index];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void AddChild(Node child)
|
||||
{
|
||||
if (child != null)
|
||||
{
|
||||
child.SetParent(this);
|
||||
_children.Add(child);
|
||||
}
|
||||
}
|
||||
|
||||
public ProductionPattern Pattern => _pattern;
|
||||
|
||||
public ProductionPattern GetPattern()
|
||||
{
|
||||
return Pattern;
|
||||
}
|
||||
|
||||
internal override bool IsHidden()
|
||||
{
|
||||
return _pattern.Synthetic;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return _pattern.Name + '(' + _pattern.Id + ')';
|
||||
}
|
||||
}
|
||||
}
|
||||
213
Parsing/ProductionPattern.cs
Normal file
213
Parsing/ProductionPattern.cs
Normal file
@@ -0,0 +1,213 @@
|
||||
using System.Collections;
|
||||
using System.Text;
|
||||
|
||||
|
||||
namespace Flee.Parsing
|
||||
{
|
||||
|
||||
/**
|
||||
* A production pattern. This class represents a set of production
|
||||
* alternatives that together forms a single production. A
|
||||
* production pattern is identified by an integer id and a name,
|
||||
* both provided upon creation. The pattern id is used for
|
||||
* referencing the production pattern from production pattern
|
||||
* elements.
|
||||
*/
|
||||
internal class ProductionPattern
|
||||
{
|
||||
|
||||
private readonly int _id;
|
||||
private readonly string _name;
|
||||
private bool _synthetic;
|
||||
private readonly ArrayList _alternatives;
|
||||
private int _defaultAlt;
|
||||
private LookAheadSet _lookAhead;
|
||||
|
||||
public ProductionPattern(int id, string name)
|
||||
{
|
||||
this._id = id;
|
||||
this._name = name;
|
||||
this._synthetic = false;
|
||||
this._alternatives = new ArrayList();
|
||||
this._defaultAlt = -1;
|
||||
this._lookAhead = null;
|
||||
}
|
||||
public int Id => _id;
|
||||
|
||||
public int GetId()
|
||||
{
|
||||
return Id;
|
||||
}
|
||||
|
||||
public string Name => _name;
|
||||
|
||||
public string GetName()
|
||||
{
|
||||
return Name;
|
||||
}
|
||||
|
||||
public bool Synthetic
|
||||
{
|
||||
get
|
||||
{
|
||||
return _synthetic;
|
||||
}
|
||||
set
|
||||
{
|
||||
_synthetic = value;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsSyntetic()
|
||||
{
|
||||
return Synthetic;
|
||||
}
|
||||
|
||||
public void SetSyntetic(bool synthetic)
|
||||
{
|
||||
Synthetic = synthetic;
|
||||
}
|
||||
|
||||
internal LookAheadSet LookAhead
|
||||
{
|
||||
get
|
||||
{
|
||||
return _lookAhead;
|
||||
}
|
||||
set
|
||||
{
|
||||
_lookAhead = value;
|
||||
}
|
||||
}
|
||||
|
||||
internal ProductionPatternAlternative DefaultAlternative
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_defaultAlt >= 0)
|
||||
{
|
||||
object obj = _alternatives[_defaultAlt];
|
||||
return (ProductionPatternAlternative)obj;
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
set
|
||||
{
|
||||
_defaultAlt = 0;
|
||||
for (int i = 0; i < _alternatives.Count; i++)
|
||||
{
|
||||
if (_alternatives[i] == value)
|
||||
{
|
||||
_defaultAlt = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int Count => _alternatives.Count;
|
||||
|
||||
public int GetAlternativeCount()
|
||||
{
|
||||
return Count;
|
||||
}
|
||||
|
||||
public ProductionPatternAlternative this[int index] => (ProductionPatternAlternative)_alternatives[index];
|
||||
|
||||
public ProductionPatternAlternative GetAlternative(int pos)
|
||||
{
|
||||
return this[pos];
|
||||
}
|
||||
|
||||
public bool IsLeftRecursive()
|
||||
{
|
||||
ProductionPatternAlternative alt;
|
||||
|
||||
for (int i = 0; i < _alternatives.Count; i++)
|
||||
{
|
||||
alt = (ProductionPatternAlternative)_alternatives[i];
|
||||
if (alt.IsLeftRecursive())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool IsRightRecursive()
|
||||
{
|
||||
ProductionPatternAlternative alt;
|
||||
|
||||
for (int i = 0; i < _alternatives.Count; i++)
|
||||
{
|
||||
alt = (ProductionPatternAlternative)_alternatives[i];
|
||||
if (alt.IsRightRecursive())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool IsMatchingEmpty()
|
||||
{
|
||||
ProductionPatternAlternative alt;
|
||||
|
||||
for (int i = 0; i < _alternatives.Count; i++)
|
||||
{
|
||||
alt = (ProductionPatternAlternative)_alternatives[i];
|
||||
if (alt.IsMatchingEmpty())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void AddAlternative(ProductionPatternAlternative alt)
|
||||
{
|
||||
if (_alternatives.Contains(alt))
|
||||
{
|
||||
throw new ParserCreationException(
|
||||
ParserCreationException.ErrorType.INVALID_PRODUCTION,
|
||||
_name,
|
||||
"two identical alternatives exist");
|
||||
}
|
||||
alt.SetPattern(this);
|
||||
_alternatives.Add(alt);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
StringBuilder buffer = new StringBuilder();
|
||||
StringBuilder indent = new StringBuilder();
|
||||
int i;
|
||||
|
||||
buffer.Append(_name);
|
||||
buffer.Append("(");
|
||||
buffer.Append(_id);
|
||||
buffer.Append(") ");
|
||||
for (i = 0; i < buffer.Length; i++)
|
||||
{
|
||||
indent.Append(" ");
|
||||
}
|
||||
for (i = 0; i < _alternatives.Count; i++)
|
||||
{
|
||||
if (i == 0)
|
||||
{
|
||||
buffer.Append("= ");
|
||||
}
|
||||
else
|
||||
{
|
||||
buffer.Append("\n");
|
||||
buffer.Append(indent);
|
||||
buffer.Append("| ");
|
||||
}
|
||||
buffer.Append(_alternatives[i]);
|
||||
}
|
||||
return buffer.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
211
Parsing/ProductionPatternAlternative.cs
Normal file
211
Parsing/ProductionPatternAlternative.cs
Normal file
@@ -0,0 +1,211 @@
|
||||
using System.Collections;
|
||||
using System.Text;
|
||||
|
||||
namespace Flee.Parsing
|
||||
{
|
||||
|
||||
/**
|
||||
* A production pattern alternative. This class represents a list of
|
||||
* production pattern elements. In order to provide productions that
|
||||
* cannot be represented with the element occurance counters, multiple
|
||||
* alternatives must be created and added to the same production
|
||||
* pattern. A production pattern alternative is always contained
|
||||
* within a production pattern.
|
||||
*/
|
||||
internal class ProductionPatternAlternative
|
||||
{
|
||||
private ProductionPattern _pattern;
|
||||
private readonly ArrayList _elements = new ArrayList();
|
||||
private LookAheadSet _lookAhead = null;
|
||||
|
||||
public ProductionPatternAlternative()
|
||||
{
|
||||
}
|
||||
|
||||
public ProductionPattern Pattern => _pattern;
|
||||
|
||||
public ProductionPattern GetPattern()
|
||||
{
|
||||
return Pattern;
|
||||
}
|
||||
|
||||
internal LookAheadSet LookAhead
|
||||
{
|
||||
get
|
||||
{
|
||||
return _lookAhead;
|
||||
}
|
||||
set
|
||||
{
|
||||
_lookAhead = value;
|
||||
}
|
||||
}
|
||||
|
||||
public int Count => _elements.Count;
|
||||
|
||||
public int GetElementCount()
|
||||
{
|
||||
return Count;
|
||||
}
|
||||
|
||||
public ProductionPatternElement this[int index] => (ProductionPatternElement)_elements[index];
|
||||
|
||||
public ProductionPatternElement GetElement(int pos)
|
||||
{
|
||||
return this[pos];
|
||||
}
|
||||
|
||||
public bool IsLeftRecursive()
|
||||
{
|
||||
for (int i = 0; i < _elements.Count; i++)
|
||||
{
|
||||
var elem = (ProductionPatternElement)_elements[i];
|
||||
if (elem.Id == _pattern.Id)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else if (elem.MinCount > 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool IsRightRecursive()
|
||||
{
|
||||
for (int i = _elements.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var elem = (ProductionPatternElement)_elements[i];
|
||||
if (elem.Id == _pattern.Id)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else if (elem.MinCount > 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool IsMatchingEmpty()
|
||||
{
|
||||
return GetMinElementCount() == 0;
|
||||
}
|
||||
|
||||
internal void SetPattern(ProductionPattern pattern)
|
||||
{
|
||||
this._pattern = pattern;
|
||||
}
|
||||
|
||||
public int GetMinElementCount()
|
||||
{
|
||||
int min = 0;
|
||||
|
||||
for (int i = 0; i < _elements.Count; i++)
|
||||
{
|
||||
var elem = (ProductionPatternElement)_elements[i];
|
||||
min += elem.MinCount;
|
||||
}
|
||||
return min;
|
||||
}
|
||||
|
||||
public int GetMaxElementCount()
|
||||
{
|
||||
int max = 0;
|
||||
|
||||
for (int i = 0; i < _elements.Count; i++)
|
||||
{
|
||||
var elem = (ProductionPatternElement)_elements[i];
|
||||
if (elem.MaxCount >= Int32.MaxValue)
|
||||
{
|
||||
return Int32.MaxValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
max += elem.MaxCount;
|
||||
}
|
||||
}
|
||||
return max;
|
||||
}
|
||||
|
||||
public void AddToken(int id, int min, int max)
|
||||
{
|
||||
AddElement(new ProductionPatternElement(true, id, min, max));
|
||||
}
|
||||
|
||||
public void AddProduction(int id, int min, int max)
|
||||
{
|
||||
AddElement(new ProductionPatternElement(false, id, min, max));
|
||||
}
|
||||
|
||||
public void AddElement(ProductionPatternElement elem)
|
||||
{
|
||||
_elements.Add(elem);
|
||||
}
|
||||
|
||||
public void AddElement(ProductionPatternElement elem,
|
||||
int min,
|
||||
int max)
|
||||
{
|
||||
|
||||
if (elem.IsToken())
|
||||
{
|
||||
AddToken(elem.Id, min, max);
|
||||
}
|
||||
else
|
||||
{
|
||||
AddProduction(elem.Id, min, max);
|
||||
}
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (obj is ProductionPatternAlternative)
|
||||
{
|
||||
return Equals((ProductionPatternAlternative)obj);
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public bool Equals(ProductionPatternAlternative alt)
|
||||
{
|
||||
if (_elements.Count != alt._elements.Count)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
for (int i = 0; i < _elements.Count; i++)
|
||||
{
|
||||
if (!_elements[i].Equals(alt._elements[i]))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return _elements.Count.GetHashCode();
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
StringBuilder buffer = new StringBuilder();
|
||||
|
||||
for (int i = 0; i < _elements.Count; i++)
|
||||
{
|
||||
if (i > 0)
|
||||
{
|
||||
buffer.Append(" ");
|
||||
}
|
||||
buffer.Append(_elements[i]);
|
||||
}
|
||||
return buffer.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
138
Parsing/ProductionPatternElement.cs
Normal file
138
Parsing/ProductionPatternElement.cs
Normal file
@@ -0,0 +1,138 @@
|
||||
using System.Text;
|
||||
|
||||
namespace Flee.Parsing
|
||||
{
|
||||
/**
|
||||
* A production pattern element. This class represents a reference to
|
||||
* either a token or a production. Each element also contains minimum
|
||||
* and maximum occurence counters, controlling the number of
|
||||
* repetitions allowed. A production pattern element is always
|
||||
* contained within a production pattern rule.
|
||||
*/
|
||||
internal class ProductionPatternElement
|
||||
{
|
||||
private readonly bool _token;
|
||||
private readonly int _id;
|
||||
private readonly int _min;
|
||||
private readonly int _max;
|
||||
private LookAheadSet _lookAhead;
|
||||
|
||||
public ProductionPatternElement(bool isToken,
|
||||
int id,
|
||||
int min,
|
||||
int max)
|
||||
{
|
||||
|
||||
this._token = isToken;
|
||||
this._id = id;
|
||||
if (min < 0)
|
||||
{
|
||||
min = 0;
|
||||
}
|
||||
this._min = min;
|
||||
if (max <= 0)
|
||||
{
|
||||
max = Int32.MaxValue;
|
||||
}
|
||||
else if (max < min)
|
||||
{
|
||||
max = min;
|
||||
}
|
||||
this._max = max;
|
||||
this._lookAhead = null;
|
||||
}
|
||||
|
||||
public int Id => _id;
|
||||
|
||||
public int GetId()
|
||||
{
|
||||
return Id;
|
||||
}
|
||||
|
||||
public int MinCount => _min;
|
||||
|
||||
public int GetMinCount()
|
||||
{
|
||||
return MinCount;
|
||||
}
|
||||
|
||||
public int MaxCount => _max;
|
||||
|
||||
public int GetMaxCount()
|
||||
{
|
||||
return MaxCount;
|
||||
}
|
||||
|
||||
internal LookAheadSet LookAhead
|
||||
{
|
||||
get
|
||||
{
|
||||
return _lookAhead;
|
||||
}
|
||||
set
|
||||
{
|
||||
_lookAhead = value;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsToken()
|
||||
{
|
||||
return _token;
|
||||
}
|
||||
|
||||
public bool IsProduction()
|
||||
{
|
||||
return !_token;
|
||||
}
|
||||
|
||||
public bool IsMatch(Token token)
|
||||
{
|
||||
return IsToken() && token != null && token.Id == _id;
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (obj is ProductionPatternElement)
|
||||
{
|
||||
var elem = (ProductionPatternElement)obj;
|
||||
return this._token == elem._token
|
||||
&& this._id == elem._id
|
||||
&& this._min == elem._min
|
||||
&& this._max == elem._max;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return this._id * 37;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
StringBuilder buffer = new StringBuilder();
|
||||
|
||||
buffer.Append(_id);
|
||||
if (_token)
|
||||
{
|
||||
buffer.Append("(Token)");
|
||||
}
|
||||
else
|
||||
{
|
||||
buffer.Append("(Production)");
|
||||
}
|
||||
if (_min != 1 || _max != 1)
|
||||
{
|
||||
buffer.Append("{");
|
||||
buffer.Append(_min);
|
||||
buffer.Append(",");
|
||||
buffer.Append(_max);
|
||||
buffer.Append("}");
|
||||
}
|
||||
return buffer.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
180
Parsing/ReaderBuffer.cs
Normal file
180
Parsing/ReaderBuffer.cs
Normal file
@@ -0,0 +1,180 @@
|
||||
namespace Flee.Parsing
|
||||
{
|
||||
/**
|
||||
* A character buffer that automatically reads from an input source
|
||||
* stream when needed. This class keeps track of the current position
|
||||
* in the buffer and its line and column number in the original input
|
||||
* source. It allows unlimited look-ahead of characters in the input,
|
||||
* reading and buffering the required data internally. As the
|
||||
* position is advanced, the buffer content prior to the current
|
||||
* position is subject to removal to make space for reading new
|
||||
* content. A few characters before the current position are always
|
||||
* kept to enable boundary condition checks.
|
||||
*/
|
||||
internal class ReaderBuffer
|
||||
{
|
||||
public const int BlockSize = 1024;
|
||||
private char[] _buffer = new char[BlockSize * 4];
|
||||
private int _pos = 0;
|
||||
private int _length = 0;
|
||||
private TextReader _input;
|
||||
private int _line = 1;
|
||||
private int _column = 1;
|
||||
|
||||
public ReaderBuffer(TextReader input)
|
||||
{
|
||||
this._input = input;
|
||||
}
|
||||
public void Dispose()
|
||||
{
|
||||
_buffer = null;
|
||||
_pos = 0;
|
||||
_length = 0;
|
||||
if (_input != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
_input.Close();
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// Do nothing
|
||||
}
|
||||
_input = null;
|
||||
}
|
||||
}
|
||||
|
||||
public int Position => _pos;
|
||||
public int LineNumber => _line;
|
||||
public int ColumnNumber => _column;
|
||||
public int Length => _length;
|
||||
|
||||
public string Substring(int index, int length)
|
||||
{
|
||||
return new string(_buffer, index, length);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return new string(_buffer, 0, _length);
|
||||
}
|
||||
|
||||
public int Peek(int offset)
|
||||
{
|
||||
int index = _pos + offset;
|
||||
|
||||
// Avoid most calls to EnsureBuffered(), since we are in a
|
||||
// performance hotspot here. This check is not exhaustive,
|
||||
// but only present here to speed things up.
|
||||
if (index >= _length)
|
||||
{
|
||||
EnsureBuffered(offset + 1);
|
||||
index = _pos + offset;
|
||||
}
|
||||
return (index >= _length) ? -1 : _buffer[index];
|
||||
}
|
||||
|
||||
public string Read(int offset)
|
||||
{
|
||||
EnsureBuffered(offset + 1);
|
||||
if (_pos >= _length)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
else
|
||||
{
|
||||
var count = _length - _pos;
|
||||
if (count > offset)
|
||||
{
|
||||
count = offset;
|
||||
}
|
||||
UpdateLineColumnNumbers(count);
|
||||
var result = new string(_buffer, _pos, count);
|
||||
_pos += count;
|
||||
if (_input == null && _pos >= _length)
|
||||
{
|
||||
Dispose();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateLineColumnNumbers(int offset)
|
||||
{
|
||||
for (int i = 0; i < offset; i++)
|
||||
{
|
||||
if (_buffer[_pos + i] == '\n')
|
||||
{
|
||||
_line++;
|
||||
_column = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
_column++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void EnsureBuffered(int offset)
|
||||
{
|
||||
// Check for end of stream or already read characters
|
||||
if (_input == null || _pos + offset < _length)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove (almost all) old characters from buffer
|
||||
if (_pos > BlockSize)
|
||||
{
|
||||
_length -= (_pos - 16);
|
||||
Array.Copy(_buffer, _pos - 16, _buffer, 0, _length);
|
||||
_pos = 16;
|
||||
}
|
||||
|
||||
// Calculate number of characters to read
|
||||
var size = _pos + offset - _length + 1;
|
||||
if (size % BlockSize != 0)
|
||||
{
|
||||
size = (1 + size / BlockSize) * BlockSize;
|
||||
}
|
||||
EnsureCapacity(_length + size);
|
||||
|
||||
// Read characters
|
||||
try
|
||||
{
|
||||
while (_input != null && size > 0)
|
||||
{
|
||||
var readSize = _input.Read(_buffer, _length, size);
|
||||
if (readSize > 0)
|
||||
{
|
||||
_length += readSize;
|
||||
size -= readSize;
|
||||
}
|
||||
else
|
||||
{
|
||||
_input.Close();
|
||||
_input = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
_input = null;
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
private void EnsureCapacity(int size)
|
||||
{
|
||||
if (_buffer.Length >= size)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (size % BlockSize != 0)
|
||||
{
|
||||
size = (1 + size / BlockSize) * BlockSize;
|
||||
}
|
||||
Array.Resize(ref _buffer, size);
|
||||
}
|
||||
}
|
||||
}
|
||||
648
Parsing/RecursiveDescentParser.cs
Normal file
648
Parsing/RecursiveDescentParser.cs
Normal file
@@ -0,0 +1,648 @@
|
||||
using System.Collections;
|
||||
|
||||
namespace Flee.Parsing
|
||||
{
|
||||
/**
|
||||
* A recursive descent parser. This parser handles LL(n) grammars,
|
||||
* selecting the appropriate pattern to parse based on the next few
|
||||
* tokens. The parser is more efficient the fewer look-ahead tokens
|
||||
* that is has to consider.
|
||||
*/
|
||||
internal class RecursiveDescentParser : Parser
|
||||
{
|
||||
private int _stackdepth = 0;
|
||||
|
||||
public RecursiveDescentParser(TextReader input) : base(input)
|
||||
{
|
||||
}
|
||||
|
||||
public RecursiveDescentParser(TextReader input, Analyzer analyzer)
|
||||
: base(input, analyzer)
|
||||
{
|
||||
}
|
||||
|
||||
public RecursiveDescentParser(Tokenizer tokenizer)
|
||||
: base(tokenizer)
|
||||
{
|
||||
}
|
||||
|
||||
public RecursiveDescentParser(Tokenizer tokenizer,
|
||||
Analyzer analyzer)
|
||||
: base(tokenizer, analyzer)
|
||||
{
|
||||
}
|
||||
|
||||
public override void AddPattern(ProductionPattern pattern)
|
||||
{
|
||||
|
||||
// Check for empty matches
|
||||
if (pattern.IsMatchingEmpty())
|
||||
{
|
||||
throw new ParserCreationException(
|
||||
ParserCreationException.ErrorType.INVALID_PRODUCTION,
|
||||
pattern.Name,
|
||||
"zero elements can be matched (minimum is one)");
|
||||
}
|
||||
|
||||
// Check for left-recusive patterns
|
||||
if (pattern.IsLeftRecursive())
|
||||
{
|
||||
throw new ParserCreationException(
|
||||
ParserCreationException.ErrorType.INVALID_PRODUCTION,
|
||||
pattern.Name,
|
||||
"left recursive patterns are not allowed");
|
||||
}
|
||||
|
||||
// Add pattern
|
||||
base.AddPattern(pattern);
|
||||
}
|
||||
|
||||
public override void Prepare()
|
||||
{
|
||||
// Performs production pattern checks
|
||||
base.Prepare();
|
||||
SetInitialized(false);
|
||||
|
||||
// Calculate production look-ahead sets
|
||||
var e = GetPatterns().GetEnumerator();
|
||||
while (e.MoveNext())
|
||||
{
|
||||
CalculateLookAhead((ProductionPattern)e.Current);
|
||||
}
|
||||
|
||||
// Set initialized flag
|
||||
SetInitialized(true);
|
||||
}
|
||||
|
||||
protected override Node ParseStart()
|
||||
{
|
||||
_stackdepth = 0;
|
||||
var node = ParsePattern(GetStartPattern());
|
||||
var token = PeekToken(0);
|
||||
if (token != null)
|
||||
{
|
||||
var list = new ArrayList(1) { "<EOF>" };
|
||||
throw new ParseException(
|
||||
ParseException.ErrorType.UNEXPECTED_TOKEN,
|
||||
token.ToShortString(),
|
||||
list,
|
||||
token.StartLine,
|
||||
token.StartColumn);
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
|
||||
private Node ParsePattern(ProductionPattern pattern)
|
||||
{
|
||||
_stackdepth++;
|
||||
|
||||
if (_stackdepth > 200)
|
||||
{
|
||||
throw new System.StackOverflowException();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var defaultAlt = pattern.DefaultAlternative;
|
||||
for (int i = 0; i < pattern.Count; i++)
|
||||
{
|
||||
var alt = pattern[i];
|
||||
if (defaultAlt != alt && IsNext(alt))
|
||||
{
|
||||
return ParseAlternative(alt);
|
||||
}
|
||||
}
|
||||
if (defaultAlt == null || !IsNext(defaultAlt))
|
||||
{
|
||||
ThrowParseException(FindUnion(pattern));
|
||||
}
|
||||
return ParseAlternative(defaultAlt);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_stackdepth--;
|
||||
}
|
||||
}
|
||||
|
||||
private Node ParseAlternative(ProductionPatternAlternative alt)
|
||||
{
|
||||
var node = NewProduction(alt.Pattern);
|
||||
EnterNode(node);
|
||||
for (int i = 0; i < alt.Count; i++)
|
||||
{
|
||||
try
|
||||
{
|
||||
ParseElement(node, alt[i]);
|
||||
}
|
||||
catch (ParseException e)
|
||||
{
|
||||
AddError(e, true);
|
||||
NextToken();
|
||||
i--;
|
||||
}
|
||||
}
|
||||
return ExitNode(node);
|
||||
}
|
||||
|
||||
private void ParseElement(Production node,
|
||||
ProductionPatternElement elem)
|
||||
{
|
||||
for (int i = 0; i < elem.MaxCount; i++)
|
||||
{
|
||||
if (i < elem.MinCount || IsNext(elem))
|
||||
{
|
||||
Node child;
|
||||
if (elem.IsToken())
|
||||
{
|
||||
child = NextToken(elem.Id);
|
||||
EnterNode(child);
|
||||
AddNode(node, ExitNode(child));
|
||||
}
|
||||
else
|
||||
{
|
||||
child = ParsePattern(GetPattern(elem.Id));
|
||||
AddNode(node, child);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsNext(ProductionPattern pattern)
|
||||
{
|
||||
LookAheadSet set = pattern.LookAhead;
|
||||
|
||||
if (set == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
return set.IsNext(this);
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsNext(ProductionPatternAlternative alt)
|
||||
{
|
||||
LookAheadSet set = alt.LookAhead;
|
||||
|
||||
if (set == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
return set.IsNext(this);
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsNext(ProductionPatternElement elem)
|
||||
{
|
||||
LookAheadSet set = elem.LookAhead;
|
||||
|
||||
if (set != null)
|
||||
{
|
||||
return set.IsNext(this);
|
||||
}
|
||||
else if (elem.IsToken())
|
||||
{
|
||||
return elem.IsMatch(PeekToken(0));
|
||||
}
|
||||
else
|
||||
{
|
||||
return IsNext(GetPattern(elem.Id));
|
||||
}
|
||||
}
|
||||
|
||||
private void CalculateLookAhead(ProductionPattern pattern)
|
||||
{
|
||||
ProductionPatternAlternative alt;
|
||||
LookAheadSet previous = new LookAheadSet(0);
|
||||
int length = 1;
|
||||
int i;
|
||||
CallStack stack = new CallStack();
|
||||
|
||||
// Calculate simple look-ahead
|
||||
stack.Push(pattern.Name, 1);
|
||||
var result = new LookAheadSet(1);
|
||||
var alternatives = new LookAheadSet[pattern.Count];
|
||||
for (i = 0; i < pattern.Count; i++)
|
||||
{
|
||||
alt = pattern[i];
|
||||
alternatives[i] = FindLookAhead(alt, 1, 0, stack, null);
|
||||
alt.LookAhead = alternatives[i];
|
||||
result.AddAll(alternatives[i]);
|
||||
}
|
||||
if (pattern.LookAhead == null)
|
||||
{
|
||||
pattern.LookAhead = result;
|
||||
}
|
||||
var conflicts = FindConflicts(pattern, 1);
|
||||
|
||||
// Resolve conflicts
|
||||
while (conflicts.Size() > 0)
|
||||
{
|
||||
length++;
|
||||
stack.Clear();
|
||||
stack.Push(pattern.Name, length);
|
||||
conflicts.AddAll(previous);
|
||||
for (i = 0; i < pattern.Count; i++)
|
||||
{
|
||||
alt = pattern[i];
|
||||
if (alternatives[i].Intersects(conflicts))
|
||||
{
|
||||
alternatives[i] = FindLookAhead(alt,
|
||||
length,
|
||||
0,
|
||||
stack,
|
||||
conflicts);
|
||||
alt.LookAhead = alternatives[i];
|
||||
}
|
||||
if (alternatives[i].Intersects(conflicts))
|
||||
{
|
||||
if (pattern.DefaultAlternative == null)
|
||||
{
|
||||
pattern.DefaultAlternative = alt;
|
||||
}
|
||||
else if (pattern.DefaultAlternative != alt)
|
||||
{
|
||||
result = alternatives[i].CreateIntersection(conflicts);
|
||||
ThrowAmbiguityException(pattern.Name,
|
||||
null,
|
||||
result);
|
||||
}
|
||||
}
|
||||
}
|
||||
previous = conflicts;
|
||||
conflicts = FindConflicts(pattern, length);
|
||||
}
|
||||
|
||||
// Resolve conflicts inside rules
|
||||
for (i = 0; i < pattern.Count; i++)
|
||||
{
|
||||
CalculateLookAhead(pattern[i], 0);
|
||||
}
|
||||
}
|
||||
|
||||
private void CalculateLookAhead(ProductionPatternAlternative alt,
|
||||
int pos)
|
||||
{
|
||||
LookAheadSet previous = new LookAheadSet(0);
|
||||
int length = 1;
|
||||
|
||||
// Check trivial cases
|
||||
if (pos >= alt.Count)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for non-optional element
|
||||
var pattern = alt.Pattern;
|
||||
var elem = alt[pos];
|
||||
if (elem.MinCount == elem.MaxCount)
|
||||
{
|
||||
CalculateLookAhead(alt, pos + 1);
|
||||
return;
|
||||
}
|
||||
|
||||
// Calculate simple look-aheads
|
||||
var first = FindLookAhead(elem, 1, new CallStack(), null);
|
||||
var follow = FindLookAhead(alt, 1, pos + 1, new CallStack(), null);
|
||||
|
||||
// Resolve conflicts
|
||||
var location = "at position " + (pos + 1);
|
||||
var conflicts = FindConflicts(pattern.Name,
|
||||
location,
|
||||
first,
|
||||
follow);
|
||||
while (conflicts.Size() > 0)
|
||||
{
|
||||
length++;
|
||||
conflicts.AddAll(previous);
|
||||
first = FindLookAhead(elem,
|
||||
length,
|
||||
new CallStack(),
|
||||
conflicts);
|
||||
follow = FindLookAhead(alt,
|
||||
length,
|
||||
pos + 1,
|
||||
new CallStack(),
|
||||
conflicts);
|
||||
first = first.CreateCombination(follow);
|
||||
elem.LookAhead = first;
|
||||
if (first.Intersects(conflicts))
|
||||
{
|
||||
first = first.CreateIntersection(conflicts);
|
||||
ThrowAmbiguityException(pattern.Name, location, first);
|
||||
}
|
||||
previous = conflicts;
|
||||
conflicts = FindConflicts(pattern.Name,
|
||||
location,
|
||||
first,
|
||||
follow);
|
||||
}
|
||||
|
||||
// Check remaining elements
|
||||
CalculateLookAhead(alt, pos + 1);
|
||||
}
|
||||
|
||||
private LookAheadSet FindLookAhead(ProductionPattern pattern,
|
||||
int length,
|
||||
CallStack stack,
|
||||
LookAheadSet filter)
|
||||
{
|
||||
// Check for infinite loop
|
||||
if (stack.Contains(pattern.Name, length))
|
||||
{
|
||||
throw new ParserCreationException(
|
||||
ParserCreationException.ErrorType.INFINITE_LOOP,
|
||||
pattern.Name,
|
||||
(String)null);
|
||||
}
|
||||
|
||||
// Find pattern look-ahead
|
||||
stack.Push(pattern.Name, length);
|
||||
var result = new LookAheadSet(length);
|
||||
for (int i = 0; i < pattern.Count; i++)
|
||||
{
|
||||
var temp = FindLookAhead(pattern[i],
|
||||
length,
|
||||
0,
|
||||
stack,
|
||||
filter);
|
||||
result.AddAll(temp);
|
||||
}
|
||||
stack.Pop();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private LookAheadSet FindLookAhead(ProductionPatternAlternative alt,
|
||||
int length,
|
||||
int pos,
|
||||
CallStack stack,
|
||||
LookAheadSet filter)
|
||||
{
|
||||
LookAheadSet follow;
|
||||
// Check trivial cases
|
||||
if (length <= 0 || pos >= alt.Count)
|
||||
{
|
||||
return new LookAheadSet(0);
|
||||
}
|
||||
|
||||
// Find look-ahead for this element
|
||||
var first = FindLookAhead(alt[pos], length, stack, filter);
|
||||
if (alt[pos].MinCount == 0)
|
||||
{
|
||||
first.AddEmpty();
|
||||
}
|
||||
|
||||
// Find remaining look-ahead
|
||||
if (filter == null)
|
||||
{
|
||||
length -= first.GetMinLength();
|
||||
if (length > 0)
|
||||
{
|
||||
follow = FindLookAhead(alt, length, pos + 1, stack, null);
|
||||
first = first.CreateCombination(follow);
|
||||
}
|
||||
}
|
||||
else if (filter.IsOverlap(first))
|
||||
{
|
||||
var overlaps = first.CreateOverlaps(filter);
|
||||
length -= overlaps.GetMinLength();
|
||||
filter = filter.CreateFilter(overlaps);
|
||||
follow = FindLookAhead(alt, length, pos + 1, stack, filter);
|
||||
first.RemoveAll(overlaps);
|
||||
first.AddAll(overlaps.CreateCombination(follow));
|
||||
}
|
||||
|
||||
return first;
|
||||
}
|
||||
|
||||
private LookAheadSet FindLookAhead(ProductionPatternElement elem,
|
||||
int length,
|
||||
CallStack stack,
|
||||
LookAheadSet filter)
|
||||
{
|
||||
// Find initial element look-ahead
|
||||
var first = FindLookAhead(elem, length, 0, stack, filter);
|
||||
var result = new LookAheadSet(length);
|
||||
result.AddAll(first);
|
||||
if (filter == null || !filter.IsOverlap(result))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
// Handle element repetitions
|
||||
if (elem.MaxCount == Int32.MaxValue)
|
||||
{
|
||||
first = first.CreateRepetitive();
|
||||
}
|
||||
var max = elem.MaxCount;
|
||||
if (length < max)
|
||||
{
|
||||
max = length;
|
||||
}
|
||||
for (int i = 1; i < max; i++)
|
||||
{
|
||||
first = first.CreateOverlaps(filter);
|
||||
if (first.Size() <= 0 || first.GetMinLength() >= length)
|
||||
{
|
||||
break;
|
||||
}
|
||||
var follow = FindLookAhead(elem,
|
||||
length,
|
||||
0,
|
||||
stack,
|
||||
filter.CreateFilter(first));
|
||||
first = first.CreateCombination(follow);
|
||||
result.AddAll(first);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private LookAheadSet FindLookAhead(ProductionPatternElement elem,
|
||||
int length,
|
||||
int dummy,
|
||||
CallStack stack,
|
||||
LookAheadSet filter)
|
||||
{
|
||||
LookAheadSet result;
|
||||
|
||||
if (elem.IsToken())
|
||||
{
|
||||
result = new LookAheadSet(length);
|
||||
result.Add(elem.Id);
|
||||
}
|
||||
else
|
||||
{
|
||||
var pattern = GetPattern(elem.Id);
|
||||
result = FindLookAhead(pattern, length, stack, filter);
|
||||
if (stack.Contains(pattern.Name))
|
||||
{
|
||||
result = result.CreateRepetitive();
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private LookAheadSet FindConflicts(ProductionPattern pattern,
|
||||
int maxLength)
|
||||
{
|
||||
|
||||
LookAheadSet result = new LookAheadSet(maxLength);
|
||||
for (int i = 0; i < pattern.Count; i++)
|
||||
{
|
||||
var set1 = pattern[i].LookAhead;
|
||||
for (int j = 0; j < i; j++)
|
||||
{
|
||||
var set2 = pattern[j].LookAhead;
|
||||
result.AddAll(set1.CreateIntersection(set2));
|
||||
}
|
||||
}
|
||||
if (result.IsRepetitive())
|
||||
{
|
||||
ThrowAmbiguityException(pattern.Name, null, result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private LookAheadSet FindConflicts(string pattern,
|
||||
string location,
|
||||
LookAheadSet set1,
|
||||
LookAheadSet set2)
|
||||
{
|
||||
var result = set1.CreateIntersection(set2);
|
||||
if (result.IsRepetitive())
|
||||
{
|
||||
ThrowAmbiguityException(pattern, location, result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private LookAheadSet FindUnion(ProductionPattern pattern)
|
||||
{
|
||||
LookAheadSet result;
|
||||
int length = 0;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < pattern.Count; i++)
|
||||
{
|
||||
result = pattern[i].LookAhead;
|
||||
if (result.GetMaxLength() > length)
|
||||
{
|
||||
length = result.GetMaxLength();
|
||||
}
|
||||
}
|
||||
result = new LookAheadSet(length);
|
||||
for (i = 0; i < pattern.Count; i++)
|
||||
{
|
||||
result.AddAll(pattern[i].LookAhead);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
private void ThrowParseException(LookAheadSet set)
|
||||
{
|
||||
ArrayList list = new ArrayList();
|
||||
|
||||
// Read tokens until mismatch
|
||||
while (set.IsNext(this, 1))
|
||||
{
|
||||
set = set.CreateNextSet(NextToken().Id);
|
||||
}
|
||||
|
||||
// Find next token descriptions
|
||||
var initials = set.GetInitialTokens();
|
||||
for (int i = 0; i < initials.Length; i++)
|
||||
{
|
||||
list.Add(GetTokenDescription(initials[i]));
|
||||
}
|
||||
|
||||
// Create exception
|
||||
var token = NextToken();
|
||||
throw new ParseException(ParseException.ErrorType.UNEXPECTED_TOKEN,
|
||||
token.ToShortString(),
|
||||
list,
|
||||
token.StartLine,
|
||||
token.StartColumn);
|
||||
}
|
||||
|
||||
private void ThrowAmbiguityException(string pattern,
|
||||
string location,
|
||||
LookAheadSet set)
|
||||
{
|
||||
|
||||
ArrayList list = new ArrayList();
|
||||
|
||||
// Find next token descriptions
|
||||
var initials = set.GetInitialTokens();
|
||||
for (int i = 0; i < initials.Length; i++)
|
||||
{
|
||||
list.Add(GetTokenDescription(initials[i]));
|
||||
}
|
||||
|
||||
// Create exception
|
||||
throw new ParserCreationException(
|
||||
ParserCreationException.ErrorType.INHERENT_AMBIGUITY,
|
||||
pattern,
|
||||
location,
|
||||
list);
|
||||
}
|
||||
|
||||
|
||||
private class CallStack
|
||||
{
|
||||
private readonly ArrayList _nameStack = new ArrayList();
|
||||
private readonly ArrayList _valueStack = new ArrayList();
|
||||
public bool Contains(string name)
|
||||
{
|
||||
return _nameStack.Contains(name);
|
||||
}
|
||||
|
||||
public bool Contains(string name, int value)
|
||||
{
|
||||
for (int i = 0; i < _nameStack.Count; i++)
|
||||
{
|
||||
if (_nameStack[i].Equals(name)
|
||||
&& _valueStack[i].Equals(value))
|
||||
{
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
_nameStack.Clear();
|
||||
_valueStack.Clear();
|
||||
}
|
||||
|
||||
public void Push(string name, int value)
|
||||
{
|
||||
_nameStack.Add(name);
|
||||
_valueStack.Add(value);
|
||||
}
|
||||
|
||||
public void Pop()
|
||||
{
|
||||
if (_nameStack.Count > 0)
|
||||
{
|
||||
_nameStack.RemoveAt(_nameStack.Count - 1);
|
||||
_valueStack.RemoveAt(_valueStack.Count - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
505
Parsing/RegExp.cs
Normal file
505
Parsing/RegExp.cs
Normal file
@@ -0,0 +1,505 @@
|
||||
using System.Collections;
|
||||
using System.Globalization;
|
||||
using System.Text;
|
||||
|
||||
|
||||
namespace Flee.Parsing
|
||||
{
|
||||
/**
|
||||
* A regular expression. This class creates and holds an internal
|
||||
* data structure representing a regular expression. It also
|
||||
* allows creating matchers. This class is thread-safe. Multiple
|
||||
* matchers may operate simultanously on the same regular
|
||||
* expression.
|
||||
*/
|
||||
internal class RegExp
|
||||
{
|
||||
private readonly Element _element;
|
||||
private readonly string _pattern;
|
||||
private readonly bool _ignoreCase;
|
||||
private int _pos;
|
||||
|
||||
public RegExp(string pattern)
|
||||
: this(pattern, false)
|
||||
{
|
||||
}
|
||||
|
||||
public RegExp(string pattern, bool ignoreCase)
|
||||
{
|
||||
this._pattern = pattern;
|
||||
this._ignoreCase = ignoreCase;
|
||||
this._pos = 0;
|
||||
this._element = ParseExpr();
|
||||
if (_pos < pattern.Length)
|
||||
{
|
||||
throw new RegExpException(
|
||||
RegExpException.ErrorType.UNEXPECTED_CHARACTER,
|
||||
_pos,
|
||||
pattern);
|
||||
}
|
||||
}
|
||||
|
||||
public Matcher Matcher(string str)
|
||||
{
|
||||
return Matcher(new ReaderBuffer(new StringReader(str)));
|
||||
}
|
||||
|
||||
public Matcher Matcher(ReaderBuffer buffer)
|
||||
{
|
||||
return new Matcher((Element)_element.Clone(), buffer, _ignoreCase);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
var str = new StringWriter();
|
||||
str.WriteLine("Regular Expression");
|
||||
str.WriteLine(" Pattern: " + _pattern);
|
||||
str.Write(" Flags:");
|
||||
if (_ignoreCase)
|
||||
{
|
||||
str.Write(" caseignore");
|
||||
}
|
||||
str.WriteLine();
|
||||
str.WriteLine(" Compiled:");
|
||||
_element.PrintTo(str, " ");
|
||||
return str.ToString();
|
||||
}
|
||||
|
||||
private Element ParseExpr()
|
||||
{
|
||||
var first = ParseTerm();
|
||||
if (PeekChar(0) != '|')
|
||||
{
|
||||
return first;
|
||||
}
|
||||
else
|
||||
{
|
||||
ReadChar('|');
|
||||
var second = ParseExpr();
|
||||
return new AlternativeElement(first, second);
|
||||
}
|
||||
}
|
||||
|
||||
private Element ParseTerm()
|
||||
{
|
||||
ArrayList list = new ArrayList();
|
||||
|
||||
list.Add(ParseFact());
|
||||
while (true)
|
||||
{
|
||||
switch (PeekChar(0))
|
||||
{
|
||||
case -1:
|
||||
case ')':
|
||||
case ']':
|
||||
case '{':
|
||||
case '}':
|
||||
case '?':
|
||||
case '+':
|
||||
case '|':
|
||||
return CombineElements(list);
|
||||
default:
|
||||
list.Add(ParseFact());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Element ParseFact()
|
||||
{
|
||||
var elem = ParseAtom();
|
||||
switch (PeekChar(0))
|
||||
{
|
||||
case '?':
|
||||
case '*':
|
||||
case '+':
|
||||
case '{':
|
||||
return ParseAtomModifier(elem);
|
||||
default:
|
||||
return elem;
|
||||
}
|
||||
}
|
||||
|
||||
private Element ParseAtom()
|
||||
{
|
||||
Element elem;
|
||||
|
||||
switch (PeekChar(0))
|
||||
{
|
||||
case '.':
|
||||
ReadChar('.');
|
||||
return CharacterSetElement.Dot;
|
||||
case '(':
|
||||
ReadChar('(');
|
||||
elem = ParseExpr();
|
||||
ReadChar(')');
|
||||
return elem;
|
||||
case '[':
|
||||
ReadChar('[');
|
||||
elem = ParseCharSet();
|
||||
ReadChar(']');
|
||||
return elem;
|
||||
case -1:
|
||||
case ')':
|
||||
case ']':
|
||||
case '{':
|
||||
case '}':
|
||||
case '?':
|
||||
case '*':
|
||||
case '+':
|
||||
case '|':
|
||||
throw new RegExpException(
|
||||
RegExpException.ErrorType.UNEXPECTED_CHARACTER,
|
||||
_pos,
|
||||
_pattern);
|
||||
default:
|
||||
return ParseChar();
|
||||
}
|
||||
}
|
||||
|
||||
private Element ParseAtomModifier(Element elem)
|
||||
{
|
||||
int min = 0;
|
||||
int max = -1;
|
||||
RepeatElement.RepeatType type;
|
||||
int firstPos;
|
||||
|
||||
// Read min and max
|
||||
type = RepeatElement.RepeatType.GREEDY;
|
||||
switch (ReadChar())
|
||||
{
|
||||
case '?':
|
||||
min = 0;
|
||||
max = 1;
|
||||
break;
|
||||
case '*':
|
||||
min = 0;
|
||||
max = -1;
|
||||
break;
|
||||
case '+':
|
||||
min = 1;
|
||||
max = -1;
|
||||
break;
|
||||
case '{':
|
||||
firstPos = _pos - 1;
|
||||
min = ReadNumber();
|
||||
max = min;
|
||||
if (PeekChar(0) == ',')
|
||||
{
|
||||
ReadChar(',');
|
||||
max = -1;
|
||||
if (PeekChar(0) != '}')
|
||||
{
|
||||
max = ReadNumber();
|
||||
}
|
||||
}
|
||||
ReadChar('}');
|
||||
if (max == 0 || (max > 0 && min > max))
|
||||
{
|
||||
throw new RegExpException(
|
||||
RegExpException.ErrorType.INVALID_REPEAT_COUNT,
|
||||
firstPos,
|
||||
_pattern);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new RegExpException(
|
||||
RegExpException.ErrorType.UNEXPECTED_CHARACTER,
|
||||
_pos - 1,
|
||||
_pattern);
|
||||
}
|
||||
|
||||
// Read operator mode
|
||||
if (PeekChar(0) == '?')
|
||||
{
|
||||
ReadChar('?');
|
||||
type = RepeatElement.RepeatType.RELUCTANT;
|
||||
}
|
||||
else if (PeekChar(0) == '+')
|
||||
{
|
||||
ReadChar('+');
|
||||
type = RepeatElement.RepeatType.POSSESSIVE;
|
||||
}
|
||||
|
||||
return new RepeatElement(elem, min, max, type);
|
||||
}
|
||||
|
||||
private Element ParseCharSet()
|
||||
{
|
||||
CharacterSetElement charset;
|
||||
bool repeat = true;
|
||||
|
||||
if (PeekChar(0) == '^')
|
||||
{
|
||||
ReadChar('^');
|
||||
charset = new CharacterSetElement(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
charset = new CharacterSetElement(false);
|
||||
}
|
||||
|
||||
while (PeekChar(0) > 0 && repeat)
|
||||
{
|
||||
var start = (char)PeekChar(0);
|
||||
switch (start)
|
||||
{
|
||||
case ']':
|
||||
repeat = false;
|
||||
break;
|
||||
case '\\':
|
||||
var elem = ParseEscapeChar();
|
||||
if (elem is StringElement)
|
||||
{
|
||||
charset.AddCharacters((StringElement)elem);
|
||||
}
|
||||
else
|
||||
{
|
||||
charset.AddCharacterSet((CharacterSetElement)elem);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
ReadChar(start);
|
||||
if (PeekChar(0) == '-'
|
||||
&& PeekChar(1) > 0
|
||||
&& PeekChar(1) != ']')
|
||||
{
|
||||
|
||||
ReadChar('-');
|
||||
var end = ReadChar();
|
||||
charset.AddRange(FixChar(start), FixChar(end));
|
||||
}
|
||||
else
|
||||
{
|
||||
charset.AddCharacter(FixChar(start));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return charset;
|
||||
}
|
||||
|
||||
private Element ParseChar()
|
||||
{
|
||||
switch (PeekChar(0))
|
||||
{
|
||||
case '\\':
|
||||
return ParseEscapeChar();
|
||||
case '^':
|
||||
case '$':
|
||||
throw new RegExpException(
|
||||
RegExpException.ErrorType.UNSUPPORTED_SPECIAL_CHARACTER,
|
||||
_pos,
|
||||
_pattern);
|
||||
default:
|
||||
return new StringElement(FixChar(ReadChar()));
|
||||
}
|
||||
}
|
||||
|
||||
private Element ParseEscapeChar()
|
||||
{
|
||||
char c;
|
||||
string str;
|
||||
int value;
|
||||
|
||||
ReadChar('\\');
|
||||
c = ReadChar();
|
||||
switch (c)
|
||||
{
|
||||
case '0':
|
||||
c = ReadChar();
|
||||
if (c < '0' || c > '3')
|
||||
{
|
||||
throw new RegExpException(
|
||||
RegExpException.ErrorType.UNSUPPORTED_ESCAPE_CHARACTER,
|
||||
_pos - 3,
|
||||
_pattern);
|
||||
}
|
||||
value = c - '0';
|
||||
c = (char)PeekChar(0);
|
||||
if ('0' <= c && c <= '7')
|
||||
{
|
||||
value *= 8;
|
||||
value += ReadChar() - '0';
|
||||
c = (char)PeekChar(0);
|
||||
if ('0' <= c && c <= '7')
|
||||
{
|
||||
value *= 8;
|
||||
value += ReadChar() - '0';
|
||||
}
|
||||
}
|
||||
return new StringElement(FixChar((char)value));
|
||||
case 'x':
|
||||
str = ReadChar().ToString() +
|
||||
ReadChar().ToString();
|
||||
try
|
||||
{
|
||||
value = Int32.Parse(str,
|
||||
NumberStyles.AllowHexSpecifier);
|
||||
return new StringElement(FixChar((char)value));
|
||||
}
|
||||
catch (FormatException)
|
||||
{
|
||||
throw new RegExpException(
|
||||
RegExpException.ErrorType.UNSUPPORTED_ESCAPE_CHARACTER,
|
||||
_pos - str.Length - 2,
|
||||
_pattern);
|
||||
}
|
||||
case 'u':
|
||||
str = ReadChar().ToString() +
|
||||
ReadChar().ToString() +
|
||||
ReadChar().ToString() +
|
||||
ReadChar().ToString();
|
||||
try
|
||||
{
|
||||
value = Int32.Parse(str,
|
||||
NumberStyles.AllowHexSpecifier);
|
||||
return new StringElement(FixChar((char)value));
|
||||
}
|
||||
catch (FormatException)
|
||||
{
|
||||
throw new RegExpException(
|
||||
RegExpException.ErrorType.UNSUPPORTED_ESCAPE_CHARACTER,
|
||||
_pos - str.Length - 2,
|
||||
_pattern);
|
||||
}
|
||||
case 't':
|
||||
return new StringElement('\t');
|
||||
case 'n':
|
||||
return new StringElement('\n');
|
||||
case 'r':
|
||||
return new StringElement('\r');
|
||||
case 'f':
|
||||
return new StringElement('\f');
|
||||
case 'a':
|
||||
return new StringElement('\u0007');
|
||||
case 'e':
|
||||
return new StringElement('\u001B');
|
||||
case 'd':
|
||||
return CharacterSetElement.Digit;
|
||||
case 'D':
|
||||
return CharacterSetElement.NonDigit;
|
||||
case 's':
|
||||
return CharacterSetElement.Whitespace;
|
||||
case 'S':
|
||||
return CharacterSetElement.NonWhitespace;
|
||||
case 'w':
|
||||
return CharacterSetElement.Word;
|
||||
case 'W':
|
||||
return CharacterSetElement.NonWord;
|
||||
default:
|
||||
if (('A' <= c && c <= 'Z') || ('a' <= c && c <= 'z'))
|
||||
{
|
||||
throw new RegExpException(
|
||||
RegExpException.ErrorType.UNSUPPORTED_ESCAPE_CHARACTER,
|
||||
_pos - 2,
|
||||
_pattern);
|
||||
}
|
||||
return new StringElement(FixChar(c));
|
||||
}
|
||||
}
|
||||
|
||||
private char FixChar(char c)
|
||||
{
|
||||
return _ignoreCase ? Char.ToLower(c) : c;
|
||||
}
|
||||
|
||||
private int ReadNumber()
|
||||
{
|
||||
StringBuilder buf = new StringBuilder();
|
||||
int c;
|
||||
|
||||
c = PeekChar(0);
|
||||
while ('0' <= c && c <= '9')
|
||||
{
|
||||
buf.Append(ReadChar());
|
||||
c = PeekChar(0);
|
||||
}
|
||||
if (buf.Length <= 0)
|
||||
{
|
||||
throw new RegExpException(
|
||||
RegExpException.ErrorType.UNEXPECTED_CHARACTER,
|
||||
_pos,
|
||||
_pattern);
|
||||
}
|
||||
return Int32.Parse(buf.ToString());
|
||||
}
|
||||
|
||||
private char ReadChar()
|
||||
{
|
||||
int c = PeekChar(0);
|
||||
|
||||
if (c < 0)
|
||||
{
|
||||
throw new RegExpException(
|
||||
RegExpException.ErrorType.UNTERMINATED_PATTERN,
|
||||
_pos,
|
||||
_pattern);
|
||||
}
|
||||
else
|
||||
{
|
||||
_pos++;
|
||||
return (char)c;
|
||||
}
|
||||
}
|
||||
|
||||
private char ReadChar(char c)
|
||||
{
|
||||
if (c != ReadChar())
|
||||
{
|
||||
throw new RegExpException(
|
||||
RegExpException.ErrorType.UNEXPECTED_CHARACTER,
|
||||
_pos - 1,
|
||||
_pattern);
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
private int PeekChar(int count)
|
||||
{
|
||||
if (_pos + count < _pattern.Length)
|
||||
{
|
||||
return _pattern[_pos + count];
|
||||
}
|
||||
else
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
private Element CombineElements(ArrayList list)
|
||||
{
|
||||
Element elem;
|
||||
int i;
|
||||
// Concatenate string elements
|
||||
var prev = (Element)list[0];
|
||||
for (i = 1; i < list.Count; i++)
|
||||
{
|
||||
elem = (Element)list[i];
|
||||
if (prev is StringElement
|
||||
&& elem is StringElement)
|
||||
{
|
||||
|
||||
var str = ((StringElement)prev).GetString() +
|
||||
((StringElement)elem).GetString();
|
||||
elem = new StringElement(str);
|
||||
list.RemoveAt(i);
|
||||
list[i - 1] = elem;
|
||||
i--;
|
||||
}
|
||||
prev = elem;
|
||||
}
|
||||
|
||||
// Combine all remaining elements
|
||||
elem = (Element)list[list.Count - 1];
|
||||
for (i = list.Count - 2; i >= 0; i--)
|
||||
{
|
||||
prev = (Element)list[i];
|
||||
elem = new CombineElement(prev, elem);
|
||||
}
|
||||
|
||||
return elem;
|
||||
}
|
||||
}
|
||||
}
|
||||
113
Parsing/RegExpException.cs
Normal file
113
Parsing/RegExpException.cs
Normal file
@@ -0,0 +1,113 @@
|
||||
using System.Text;
|
||||
|
||||
|
||||
namespace Flee.Parsing
|
||||
{
|
||||
/**
|
||||
* A regular expression exception. This exception is thrown if a
|
||||
* regular expression couldn't be processed (or "compiled")
|
||||
* properly.
|
||||
*/
|
||||
internal class RegExpException : Exception
|
||||
{
|
||||
public enum ErrorType
|
||||
{
|
||||
|
||||
/**
|
||||
* The unexpected character error constant. This error is
|
||||
* used when a character was read that didn't match the
|
||||
* allowed set of characters at the given position.
|
||||
*/
|
||||
UNEXPECTED_CHARACTER,
|
||||
|
||||
/**
|
||||
* The unterminated pattern error constant. This error is
|
||||
* used when more characters were expected in the pattern.
|
||||
*/
|
||||
UNTERMINATED_PATTERN,
|
||||
|
||||
/**
|
||||
* The unsupported special character error constant. This
|
||||
* error is used when special regular expression
|
||||
* characters are used in the pattern, but not supported
|
||||
* in this implementation.
|
||||
*/
|
||||
UNSUPPORTED_SPECIAL_CHARACTER,
|
||||
|
||||
/**
|
||||
* The unsupported escape character error constant. This
|
||||
* error is used when an escape character construct is
|
||||
* used in the pattern, but not supported in this
|
||||
* implementation.
|
||||
*/
|
||||
UNSUPPORTED_ESCAPE_CHARACTER,
|
||||
|
||||
/**
|
||||
* The invalid repeat count error constant. This error is
|
||||
* used when a repetition count of zero is specified, or
|
||||
* when the minimum exceeds the maximum.
|
||||
*/
|
||||
INVALID_REPEAT_COUNT
|
||||
}
|
||||
|
||||
private readonly ErrorType _type;
|
||||
private readonly int _position;
|
||||
private readonly string _pattern;
|
||||
|
||||
public RegExpException(ErrorType type, int pos, string pattern)
|
||||
{
|
||||
this._type = type;
|
||||
this._position = pos;
|
||||
this._pattern = pattern;
|
||||
}
|
||||
|
||||
public override string Message => GetMessage();
|
||||
|
||||
public string GetMessage()
|
||||
{
|
||||
StringBuilder buffer = new StringBuilder();
|
||||
|
||||
// Append error type name
|
||||
switch (_type)
|
||||
{
|
||||
case ErrorType.UNEXPECTED_CHARACTER:
|
||||
buffer.Append("unexpected character");
|
||||
break;
|
||||
case ErrorType.UNTERMINATED_PATTERN:
|
||||
buffer.Append("unterminated pattern");
|
||||
break;
|
||||
case ErrorType.UNSUPPORTED_SPECIAL_CHARACTER:
|
||||
buffer.Append("unsupported character");
|
||||
break;
|
||||
case ErrorType.UNSUPPORTED_ESCAPE_CHARACTER:
|
||||
buffer.Append("unsupported escape character");
|
||||
break;
|
||||
case ErrorType.INVALID_REPEAT_COUNT:
|
||||
buffer.Append("invalid repeat count");
|
||||
break;
|
||||
default:
|
||||
buffer.Append("internal error");
|
||||
break;
|
||||
}
|
||||
|
||||
// Append erroneous character
|
||||
buffer.Append(": ");
|
||||
if (_position < _pattern.Length)
|
||||
{
|
||||
buffer.Append('\'');
|
||||
buffer.Append(_pattern.Substring(_position));
|
||||
buffer.Append('\'');
|
||||
}
|
||||
else
|
||||
{
|
||||
buffer.Append("<end of pattern>");
|
||||
}
|
||||
|
||||
// Append position
|
||||
buffer.Append(" at position ");
|
||||
buffer.Append(_position);
|
||||
|
||||
return buffer.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
239
Parsing/RepeatElement.cs
Normal file
239
Parsing/RepeatElement.cs
Normal file
@@ -0,0 +1,239 @@
|
||||
using System.Collections;
|
||||
|
||||
namespace Flee.Parsing
|
||||
{
|
||||
|
||||
/**
|
||||
* A regular expression element repeater. The element repeats the
|
||||
* matches from a specified element, attempting to reach the
|
||||
* maximum repetition count.
|
||||
*/
|
||||
internal class RepeatElement : Element
|
||||
{
|
||||
public enum RepeatType
|
||||
{
|
||||
GREEDY = 1,
|
||||
RELUCTANT = 2,
|
||||
POSSESSIVE = 3
|
||||
}
|
||||
private readonly Element _elem;
|
||||
private readonly int _min;
|
||||
private readonly int _max;
|
||||
private readonly RepeatType _type;
|
||||
private int _matchStart;
|
||||
private BitArray _matches;
|
||||
|
||||
public RepeatElement(Element elem,
|
||||
int min,
|
||||
int max,
|
||||
RepeatType type)
|
||||
{
|
||||
|
||||
this._elem = elem;
|
||||
this._min = min;
|
||||
if (max <= 0)
|
||||
{
|
||||
this._max = Int32.MaxValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
this._max = max;
|
||||
}
|
||||
this._type = type;
|
||||
this._matchStart = -1;
|
||||
this._matches = null;
|
||||
}
|
||||
|
||||
public override object Clone()
|
||||
{
|
||||
return new RepeatElement((Element)_elem.Clone(),
|
||||
_min,
|
||||
_max,
|
||||
_type);
|
||||
}
|
||||
|
||||
public override int Match(Matcher m,
|
||||
ReaderBuffer buffer,
|
||||
int start,
|
||||
int skip)
|
||||
{
|
||||
if (skip == 0)
|
||||
{
|
||||
_matchStart = -1;
|
||||
_matches = null;
|
||||
}
|
||||
switch (_type)
|
||||
{
|
||||
case RepeatType.GREEDY:
|
||||
return MatchGreedy(m, buffer, start, skip);
|
||||
case RepeatType.RELUCTANT:
|
||||
return MatchReluctant(m, buffer, start, skip);
|
||||
case RepeatType.POSSESSIVE:
|
||||
if (skip == 0)
|
||||
{
|
||||
return MatchPossessive(m, buffer, start, 0);
|
||||
}
|
||||
break;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
private int MatchGreedy(Matcher m,
|
||||
ReaderBuffer buffer,
|
||||
int start,
|
||||
int skip)
|
||||
{
|
||||
// Check for simple case
|
||||
if (skip == 0)
|
||||
{
|
||||
return MatchPossessive(m, buffer, start, 0);
|
||||
}
|
||||
|
||||
// Find all matches
|
||||
if (_matchStart != start)
|
||||
{
|
||||
_matchStart = start;
|
||||
_matches = new BitArray(10);
|
||||
FindMatches(m, buffer, start, 0, 0, 0);
|
||||
}
|
||||
|
||||
// Find first non-skipped match
|
||||
for (int i = _matches.Count - 1; i >= 0; i--)
|
||||
{
|
||||
if (_matches[i])
|
||||
{
|
||||
if (skip == 0)
|
||||
{
|
||||
return i;
|
||||
}
|
||||
skip--;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
private int MatchReluctant(Matcher m,
|
||||
ReaderBuffer buffer,
|
||||
int start,
|
||||
int skip)
|
||||
{
|
||||
if (_matchStart != start)
|
||||
{
|
||||
_matchStart = start;
|
||||
_matches = new BitArray(10);
|
||||
FindMatches(m, buffer, start, 0, 0, 0);
|
||||
}
|
||||
|
||||
// Find first non-skipped match
|
||||
for (int i = 0; i < _matches.Count; i++)
|
||||
{
|
||||
if (_matches[i])
|
||||
{
|
||||
if (skip == 0)
|
||||
{
|
||||
return i;
|
||||
}
|
||||
skip--;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
private int MatchPossessive(Matcher m,
|
||||
ReaderBuffer buffer,
|
||||
int start,
|
||||
int count)
|
||||
{
|
||||
int length = 0;
|
||||
int subLength = 1;
|
||||
|
||||
// Match as many elements as possible
|
||||
while (subLength > 0 && count < _max)
|
||||
{
|
||||
subLength = _elem.Match(m, buffer, start + length, 0);
|
||||
if (subLength >= 0)
|
||||
{
|
||||
count++;
|
||||
length += subLength;
|
||||
}
|
||||
}
|
||||
|
||||
// Return result
|
||||
if (_min <= count && count <= _max)
|
||||
{
|
||||
return length;
|
||||
}
|
||||
else
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
private void FindMatches(Matcher m,
|
||||
ReaderBuffer buffer,
|
||||
int start,
|
||||
int length,
|
||||
int count,
|
||||
int attempt)
|
||||
{
|
||||
int subLength;
|
||||
|
||||
// Check match ending here
|
||||
if (count > _max)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (_min <= count && attempt == 0)
|
||||
{
|
||||
if (_matches.Length <= length)
|
||||
{
|
||||
_matches.Length = length + 10;
|
||||
}
|
||||
_matches[length] = true;
|
||||
}
|
||||
|
||||
// Check element match
|
||||
subLength = _elem.Match(m, buffer, start, attempt);
|
||||
if (subLength < 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
else if (subLength == 0)
|
||||
{
|
||||
if (_min == count + 1)
|
||||
{
|
||||
if (_matches.Length <= length)
|
||||
{
|
||||
_matches.Length = length + 10;
|
||||
}
|
||||
_matches[length] = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Find alternative and subsequent matches
|
||||
FindMatches(m, buffer, start, length, count, attempt + 1);
|
||||
FindMatches(m,
|
||||
buffer,
|
||||
start + subLength,
|
||||
length + subLength,
|
||||
count + 1,
|
||||
0);
|
||||
}
|
||||
|
||||
public override void PrintTo(TextWriter output, string indent)
|
||||
{
|
||||
output.Write(indent + "Repeat (" + _min + "," + _max + ")");
|
||||
if (_type == RepeatType.RELUCTANT)
|
||||
{
|
||||
output.Write("?");
|
||||
}
|
||||
else if (_type == RepeatType.POSSESSIVE)
|
||||
{
|
||||
output.Write("+");
|
||||
}
|
||||
output.WriteLine();
|
||||
_elem.PrintTo(output, indent + " ");
|
||||
}
|
||||
}
|
||||
}
|
||||
761
Parsing/StackParser.cs
Normal file
761
Parsing/StackParser.cs
Normal file
@@ -0,0 +1,761 @@
|
||||
using System.Collections;
|
||||
|
||||
namespace Flee.Parsing
|
||||
{
|
||||
/**
|
||||
* based on recursive descent parser, this implementation removes recursion
|
||||
* and uses a stack instead. This parser handles LL(n) grammars,
|
||||
* selecting the appropriate pattern to parse based on the next few
|
||||
* tokens.
|
||||
*/
|
||||
internal class StackParser : Parser
|
||||
{
|
||||
/**
|
||||
* this is the parser state that is pushed onto the stack, simulating
|
||||
* the variable state needed in recursive version. Some variables
|
||||
* substitute for execution position, such as validnext, so patterns
|
||||
* are processed in the proper order.
|
||||
*/
|
||||
internal class ParseState
|
||||
{
|
||||
/**
|
||||
* pattern for this state
|
||||
*/
|
||||
internal ProductionPattern pattern;
|
||||
/**
|
||||
* index of the alt pattern we are currently checking
|
||||
*/
|
||||
internal int altindex;
|
||||
|
||||
/**
|
||||
* index into the list of elements for the alt pattern
|
||||
*/
|
||||
internal int elementindex;
|
||||
|
||||
/**
|
||||
* index to the token we are processing.
|
||||
*/
|
||||
internal int tokenindex;
|
||||
|
||||
/**
|
||||
* The node for current state
|
||||
*/
|
||||
internal Node node;
|
||||
|
||||
/**
|
||||
* true if we already checked IsNext on the current pattern
|
||||
* so we should not call it again
|
||||
*/
|
||||
internal bool validnext;
|
||||
|
||||
}
|
||||
|
||||
|
||||
public StackParser(TextReader input) : base(input)
|
||||
{
|
||||
}
|
||||
|
||||
public StackParser(TextReader input, Analyzer analyzer)
|
||||
: base(input, analyzer)
|
||||
{
|
||||
}
|
||||
|
||||
public StackParser(Tokenizer tokenizer)
|
||||
: base(tokenizer)
|
||||
{
|
||||
}
|
||||
|
||||
public StackParser(Tokenizer tokenizer,
|
||||
Analyzer analyzer)
|
||||
: base(tokenizer, analyzer)
|
||||
{
|
||||
}
|
||||
|
||||
public override void AddPattern(ProductionPattern pattern)
|
||||
{
|
||||
|
||||
// Check for empty matches
|
||||
if (pattern.IsMatchingEmpty())
|
||||
{
|
||||
throw new ParserCreationException(
|
||||
ParserCreationException.ErrorType.INVALID_PRODUCTION,
|
||||
pattern.Name,
|
||||
"zero elements can be matched (minimum is one)");
|
||||
}
|
||||
|
||||
// Check for left-recusive patterns
|
||||
if (pattern.IsLeftRecursive())
|
||||
{
|
||||
throw new ParserCreationException(
|
||||
ParserCreationException.ErrorType.INVALID_PRODUCTION,
|
||||
pattern.Name,
|
||||
"left recursive patterns are not allowed");
|
||||
}
|
||||
|
||||
// Add pattern
|
||||
base.AddPattern(pattern);
|
||||
}
|
||||
|
||||
public override void Prepare()
|
||||
{
|
||||
// Performs production pattern checks
|
||||
base.Prepare();
|
||||
SetInitialized(false);
|
||||
|
||||
// Calculate production look-ahead sets
|
||||
var e = GetPatterns().GetEnumerator();
|
||||
while (e.MoveNext())
|
||||
{
|
||||
CalculateLookAhead((ProductionPattern)e.Current);
|
||||
}
|
||||
|
||||
// Set initialized flag
|
||||
SetInitialized(true);
|
||||
}
|
||||
|
||||
protected override Node ParseStart()
|
||||
{
|
||||
var node = ParsePatterns(GetStartPattern());
|
||||
|
||||
|
||||
var token = PeekToken(0);
|
||||
if (token != null)
|
||||
{
|
||||
var list = new ArrayList(1) { "<EOF>" };
|
||||
throw new ParseException(
|
||||
ParseException.ErrorType.UNEXPECTED_TOKEN,
|
||||
token.ToShortString(),
|
||||
list,
|
||||
token.StartLine,
|
||||
token.StartColumn);
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
|
||||
|
||||
private ParseState NewState(ProductionPattern pattern)
|
||||
{
|
||||
return new ParseState()
|
||||
{
|
||||
pattern = pattern,
|
||||
altindex = 0,
|
||||
elementindex = 0,
|
||||
tokenindex = 0,
|
||||
node = null,
|
||||
validnext = false
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// parse patterns using a stack. The stack is local to this method, since the parser
|
||||
/// is a singleton and may be parsing expressions from multiple threads, so cannot
|
||||
/// use the object to store our stack.
|
||||
/// </summary>
|
||||
/// <param name="start"></param>
|
||||
/// <returns></returns>
|
||||
private Node ParsePatterns(ProductionPattern start)
|
||||
{
|
||||
Stack<ParseState> _stack = new Stack<ParseState>();
|
||||
_stack.Push(NewState(start));
|
||||
|
||||
while (_stack.Count > 0)
|
||||
{
|
||||
ParseState state = _stack.Peek();
|
||||
ProductionPattern pattern = state.pattern;
|
||||
var defaultAlt = pattern.DefaultAlternative;
|
||||
ProductionPattern nextpattern = null;
|
||||
while (state.altindex < pattern.Count)
|
||||
{
|
||||
var alt = pattern[state.altindex];
|
||||
if (state.validnext || (defaultAlt != alt && IsNext(alt)))
|
||||
{
|
||||
state.validnext = true;
|
||||
nextpattern = ParseAlternative(state, alt);
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
state.altindex++;
|
||||
state.validnext = false;
|
||||
}
|
||||
}
|
||||
|
||||
// check if completed pass through alt patterns. try default
|
||||
if (state.altindex >= pattern.Count)
|
||||
{
|
||||
if (!state.validnext && (defaultAlt == null || !IsNext(defaultAlt)))
|
||||
{
|
||||
ThrowParseException(FindUnion(pattern));
|
||||
}
|
||||
else
|
||||
{
|
||||
state.validnext = true;
|
||||
nextpattern = ParseAlternative(state, defaultAlt);
|
||||
}
|
||||
}
|
||||
|
||||
if (nextpattern != null)
|
||||
{
|
||||
_stack.Push(NewState(nextpattern));
|
||||
}
|
||||
|
||||
// we finished current pattern, so back up to previous state.
|
||||
else
|
||||
{
|
||||
// if we have a node set, add it to the parent
|
||||
var child = state.node;
|
||||
_stack.Pop();
|
||||
if (_stack.Count == 0)
|
||||
{
|
||||
// back to top, can return our result, which is top node
|
||||
return child;
|
||||
}
|
||||
state = _stack.Peek();
|
||||
AddNode((Production)state.node, child);
|
||||
}
|
||||
}
|
||||
|
||||
// should never get here, but must show we return something.
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* return the pattern to push onto stack and process next.
|
||||
*/
|
||||
private ProductionPattern ParseAlternative(ParseState state, ProductionPatternAlternative alt)
|
||||
{
|
||||
if (state.node == null)
|
||||
{
|
||||
state.node = NewProduction(alt.Pattern);
|
||||
state.elementindex = 0;
|
||||
EnterNode(state.node);
|
||||
}
|
||||
while (state.elementindex < alt.Count)
|
||||
{
|
||||
try
|
||||
{
|
||||
var pattern = ParseElement(state, alt[state.elementindex]);
|
||||
if (pattern == null)
|
||||
state.elementindex++;
|
||||
else
|
||||
return pattern;
|
||||
}
|
||||
catch (ParseException e)
|
||||
{
|
||||
AddError(e, true);
|
||||
NextToken();
|
||||
}
|
||||
}
|
||||
|
||||
state.node = ExitNode(state.node);
|
||||
return null;
|
||||
}
|
||||
|
||||
private ProductionPattern ParseElement(ParseState state,
|
||||
ProductionPatternElement elem)
|
||||
{
|
||||
for (int i = state.tokenindex; i < elem.MaxCount; i++)
|
||||
{
|
||||
if (i < elem.MinCount || IsNext(elem))
|
||||
{
|
||||
Node child;
|
||||
if (elem.IsToken())
|
||||
{
|
||||
child = NextToken(elem.Id);
|
||||
EnterNode(child);
|
||||
AddNode((Production)state.node, ExitNode(child));
|
||||
}
|
||||
else
|
||||
{
|
||||
// continue from next token when we return
|
||||
state.tokenindex = i + 1;
|
||||
// return to start processing the new pattern at this state
|
||||
return GetPattern(elem.Id); ;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
//
|
||||
// we completed processing this element
|
||||
state.tokenindex = 0;
|
||||
return null;
|
||||
}
|
||||
|
||||
private bool IsNext(ProductionPattern pattern)
|
||||
{
|
||||
LookAheadSet set = pattern.LookAhead;
|
||||
|
||||
if (set == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
return set.IsNext(this);
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsNext(ProductionPatternAlternative alt)
|
||||
{
|
||||
LookAheadSet set = alt.LookAhead;
|
||||
|
||||
if (set == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
return set.IsNext(this);
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsNext(ProductionPatternElement elem)
|
||||
{
|
||||
LookAheadSet set = elem.LookAhead;
|
||||
|
||||
if (set != null)
|
||||
{
|
||||
return set.IsNext(this);
|
||||
}
|
||||
else if (elem.IsToken())
|
||||
{
|
||||
return elem.IsMatch(PeekToken(0));
|
||||
}
|
||||
else
|
||||
{
|
||||
return IsNext(GetPattern(elem.Id));
|
||||
}
|
||||
}
|
||||
|
||||
private void CalculateLookAhead(ProductionPattern pattern)
|
||||
{
|
||||
ProductionPatternAlternative alt;
|
||||
LookAheadSet previous = new LookAheadSet(0);
|
||||
int length = 1;
|
||||
int i;
|
||||
CallStack stack = new CallStack();
|
||||
|
||||
// Calculate simple look-ahead
|
||||
stack.Push(pattern.Name, 1);
|
||||
var result = new LookAheadSet(1);
|
||||
var alternatives = new LookAheadSet[pattern.Count];
|
||||
for (i = 0; i < pattern.Count; i++)
|
||||
{
|
||||
alt = pattern[i];
|
||||
alternatives[i] = FindLookAhead(alt, 1, 0, stack, null);
|
||||
alt.LookAhead = alternatives[i];
|
||||
result.AddAll(alternatives[i]);
|
||||
}
|
||||
if (pattern.LookAhead == null)
|
||||
{
|
||||
pattern.LookAhead = result;
|
||||
}
|
||||
var conflicts = FindConflicts(pattern, 1);
|
||||
|
||||
// Resolve conflicts
|
||||
while (conflicts.Size() > 0)
|
||||
{
|
||||
length++;
|
||||
stack.Clear();
|
||||
stack.Push(pattern.Name, length);
|
||||
conflicts.AddAll(previous);
|
||||
for (i = 0; i < pattern.Count; i++)
|
||||
{
|
||||
alt = pattern[i];
|
||||
if (alternatives[i].Intersects(conflicts))
|
||||
{
|
||||
alternatives[i] = FindLookAhead(alt,
|
||||
length,
|
||||
0,
|
||||
stack,
|
||||
conflicts);
|
||||
alt.LookAhead = alternatives[i];
|
||||
}
|
||||
if (alternatives[i].Intersects(conflicts))
|
||||
{
|
||||
if (pattern.DefaultAlternative == null)
|
||||
{
|
||||
pattern.DefaultAlternative = alt;
|
||||
}
|
||||
else if (pattern.DefaultAlternative != alt)
|
||||
{
|
||||
result = alternatives[i].CreateIntersection(conflicts);
|
||||
ThrowAmbiguityException(pattern.Name,
|
||||
null,
|
||||
result);
|
||||
}
|
||||
}
|
||||
}
|
||||
previous = conflicts;
|
||||
conflicts = FindConflicts(pattern, length);
|
||||
}
|
||||
|
||||
// Resolve conflicts inside rules
|
||||
for (i = 0; i < pattern.Count; i++)
|
||||
{
|
||||
CalculateLookAhead(pattern[i], 0);
|
||||
}
|
||||
}
|
||||
|
||||
private void CalculateLookAhead(ProductionPatternAlternative alt,
|
||||
int pos)
|
||||
{
|
||||
LookAheadSet previous = new LookAheadSet(0);
|
||||
int length = 1;
|
||||
|
||||
// Check trivial cases
|
||||
if (pos >= alt.Count)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for non-optional element
|
||||
var pattern = alt.Pattern;
|
||||
var elem = alt[pos];
|
||||
if (elem.MinCount == elem.MaxCount)
|
||||
{
|
||||
CalculateLookAhead(alt, pos + 1);
|
||||
return;
|
||||
}
|
||||
|
||||
// Calculate simple look-aheads
|
||||
var first = FindLookAhead(elem, 1, new CallStack(), null);
|
||||
var follow = FindLookAhead(alt, 1, pos + 1, new CallStack(), null);
|
||||
|
||||
// Resolve conflicts
|
||||
var location = "at position " + (pos + 1);
|
||||
var conflicts = FindConflicts(pattern.Name,
|
||||
location,
|
||||
first,
|
||||
follow);
|
||||
while (conflicts.Size() > 0)
|
||||
{
|
||||
length++;
|
||||
conflicts.AddAll(previous);
|
||||
first = FindLookAhead(elem,
|
||||
length,
|
||||
new CallStack(),
|
||||
conflicts);
|
||||
follow = FindLookAhead(alt,
|
||||
length,
|
||||
pos + 1,
|
||||
new CallStack(),
|
||||
conflicts);
|
||||
first = first.CreateCombination(follow);
|
||||
elem.LookAhead = first;
|
||||
if (first.Intersects(conflicts))
|
||||
{
|
||||
first = first.CreateIntersection(conflicts);
|
||||
ThrowAmbiguityException(pattern.Name, location, first);
|
||||
}
|
||||
previous = conflicts;
|
||||
conflicts = FindConflicts(pattern.Name,
|
||||
location,
|
||||
first,
|
||||
follow);
|
||||
}
|
||||
|
||||
// Check remaining elements
|
||||
CalculateLookAhead(alt, pos + 1);
|
||||
}
|
||||
|
||||
private LookAheadSet FindLookAhead(ProductionPattern pattern,
|
||||
int length,
|
||||
CallStack stack,
|
||||
LookAheadSet filter)
|
||||
{
|
||||
// Check for infinite loop
|
||||
if (stack.Contains(pattern.Name, length))
|
||||
{
|
||||
throw new ParserCreationException(
|
||||
ParserCreationException.ErrorType.INFINITE_LOOP,
|
||||
pattern.Name,
|
||||
(String)null);
|
||||
}
|
||||
|
||||
// Find pattern look-ahead
|
||||
stack.Push(pattern.Name, length);
|
||||
var result = new LookAheadSet(length);
|
||||
for (int i = 0; i < pattern.Count; i++)
|
||||
{
|
||||
var temp = FindLookAhead(pattern[i],
|
||||
length,
|
||||
0,
|
||||
stack,
|
||||
filter);
|
||||
result.AddAll(temp);
|
||||
}
|
||||
stack.Pop();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private LookAheadSet FindLookAhead(ProductionPatternAlternative alt,
|
||||
int length,
|
||||
int pos,
|
||||
CallStack stack,
|
||||
LookAheadSet filter)
|
||||
{
|
||||
LookAheadSet follow;
|
||||
// Check trivial cases
|
||||
if (length <= 0 || pos >= alt.Count)
|
||||
{
|
||||
return new LookAheadSet(0);
|
||||
}
|
||||
|
||||
// Find look-ahead for this element
|
||||
var first = FindLookAhead(alt[pos], length, stack, filter);
|
||||
if (alt[pos].MinCount == 0)
|
||||
{
|
||||
first.AddEmpty();
|
||||
}
|
||||
|
||||
// Find remaining look-ahead
|
||||
if (filter == null)
|
||||
{
|
||||
length -= first.GetMinLength();
|
||||
if (length > 0)
|
||||
{
|
||||
follow = FindLookAhead(alt, length, pos + 1, stack, null);
|
||||
first = first.CreateCombination(follow);
|
||||
}
|
||||
}
|
||||
else if (filter.IsOverlap(first))
|
||||
{
|
||||
var overlaps = first.CreateOverlaps(filter);
|
||||
length -= overlaps.GetMinLength();
|
||||
filter = filter.CreateFilter(overlaps);
|
||||
follow = FindLookAhead(alt, length, pos + 1, stack, filter);
|
||||
first.RemoveAll(overlaps);
|
||||
first.AddAll(overlaps.CreateCombination(follow));
|
||||
}
|
||||
|
||||
return first;
|
||||
}
|
||||
|
||||
private LookAheadSet FindLookAhead(ProductionPatternElement elem,
|
||||
int length,
|
||||
CallStack stack,
|
||||
LookAheadSet filter)
|
||||
{
|
||||
// Find initial element look-ahead
|
||||
var first = FindLookAhead(elem, length, 0, stack, filter);
|
||||
var result = new LookAheadSet(length);
|
||||
result.AddAll(first);
|
||||
if (filter == null || !filter.IsOverlap(result))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
// Handle element repetitions
|
||||
if (elem.MaxCount == Int32.MaxValue)
|
||||
{
|
||||
first = first.CreateRepetitive();
|
||||
}
|
||||
var max = elem.MaxCount;
|
||||
if (length < max)
|
||||
{
|
||||
max = length;
|
||||
}
|
||||
for (int i = 1; i < max; i++)
|
||||
{
|
||||
first = first.CreateOverlaps(filter);
|
||||
if (first.Size() <= 0 || first.GetMinLength() >= length)
|
||||
{
|
||||
break;
|
||||
}
|
||||
var follow = FindLookAhead(elem,
|
||||
length,
|
||||
0,
|
||||
stack,
|
||||
filter.CreateFilter(first));
|
||||
first = first.CreateCombination(follow);
|
||||
result.AddAll(first);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private LookAheadSet FindLookAhead(ProductionPatternElement elem,
|
||||
int length,
|
||||
int dummy,
|
||||
CallStack stack,
|
||||
LookAheadSet filter)
|
||||
{
|
||||
LookAheadSet result;
|
||||
|
||||
if (elem.IsToken())
|
||||
{
|
||||
result = new LookAheadSet(length);
|
||||
result.Add(elem.Id);
|
||||
}
|
||||
else
|
||||
{
|
||||
var pattern = GetPattern(elem.Id);
|
||||
result = FindLookAhead(pattern, length, stack, filter);
|
||||
if (stack.Contains(pattern.Name))
|
||||
{
|
||||
result = result.CreateRepetitive();
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private LookAheadSet FindConflicts(ProductionPattern pattern,
|
||||
int maxLength)
|
||||
{
|
||||
|
||||
LookAheadSet result = new LookAheadSet(maxLength);
|
||||
for (int i = 0; i < pattern.Count; i++)
|
||||
{
|
||||
var set1 = pattern[i].LookAhead;
|
||||
for (int j = 0; j < i; j++)
|
||||
{
|
||||
var set2 = pattern[j].LookAhead;
|
||||
result.AddAll(set1.CreateIntersection(set2));
|
||||
}
|
||||
}
|
||||
if (result.IsRepetitive())
|
||||
{
|
||||
ThrowAmbiguityException(pattern.Name, null, result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private LookAheadSet FindConflicts(string pattern,
|
||||
string location,
|
||||
LookAheadSet set1,
|
||||
LookAheadSet set2)
|
||||
{
|
||||
var result = set1.CreateIntersection(set2);
|
||||
if (result.IsRepetitive())
|
||||
{
|
||||
ThrowAmbiguityException(pattern, location, result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private LookAheadSet FindUnion(ProductionPattern pattern)
|
||||
{
|
||||
LookAheadSet result;
|
||||
int length = 0;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < pattern.Count; i++)
|
||||
{
|
||||
result = pattern[i].LookAhead;
|
||||
if (result.GetMaxLength() > length)
|
||||
{
|
||||
length = result.GetMaxLength();
|
||||
}
|
||||
}
|
||||
result = new LookAheadSet(length);
|
||||
for (i = 0; i < pattern.Count; i++)
|
||||
{
|
||||
result.AddAll(pattern[i].LookAhead);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
private void ThrowParseException(LookAheadSet set)
|
||||
{
|
||||
ArrayList list = new ArrayList();
|
||||
|
||||
// Read tokens until mismatch
|
||||
while (set.IsNext(this, 1))
|
||||
{
|
||||
set = set.CreateNextSet(NextToken().Id);
|
||||
}
|
||||
|
||||
// Find next token descriptions
|
||||
var initials = set.GetInitialTokens();
|
||||
for (int i = 0; i < initials.Length; i++)
|
||||
{
|
||||
list.Add(GetTokenDescription(initials[i]));
|
||||
}
|
||||
|
||||
// Create exception
|
||||
var token = NextToken();
|
||||
throw new ParseException(ParseException.ErrorType.UNEXPECTED_TOKEN,
|
||||
token.ToShortString(),
|
||||
list,
|
||||
token.StartLine,
|
||||
token.StartColumn);
|
||||
}
|
||||
|
||||
private void ThrowAmbiguityException(string pattern,
|
||||
string location,
|
||||
LookAheadSet set)
|
||||
{
|
||||
|
||||
ArrayList list = new ArrayList();
|
||||
|
||||
// Find next token descriptions
|
||||
var initials = set.GetInitialTokens();
|
||||
for (int i = 0; i < initials.Length; i++)
|
||||
{
|
||||
list.Add(GetTokenDescription(initials[i]));
|
||||
}
|
||||
|
||||
// Create exception
|
||||
throw new ParserCreationException(
|
||||
ParserCreationException.ErrorType.INHERENT_AMBIGUITY,
|
||||
pattern,
|
||||
location,
|
||||
list);
|
||||
}
|
||||
|
||||
|
||||
private class CallStack
|
||||
{
|
||||
private readonly ArrayList _nameStack = new ArrayList();
|
||||
private readonly ArrayList _valueStack = new ArrayList();
|
||||
public bool Contains(string name)
|
||||
{
|
||||
return _nameStack.Contains(name);
|
||||
}
|
||||
|
||||
public bool Contains(string name, int value)
|
||||
{
|
||||
for (int i = 0; i < _nameStack.Count; i++)
|
||||
{
|
||||
if (_nameStack[i].Equals(name)
|
||||
&& _valueStack[i].Equals(value))
|
||||
{
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
_nameStack.Clear();
|
||||
_valueStack.Clear();
|
||||
}
|
||||
|
||||
public void Push(string name, int value)
|
||||
{
|
||||
_nameStack.Add(name);
|
||||
_valueStack.Add(value);
|
||||
}
|
||||
|
||||
public void Pop()
|
||||
{
|
||||
if (_nameStack.Count > 0)
|
||||
{
|
||||
_nameStack.RemoveAt(_nameStack.Count - 1);
|
||||
_valueStack.RemoveAt(_valueStack.Count - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
64
Parsing/StringElement.cs
Normal file
64
Parsing/StringElement.cs
Normal file
@@ -0,0 +1,64 @@
|
||||
namespace Flee.Parsing
|
||||
{
|
||||
/**
|
||||
* A regular expression string element. This element only matches
|
||||
* an exact string. Once created, the string element is immutable.
|
||||
*/
|
||||
internal class StringElement : Element
|
||||
{
|
||||
private readonly string _value;
|
||||
public StringElement(char c)
|
||||
: this(c.ToString())
|
||||
{
|
||||
}
|
||||
|
||||
public StringElement(string str)
|
||||
{
|
||||
_value = str;
|
||||
}
|
||||
|
||||
public string GetString()
|
||||
{
|
||||
return _value;
|
||||
}
|
||||
|
||||
public override object Clone()
|
||||
{
|
||||
return this;
|
||||
}
|
||||
|
||||
public override int Match(Matcher m,
|
||||
ReaderBuffer buffer,
|
||||
int start,
|
||||
int skip)
|
||||
{
|
||||
if (skip != 0)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
for (int i = 0; i < _value.Length; i++)
|
||||
{
|
||||
var c = buffer.Peek(start + i);
|
||||
if (c < 0)
|
||||
{
|
||||
m.SetReadEndOfString();
|
||||
return -1;
|
||||
}
|
||||
if (m.IsCaseInsensitive())
|
||||
{
|
||||
c = (int)Char.ToLower((char)c);
|
||||
}
|
||||
if (c != (int)_value[i])
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return _value.Length;
|
||||
}
|
||||
|
||||
public override void PrintTo(TextWriter output, string indent)
|
||||
{
|
||||
output.WriteLine(indent + "'" + _value + "'");
|
||||
}
|
||||
}
|
||||
}
|
||||
168
Parsing/Token.cs
Normal file
168
Parsing/Token.cs
Normal file
@@ -0,0 +1,168 @@
|
||||
using System.Text;
|
||||
|
||||
namespace Flee.Parsing
|
||||
{
|
||||
/**
|
||||
* A token node. This class represents a token (i.e. a set of adjacent
|
||||
* characters) in a parse tree. The tokens are created by a tokenizer,
|
||||
* that groups characters together into tokens according to a set of
|
||||
* token patterns.
|
||||
*/
|
||||
internal class Token : Node
|
||||
{
|
||||
private readonly TokenPattern _pattern;
|
||||
private readonly string _image;
|
||||
private readonly int _startLine;
|
||||
private readonly int _startColumn;
|
||||
private readonly int _endLine;
|
||||
private readonly int _endColumn;
|
||||
private Token _previous = null;
|
||||
private Token _next = null;
|
||||
|
||||
public Token(TokenPattern pattern, string image, int line, int col)
|
||||
{
|
||||
this._pattern = pattern;
|
||||
this._image = image;
|
||||
this._startLine = line;
|
||||
this._startColumn = col;
|
||||
this._endLine = line;
|
||||
this._endColumn = col + image.Length - 1;
|
||||
for (int pos = 0; image.IndexOf('\n', pos) >= 0;)
|
||||
{
|
||||
pos = image.IndexOf('\n', pos) + 1;
|
||||
this._endLine++;
|
||||
_endColumn = image.Length - pos;
|
||||
}
|
||||
}
|
||||
|
||||
public override int Id => _pattern.Id;
|
||||
|
||||
public override string Name => _pattern.Name;
|
||||
|
||||
public override int StartLine => _startLine;
|
||||
|
||||
public override int StartColumn => _startColumn;
|
||||
|
||||
public override int EndLine => _endLine;
|
||||
|
||||
public override int EndColumn => _endColumn;
|
||||
|
||||
public string Image => _image;
|
||||
|
||||
public string GetImage()
|
||||
{
|
||||
return Image;
|
||||
}
|
||||
|
||||
internal TokenPattern Pattern => _pattern;
|
||||
public Token Previous
|
||||
{
|
||||
get
|
||||
{
|
||||
return _previous;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (_previous != null)
|
||||
{
|
||||
_previous._next = null;
|
||||
}
|
||||
_previous = value;
|
||||
if (_previous != null)
|
||||
{
|
||||
_previous._next = this;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Token GetPreviousToken()
|
||||
{
|
||||
return Previous;
|
||||
}
|
||||
|
||||
public Token Next
|
||||
{
|
||||
get
|
||||
{
|
||||
return _next;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (_next != null)
|
||||
{
|
||||
_next._previous = null;
|
||||
}
|
||||
_next = value;
|
||||
if (_next != null)
|
||||
{
|
||||
_next._previous = this;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Token GetNextToken()
|
||||
{
|
||||
return Next;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
StringBuilder buffer = new StringBuilder();
|
||||
int newline = _image.IndexOf('\n');
|
||||
|
||||
buffer.Append(_pattern.Name);
|
||||
buffer.Append("(");
|
||||
buffer.Append(_pattern.Id);
|
||||
buffer.Append("): \"");
|
||||
if (newline >= 0)
|
||||
{
|
||||
if (newline > 0 && _image[newline - 1] == '\r')
|
||||
{
|
||||
newline--;
|
||||
}
|
||||
buffer.Append(_image.Substring(0, newline));
|
||||
buffer.Append("(...)");
|
||||
}
|
||||
else
|
||||
{
|
||||
buffer.Append(_image);
|
||||
}
|
||||
buffer.Append("\", line: ");
|
||||
buffer.Append(_startLine);
|
||||
buffer.Append(", col: ");
|
||||
buffer.Append(_startColumn);
|
||||
|
||||
return buffer.ToString();
|
||||
}
|
||||
|
||||
public string ToShortString()
|
||||
{
|
||||
StringBuilder buffer = new StringBuilder();
|
||||
int newline = _image.IndexOf('\n');
|
||||
|
||||
buffer.Append('"');
|
||||
if (newline >= 0)
|
||||
{
|
||||
if (newline > 0 && _image[newline - 1] == '\r')
|
||||
{
|
||||
newline--;
|
||||
}
|
||||
buffer.Append(_image.Substring(0, newline));
|
||||
buffer.Append("(...)");
|
||||
}
|
||||
else
|
||||
{
|
||||
buffer.Append(_image);
|
||||
}
|
||||
buffer.Append('"');
|
||||
if (_pattern.Type == TokenPattern.PatternType.REGEXP)
|
||||
{
|
||||
buffer.Append(" <");
|
||||
buffer.Append(_pattern.Name);
|
||||
buffer.Append(">");
|
||||
}
|
||||
|
||||
return buffer.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
31
Parsing/TokenMatch.cs
Normal file
31
Parsing/TokenMatch.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
namespace Flee.Parsing
|
||||
{
|
||||
/**
|
||||
* The token match status. This class contains logic to ensure that
|
||||
* only the longest match is considered.
|
||||
*/
|
||||
internal class TokenMatch
|
||||
{
|
||||
private int _length = 0;
|
||||
private TokenPattern _pattern = null;
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
_length = 0;
|
||||
_pattern = null;
|
||||
}
|
||||
|
||||
public int Length => _length;
|
||||
|
||||
public TokenPattern Pattern => _pattern;
|
||||
|
||||
public void Update(int length, TokenPattern pattern)
|
||||
{
|
||||
if (this._length < length)
|
||||
{
|
||||
this._length = length;
|
||||
this._pattern = pattern;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
825
Parsing/TokenNFA.cs
Normal file
825
Parsing/TokenNFA.cs
Normal file
@@ -0,0 +1,825 @@
|
||||
namespace Flee.Parsing
|
||||
{
|
||||
/**
|
||||
* A non-deterministic finite state automaton (NFA) for matching
|
||||
* tokens. It supports both fixed strings and simple regular
|
||||
* expressions, but should perform similar to a DFA due to highly
|
||||
* optimized data structures and tuning. The memory footprint during
|
||||
* matching should be near zero, since no heap memory is allocated
|
||||
* unless the pre-allocated queues need to be enlarged. The NFA also
|
||||
* does not use recursion, but iterates in a loop instead.
|
||||
*/
|
||||
internal class TokenNFA
|
||||
{
|
||||
private readonly NFAState[] _initialChar = new NFAState[128];
|
||||
private readonly NFAState _initial = new NFAState();
|
||||
private readonly NFAStateQueue _queue = new NFAStateQueue();
|
||||
|
||||
public void AddTextMatch(string str, bool ignoreCase, TokenPattern value)
|
||||
{
|
||||
NFAState state;
|
||||
char ch = str[0];
|
||||
|
||||
if (ch < 128 && !ignoreCase)
|
||||
{
|
||||
state = _initialChar[ch];
|
||||
if (state == null)
|
||||
{
|
||||
state = _initialChar[ch] = new NFAState();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
state = _initial.AddOut(ch, ignoreCase, null);
|
||||
}
|
||||
for (int i = 1; i < str.Length; i++)
|
||||
{
|
||||
state = state.AddOut(str[i], ignoreCase, null);
|
||||
}
|
||||
state.Value = value;
|
||||
}
|
||||
|
||||
public void AddRegExpMatch(string pattern,
|
||||
bool ignoreCase,
|
||||
TokenPattern value)
|
||||
{
|
||||
TokenRegExpParser parser = new TokenRegExpParser(pattern, ignoreCase);
|
||||
string debug = "DFA regexp; " + parser.GetDebugInfo();
|
||||
|
||||
var isAscii = parser.Start.IsAsciiOutgoing();
|
||||
for (int i = 0; isAscii && i < 128; i++)
|
||||
{
|
||||
bool match = false;
|
||||
for (int j = 0; j < parser.Start.Outgoing.Length; j++)
|
||||
{
|
||||
if (parser.Start.Outgoing[j].Match((char)i))
|
||||
{
|
||||
if (match)
|
||||
{
|
||||
isAscii = false;
|
||||
break;
|
||||
}
|
||||
match = true;
|
||||
}
|
||||
}
|
||||
if (match && _initialChar[i] != null)
|
||||
{
|
||||
isAscii = false;
|
||||
}
|
||||
}
|
||||
if (parser.Start.Incoming.Length > 0)
|
||||
{
|
||||
_initial.AddOut(new NFAEpsilonTransition(parser.Start));
|
||||
debug += ", uses initial epsilon";
|
||||
}
|
||||
else if (isAscii && !ignoreCase)
|
||||
{
|
||||
for (int i = 0; isAscii && i < 128; i++)
|
||||
{
|
||||
for (int j = 0; j < parser.Start.Outgoing.Length; j++)
|
||||
{
|
||||
if (parser.Start.Outgoing[j].Match((char)i))
|
||||
{
|
||||
_initialChar[i] = parser.Start.Outgoing[j].State;
|
||||
}
|
||||
}
|
||||
}
|
||||
debug += ", uses ASCII lookup";
|
||||
}
|
||||
else
|
||||
{
|
||||
parser.Start.MergeInto(_initial);
|
||||
debug += ", uses initial state";
|
||||
}
|
||||
parser.End.Value = value;
|
||||
value.DebugInfo = debug;
|
||||
}
|
||||
|
||||
public int Match(ReaderBuffer buffer, TokenMatch match)
|
||||
{
|
||||
int length = 0;
|
||||
int pos = 1;
|
||||
NFAState state;
|
||||
|
||||
// The first step of the match loop has been unrolled and
|
||||
// optimized for performance below.
|
||||
this._queue.Clear();
|
||||
var peekChar = buffer.Peek(0);
|
||||
if (0 <= peekChar && peekChar < 128)
|
||||
{
|
||||
state = this._initialChar[peekChar];
|
||||
if (state != null)
|
||||
{
|
||||
this._queue.AddLast(state);
|
||||
}
|
||||
}
|
||||
if (peekChar >= 0)
|
||||
{
|
||||
this._initial.MatchTransitions((char)peekChar, this._queue, true);
|
||||
}
|
||||
this._queue.MarkEnd();
|
||||
peekChar = buffer.Peek(1);
|
||||
|
||||
// The remaining match loop processes all subsequent states
|
||||
while (!this._queue.Empty)
|
||||
{
|
||||
if (this._queue.Marked)
|
||||
{
|
||||
pos++;
|
||||
peekChar = buffer.Peek(pos);
|
||||
this._queue.MarkEnd();
|
||||
}
|
||||
state = this._queue.RemoveFirst();
|
||||
if (state.Value != null)
|
||||
{
|
||||
match.Update(pos, state.Value);
|
||||
}
|
||||
if (peekChar >= 0)
|
||||
{
|
||||
state.MatchTransitions((char)peekChar, this._queue, false);
|
||||
}
|
||||
}
|
||||
return length;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* An NFA state. The NFA consists of a series of states, each
|
||||
* having zero or more transitions to other states.
|
||||
*/
|
||||
internal class NFAState
|
||||
{
|
||||
internal TokenPattern Value = null;
|
||||
internal NFATransition[] Incoming = new NFATransition[0];
|
||||
internal NFATransition[] Outgoing = new NFATransition[0];
|
||||
internal bool EpsilonOut = false;
|
||||
|
||||
public bool HasTransitions()
|
||||
{
|
||||
return Incoming.Length > 0 || Outgoing.Length > 0;
|
||||
}
|
||||
public bool IsAsciiOutgoing()
|
||||
{
|
||||
for (int i = 0; i < Outgoing.Length; i++)
|
||||
{
|
||||
if (!Outgoing[i].IsAscii())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public void AddIn(NFATransition trans)
|
||||
{
|
||||
Array.Resize(ref Incoming, Incoming.Length + 1);
|
||||
Incoming[Incoming.Length - 1] = trans;
|
||||
}
|
||||
|
||||
public NFAState AddOut(char ch, bool ignoreCase, NFAState state)
|
||||
{
|
||||
if (ignoreCase)
|
||||
{
|
||||
if (state == null)
|
||||
{
|
||||
state = new NFAState();
|
||||
}
|
||||
AddOut(new NFACharTransition(Char.ToLower(ch), state));
|
||||
AddOut(new NFACharTransition(Char.ToUpper(ch), state));
|
||||
return state;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (state == null)
|
||||
{
|
||||
state = FindUniqueCharTransition(ch);
|
||||
if (state != null)
|
||||
{
|
||||
return state;
|
||||
}
|
||||
state = new NFAState();
|
||||
}
|
||||
return AddOut(new NFACharTransition(ch, state));
|
||||
}
|
||||
}
|
||||
|
||||
public NFAState AddOut(NFATransition trans)
|
||||
{
|
||||
Array.Resize(ref Outgoing, Outgoing.Length + 1);
|
||||
Outgoing[Outgoing.Length - 1] = trans;
|
||||
if (trans is NFAEpsilonTransition)
|
||||
{
|
||||
EpsilonOut = true;
|
||||
}
|
||||
return trans.State;
|
||||
}
|
||||
|
||||
public void MergeInto(NFAState state)
|
||||
{
|
||||
for (int i = 0; i < Incoming.Length; i++)
|
||||
{
|
||||
state.AddIn(Incoming[i]);
|
||||
Incoming[i].State = state;
|
||||
}
|
||||
Incoming = null;
|
||||
for (int i = 0; i < Outgoing.Length; i++)
|
||||
{
|
||||
state.AddOut(Outgoing[i]);
|
||||
}
|
||||
Outgoing = null;
|
||||
}
|
||||
|
||||
private NFAState FindUniqueCharTransition(char ch)
|
||||
{
|
||||
NFATransition res = null;
|
||||
NFATransition trans;
|
||||
|
||||
for (int i = 0; i < Outgoing.Length; i++)
|
||||
{
|
||||
trans = Outgoing[i];
|
||||
if (trans.Match(ch) && trans is NFACharTransition)
|
||||
{
|
||||
if (res != null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
res = trans;
|
||||
}
|
||||
}
|
||||
for (int i = 0; res != null && i < Outgoing.Length; i++)
|
||||
{
|
||||
trans = Outgoing[i];
|
||||
if (trans != res && trans.State == res.State)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return res?.State;
|
||||
}
|
||||
|
||||
public void MatchTransitions(char ch, NFAStateQueue queue, bool initial)
|
||||
{
|
||||
for (int i = 0; i < Outgoing.Length; i++)
|
||||
{
|
||||
var trans = Outgoing[i];
|
||||
var target = trans.State;
|
||||
if (initial && trans is NFAEpsilonTransition)
|
||||
{
|
||||
target.MatchTransitions(ch, queue, true);
|
||||
}
|
||||
else if (trans.Match(ch))
|
||||
{
|
||||
queue.AddLast(target);
|
||||
if (target.EpsilonOut)
|
||||
{
|
||||
target.MatchEmpty(queue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void MatchEmpty(NFAStateQueue queue)
|
||||
{
|
||||
for (int i = 0; i < Outgoing.Length; i++)
|
||||
{
|
||||
var trans = Outgoing[i];
|
||||
if (trans is NFAEpsilonTransition)
|
||||
{
|
||||
var target = trans.State;
|
||||
queue.AddLast(target);
|
||||
if (target.EpsilonOut)
|
||||
{
|
||||
target.MatchEmpty(queue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* An NFA state transition. A transition checks a single
|
||||
* character of input an determines if it is a match. If a match
|
||||
* is encountered, the NFA should move forward to the transition
|
||||
* state.
|
||||
*/
|
||||
internal abstract class NFATransition
|
||||
{
|
||||
|
||||
internal NFAState State;
|
||||
|
||||
protected NFATransition(NFAState state)
|
||||
{
|
||||
this.State = state;
|
||||
this.State.AddIn(this);
|
||||
}
|
||||
|
||||
public abstract bool IsAscii();
|
||||
|
||||
public abstract bool Match(char ch);
|
||||
|
||||
public abstract NFATransition Copy(NFAState state);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The special epsilon transition. This transition matches the
|
||||
* empty input, i.e. it is an automatic transition that doesn't
|
||||
* read any input. As such, it returns false in the match method
|
||||
* and is handled specially everywhere.
|
||||
*/
|
||||
internal class NFAEpsilonTransition : NFATransition
|
||||
{
|
||||
public NFAEpsilonTransition(NFAState state) : base(state)
|
||||
{
|
||||
}
|
||||
|
||||
public override bool IsAscii()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public override bool Match(char ch)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public override NFATransition Copy(NFAState state)
|
||||
{
|
||||
return new NFAEpsilonTransition(state);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* A single character match transition.
|
||||
*/
|
||||
internal class NFACharTransition : NFATransition
|
||||
{
|
||||
private readonly char _match;
|
||||
|
||||
public NFACharTransition(char match, NFAState state) : base(state)
|
||||
{
|
||||
_match = match;
|
||||
}
|
||||
|
||||
public override bool IsAscii()
|
||||
{
|
||||
return 0 <= _match && _match < 128;
|
||||
}
|
||||
|
||||
public override bool Match(char ch)
|
||||
{
|
||||
return this._match == ch;
|
||||
}
|
||||
|
||||
public override NFATransition Copy(NFAState state)
|
||||
{
|
||||
return new NFACharTransition(_match, state);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* A character range match transition. Used for user-defined
|
||||
* character sets in regular expressions.
|
||||
*/
|
||||
internal class NFACharRangeTransition : NFATransition
|
||||
{
|
||||
|
||||
protected bool Inverse;
|
||||
protected bool IgnoreCase;
|
||||
|
||||
private object[] _contents = new object[0];
|
||||
|
||||
public NFACharRangeTransition(bool inverse,
|
||||
bool ignoreCase,
|
||||
NFAState state) : base(state)
|
||||
{
|
||||
this.Inverse = inverse;
|
||||
this.IgnoreCase = ignoreCase;
|
||||
}
|
||||
|
||||
public override bool IsAscii()
|
||||
{
|
||||
if (Inverse)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
for (int i = 0; i < _contents.Length; i++)
|
||||
{
|
||||
var obj = _contents[i];
|
||||
if (obj is char)
|
||||
{
|
||||
var c = (char)obj;
|
||||
if (c < 0 || 128 <= c)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (obj is Range)
|
||||
{
|
||||
if (!((Range)obj).IsAscii())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public void AddCharacter(char c)
|
||||
{
|
||||
if (IgnoreCase)
|
||||
{
|
||||
c = Char.ToLower(c);
|
||||
}
|
||||
AddContent(c);
|
||||
}
|
||||
|
||||
public void AddRange(char min, char max)
|
||||
{
|
||||
if (IgnoreCase)
|
||||
{
|
||||
min = Char.ToLower(min);
|
||||
max = Char.ToLower(max);
|
||||
}
|
||||
AddContent(new Range(min, max));
|
||||
}
|
||||
|
||||
private void AddContent(Object obj)
|
||||
{
|
||||
Array.Resize(ref _contents, _contents.Length + 1);
|
||||
_contents[_contents.Length - 1] = obj;
|
||||
}
|
||||
|
||||
public override bool Match(char ch)
|
||||
{
|
||||
object obj;
|
||||
char c;
|
||||
Range r;
|
||||
|
||||
if (IgnoreCase)
|
||||
{
|
||||
ch = Char.ToLower(ch);
|
||||
}
|
||||
for (int i = 0; i < _contents.Length; i++)
|
||||
{
|
||||
obj = _contents[i];
|
||||
if (obj is char)
|
||||
{
|
||||
c = (char)obj;
|
||||
if (c == ch)
|
||||
{
|
||||
return !Inverse;
|
||||
}
|
||||
}
|
||||
else if (obj is Range)
|
||||
{
|
||||
r = (Range)obj;
|
||||
if (r.Inside(ch))
|
||||
{
|
||||
return !Inverse;
|
||||
}
|
||||
}
|
||||
}
|
||||
return Inverse;
|
||||
}
|
||||
|
||||
public override NFATransition Copy(NFAState state)
|
||||
{
|
||||
var copy = new NFACharRangeTransition(Inverse, IgnoreCase, state) { _contents = _contents };
|
||||
return copy;
|
||||
}
|
||||
|
||||
private class Range
|
||||
{
|
||||
private readonly char _min;
|
||||
private readonly char _max;
|
||||
|
||||
public Range(char min, char max)
|
||||
{
|
||||
this._min = min;
|
||||
this._max = max;
|
||||
}
|
||||
|
||||
public bool IsAscii()
|
||||
{
|
||||
return 0 <= _min && _min < 128 &&
|
||||
0 <= _max && _max < 128;
|
||||
}
|
||||
|
||||
public bool Inside(char c)
|
||||
{
|
||||
return _min <= c && c <= _max;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The dot ('.') character set transition. This transition
|
||||
* matches a single character that is not equal to a newline
|
||||
* character.
|
||||
*/
|
||||
internal class NFADotTransition : NFATransition
|
||||
{
|
||||
public NFADotTransition(NFAState state) : base(state)
|
||||
{
|
||||
}
|
||||
|
||||
public override bool IsAscii()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public override bool Match(char ch)
|
||||
{
|
||||
switch (ch)
|
||||
{
|
||||
case '\n':
|
||||
case '\r':
|
||||
case '\u0085':
|
||||
case '\u2028':
|
||||
case '\u2029':
|
||||
return false;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public override NFATransition Copy(NFAState state)
|
||||
{
|
||||
return new NFADotTransition(state);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The digit character set transition. This transition matches a
|
||||
* single numeric character.
|
||||
*/
|
||||
internal class NFADigitTransition : NFATransition
|
||||
{
|
||||
public NFADigitTransition(NFAState state) : base(state)
|
||||
{
|
||||
}
|
||||
|
||||
public override bool IsAscii()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public override bool Match(char ch)
|
||||
{
|
||||
return '0' <= ch && ch <= '9';
|
||||
}
|
||||
|
||||
public override NFATransition Copy(NFAState state)
|
||||
{
|
||||
return new NFADigitTransition(state);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The non-digit character set transition. This transition
|
||||
* matches a single non-numeric character.
|
||||
*/
|
||||
internal class NFANonDigitTransition : NFATransition
|
||||
{
|
||||
public NFANonDigitTransition(NFAState state) : base(state)
|
||||
{
|
||||
}
|
||||
|
||||
public override bool IsAscii()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public override bool Match(char ch)
|
||||
{
|
||||
return ch < '0' || '9' < ch;
|
||||
}
|
||||
|
||||
public override NFATransition Copy(NFAState state)
|
||||
{
|
||||
return new NFANonDigitTransition(state);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The whitespace character set transition. This transition
|
||||
* matches a single whitespace character.
|
||||
*/
|
||||
internal class NFAWhitespaceTransition : NFATransition
|
||||
{
|
||||
public NFAWhitespaceTransition(NFAState state) : base(state)
|
||||
{
|
||||
}
|
||||
|
||||
public override bool IsAscii()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public override bool Match(char ch)
|
||||
{
|
||||
switch (ch)
|
||||
{
|
||||
case ' ':
|
||||
case '\t':
|
||||
case '\n':
|
||||
case '\f':
|
||||
case '\r':
|
||||
case (char)11:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public override NFATransition Copy(NFAState state)
|
||||
{
|
||||
return new NFAWhitespaceTransition(state);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The non-whitespace character set transition. This transition
|
||||
* matches a single non-whitespace character.
|
||||
*/
|
||||
internal class NFANonWhitespaceTransition : NFATransition
|
||||
{
|
||||
|
||||
public NFANonWhitespaceTransition(NFAState state) : base(state)
|
||||
{
|
||||
}
|
||||
|
||||
public override bool IsAscii()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public override bool Match(char ch)
|
||||
{
|
||||
switch (ch)
|
||||
{
|
||||
case ' ':
|
||||
case '\t':
|
||||
case '\n':
|
||||
case '\f':
|
||||
case '\r':
|
||||
case (char)11:
|
||||
return false;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public override NFATransition Copy(NFAState state)
|
||||
{
|
||||
return new NFANonWhitespaceTransition(state);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The word character set transition. This transition matches a
|
||||
* single word character.
|
||||
*/
|
||||
internal class NFAWordTransition : NFATransition
|
||||
{
|
||||
|
||||
public NFAWordTransition(NFAState state) : base(state)
|
||||
{
|
||||
}
|
||||
|
||||
public override bool IsAscii()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public override bool Match(char ch)
|
||||
{
|
||||
return ('a' <= ch && ch <= 'z')
|
||||
|| ('A' <= ch && ch <= 'Z')
|
||||
|| ('0' <= ch && ch <= '9')
|
||||
|| ch == '_';
|
||||
}
|
||||
|
||||
public override NFATransition Copy(NFAState state)
|
||||
{
|
||||
return new NFAWordTransition(state);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The non-word character set transition. This transition matches
|
||||
* a single non-word character.
|
||||
*/
|
||||
internal class NFANonWordTransition : NFATransition
|
||||
{
|
||||
public NFANonWordTransition(NFAState state) : base(state)
|
||||
{
|
||||
}
|
||||
|
||||
public override bool IsAscii()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public override bool Match(char ch)
|
||||
{
|
||||
bool word = ('a' <= ch && ch <= 'z')
|
||||
|| ('A' <= ch && ch <= 'Z')
|
||||
|| ('0' <= ch && ch <= '9')
|
||||
|| ch == '_';
|
||||
return !word;
|
||||
}
|
||||
|
||||
public override NFATransition Copy(NFAState state)
|
||||
{
|
||||
return new NFANonWordTransition(state);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* An NFA state queue. This queue is used during processing to
|
||||
* keep track of the current and subsequent NFA states. The
|
||||
* current state is read from the beginning of the queue, and new
|
||||
* states are added at the end. A marker index is used to
|
||||
* separate the current from the subsequent states.<p>
|
||||
*
|
||||
* The queue implementation is optimized for quick removal at the
|
||||
* beginning and addition at the end. It will attempt to use a
|
||||
* fixed-size array to store the whole queue, and moves the data
|
||||
* in this array only when absolutely needed. The array is also
|
||||
* enlarged automatically if too many states are being processed
|
||||
* at a single time.
|
||||
*/
|
||||
internal class NFAStateQueue
|
||||
{
|
||||
|
||||
private NFAState[] _queue = new NFAState[2048];
|
||||
|
||||
private int _first = 0;
|
||||
|
||||
private int _last = 0;
|
||||
|
||||
private int _mark = 0;
|
||||
|
||||
public bool Empty => (_last <= _first);
|
||||
|
||||
public bool Marked => _first == _mark;
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
_first = 0;
|
||||
_last = 0;
|
||||
_mark = 0;
|
||||
}
|
||||
|
||||
public void MarkEnd()
|
||||
{
|
||||
_mark = _last;
|
||||
}
|
||||
|
||||
public NFAState RemoveFirst()
|
||||
{
|
||||
if (_first < _last)
|
||||
{
|
||||
_first++;
|
||||
return _queue[_first - 1];
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public void AddLast(NFAState state)
|
||||
{
|
||||
if (_last >= _queue.Length)
|
||||
{
|
||||
if (_first <= 0)
|
||||
{
|
||||
Array.Resize(ref _queue, _queue.Length * 2);
|
||||
}
|
||||
else
|
||||
{
|
||||
Array.Copy(_queue, _first, _queue, 0, _last - _first);
|
||||
_last -= _first;
|
||||
_mark -= _first;
|
||||
_first = 0;
|
||||
}
|
||||
}
|
||||
_queue[_last++] = state;
|
||||
}
|
||||
}
|
||||
}
|
||||
303
Parsing/TokenPattern.cs
Normal file
303
Parsing/TokenPattern.cs
Normal file
@@ -0,0 +1,303 @@
|
||||
using System.Text;
|
||||
|
||||
namespace Flee.Parsing
|
||||
{
|
||||
/**
|
||||
* A token pattern. This class contains the definition of a token
|
||||
* (i.e. it's pattern), and allows testing a string against this
|
||||
* pattern. A token pattern is uniquely identified by an integer id,
|
||||
* that must be provided upon creation.
|
||||
*
|
||||
|
||||
*/
|
||||
internal class TokenPattern
|
||||
{
|
||||
public enum PatternType
|
||||
{
|
||||
|
||||
/**
|
||||
* The string pattern type is used for tokens that only
|
||||
* match an exact string.
|
||||
*/
|
||||
STRING,
|
||||
|
||||
/**
|
||||
* The regular expression pattern type is used for tokens
|
||||
* that match a regular expression.
|
||||
*/
|
||||
REGEXP
|
||||
}
|
||||
|
||||
private int _id;
|
||||
private string _name;
|
||||
private PatternType _type;
|
||||
private string _pattern;
|
||||
private bool _error;
|
||||
private string _errorMessage;
|
||||
private bool _ignore;
|
||||
private string _ignoreMessage;
|
||||
private string _debugInfo;
|
||||
|
||||
public TokenPattern(int id,
|
||||
string name,
|
||||
PatternType type,
|
||||
string pattern)
|
||||
{
|
||||
|
||||
this._id = id;
|
||||
this._name = name;
|
||||
this._type = type;
|
||||
this._pattern = pattern;
|
||||
}
|
||||
|
||||
public int Id
|
||||
{
|
||||
get
|
||||
{
|
||||
return _id;
|
||||
}
|
||||
set { _id = value; }
|
||||
}
|
||||
|
||||
public int GetId()
|
||||
{
|
||||
return _id;
|
||||
}
|
||||
|
||||
public string Name
|
||||
{
|
||||
get
|
||||
{
|
||||
return _name;
|
||||
}
|
||||
set { _name = value; }
|
||||
}
|
||||
|
||||
public string GetName()
|
||||
{
|
||||
return _name;
|
||||
}
|
||||
|
||||
public PatternType Type
|
||||
{
|
||||
get
|
||||
{
|
||||
return _type;
|
||||
}
|
||||
set { _type = value; }
|
||||
}
|
||||
|
||||
public PatternType GetPatternType()
|
||||
{
|
||||
return _type;
|
||||
}
|
||||
|
||||
public string Pattern
|
||||
{
|
||||
get
|
||||
{
|
||||
return _pattern;
|
||||
}
|
||||
set { _pattern = value; }
|
||||
}
|
||||
|
||||
public string GetPattern()
|
||||
{
|
||||
return _pattern;
|
||||
}
|
||||
|
||||
public bool Error
|
||||
{
|
||||
get
|
||||
{
|
||||
return _error;
|
||||
}
|
||||
set
|
||||
{
|
||||
_error = value;
|
||||
if (_error && _errorMessage == null)
|
||||
{
|
||||
_errorMessage = "unrecognized token found";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string ErrorMessage
|
||||
{
|
||||
get
|
||||
{
|
||||
return _errorMessage;
|
||||
}
|
||||
set
|
||||
{
|
||||
_error = true;
|
||||
_errorMessage = value;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsError()
|
||||
{
|
||||
return Error;
|
||||
}
|
||||
|
||||
public string GetErrorMessage()
|
||||
{
|
||||
return ErrorMessage;
|
||||
}
|
||||
|
||||
public void SetError()
|
||||
{
|
||||
Error = true;
|
||||
}
|
||||
|
||||
public void SetError(string message)
|
||||
{
|
||||
ErrorMessage = message;
|
||||
}
|
||||
|
||||
public bool Ignore
|
||||
{
|
||||
get
|
||||
{
|
||||
return _ignore;
|
||||
}
|
||||
set
|
||||
{
|
||||
_ignore = value;
|
||||
}
|
||||
}
|
||||
|
||||
public string IgnoreMessage
|
||||
{
|
||||
get
|
||||
{
|
||||
return _ignoreMessage;
|
||||
}
|
||||
set
|
||||
{
|
||||
_ignore = true;
|
||||
_ignoreMessage = value;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsIgnore()
|
||||
{
|
||||
return Ignore;
|
||||
}
|
||||
|
||||
public string GetIgnoreMessage()
|
||||
{
|
||||
return IgnoreMessage;
|
||||
}
|
||||
|
||||
|
||||
public void SetIgnore()
|
||||
{
|
||||
Ignore = true;
|
||||
}
|
||||
|
||||
|
||||
public void SetIgnore(string message)
|
||||
{
|
||||
IgnoreMessage = message;
|
||||
}
|
||||
|
||||
public string DebugInfo
|
||||
{
|
||||
get
|
||||
{
|
||||
return _debugInfo;
|
||||
}
|
||||
set
|
||||
{
|
||||
_debugInfo = value;
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
StringBuilder buffer = new StringBuilder();
|
||||
|
||||
buffer.Append(_name);
|
||||
buffer.Append(" (");
|
||||
buffer.Append(_id);
|
||||
buffer.Append("): ");
|
||||
switch (_type)
|
||||
{
|
||||
case PatternType.STRING:
|
||||
buffer.Append("\"");
|
||||
buffer.Append(_pattern);
|
||||
buffer.Append("\"");
|
||||
break;
|
||||
case PatternType.REGEXP:
|
||||
buffer.Append("<<");
|
||||
buffer.Append(_pattern);
|
||||
buffer.Append(">>");
|
||||
break;
|
||||
}
|
||||
if (_error)
|
||||
{
|
||||
buffer.Append(" ERROR: \"");
|
||||
buffer.Append(_errorMessage);
|
||||
buffer.Append("\"");
|
||||
}
|
||||
if (_ignore)
|
||||
{
|
||||
buffer.Append(" IGNORE");
|
||||
if (_ignoreMessage != null)
|
||||
{
|
||||
buffer.Append(": \"");
|
||||
buffer.Append(_ignoreMessage);
|
||||
buffer.Append("\"");
|
||||
}
|
||||
}
|
||||
if (_debugInfo != null)
|
||||
{
|
||||
buffer.Append("\n ");
|
||||
buffer.Append(_debugInfo);
|
||||
}
|
||||
return buffer.ToString();
|
||||
}
|
||||
|
||||
public string ToShortString()
|
||||
{
|
||||
StringBuilder buffer = new StringBuilder();
|
||||
int newline = _pattern.IndexOf('\n');
|
||||
|
||||
if (_type == PatternType.STRING)
|
||||
{
|
||||
buffer.Append("\"");
|
||||
if (newline >= 0)
|
||||
{
|
||||
if (newline > 0 && _pattern[newline - 1] == '\r')
|
||||
{
|
||||
newline--;
|
||||
}
|
||||
buffer.Append(_pattern.Substring(0, newline));
|
||||
buffer.Append("(...)");
|
||||
}
|
||||
else
|
||||
{
|
||||
buffer.Append(_pattern);
|
||||
}
|
||||
buffer.Append("\"");
|
||||
}
|
||||
else
|
||||
{
|
||||
buffer.Append("<");
|
||||
buffer.Append(_name);
|
||||
buffer.Append(">");
|
||||
}
|
||||
|
||||
return buffer.ToString();
|
||||
}
|
||||
|
||||
public void SetData(int id, string name, PatternType type, string pattern)
|
||||
{
|
||||
Id = id;
|
||||
Name = name;
|
||||
Type = type;
|
||||
Pattern = pattern;
|
||||
}
|
||||
}
|
||||
}
|
||||
545
Parsing/TokenRegExpParser.cs
Normal file
545
Parsing/TokenRegExpParser.cs
Normal file
@@ -0,0 +1,545 @@
|
||||
using System.Collections;
|
||||
using System.Globalization;
|
||||
using System.Text;
|
||||
|
||||
namespace Flee.Parsing
|
||||
{
|
||||
/**
|
||||
* A regular expression parser. The parser creates an NFA for the
|
||||
* regular expression having a single start and acceptance states.
|
||||
*/
|
||||
internal class TokenRegExpParser
|
||||
{
|
||||
private readonly string _pattern;
|
||||
private readonly bool _ignoreCase;
|
||||
private int _pos;
|
||||
internal NFAState Start = new NFAState();
|
||||
internal NFAState End;
|
||||
private int _stateCount;
|
||||
private int _transitionCount;
|
||||
private int _epsilonCount;
|
||||
|
||||
public TokenRegExpParser(string pattern) : this(pattern, false)
|
||||
{
|
||||
}
|
||||
|
||||
public TokenRegExpParser(string pattern, bool ignoreCase)
|
||||
{
|
||||
this._pattern = pattern;
|
||||
this._ignoreCase = ignoreCase;
|
||||
this._pos = 0;
|
||||
this.End = ParseExpr(Start);
|
||||
if (_pos < pattern.Length)
|
||||
{
|
||||
throw new RegExpException(
|
||||
RegExpException.ErrorType.UNEXPECTED_CHARACTER,
|
||||
_pos,
|
||||
pattern);
|
||||
}
|
||||
}
|
||||
|
||||
public string GetDebugInfo()
|
||||
{
|
||||
if (_stateCount == 0)
|
||||
{
|
||||
UpdateStats(Start, new Hashtable());
|
||||
}
|
||||
return _stateCount + " states, " +
|
||||
_transitionCount + " transitions, " +
|
||||
_epsilonCount + " epsilons";
|
||||
}
|
||||
|
||||
private void UpdateStats(NFAState state, Hashtable visited)
|
||||
{
|
||||
if (!visited.ContainsKey(state))
|
||||
{
|
||||
visited.Add(state, state);
|
||||
_stateCount++;
|
||||
for (int i = 0; i < state.Outgoing.Length; i++)
|
||||
{
|
||||
_transitionCount++;
|
||||
if (state.Outgoing[i] is NFAEpsilonTransition)
|
||||
{
|
||||
_epsilonCount++;
|
||||
}
|
||||
UpdateStats(state.Outgoing[i].State, visited);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private NFAState ParseExpr(NFAState start)
|
||||
{
|
||||
NFAState end = new NFAState();
|
||||
do
|
||||
{
|
||||
if (PeekChar(0) == '|')
|
||||
{
|
||||
ReadChar('|');
|
||||
}
|
||||
var subStart = new NFAState();
|
||||
var subEnd = ParseTerm(subStart);
|
||||
if (subStart.Incoming.Length == 0)
|
||||
{
|
||||
subStart.MergeInto(start);
|
||||
}
|
||||
else
|
||||
{
|
||||
start.AddOut(new NFAEpsilonTransition(subStart));
|
||||
}
|
||||
if (subEnd.Outgoing.Length == 0 ||
|
||||
(!end.HasTransitions() && PeekChar(0) != '|'))
|
||||
{
|
||||
subEnd.MergeInto(end);
|
||||
}
|
||||
else
|
||||
{
|
||||
subEnd.AddOut(new NFAEpsilonTransition(end));
|
||||
}
|
||||
} while (PeekChar(0) == '|');
|
||||
return end;
|
||||
}
|
||||
|
||||
private NFAState ParseTerm(NFAState start)
|
||||
{
|
||||
var end = ParseFact(start);
|
||||
while (true)
|
||||
{
|
||||
switch (PeekChar(0))
|
||||
{
|
||||
case -1:
|
||||
case ')':
|
||||
case ']':
|
||||
case '{':
|
||||
case '}':
|
||||
case '?':
|
||||
case '+':
|
||||
case '|':
|
||||
return end;
|
||||
default:
|
||||
end = ParseFact(end);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private NFAState ParseFact(NFAState start)
|
||||
{
|
||||
NFAState placeholder = new NFAState();
|
||||
|
||||
var end = ParseAtom(placeholder);
|
||||
switch (PeekChar(0))
|
||||
{
|
||||
case '?':
|
||||
case '*':
|
||||
case '+':
|
||||
case '{':
|
||||
end = ParseAtomModifier(placeholder, end);
|
||||
break;
|
||||
}
|
||||
if (placeholder.Incoming.Length > 0 && start.Outgoing.Length > 0)
|
||||
{
|
||||
start.AddOut(new NFAEpsilonTransition(placeholder));
|
||||
return end;
|
||||
}
|
||||
else
|
||||
{
|
||||
placeholder.MergeInto(start);
|
||||
return (end == placeholder) ? start : end;
|
||||
}
|
||||
}
|
||||
|
||||
private NFAState ParseAtom(NFAState start)
|
||||
{
|
||||
NFAState end;
|
||||
|
||||
switch (PeekChar(0))
|
||||
{
|
||||
case '.':
|
||||
ReadChar('.');
|
||||
return start.AddOut(new NFADotTransition(new NFAState()));
|
||||
case '(':
|
||||
ReadChar('(');
|
||||
end = ParseExpr(start);
|
||||
ReadChar(')');
|
||||
return end;
|
||||
case '[':
|
||||
ReadChar('[');
|
||||
end = ParseCharSet(start);
|
||||
ReadChar(']');
|
||||
return end;
|
||||
case -1:
|
||||
case ')':
|
||||
case ']':
|
||||
case '{':
|
||||
case '}':
|
||||
case '?':
|
||||
case '*':
|
||||
case '+':
|
||||
case '|':
|
||||
throw new RegExpException(
|
||||
RegExpException.ErrorType.UNEXPECTED_CHARACTER,
|
||||
_pos,
|
||||
_pattern);
|
||||
default:
|
||||
return ParseChar(start);
|
||||
}
|
||||
}
|
||||
|
||||
private NFAState ParseAtomModifier(NFAState start, NFAState end)
|
||||
{
|
||||
int min = 0;
|
||||
int max = -1;
|
||||
int firstPos = _pos;
|
||||
|
||||
// Read min and max
|
||||
switch (ReadChar())
|
||||
{
|
||||
case '?':
|
||||
min = 0;
|
||||
max = 1;
|
||||
break;
|
||||
case '*':
|
||||
min = 0;
|
||||
max = -1;
|
||||
break;
|
||||
case '+':
|
||||
min = 1;
|
||||
max = -1;
|
||||
break;
|
||||
case '{':
|
||||
min = ReadNumber();
|
||||
max = min;
|
||||
if (PeekChar(0) == ',')
|
||||
{
|
||||
ReadChar(',');
|
||||
max = -1;
|
||||
if (PeekChar(0) != '}')
|
||||
{
|
||||
max = ReadNumber();
|
||||
}
|
||||
}
|
||||
ReadChar('}');
|
||||
if (max == 0 || (max > 0 && min > max))
|
||||
{
|
||||
throw new RegExpException(
|
||||
RegExpException.ErrorType.INVALID_REPEAT_COUNT,
|
||||
firstPos,
|
||||
_pattern);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new RegExpException(
|
||||
RegExpException.ErrorType.UNEXPECTED_CHARACTER,
|
||||
_pos - 1,
|
||||
_pattern);
|
||||
}
|
||||
|
||||
// Read possessive or reluctant modifiers
|
||||
if (PeekChar(0) == '?')
|
||||
{
|
||||
throw new RegExpException(
|
||||
RegExpException.ErrorType.UNSUPPORTED_SPECIAL_CHARACTER,
|
||||
_pos,
|
||||
_pattern);
|
||||
}
|
||||
else if (PeekChar(0) == '+')
|
||||
{
|
||||
throw new RegExpException(
|
||||
RegExpException.ErrorType.UNSUPPORTED_SPECIAL_CHARACTER,
|
||||
_pos,
|
||||
_pattern);
|
||||
}
|
||||
|
||||
// Handle supported repeaters
|
||||
if (min == 0 && max == 1)
|
||||
{
|
||||
return start.AddOut(new NFAEpsilonTransition(end));
|
||||
}
|
||||
else if (min == 0 && max == -1)
|
||||
{
|
||||
if (end.Outgoing.Length == 0)
|
||||
{
|
||||
end.MergeInto(start);
|
||||
}
|
||||
else
|
||||
{
|
||||
end.AddOut(new NFAEpsilonTransition(start));
|
||||
}
|
||||
return start;
|
||||
}
|
||||
else if (min == 1 && max == -1)
|
||||
{
|
||||
if (start.Outgoing.Length == 1 &&
|
||||
end.Outgoing.Length == 0 &&
|
||||
end.Incoming.Length == 1 &&
|
||||
start.Outgoing[0] == end.Incoming[0])
|
||||
{
|
||||
|
||||
end.AddOut(start.Outgoing[0].Copy(end));
|
||||
}
|
||||
else
|
||||
{
|
||||
end.AddOut(new NFAEpsilonTransition(start));
|
||||
}
|
||||
return end;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new RegExpException(
|
||||
RegExpException.ErrorType.INVALID_REPEAT_COUNT,
|
||||
firstPos,
|
||||
_pattern);
|
||||
}
|
||||
}
|
||||
|
||||
private NFAState ParseCharSet(NFAState start)
|
||||
{
|
||||
NFAState end = new NFAState();
|
||||
NFACharRangeTransition range;
|
||||
|
||||
if (PeekChar(0) == '^')
|
||||
{
|
||||
ReadChar('^');
|
||||
range = new NFACharRangeTransition(true, _ignoreCase, end);
|
||||
}
|
||||
else
|
||||
{
|
||||
range = new NFACharRangeTransition(false, _ignoreCase, end);
|
||||
}
|
||||
start.AddOut(range);
|
||||
while (PeekChar(0) > 0)
|
||||
{
|
||||
var min = (char)PeekChar(0);
|
||||
switch (min)
|
||||
{
|
||||
case ']':
|
||||
return end;
|
||||
case '\\':
|
||||
range.AddCharacter(ReadEscapeChar());
|
||||
break;
|
||||
default:
|
||||
ReadChar(min);
|
||||
if (PeekChar(0) == '-' &&
|
||||
PeekChar(1) > 0 &&
|
||||
PeekChar(1) != ']')
|
||||
{
|
||||
|
||||
ReadChar('-');
|
||||
var max = ReadChar();
|
||||
range.AddRange(min, max);
|
||||
}
|
||||
else
|
||||
{
|
||||
range.AddCharacter(min);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return end;
|
||||
}
|
||||
|
||||
private NFAState ParseChar(NFAState start)
|
||||
{
|
||||
switch (PeekChar(0))
|
||||
{
|
||||
case '\\':
|
||||
return ParseEscapeChar(start);
|
||||
case '^':
|
||||
case '$':
|
||||
throw new RegExpException(
|
||||
RegExpException.ErrorType.UNSUPPORTED_SPECIAL_CHARACTER,
|
||||
_pos,
|
||||
_pattern);
|
||||
default:
|
||||
return start.AddOut(ReadChar(), _ignoreCase, new NFAState());
|
||||
}
|
||||
}
|
||||
|
||||
private NFAState ParseEscapeChar(NFAState start)
|
||||
{
|
||||
NFAState end = new NFAState();
|
||||
|
||||
if (PeekChar(0) == '\\' && PeekChar(1) > 0)
|
||||
{
|
||||
switch ((char)PeekChar(1))
|
||||
{
|
||||
case 'd':
|
||||
ReadChar();
|
||||
ReadChar();
|
||||
return start.AddOut(new NFADigitTransition(end));
|
||||
case 'D':
|
||||
ReadChar();
|
||||
ReadChar();
|
||||
return start.AddOut(new NFANonDigitTransition(end));
|
||||
case 's':
|
||||
ReadChar();
|
||||
ReadChar();
|
||||
return start.AddOut(new NFAWhitespaceTransition(end));
|
||||
case 'S':
|
||||
ReadChar();
|
||||
ReadChar();
|
||||
return start.AddOut(new NFANonWhitespaceTransition(end));
|
||||
case 'w':
|
||||
ReadChar();
|
||||
ReadChar();
|
||||
return start.AddOut(new NFAWordTransition(end));
|
||||
case 'W':
|
||||
ReadChar();
|
||||
ReadChar();
|
||||
return start.AddOut(new NFANonWordTransition(end));
|
||||
}
|
||||
}
|
||||
return start.AddOut(ReadEscapeChar(), _ignoreCase, end);
|
||||
}
|
||||
|
||||
private char ReadEscapeChar()
|
||||
{
|
||||
string str;
|
||||
int value;
|
||||
|
||||
ReadChar('\\');
|
||||
var c = ReadChar();
|
||||
switch (c)
|
||||
{
|
||||
case '0':
|
||||
c = ReadChar();
|
||||
if (c < '0' || c > '3')
|
||||
{
|
||||
throw new RegExpException(
|
||||
RegExpException.ErrorType.UNSUPPORTED_ESCAPE_CHARACTER,
|
||||
_pos - 3,
|
||||
_pattern);
|
||||
}
|
||||
value = c - '0';
|
||||
c = (char)PeekChar(0);
|
||||
if ('0' <= c && c <= '7')
|
||||
{
|
||||
value *= 8;
|
||||
value += ReadChar() - '0';
|
||||
c = (char)PeekChar(0);
|
||||
if ('0' <= c && c <= '7')
|
||||
{
|
||||
value *= 8;
|
||||
value += ReadChar() - '0';
|
||||
}
|
||||
}
|
||||
return (char)value;
|
||||
case 'x':
|
||||
str = ReadChar().ToString() + ReadChar().ToString();
|
||||
try
|
||||
{
|
||||
value = Int32.Parse(str, NumberStyles.AllowHexSpecifier);
|
||||
return (char)value;
|
||||
}
|
||||
catch (FormatException)
|
||||
{
|
||||
throw new RegExpException(
|
||||
RegExpException.ErrorType.UNSUPPORTED_ESCAPE_CHARACTER,
|
||||
_pos - str.Length - 2,
|
||||
_pattern);
|
||||
}
|
||||
case 'u':
|
||||
str = ReadChar().ToString() +
|
||||
ReadChar().ToString() +
|
||||
ReadChar().ToString() +
|
||||
ReadChar().ToString();
|
||||
try
|
||||
{
|
||||
value = Int32.Parse(str, NumberStyles.AllowHexSpecifier);
|
||||
return (char)value;
|
||||
}
|
||||
catch (FormatException)
|
||||
{
|
||||
throw new RegExpException(
|
||||
RegExpException.ErrorType.UNSUPPORTED_ESCAPE_CHARACTER,
|
||||
_pos - str.Length - 2,
|
||||
_pattern);
|
||||
}
|
||||
case 't':
|
||||
return '\t';
|
||||
case 'n':
|
||||
return '\n';
|
||||
case 'r':
|
||||
return '\r';
|
||||
case 'f':
|
||||
return '\f';
|
||||
case 'a':
|
||||
return '\u0007';
|
||||
case 'e':
|
||||
return '\u001B';
|
||||
default:
|
||||
if (('A' <= c && c <= 'Z') || ('a' <= c && c <= 'z'))
|
||||
{
|
||||
throw new RegExpException(
|
||||
RegExpException.ErrorType.UNSUPPORTED_ESCAPE_CHARACTER,
|
||||
_pos - 2,
|
||||
_pattern);
|
||||
}
|
||||
return c;
|
||||
}
|
||||
}
|
||||
|
||||
private int ReadNumber()
|
||||
{
|
||||
StringBuilder buf = new StringBuilder();
|
||||
int c;
|
||||
|
||||
c = PeekChar(0);
|
||||
while ('0' <= c && c <= '9')
|
||||
{
|
||||
buf.Append(ReadChar());
|
||||
c = PeekChar(0);
|
||||
}
|
||||
if (buf.Length <= 0)
|
||||
{
|
||||
throw new RegExpException(
|
||||
RegExpException.ErrorType.UNEXPECTED_CHARACTER,
|
||||
_pos,
|
||||
_pattern);
|
||||
}
|
||||
return Int32.Parse(buf.ToString());
|
||||
}
|
||||
|
||||
private char ReadChar()
|
||||
{
|
||||
int c = PeekChar(0);
|
||||
|
||||
if (c < 0)
|
||||
{
|
||||
throw new RegExpException(
|
||||
RegExpException.ErrorType.UNTERMINATED_PATTERN,
|
||||
_pos,
|
||||
_pattern);
|
||||
}
|
||||
else
|
||||
{
|
||||
_pos++;
|
||||
return (char)c;
|
||||
}
|
||||
}
|
||||
|
||||
private char ReadChar(char c)
|
||||
{
|
||||
if (c != ReadChar())
|
||||
{
|
||||
throw new RegExpException(
|
||||
RegExpException.ErrorType.UNEXPECTED_CHARACTER,
|
||||
_pos - 1,
|
||||
_pattern);
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
private int PeekChar(int count)
|
||||
{
|
||||
if (_pos + count < _pattern.Length)
|
||||
{
|
||||
return _pattern[_pos + count];
|
||||
}
|
||||
else
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
213
Parsing/TokenStringDFA.cs
Normal file
213
Parsing/TokenStringDFA.cs
Normal file
@@ -0,0 +1,213 @@
|
||||
using System.Text;
|
||||
|
||||
namespace Flee.Parsing
|
||||
{
|
||||
/**
|
||||
* A deterministic finite state automaton for matching exact strings.
|
||||
* It uses a sorted binary tree representation of the state
|
||||
* transitions in order to enable quick matches with a minimal memory
|
||||
* footprint. It only supports a single character transition between
|
||||
* states, but may be run in an all case-insensitive mode.
|
||||
*/
|
||||
internal class TokenStringDFA
|
||||
{
|
||||
|
||||
private readonly DFAState[] _ascii = new DFAState[128];
|
||||
private readonly DFAState _nonAscii = new DFAState();
|
||||
|
||||
public TokenStringDFA()
|
||||
{
|
||||
}
|
||||
|
||||
public void AddMatch(string str, bool caseInsensitive, TokenPattern value)
|
||||
{
|
||||
DFAState state;
|
||||
char c = str[0];
|
||||
int start = 0;
|
||||
|
||||
if (caseInsensitive)
|
||||
{
|
||||
c = Char.ToLower(c);
|
||||
}
|
||||
if (c < 128)
|
||||
{
|
||||
state = _ascii[c];
|
||||
if (state == null)
|
||||
{
|
||||
state = _ascii[c] = new DFAState();
|
||||
}
|
||||
start++;
|
||||
}
|
||||
else
|
||||
{
|
||||
state = _nonAscii;
|
||||
}
|
||||
for (int i = start; i < str.Length; i++)
|
||||
{
|
||||
var next = state.Tree.Find(str[i], caseInsensitive);
|
||||
if (next == null)
|
||||
{
|
||||
next = new DFAState();
|
||||
state.Tree.Add(str[i], caseInsensitive, next);
|
||||
}
|
||||
state = next;
|
||||
}
|
||||
state.Value = value;
|
||||
}
|
||||
|
||||
public TokenPattern Match(ReaderBuffer buffer, bool caseInsensitive)
|
||||
{
|
||||
TokenPattern result = null;
|
||||
DFAState state;
|
||||
int pos = 0;
|
||||
|
||||
var c = buffer.Peek(0);
|
||||
if (c < 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
if (caseInsensitive)
|
||||
{
|
||||
c = Char.ToLower((char)c);
|
||||
}
|
||||
if (c < 128)
|
||||
{
|
||||
state = _ascii[c];
|
||||
if (state == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
else if (state.Value != null)
|
||||
{
|
||||
result = state.Value;
|
||||
}
|
||||
pos++;
|
||||
}
|
||||
else
|
||||
{
|
||||
state = _nonAscii;
|
||||
}
|
||||
while ((c = buffer.Peek(pos)) >= 0)
|
||||
{
|
||||
state = state.Tree.Find((char)c, caseInsensitive);
|
||||
if (state == null)
|
||||
{
|
||||
break;
|
||||
}
|
||||
else if (state.Value != null)
|
||||
{
|
||||
result = state.Value;
|
||||
}
|
||||
pos++;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
StringBuilder buffer = new StringBuilder();
|
||||
|
||||
for (int i = 0; i < _ascii.Length; i++)
|
||||
{
|
||||
if (_ascii[i] != null)
|
||||
{
|
||||
buffer.Append((char)i);
|
||||
if (_ascii[i].Value != null)
|
||||
{
|
||||
buffer.Append(": ");
|
||||
buffer.Append(_ascii[i].Value);
|
||||
buffer.Append("\n");
|
||||
}
|
||||
_ascii[i].Tree.PrintTo(buffer, " ");
|
||||
}
|
||||
}
|
||||
_nonAscii.Tree.PrintTo(buffer, "");
|
||||
return buffer.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
internal class DFAState
|
||||
{
|
||||
|
||||
internal TokenPattern Value;
|
||||
|
||||
internal TransitionTree Tree = new TransitionTree();
|
||||
}
|
||||
|
||||
|
||||
internal class TransitionTree
|
||||
{
|
||||
private char _value = '\0';
|
||||
private DFAState _state;
|
||||
private TransitionTree _left;
|
||||
private TransitionTree _right;
|
||||
|
||||
public TransitionTree()
|
||||
{
|
||||
}
|
||||
|
||||
public DFAState Find(char c, bool lowerCase)
|
||||
{
|
||||
if (lowerCase)
|
||||
{
|
||||
c = Char.ToLower(c);
|
||||
}
|
||||
if (_value == '\0' || _value == c)
|
||||
{
|
||||
return _state;
|
||||
}
|
||||
else if (_value > c)
|
||||
{
|
||||
return _left.Find(c, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
return _right.Find(c, false);
|
||||
}
|
||||
}
|
||||
|
||||
public void Add(char c, bool lowerCase, DFAState state)
|
||||
{
|
||||
if (lowerCase)
|
||||
{
|
||||
c = Char.ToLower(c);
|
||||
}
|
||||
if (_value == '\0')
|
||||
{
|
||||
this._value = c;
|
||||
this._state = state;
|
||||
this._left = new TransitionTree();
|
||||
this._right = new TransitionTree();
|
||||
}
|
||||
else if (_value > c)
|
||||
{
|
||||
_left.Add(c, false, state);
|
||||
}
|
||||
else
|
||||
{
|
||||
_right.Add(c, false, state);
|
||||
}
|
||||
}
|
||||
|
||||
public void PrintTo(StringBuilder buffer, String indent)
|
||||
{
|
||||
_left?.PrintTo(buffer, indent);
|
||||
if (this._value != '\0')
|
||||
{
|
||||
if (buffer.Length > 0 && buffer[buffer.Length - 1] == '\n')
|
||||
{
|
||||
buffer.Append(indent);
|
||||
}
|
||||
buffer.Append(this._value);
|
||||
if (this._state.Value != null)
|
||||
{
|
||||
buffer.Append(": ");
|
||||
buffer.Append(this._state.Value);
|
||||
buffer.Append("\n");
|
||||
}
|
||||
this._state.Tree.PrintTo(buffer, indent + " ");
|
||||
}
|
||||
_right?.PrintTo(buffer, indent);
|
||||
}
|
||||
}
|
||||
}
|
||||
444
Parsing/Tokenizer.cs
Normal file
444
Parsing/Tokenizer.cs
Normal file
@@ -0,0 +1,444 @@
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Flee.Parsing
|
||||
{
|
||||
/**
|
||||
* A character stream tokenizer. This class groups the characters read
|
||||
* from the stream together into tokens ("words"). The grouping is
|
||||
* controlled by token patterns that contain either a fixed string to
|
||||
* search for, or a regular expression. If the stream of characters
|
||||
* don't match any of the token patterns, a parse exception is thrown.
|
||||
*/
|
||||
internal class Tokenizer
|
||||
{
|
||||
private bool _useTokenList = false;
|
||||
private readonly StringDFAMatcher _stringDfaMatcher;
|
||||
private readonly NFAMatcher _nfaMatcher;
|
||||
private readonly RegExpMatcher _regExpMatcher;
|
||||
private ReaderBuffer _buffer = null;
|
||||
private readonly TokenMatch _lastMatch = new TokenMatch();
|
||||
private Token _previousToken = null;
|
||||
|
||||
public Tokenizer(TextReader input)
|
||||
: this(input, false)
|
||||
{
|
||||
}
|
||||
|
||||
public Tokenizer(TextReader input, bool ignoreCase)
|
||||
{
|
||||
this._stringDfaMatcher = new StringDFAMatcher(ignoreCase);
|
||||
this._nfaMatcher = new NFAMatcher(ignoreCase);
|
||||
this._regExpMatcher = new RegExpMatcher(ignoreCase);
|
||||
this._buffer = new ReaderBuffer(input);
|
||||
}
|
||||
|
||||
public bool UseTokenList
|
||||
{
|
||||
get
|
||||
{
|
||||
return _useTokenList;
|
||||
}
|
||||
set
|
||||
{
|
||||
_useTokenList = value;
|
||||
}
|
||||
}
|
||||
|
||||
public bool GetUseTokenList()
|
||||
{
|
||||
return _useTokenList;
|
||||
}
|
||||
|
||||
public void SetUseTokenList(bool useTokenList)
|
||||
{
|
||||
this._useTokenList = useTokenList;
|
||||
}
|
||||
|
||||
public string GetPatternDescription(int id)
|
||||
{
|
||||
var pattern = _stringDfaMatcher.GetPattern(id);
|
||||
if (pattern == null)
|
||||
{
|
||||
pattern = _nfaMatcher.GetPattern(id);
|
||||
}
|
||||
if (pattern == null)
|
||||
{
|
||||
pattern = _regExpMatcher.GetPattern(id);
|
||||
}
|
||||
return pattern?.ToShortString();
|
||||
}
|
||||
|
||||
public int GetCurrentLine()
|
||||
{
|
||||
return _buffer.LineNumber;
|
||||
}
|
||||
|
||||
public int GetCurrentColumn()
|
||||
{
|
||||
return _buffer.ColumnNumber;
|
||||
}
|
||||
|
||||
/**
|
||||
* nfa - true to attempt as an nfa pattern for regexp. This handles most things except the complex repeates, ie {1,4}
|
||||
*/
|
||||
public void AddPattern(TokenPattern pattern, bool nfa=true)
|
||||
{
|
||||
switch (pattern.Type)
|
||||
{
|
||||
case TokenPattern.PatternType.STRING:
|
||||
try
|
||||
{
|
||||
_stringDfaMatcher.AddPattern(pattern);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new ParserCreationException(
|
||||
ParserCreationException.ErrorType.INVALID_TOKEN,
|
||||
pattern.Name,
|
||||
"error adding string token: " +
|
||||
e.Message);
|
||||
}
|
||||
break;
|
||||
case TokenPattern.PatternType.REGEXP:
|
||||
if (nfa)
|
||||
{
|
||||
try
|
||||
{
|
||||
_nfaMatcher.AddPattern(pattern);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
nfa = false;
|
||||
}
|
||||
}
|
||||
if (!nfa)
|
||||
{
|
||||
try
|
||||
{
|
||||
_regExpMatcher.AddPattern(pattern);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new ParserCreationException(
|
||||
ParserCreationException.ErrorType.INVALID_TOKEN,
|
||||
pattern.Name,
|
||||
"regular expression contains error(s): " +
|
||||
e.Message);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
default:
|
||||
throw new ParserCreationException(
|
||||
ParserCreationException.ErrorType.INVALID_TOKEN,
|
||||
pattern.Name,
|
||||
"pattern type " + pattern.Type +
|
||||
" is undefined");
|
||||
}
|
||||
}
|
||||
|
||||
public void Reset(TextReader input)
|
||||
{
|
||||
//this.buffer.Dispose();
|
||||
this._buffer = new ReaderBuffer(input);
|
||||
this._previousToken = null;
|
||||
this._lastMatch.Clear();
|
||||
}
|
||||
|
||||
public Token Next()
|
||||
{
|
||||
Token token = null;
|
||||
|
||||
do
|
||||
{
|
||||
token = NextToken();
|
||||
if (token == null)
|
||||
{
|
||||
_previousToken = null;
|
||||
return null;
|
||||
}
|
||||
if (_useTokenList)
|
||||
{
|
||||
token.Previous = _previousToken;
|
||||
_previousToken = token;
|
||||
}
|
||||
if (token.Pattern.Ignore)
|
||||
{
|
||||
token = null;
|
||||
}
|
||||
else if (token.Pattern.Error)
|
||||
{
|
||||
throw new ParseException(
|
||||
ParseException.ErrorType.INVALID_TOKEN,
|
||||
token.Pattern.ErrorMessage,
|
||||
token.StartLine,
|
||||
token.StartColumn);
|
||||
}
|
||||
} while (token == null);
|
||||
return token;
|
||||
}
|
||||
|
||||
private Token NextToken()
|
||||
{
|
||||
try
|
||||
{
|
||||
_lastMatch.Clear();
|
||||
_stringDfaMatcher.Match(_buffer, _lastMatch);
|
||||
_nfaMatcher.Match(_buffer, _lastMatch);
|
||||
_regExpMatcher.Match(_buffer, _lastMatch);
|
||||
int line;
|
||||
int column;
|
||||
if (_lastMatch.Length > 0)
|
||||
{
|
||||
line = _buffer.LineNumber;
|
||||
column = _buffer.ColumnNumber;
|
||||
var str = _buffer.Read(_lastMatch.Length);
|
||||
return NewToken(_lastMatch.Pattern, str, line, column);
|
||||
}
|
||||
else if (_buffer.Peek(0) < 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
else
|
||||
{
|
||||
line = _buffer.LineNumber;
|
||||
column = _buffer.ColumnNumber;
|
||||
throw new ParseException(
|
||||
ParseException.ErrorType.UNEXPECTED_CHAR,
|
||||
_buffer.Read(1),
|
||||
line,
|
||||
column);
|
||||
}
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
throw new ParseException(ParseException.ErrorType.IO,
|
||||
e.Message,
|
||||
-1,
|
||||
-1);
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual Token NewToken(TokenPattern pattern,
|
||||
string image,
|
||||
int line,
|
||||
int column)
|
||||
{
|
||||
|
||||
return new Token(pattern, image, line, column);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
StringBuilder buffer = new StringBuilder();
|
||||
buffer.Append(_stringDfaMatcher);
|
||||
buffer.Append(_nfaMatcher);
|
||||
buffer.Append(_regExpMatcher);
|
||||
return buffer.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
internal abstract class TokenMatcher
|
||||
{
|
||||
protected TokenPattern[] Patterns = new TokenPattern[0];
|
||||
|
||||
protected bool IgnoreCase = false;
|
||||
|
||||
protected TokenMatcher(bool ignoreCase)
|
||||
{
|
||||
IgnoreCase = ignoreCase;
|
||||
}
|
||||
|
||||
public abstract void Match(ReaderBuffer buffer, TokenMatch match);
|
||||
|
||||
public TokenPattern GetPattern(int id)
|
||||
{
|
||||
for (int i = 0; i < Patterns.Length; i++)
|
||||
{
|
||||
if (Patterns[i].Id == id)
|
||||
{
|
||||
return Patterns[i];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public virtual void AddPattern(TokenPattern pattern)
|
||||
{
|
||||
Array.Resize(ref Patterns, Patterns.Length + 1);
|
||||
Patterns[Patterns.Length - 1] = pattern;
|
||||
}
|
||||
public override string ToString()
|
||||
{
|
||||
StringBuilder buffer = new StringBuilder();
|
||||
|
||||
for (int i = 0; i < Patterns.Length; i++)
|
||||
{
|
||||
buffer.Append(Patterns[i]);
|
||||
buffer.Append("\n\n");
|
||||
}
|
||||
return buffer.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
internal class StringDFAMatcher : TokenMatcher
|
||||
{
|
||||
|
||||
private readonly TokenStringDFA _automaton = new TokenStringDFA();
|
||||
|
||||
public StringDFAMatcher(bool ignoreCase) : base(ignoreCase)
|
||||
{
|
||||
}
|
||||
|
||||
public override void AddPattern(TokenPattern pattern)
|
||||
{
|
||||
_automaton.AddMatch(pattern.Pattern, IgnoreCase, pattern);
|
||||
base.AddPattern(pattern);
|
||||
}
|
||||
|
||||
public override void Match(ReaderBuffer buffer, TokenMatch match)
|
||||
{
|
||||
TokenPattern res = _automaton.Match(buffer, IgnoreCase);
|
||||
|
||||
if (res != null)
|
||||
{
|
||||
match.Update(res.Pattern.Length, res);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal class NFAMatcher : TokenMatcher
|
||||
{
|
||||
|
||||
private readonly TokenNFA _automaton = new TokenNFA();
|
||||
|
||||
public NFAMatcher(bool ignoreCase) : base(ignoreCase)
|
||||
{
|
||||
}
|
||||
|
||||
public override void AddPattern(TokenPattern pattern)
|
||||
{
|
||||
if (pattern.Type == TokenPattern.PatternType.STRING)
|
||||
{
|
||||
_automaton.AddTextMatch(pattern.Pattern, IgnoreCase, pattern);
|
||||
}
|
||||
else
|
||||
{
|
||||
_automaton.AddRegExpMatch(pattern.Pattern, IgnoreCase, pattern);
|
||||
}
|
||||
base.AddPattern(pattern);
|
||||
}
|
||||
|
||||
public override void Match(ReaderBuffer buffer, TokenMatch match)
|
||||
{
|
||||
_automaton.Match(buffer, match);
|
||||
}
|
||||
}
|
||||
|
||||
internal class RegExpMatcher : TokenMatcher
|
||||
{
|
||||
private REHandler[] _regExps = new REHandler[0];
|
||||
|
||||
public RegExpMatcher(bool ignoreCase) : base(ignoreCase)
|
||||
{
|
||||
}
|
||||
|
||||
public override void AddPattern(TokenPattern pattern)
|
||||
{
|
||||
REHandler re;
|
||||
try
|
||||
{
|
||||
re = new GrammaticaRE(pattern.Pattern, IgnoreCase);
|
||||
pattern.DebugInfo = "Grammatica regexp\n" + re;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
re = new SystemRE(pattern.Pattern, IgnoreCase);
|
||||
pattern.DebugInfo = "native .NET regexp";
|
||||
}
|
||||
Array.Resize(ref _regExps, _regExps.Length + 1);
|
||||
_regExps[_regExps.Length - 1] = re;
|
||||
base.AddPattern(pattern);
|
||||
}
|
||||
|
||||
public override void Match(ReaderBuffer buffer, TokenMatch match)
|
||||
{
|
||||
for (int i = 0; i < _regExps.Length; i++)
|
||||
{
|
||||
int length = _regExps[i].Match(buffer);
|
||||
if (length > 0)
|
||||
{
|
||||
match.Update(length, Patterns[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
internal abstract class REHandler
|
||||
{
|
||||
public abstract int Match(ReaderBuffer buffer);
|
||||
}
|
||||
|
||||
internal class GrammaticaRE : REHandler
|
||||
{
|
||||
private readonly RegExp _regExp;
|
||||
private Matcher _matcher = null;
|
||||
|
||||
public GrammaticaRE(string regex, bool ignoreCase)
|
||||
{
|
||||
_regExp = new RegExp(regex, ignoreCase);
|
||||
}
|
||||
|
||||
public override int Match(ReaderBuffer buffer)
|
||||
{
|
||||
if (_matcher == null)
|
||||
{
|
||||
_matcher = _regExp.Matcher(buffer);
|
||||
}
|
||||
else
|
||||
{
|
||||
_matcher.Reset(buffer);
|
||||
}
|
||||
return _matcher.MatchFromBeginning() ? _matcher.Length() : 0;
|
||||
}
|
||||
}
|
||||
|
||||
internal class SystemRE : REHandler
|
||||
{
|
||||
private readonly Regex _reg;
|
||||
|
||||
public SystemRE(string regex, bool ignoreCase)
|
||||
{
|
||||
if (ignoreCase)
|
||||
{
|
||||
_reg = new Regex(regex, RegexOptions.IgnoreCase);
|
||||
}
|
||||
else
|
||||
{
|
||||
_reg = new Regex(regex);
|
||||
}
|
||||
}
|
||||
|
||||
public override int Match(ReaderBuffer buffer)
|
||||
{
|
||||
Match m;
|
||||
|
||||
// Ugly hack since .NET doesn't have a flag for when the
|
||||
// end of the input string was encountered...
|
||||
buffer.Peek(1024 * 16);
|
||||
// Also, there is no API to limit the search to the specified
|
||||
// position, so we double-check the index afterwards instead.
|
||||
m = _reg.Match(buffer.ToString(), buffer.Position);
|
||||
if (m.Success && m.Index == buffer.Position)
|
||||
{
|
||||
return m.Length;
|
||||
}
|
||||
else
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
67
PublicTypes/Exceptions.cs
Normal file
67
PublicTypes/Exceptions.cs
Normal file
@@ -0,0 +1,67 @@
|
||||
using Flee.InternalTypes;
|
||||
using Flee.Parsing;
|
||||
using Flee.Resources;
|
||||
|
||||
namespace Flee.PublicTypes
|
||||
{
|
||||
public enum CompileExceptionReason
|
||||
{
|
||||
SyntaxError,
|
||||
ConstantOverflow,
|
||||
TypeMismatch,
|
||||
UndefinedName,
|
||||
FunctionHasNoReturnValue,
|
||||
InvalidExplicitCast,
|
||||
AmbiguousMatch,
|
||||
AccessDenied,
|
||||
InvalidFormat
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[Serializable()]
|
||||
public sealed class ExpressionCompileException : Exception
|
||||
{
|
||||
private readonly CompileExceptionReason _myReason;
|
||||
internal ExpressionCompileException(string message, CompileExceptionReason reason) : base(message)
|
||||
{
|
||||
_myReason = reason;
|
||||
}
|
||||
|
||||
internal ExpressionCompileException(ParserLogException parseException) : base(string.Empty, parseException)
|
||||
{
|
||||
_myReason = CompileExceptionReason.SyntaxError;
|
||||
}
|
||||
|
||||
private ExpressionCompileException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) : base(info, context)
|
||||
{
|
||||
_myReason = (CompileExceptionReason)info.GetInt32("Reason");
|
||||
}
|
||||
|
||||
public override void GetObjectData(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context)
|
||||
{
|
||||
base.GetObjectData(info, context);
|
||||
info.AddValue("Reason", Convert.ToInt32(_myReason));
|
||||
}
|
||||
|
||||
public override string Message
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_myReason == CompileExceptionReason.SyntaxError)
|
||||
{
|
||||
Exception innerEx = this.InnerException;
|
||||
string msg = $"{Utility.GetCompileErrorMessage(CompileErrorResourceKeys.SyntaxError)}: {innerEx.Message}";
|
||||
return msg;
|
||||
}
|
||||
else
|
||||
{
|
||||
return base.Message;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public CompileExceptionReason Reason => _myReason;
|
||||
}
|
||||
}
|
||||
251
PublicTypes/ExpressionContext.cs
Normal file
251
PublicTypes/ExpressionContext.cs
Normal file
@@ -0,0 +1,251 @@
|
||||
using Flee.CalcEngine.InternalTypes;
|
||||
using Flee.CalcEngine.PublicTypes;
|
||||
using Flee.ExpressionElements.Base;
|
||||
using Flee.InternalTypes;
|
||||
using Flee.Parsing;
|
||||
using Flee.Resources;
|
||||
|
||||
namespace Flee.PublicTypes
|
||||
{
|
||||
public sealed class ExpressionContext
|
||||
{
|
||||
|
||||
#region "Fields"
|
||||
|
||||
private PropertyDictionary _myProperties;
|
||||
|
||||
private readonly object _mySyncRoot = new object();
|
||||
|
||||
private VariableCollection _myVariables;
|
||||
#endregion
|
||||
|
||||
#region "Constructor"
|
||||
|
||||
public ExpressionContext() : this(DefaultExpressionOwner.Instance)
|
||||
{
|
||||
}
|
||||
|
||||
public ExpressionContext(object expressionOwner)
|
||||
{
|
||||
Utility.AssertNotNull(expressionOwner, "expressionOwner");
|
||||
_myProperties = new PropertyDictionary();
|
||||
|
||||
_myProperties.SetValue("CalculationEngine", null);
|
||||
_myProperties.SetValue("CalcEngineExpressionName", null);
|
||||
_myProperties.SetValue("IdentifierParser", null);
|
||||
|
||||
_myProperties.SetValue("ExpressionOwner", expressionOwner);
|
||||
|
||||
_myProperties.SetValue("ParserOptions", new ExpressionParserOptions(this));
|
||||
|
||||
_myProperties.SetValue("Options", new ExpressionOptions(this));
|
||||
_myProperties.SetValue("Imports", new ExpressionImports());
|
||||
this.Imports.SetContext(this);
|
||||
_myVariables = new VariableCollection(this);
|
||||
|
||||
_myProperties.SetToDefault<bool>("NoClone");
|
||||
|
||||
this.RecreateParser();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region "Methods - Private"
|
||||
|
||||
private void AssertTypeIsAccessibleInternal(Type t)
|
||||
{
|
||||
bool isPublic = t.IsPublic;
|
||||
|
||||
if (t.IsNested == true)
|
||||
{
|
||||
isPublic = t.IsNestedPublic;
|
||||
}
|
||||
|
||||
bool isSameModuleAsOwner = object.ReferenceEquals(t.Module, this.ExpressionOwner.GetType().Module);
|
||||
|
||||
// Public types are always accessible. Otherwise they have to be in the same module as the owner
|
||||
bool isAccessible = isPublic | isSameModuleAsOwner;
|
||||
|
||||
if (isAccessible == false)
|
||||
{
|
||||
string msg = Utility.GetGeneralErrorMessage(GeneralErrorResourceKeys.TypeNotAccessibleToExpression, t.Name);
|
||||
throw new ArgumentException(msg);
|
||||
}
|
||||
}
|
||||
|
||||
private void AssertNestedTypeIsAccessible(Type t)
|
||||
{
|
||||
while ((t != null))
|
||||
{
|
||||
AssertTypeIsAccessibleInternal(t);
|
||||
t = t.DeclaringType;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region "Methods - Internal"
|
||||
internal ExpressionContext CloneInternal(bool cloneVariables)
|
||||
{
|
||||
ExpressionContext context = (ExpressionContext)this.MemberwiseClone();
|
||||
context._myProperties = _myProperties.Clone();
|
||||
context._myProperties.SetValue("Options", context.Options.Clone());
|
||||
context._myProperties.SetValue("ParserOptions", context.ParserOptions.Clone());
|
||||
context._myProperties.SetValue("Imports", context.Imports.Clone());
|
||||
context.Imports.SetContext(context);
|
||||
|
||||
if (cloneVariables == true)
|
||||
{
|
||||
context._myVariables = new VariableCollection(context);
|
||||
this.Variables.Copy(context._myVariables);
|
||||
}
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
internal void AssertTypeIsAccessible(Type t)
|
||||
{
|
||||
if (t.IsNested == true)
|
||||
{
|
||||
AssertNestedTypeIsAccessible(t);
|
||||
}
|
||||
else
|
||||
{
|
||||
AssertTypeIsAccessibleInternal(t);
|
||||
}
|
||||
}
|
||||
|
||||
internal ExpressionElement Parse(string expression, IServiceProvider services)
|
||||
{
|
||||
lock (_mySyncRoot)
|
||||
{
|
||||
System.IO.StringReader sr = new System.IO.StringReader(expression);
|
||||
ExpressionParser parser = this.Parser;
|
||||
parser.Reset(sr);
|
||||
parser.Tokenizer.Reset(sr);
|
||||
FleeExpressionAnalyzer analyzer = (FleeExpressionAnalyzer)parser.Analyzer;
|
||||
|
||||
analyzer.SetServices(services);
|
||||
|
||||
Node rootNode = DoParse();
|
||||
analyzer.Reset();
|
||||
ExpressionElement topElement = (ExpressionElement)rootNode.Values[0];
|
||||
return topElement;
|
||||
}
|
||||
}
|
||||
|
||||
internal void RecreateParser()
|
||||
{
|
||||
lock (_mySyncRoot)
|
||||
{
|
||||
FleeExpressionAnalyzer analyzer = new FleeExpressionAnalyzer();
|
||||
ExpressionParser parser = new ExpressionParser(TextReader.Null, analyzer, this);
|
||||
_myProperties.SetValue("ExpressionParser", parser);
|
||||
}
|
||||
}
|
||||
|
||||
internal Node DoParse()
|
||||
{
|
||||
try
|
||||
{
|
||||
return this.Parser.Parse();
|
||||
}
|
||||
catch (ParserLogException ex)
|
||||
{
|
||||
// Syntax error; wrap it in our exception and rethrow
|
||||
throw new ExpressionCompileException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
internal void SetCalcEngine(CalculationEngine engine, string calcEngineExpressionName)
|
||||
{
|
||||
_myProperties.SetValue("CalculationEngine", engine);
|
||||
_myProperties.SetValue("CalcEngineExpressionName", calcEngineExpressionName);
|
||||
}
|
||||
|
||||
internal IdentifierAnalyzer ParseIdentifiers(string expression)
|
||||
{
|
||||
ExpressionParser parser = this.IdentifierParser;
|
||||
StringReader sr = new StringReader(expression);
|
||||
parser.Reset(sr);
|
||||
parser.Tokenizer.Reset(sr);
|
||||
|
||||
IdentifierAnalyzer analyzer = (IdentifierAnalyzer)parser.Analyzer;
|
||||
analyzer.Reset();
|
||||
|
||||
parser.Parse();
|
||||
|
||||
return (IdentifierAnalyzer)parser.Analyzer;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region "Methods - Public"
|
||||
|
||||
public ExpressionContext Clone()
|
||||
{
|
||||
return this.CloneInternal(true);
|
||||
}
|
||||
|
||||
public IDynamicExpression CompileDynamic(string expression)
|
||||
{
|
||||
return new Flee.InternalTypes.Expression<object>(expression, this, false);
|
||||
}
|
||||
|
||||
public IGenericExpression<TResultType> CompileGeneric<TResultType>(string expression)
|
||||
{
|
||||
return new Flee.InternalTypes.Expression<TResultType>(expression, this, true);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region "Properties - Private"
|
||||
|
||||
private ExpressionParser IdentifierParser
|
||||
{
|
||||
get
|
||||
{
|
||||
ExpressionParser parser = _myProperties.GetValue<ExpressionParser>("IdentifierParser");
|
||||
|
||||
if (parser == null)
|
||||
{
|
||||
IdentifierAnalyzer analyzer = new IdentifierAnalyzer();
|
||||
parser = new ExpressionParser(System.IO.TextReader.Null, analyzer, this);
|
||||
//parser = new ExpressionParser(System.IO.StringReader.Null, analyzer, this);
|
||||
_myProperties.SetValue("IdentifierParser", parser);
|
||||
}
|
||||
|
||||
return parser;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region "Properties - Internal"
|
||||
|
||||
internal bool NoClone
|
||||
{
|
||||
get { return _myProperties.GetValue<bool>("NoClone"); }
|
||||
set { _myProperties.SetValue("NoClone", value); }
|
||||
}
|
||||
|
||||
internal object ExpressionOwner => _myProperties.GetValue<object>("ExpressionOwner");
|
||||
|
||||
internal string CalcEngineExpressionName => _myProperties.GetValue<string>("CalcEngineExpressionName");
|
||||
|
||||
internal ExpressionParser Parser => _myProperties.GetValue<ExpressionParser>("ExpressionParser");
|
||||
|
||||
#endregion
|
||||
|
||||
#region "Properties - Public"
|
||||
public ExpressionOptions Options => _myProperties.GetValue<ExpressionOptions>("Options");
|
||||
|
||||
public ExpressionImports Imports => _myProperties.GetValue<ExpressionImports>("Imports");
|
||||
|
||||
public VariableCollection Variables => _myVariables;
|
||||
|
||||
public CalculationEngine CalculationEngine => _myProperties.GetValue<CalculationEngine>("CalculationEngine");
|
||||
|
||||
public ExpressionParserOptions ParserOptions => _myProperties.GetValue<ExpressionParserOptions>("ParserOptions");
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
195
PublicTypes/ExpressionImports.cs
Normal file
195
PublicTypes/ExpressionImports.cs
Normal file
@@ -0,0 +1,195 @@
|
||||
using System.Reflection;
|
||||
using Flee.InternalTypes;
|
||||
using Flee.Resources;
|
||||
|
||||
namespace Flee.PublicTypes
|
||||
{
|
||||
public sealed class ExpressionImports
|
||||
{
|
||||
|
||||
private static Dictionary<string, Type> OurBuiltinTypeMap = CreateBuiltinTypeMap();
|
||||
private NamespaceImport MyRootImport;
|
||||
private TypeImport MyOwnerImport;
|
||||
|
||||
private ExpressionContext MyContext;
|
||||
internal ExpressionImports()
|
||||
{
|
||||
MyRootImport = new NamespaceImport("true");
|
||||
}
|
||||
|
||||
private static Dictionary<string, Type> CreateBuiltinTypeMap()
|
||||
{
|
||||
Dictionary<string, Type> map = new Dictionary<string, Type>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
map.Add("boolean", typeof(bool));
|
||||
map.Add("byte", typeof(byte));
|
||||
map.Add("sbyte", typeof(sbyte));
|
||||
map.Add("short", typeof(short));
|
||||
map.Add("ushort", typeof(UInt16));
|
||||
map.Add("int", typeof(Int32));
|
||||
map.Add("uint", typeof(UInt32));
|
||||
map.Add("long", typeof(long));
|
||||
map.Add("ulong", typeof(ulong));
|
||||
map.Add("single", typeof(float));
|
||||
map.Add("double", typeof(double));
|
||||
map.Add("decimal", typeof(decimal));
|
||||
map.Add("char", typeof(char));
|
||||
map.Add("object", typeof(object));
|
||||
map.Add("string", typeof(string));
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
#region "Methods - Non public"
|
||||
internal void SetContext(ExpressionContext context)
|
||||
{
|
||||
MyContext = context;
|
||||
MyRootImport.SetContext(context);
|
||||
}
|
||||
|
||||
internal ExpressionImports Clone()
|
||||
{
|
||||
ExpressionImports copy = new ExpressionImports();
|
||||
|
||||
copy.MyRootImport = (NamespaceImport)MyRootImport.Clone();
|
||||
copy.MyOwnerImport = MyOwnerImport;
|
||||
|
||||
return copy;
|
||||
}
|
||||
|
||||
internal void ImportOwner(Type ownerType)
|
||||
{
|
||||
MyOwnerImport = new TypeImport(ownerType, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static, false);
|
||||
MyOwnerImport.SetContext(MyContext);
|
||||
}
|
||||
|
||||
internal bool HasNamespace(string ns)
|
||||
{
|
||||
NamespaceImport import = MyRootImport.FindImport(ns) as NamespaceImport;
|
||||
return (import != null);
|
||||
}
|
||||
|
||||
internal NamespaceImport GetImport(string ns)
|
||||
{
|
||||
if (ns.Length == 0)
|
||||
{
|
||||
return MyRootImport;
|
||||
}
|
||||
|
||||
NamespaceImport import = MyRootImport.FindImport(ns) as NamespaceImport;
|
||||
|
||||
if (import == null)
|
||||
{
|
||||
import = new NamespaceImport(ns);
|
||||
MyRootImport.Add(import);
|
||||
}
|
||||
|
||||
return import;
|
||||
}
|
||||
|
||||
internal MemberInfo[] FindOwnerMembers(string memberName, System.Reflection.MemberTypes memberType)
|
||||
{
|
||||
return MyOwnerImport.FindMembers(memberName, memberType);
|
||||
}
|
||||
|
||||
internal Type FindType(string[] typeNameParts)
|
||||
{
|
||||
string[] namespaces = new string[typeNameParts.Length - 1];
|
||||
string typeName = typeNameParts[typeNameParts.Length - 1];
|
||||
|
||||
System.Array.Copy(typeNameParts, namespaces, namespaces.Length);
|
||||
ImportBase currentImport = MyRootImport;
|
||||
|
||||
foreach (string ns in namespaces)
|
||||
{
|
||||
currentImport = currentImport.FindImport(ns);
|
||||
if (currentImport == null)
|
||||
{
|
||||
break; // TODO: might not be correct. Was : Exit For
|
||||
}
|
||||
}
|
||||
|
||||
return currentImport?.FindType(typeName);
|
||||
}
|
||||
|
||||
static internal Type GetBuiltinType(string name)
|
||||
{
|
||||
Type t = null;
|
||||
|
||||
if (OurBuiltinTypeMap.TryGetValue(name, out t) == true)
|
||||
{
|
||||
return t;
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region "Methods - Public"
|
||||
public void AddType(Type t, string ns)
|
||||
{
|
||||
Utility.AssertNotNull(t, "t");
|
||||
Utility.AssertNotNull(ns, "namespace");
|
||||
|
||||
MyContext.AssertTypeIsAccessible(t);
|
||||
|
||||
NamespaceImport import = this.GetImport(ns);
|
||||
import.Add(new TypeImport(t, BindingFlags.Public | BindingFlags.Static, false));
|
||||
}
|
||||
|
||||
public void AddType(Type t)
|
||||
{
|
||||
this.AddType(t, string.Empty);
|
||||
}
|
||||
|
||||
public void AddMethod(string methodName, Type t, string ns)
|
||||
{
|
||||
Utility.AssertNotNull(methodName, "methodName");
|
||||
Utility.AssertNotNull(t, "t");
|
||||
Utility.AssertNotNull(ns, "namespace");
|
||||
|
||||
MethodInfo mi = t.GetMethod(methodName, BindingFlags.Public | BindingFlags.Static | BindingFlags.IgnoreCase);
|
||||
|
||||
if (mi == null)
|
||||
{
|
||||
string msg = Utility.GetGeneralErrorMessage(GeneralErrorResourceKeys.CouldNotFindPublicStaticMethodOnType, methodName, t.Name);
|
||||
throw new ArgumentException(msg);
|
||||
}
|
||||
|
||||
this.AddMethod(mi, ns);
|
||||
}
|
||||
|
||||
public void AddMethod(MethodInfo mi, string ns)
|
||||
{
|
||||
Utility.AssertNotNull(mi, "mi");
|
||||
Utility.AssertNotNull(ns, "namespace");
|
||||
|
||||
MyContext.AssertTypeIsAccessible(mi.ReflectedType);
|
||||
|
||||
if (mi.IsStatic == false | mi.IsPublic == false)
|
||||
{
|
||||
string msg = Utility.GetGeneralErrorMessage(GeneralErrorResourceKeys.OnlyPublicStaticMethodsCanBeImported);
|
||||
throw new ArgumentException(msg);
|
||||
}
|
||||
|
||||
NamespaceImport import = this.GetImport(ns);
|
||||
import.Add(new MethodImport(mi));
|
||||
}
|
||||
|
||||
public void ImportBuiltinTypes()
|
||||
{
|
||||
foreach (KeyValuePair<string, Type> pair in OurBuiltinTypeMap)
|
||||
{
|
||||
this.AddType(pair.Value, pair.Key);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region "Properties - Public"
|
||||
public NamespaceImport RootImport => MyRootImport;
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
207
PublicTypes/ExpressionOptions.cs
Normal file
207
PublicTypes/ExpressionOptions.cs
Normal file
@@ -0,0 +1,207 @@
|
||||
using System.Reflection;
|
||||
using System.Globalization;
|
||||
using Flee.InternalTypes;
|
||||
|
||||
|
||||
namespace Flee.PublicTypes
|
||||
{
|
||||
public sealed class ExpressionOptions
|
||||
{
|
||||
|
||||
private PropertyDictionary _myProperties;
|
||||
private Type _myOwnerType;
|
||||
private readonly ExpressionContext _myOwner;
|
||||
internal event EventHandler CaseSensitiveChanged;
|
||||
|
||||
internal ExpressionOptions(ExpressionContext owner)
|
||||
{
|
||||
_myOwner = owner;
|
||||
_myProperties = new PropertyDictionary();
|
||||
|
||||
this.InitializeProperties();
|
||||
}
|
||||
|
||||
#region "Methods - Private"
|
||||
|
||||
private void InitializeProperties()
|
||||
{
|
||||
this.StringComparison = System.StringComparison.Ordinal;
|
||||
this.OwnerMemberAccess = BindingFlags.Public;
|
||||
|
||||
_myProperties.SetToDefault<bool>("CaseSensitive");
|
||||
_myProperties.SetToDefault<bool>("Checked");
|
||||
_myProperties.SetToDefault<bool>("EmitToAssembly");
|
||||
_myProperties.SetToDefault<Type>("ResultType");
|
||||
_myProperties.SetToDefault<bool>("IsGeneric");
|
||||
_myProperties.SetToDefault<bool>("IntegersAsDoubles");
|
||||
_myProperties.SetValue("ParseCulture", CultureInfo.CurrentCulture);
|
||||
this.SetParseCulture(this.ParseCulture);
|
||||
_myProperties.SetValue("RealLiteralDataType", RealLiteralDataType.Double);
|
||||
}
|
||||
|
||||
private void SetParseCulture(CultureInfo ci)
|
||||
{
|
||||
ExpressionParserOptions po = _myOwner.ParserOptions;
|
||||
po.DecimalSeparator = Convert.ToChar(ci.NumberFormat.NumberDecimalSeparator);
|
||||
po.FunctionArgumentSeparator = Convert.ToChar(ci.TextInfo.ListSeparator);
|
||||
po.DateTimeFormat = ci.DateTimeFormat.ShortDatePattern;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region "Methods - Internal"
|
||||
|
||||
internal ExpressionOptions Clone()
|
||||
{
|
||||
ExpressionOptions clonedOptions = (ExpressionOptions)this.MemberwiseClone();
|
||||
clonedOptions._myProperties = _myProperties.Clone();
|
||||
return clonedOptions;
|
||||
}
|
||||
|
||||
internal bool IsOwnerType(Type t)
|
||||
{
|
||||
return this._myOwnerType.IsAssignableFrom(t);
|
||||
}
|
||||
|
||||
internal void SetOwnerType(Type ownerType)
|
||||
{
|
||||
_myOwnerType = ownerType;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region "Properties - Public"
|
||||
public Type ResultType
|
||||
{
|
||||
get { return _myProperties.GetValue<Type>("ResultType"); }
|
||||
set
|
||||
{
|
||||
Utility.AssertNotNull(value, "value");
|
||||
_myProperties.SetValue("ResultType", value);
|
||||
}
|
||||
}
|
||||
|
||||
public bool Checked
|
||||
{
|
||||
get { return _myProperties.GetValue<bool>("Checked"); }
|
||||
set { _myProperties.SetValue("Checked", value); }
|
||||
}
|
||||
|
||||
public StringComparison StringComparison
|
||||
{
|
||||
get { return _myProperties.GetValue<StringComparison>("StringComparison"); }
|
||||
set { _myProperties.SetValue("StringComparison", value); }
|
||||
}
|
||||
|
||||
public bool EmitToAssembly
|
||||
{
|
||||
get { return _myProperties.GetValue<bool>("EmitToAssembly"); }
|
||||
set { _myProperties.SetValue("EmitToAssembly", value); }
|
||||
}
|
||||
|
||||
public BindingFlags OwnerMemberAccess
|
||||
{
|
||||
get { return _myProperties.GetValue<BindingFlags>("OwnerMemberAccess"); }
|
||||
set { _myProperties.SetValue("OwnerMemberAccess", value); }
|
||||
}
|
||||
|
||||
public bool CaseSensitive
|
||||
{
|
||||
get { return _myProperties.GetValue<bool>("CaseSensitive"); }
|
||||
set
|
||||
{
|
||||
if (this.CaseSensitive != value)
|
||||
{
|
||||
_myProperties.SetValue("CaseSensitive", value);
|
||||
if (CaseSensitiveChanged != null)
|
||||
{
|
||||
CaseSensitiveChanged(this, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool IntegersAsDoubles
|
||||
{
|
||||
get { return _myProperties.GetValue<bool>("IntegersAsDoubles"); }
|
||||
set { _myProperties.SetValue("IntegersAsDoubles", value); }
|
||||
}
|
||||
|
||||
public CultureInfo ParseCulture
|
||||
{
|
||||
get { return _myProperties.GetValue<CultureInfo>("ParseCulture"); }
|
||||
set
|
||||
{
|
||||
Utility.AssertNotNull(value, "ParseCulture");
|
||||
if ((value.LCID != this.ParseCulture.LCID))
|
||||
{
|
||||
_myProperties.SetValue("ParseCulture", value);
|
||||
this.SetParseCulture(value);
|
||||
_myOwner.ParserOptions.RecreateParser();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public RealLiteralDataType RealLiteralDataType
|
||||
{
|
||||
get { return _myProperties.GetValue<RealLiteralDataType>("RealLiteralDataType"); }
|
||||
set { _myProperties.SetValue("RealLiteralDataType", value); }
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region "Properties - Non Public"
|
||||
internal IEqualityComparer<string> StringComparer
|
||||
{
|
||||
get
|
||||
{
|
||||
if (this.CaseSensitive == true)
|
||||
{
|
||||
return System.StringComparer.Ordinal;
|
||||
}
|
||||
else
|
||||
{
|
||||
return System.StringComparer.OrdinalIgnoreCase;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal MemberFilter MemberFilter
|
||||
{
|
||||
get
|
||||
{
|
||||
if (this.CaseSensitive == true)
|
||||
{
|
||||
return Type.FilterName;
|
||||
}
|
||||
else
|
||||
{
|
||||
return Type.FilterNameIgnoreCase;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal StringComparison MemberStringComparison
|
||||
{
|
||||
get
|
||||
{
|
||||
if (this.CaseSensitive == true)
|
||||
{
|
||||
return System.StringComparison.Ordinal;
|
||||
}
|
||||
else
|
||||
{
|
||||
return System.StringComparison.OrdinalIgnoreCase;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal Type OwnerType => _myOwnerType;
|
||||
|
||||
internal bool IsGeneric
|
||||
{
|
||||
get { return _myProperties.GetValue<bool>("IsGeneric"); }
|
||||
set { _myProperties.SetValue("IsGeneric", value); }
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
99
PublicTypes/ExpressionParserOptions.cs
Normal file
99
PublicTypes/ExpressionParserOptions.cs
Normal file
@@ -0,0 +1,99 @@
|
||||
using System.Globalization;
|
||||
using Flee.InternalTypes;
|
||||
|
||||
namespace Flee.PublicTypes
|
||||
{
|
||||
public class ExpressionParserOptions
|
||||
{
|
||||
private PropertyDictionary _myProperties;
|
||||
private readonly ExpressionContext _myOwner;
|
||||
private readonly CultureInfo _myParseCulture;
|
||||
|
||||
private NumberStyles NumberStyles = NumberStyles.AllowDecimalPoint | NumberStyles.AllowExponent | NumberStyles.None;
|
||||
internal ExpressionParserOptions(ExpressionContext owner)
|
||||
{
|
||||
_myOwner = owner;
|
||||
_myProperties = new PropertyDictionary();
|
||||
_myParseCulture = (CultureInfo)CultureInfo.InvariantCulture.Clone();
|
||||
this.InitializeProperties();
|
||||
}
|
||||
|
||||
#region "Methods - Public"
|
||||
|
||||
public void RecreateParser()
|
||||
{
|
||||
_myOwner.RecreateParser();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region "Methods - Internal"
|
||||
|
||||
internal ExpressionParserOptions Clone()
|
||||
{
|
||||
ExpressionParserOptions copy = (ExpressionParserOptions)this.MemberwiseClone();
|
||||
copy._myProperties = _myProperties.Clone();
|
||||
return copy;
|
||||
}
|
||||
|
||||
internal double ParseDouble(string image)
|
||||
{
|
||||
return double.Parse(image, NumberStyles, _myParseCulture);
|
||||
}
|
||||
|
||||
internal float ParseSingle(string image)
|
||||
{
|
||||
return float.Parse(image, NumberStyles, _myParseCulture);
|
||||
}
|
||||
|
||||
internal decimal ParseDecimal(string image)
|
||||
{
|
||||
return decimal.Parse(image, NumberStyles, _myParseCulture);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region "Methods - Private"
|
||||
|
||||
private void InitializeProperties()
|
||||
{
|
||||
this.DateTimeFormat = "dd/MM/yyyy";
|
||||
this.RequireDigitsBeforeDecimalPoint = false;
|
||||
this.DecimalSeparator = '.';
|
||||
this.FunctionArgumentSeparator = ',';
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region "Properties - Public"
|
||||
|
||||
public string DateTimeFormat
|
||||
{
|
||||
get { return _myProperties.GetValue<string>("DateTimeFormat"); }
|
||||
set { _myProperties.SetValue("DateTimeFormat", value); }
|
||||
}
|
||||
|
||||
public bool RequireDigitsBeforeDecimalPoint
|
||||
{
|
||||
get { return _myProperties.GetValue<bool>("RequireDigitsBeforeDecimalPoint"); }
|
||||
set { _myProperties.SetValue("RequireDigitsBeforeDecimalPoint", value); }
|
||||
}
|
||||
|
||||
public char DecimalSeparator
|
||||
{
|
||||
get { return _myProperties.GetValue<char>("DecimalSeparator"); }
|
||||
set
|
||||
{
|
||||
_myProperties.SetValue("DecimalSeparator", value);
|
||||
_myParseCulture.NumberFormat.NumberDecimalSeparator = value.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
public char FunctionArgumentSeparator
|
||||
{
|
||||
get { return _myProperties.GetValue<char>("FunctionArgumentSeparator"); }
|
||||
set { _myProperties.SetValue("FunctionArgumentSeparator", value); }
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
417
PublicTypes/ImportTypes.cs
Normal file
417
PublicTypes/ImportTypes.cs
Normal file
@@ -0,0 +1,417 @@
|
||||
using System.Reflection;
|
||||
using Flee.InternalTypes;
|
||||
using Flee.Resources;
|
||||
|
||||
namespace Flee.PublicTypes
|
||||
{
|
||||
public abstract class ImportBase : IEnumerable<ImportBase>, IEquatable<ImportBase>
|
||||
{
|
||||
private ExpressionContext _myContext;
|
||||
|
||||
internal ImportBase()
|
||||
{
|
||||
}
|
||||
|
||||
#region "Methods - Non Public"
|
||||
internal virtual void SetContext(ExpressionContext context)
|
||||
{
|
||||
_myContext = context;
|
||||
this.Validate();
|
||||
}
|
||||
|
||||
internal abstract void Validate();
|
||||
|
||||
protected abstract void AddMembers(string memberName, MemberTypes memberType, ICollection<MemberInfo> dest);
|
||||
protected abstract void AddMembers(MemberTypes memberType, ICollection<MemberInfo> dest);
|
||||
|
||||
internal ImportBase Clone()
|
||||
{
|
||||
return (ImportBase)this.MemberwiseClone();
|
||||
}
|
||||
|
||||
protected static void AddImportMembers(ImportBase import, string memberName, MemberTypes memberType, ICollection<MemberInfo> dest)
|
||||
{
|
||||
import.AddMembers(memberName, memberType, dest);
|
||||
}
|
||||
|
||||
protected static void AddImportMembers(ImportBase import, MemberTypes memberType, ICollection<MemberInfo> dest)
|
||||
{
|
||||
import.AddMembers(memberType, dest);
|
||||
}
|
||||
|
||||
protected static void AddMemberRange(ICollection<MemberInfo> members, ICollection<MemberInfo> dest)
|
||||
{
|
||||
foreach (MemberInfo mi in members)
|
||||
{
|
||||
dest.Add(mi);
|
||||
}
|
||||
}
|
||||
|
||||
protected bool AlwaysMemberFilter(MemberInfo member, object criteria)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
internal abstract bool IsMatch(string name);
|
||||
internal abstract Type FindType(string typename);
|
||||
|
||||
internal virtual ImportBase FindImport(string name)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
internal MemberInfo[] FindMembers(string memberName, MemberTypes memberType)
|
||||
{
|
||||
List<MemberInfo> found = new List<MemberInfo>();
|
||||
this.AddMembers(memberName, memberType, found);
|
||||
return found.ToArray();
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region "Methods - Public"
|
||||
public MemberInfo[] GetMembers(MemberTypes memberType)
|
||||
{
|
||||
List<MemberInfo> found = new List<MemberInfo>();
|
||||
this.AddMembers(memberType, found);
|
||||
return found.ToArray();
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region "IEnumerable Implementation"
|
||||
public virtual System.Collections.Generic.IEnumerator<ImportBase> GetEnumerator()
|
||||
{
|
||||
List<ImportBase> coll = new List<ImportBase>();
|
||||
return coll.GetEnumerator();
|
||||
}
|
||||
|
||||
private System.Collections.IEnumerator GetEnumerator1()
|
||||
{
|
||||
return this.GetEnumerator();
|
||||
}
|
||||
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator1();
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region "IEquatable Implementation"
|
||||
public bool Equals(ImportBase other)
|
||||
{
|
||||
return this.EqualsInternal(other);
|
||||
}
|
||||
|
||||
protected abstract bool EqualsInternal(ImportBase import);
|
||||
#endregion
|
||||
|
||||
#region "Properties - Protected"
|
||||
protected ExpressionContext Context => _myContext;
|
||||
|
||||
#endregion
|
||||
|
||||
#region "Properties - Public"
|
||||
public abstract string Name { get; }
|
||||
|
||||
public virtual bool IsContainer => false;
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
public sealed class TypeImport : ImportBase
|
||||
{
|
||||
private readonly Type _myType;
|
||||
private readonly BindingFlags _myBindFlags;
|
||||
private readonly bool _myUseTypeNameAsNamespace;
|
||||
public TypeImport(Type importType) : this(importType, false)
|
||||
{
|
||||
}
|
||||
|
||||
public TypeImport(Type importType, bool useTypeNameAsNamespace) : this(importType, BindingFlags.Public | BindingFlags.Static, useTypeNameAsNamespace)
|
||||
{
|
||||
}
|
||||
|
||||
#region "Methods - Non Public"
|
||||
internal TypeImport(Type t, BindingFlags flags, bool useTypeNameAsNamespace)
|
||||
{
|
||||
Utility.AssertNotNull(t, "t");
|
||||
_myType = t;
|
||||
_myBindFlags = flags;
|
||||
_myUseTypeNameAsNamespace = useTypeNameAsNamespace;
|
||||
}
|
||||
|
||||
internal override void Validate()
|
||||
{
|
||||
this.Context.AssertTypeIsAccessible(_myType);
|
||||
}
|
||||
|
||||
protected override void AddMembers(string memberName, MemberTypes memberType, ICollection<MemberInfo> dest)
|
||||
{
|
||||
MemberInfo[] members = _myType.FindMembers(memberType, _myBindFlags, this.Context.Options.MemberFilter, memberName);
|
||||
ImportBase.AddMemberRange(members, dest);
|
||||
}
|
||||
|
||||
protected override void AddMembers(MemberTypes memberType, ICollection<MemberInfo> dest)
|
||||
{
|
||||
if (_myUseTypeNameAsNamespace == false)
|
||||
{
|
||||
MemberInfo[] members = _myType.FindMembers(memberType, _myBindFlags, this.AlwaysMemberFilter, null);
|
||||
ImportBase.AddMemberRange(members, dest);
|
||||
}
|
||||
}
|
||||
|
||||
internal override bool IsMatch(string name)
|
||||
{
|
||||
if (_myUseTypeNameAsNamespace == true)
|
||||
{
|
||||
return string.Equals(_myType.Name, name, this.Context.Options.MemberStringComparison);
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
internal override Type FindType(string typeName)
|
||||
{
|
||||
if (string.Equals(typeName, _myType.Name, this.Context.Options.MemberStringComparison) == true)
|
||||
{
|
||||
return _myType;
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool EqualsInternal(ImportBase import)
|
||||
{
|
||||
TypeImport otherSameType = import as TypeImport;
|
||||
return (otherSameType != null) && object.ReferenceEquals(_myType, otherSameType._myType);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region "Methods - Public"
|
||||
public override IEnumerator<ImportBase> GetEnumerator()
|
||||
{
|
||||
if (_myUseTypeNameAsNamespace == true)
|
||||
{
|
||||
List<ImportBase> coll = new List<ImportBase>();
|
||||
coll.Add(new TypeImport(_myType, false));
|
||||
return coll.GetEnumerator();
|
||||
}
|
||||
else
|
||||
{
|
||||
return base.GetEnumerator();
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region "Properties - Public"
|
||||
public override bool IsContainer => _myUseTypeNameAsNamespace;
|
||||
|
||||
public override string Name => _myType.Name;
|
||||
|
||||
public Type Target => _myType;
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
public sealed class MethodImport : ImportBase
|
||||
{
|
||||
|
||||
private readonly MethodInfo _myMethod;
|
||||
public MethodImport(MethodInfo importMethod)
|
||||
{
|
||||
Utility.AssertNotNull(importMethod, "importMethod");
|
||||
_myMethod = importMethod;
|
||||
}
|
||||
|
||||
internal override void Validate()
|
||||
{
|
||||
this.Context.AssertTypeIsAccessible(_myMethod.ReflectedType);
|
||||
}
|
||||
|
||||
protected override void AddMembers(string memberName, MemberTypes memberType, ICollection<MemberInfo> dest)
|
||||
{
|
||||
if (string.Equals(memberName, _myMethod.Name, this.Context.Options.MemberStringComparison) == true && (memberType & MemberTypes.Method) != 0)
|
||||
{
|
||||
dest.Add(_myMethod);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void AddMembers(MemberTypes memberType, ICollection<MemberInfo> dest)
|
||||
{
|
||||
if ((memberType & MemberTypes.Method) != 0)
|
||||
{
|
||||
dest.Add(_myMethod);
|
||||
}
|
||||
}
|
||||
|
||||
internal override bool IsMatch(string name)
|
||||
{
|
||||
return string.Equals(_myMethod.Name, name, this.Context.Options.MemberStringComparison);
|
||||
}
|
||||
|
||||
internal override Type FindType(string typeName)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
protected override bool EqualsInternal(ImportBase import)
|
||||
{
|
||||
MethodImport otherSameType = import as MethodImport;
|
||||
return (otherSameType != null) && _myMethod.MethodHandle.Equals(otherSameType._myMethod.MethodHandle);
|
||||
}
|
||||
|
||||
public override string Name => _myMethod.Name;
|
||||
|
||||
public MethodInfo Target => _myMethod;
|
||||
}
|
||||
|
||||
public sealed class NamespaceImport : ImportBase, ICollection<ImportBase>
|
||||
{
|
||||
private readonly string _myNamespace;
|
||||
private readonly List<ImportBase> _myImports;
|
||||
public NamespaceImport(string importNamespace)
|
||||
{
|
||||
Utility.AssertNotNull(importNamespace, "importNamespace");
|
||||
if (importNamespace.Length == 0)
|
||||
{
|
||||
string msg = Utility.GetGeneralErrorMessage(GeneralErrorResourceKeys.InvalidNamespaceName);
|
||||
throw new ArgumentException(msg);
|
||||
}
|
||||
|
||||
_myNamespace = importNamespace;
|
||||
_myImports = new List<ImportBase>();
|
||||
}
|
||||
|
||||
internal override void SetContext(ExpressionContext context)
|
||||
{
|
||||
base.SetContext(context);
|
||||
|
||||
foreach (ImportBase import in _myImports)
|
||||
{
|
||||
import.SetContext(context);
|
||||
}
|
||||
}
|
||||
|
||||
internal override void Validate()
|
||||
{
|
||||
}
|
||||
|
||||
protected override void AddMembers(string memberName, MemberTypes memberType, ICollection<MemberInfo> dest)
|
||||
{
|
||||
foreach (ImportBase import in this.NonContainerImports)
|
||||
{
|
||||
AddImportMembers(import, memberName, memberType, dest);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void AddMembers(MemberTypes memberType, ICollection<MemberInfo> dest)
|
||||
{
|
||||
}
|
||||
|
||||
internal override Type FindType(string typeName)
|
||||
{
|
||||
foreach (ImportBase import in this.NonContainerImports)
|
||||
{
|
||||
Type t = import.FindType(typeName);
|
||||
|
||||
if ((t != null))
|
||||
{
|
||||
return t;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
internal override ImportBase FindImport(string name)
|
||||
{
|
||||
foreach (ImportBase import in _myImports)
|
||||
{
|
||||
if (import.IsMatch(name) == true)
|
||||
{
|
||||
return import;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
internal override bool IsMatch(string name)
|
||||
{
|
||||
return string.Equals(_myNamespace, name, this.Context.Options.MemberStringComparison);
|
||||
}
|
||||
|
||||
private ICollection<ImportBase> NonContainerImports
|
||||
{
|
||||
get
|
||||
{
|
||||
List<ImportBase> found = new List<ImportBase>();
|
||||
|
||||
foreach (ImportBase import in _myImports)
|
||||
{
|
||||
if (import.IsContainer == false)
|
||||
{
|
||||
found.Add(import);
|
||||
}
|
||||
}
|
||||
|
||||
return found;
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool EqualsInternal(ImportBase import)
|
||||
{
|
||||
NamespaceImport otherSameType = import as NamespaceImport;
|
||||
return (otherSameType != null) && _myNamespace.Equals(otherSameType._myNamespace, this.Context.Options.MemberStringComparison);
|
||||
}
|
||||
|
||||
public override bool IsContainer => true;
|
||||
|
||||
public override string Name => _myNamespace;
|
||||
|
||||
#region "ICollection implementation"
|
||||
public void Add(ImportBase item)
|
||||
{
|
||||
Utility.AssertNotNull(item, "item");
|
||||
|
||||
if ((this.Context != null))
|
||||
{
|
||||
item.SetContext(this.Context);
|
||||
}
|
||||
|
||||
_myImports.Add(item);
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
_myImports.Clear();
|
||||
}
|
||||
|
||||
public bool Contains(ImportBase item)
|
||||
{
|
||||
return _myImports.Contains(item);
|
||||
}
|
||||
|
||||
public void CopyTo(ImportBase[] array, int arrayIndex)
|
||||
{
|
||||
_myImports.CopyTo(array, arrayIndex);
|
||||
}
|
||||
|
||||
public bool Remove(ImportBase item)
|
||||
{
|
||||
return _myImports.Remove(item);
|
||||
}
|
||||
|
||||
public override System.Collections.Generic.IEnumerator<ImportBase> GetEnumerator()
|
||||
{
|
||||
return _myImports.GetEnumerator();
|
||||
}
|
||||
|
||||
public int Count => _myImports.Count;
|
||||
|
||||
public bool IsReadOnly => false;
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
177
PublicTypes/Miscellaneous.cs
Normal file
177
PublicTypes/Miscellaneous.cs
Normal file
@@ -0,0 +1,177 @@
|
||||
namespace Flee.PublicTypes
|
||||
{
|
||||
public interface IExpression
|
||||
{
|
||||
IExpression Clone();
|
||||
string Text { get; }
|
||||
ExpressionInfo Info { get; }
|
||||
ExpressionContext Context { get; }
|
||||
object Owner { get; set; }
|
||||
}
|
||||
|
||||
public interface IDynamicExpression : IExpression
|
||||
{
|
||||
object Evaluate();
|
||||
}
|
||||
|
||||
public interface IGenericExpression<T> : IExpression
|
||||
{
|
||||
T Evaluate();
|
||||
}
|
||||
|
||||
public sealed class ExpressionInfo
|
||||
{
|
||||
|
||||
|
||||
private readonly IDictionary<string, object> _myData;
|
||||
internal ExpressionInfo()
|
||||
{
|
||||
_myData = new Dictionary<string, object>
|
||||
{
|
||||
{"ReferencedVariables", new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)}
|
||||
};
|
||||
}
|
||||
|
||||
internal void AddReferencedVariable(string name)
|
||||
{
|
||||
IDictionary<string, string> dict = (IDictionary<string, string>)_myData["ReferencedVariables"];
|
||||
dict[name] = name;
|
||||
}
|
||||
|
||||
public string[] GetReferencedVariables()
|
||||
{
|
||||
IDictionary<string, string> dict = (IDictionary<string, string>)_myData["ReferencedVariables"];
|
||||
string[] arr = new string[dict.Count];
|
||||
dict.Keys.CopyTo(arr, 0);
|
||||
return arr;
|
||||
}
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Method | AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
|
||||
public sealed class ExpressionOwnerMemberAccessAttribute : Attribute
|
||||
{
|
||||
|
||||
|
||||
private readonly bool _myAllowAccess;
|
||||
public ExpressionOwnerMemberAccessAttribute(bool allowAccess)
|
||||
{
|
||||
_myAllowAccess = allowAccess;
|
||||
}
|
||||
|
||||
internal bool AllowAccess => _myAllowAccess;
|
||||
}
|
||||
|
||||
public class ResolveVariableTypeEventArgs : EventArgs
|
||||
{
|
||||
private readonly string _myName;
|
||||
private Type _myType;
|
||||
internal ResolveVariableTypeEventArgs(string name)
|
||||
{
|
||||
this._myName = name;
|
||||
}
|
||||
|
||||
public string VariableName => _myName;
|
||||
|
||||
public Type VariableType
|
||||
{
|
||||
get { return _myType; }
|
||||
set { _myType = value; }
|
||||
}
|
||||
}
|
||||
|
||||
public class ResolveVariableValueEventArgs : EventArgs
|
||||
{
|
||||
private readonly string _myName;
|
||||
private readonly Type _myType;
|
||||
|
||||
private object MyValue;
|
||||
internal ResolveVariableValueEventArgs(string name, Type t)
|
||||
{
|
||||
_myName = name;
|
||||
_myType = t;
|
||||
}
|
||||
|
||||
public string VariableName
|
||||
{
|
||||
get { return _myName; }
|
||||
}
|
||||
|
||||
public Type VariableType
|
||||
{
|
||||
get { return _myType; }
|
||||
}
|
||||
|
||||
public object VariableValue
|
||||
{
|
||||
get { return MyValue; }
|
||||
set { MyValue = value; }
|
||||
}
|
||||
}
|
||||
|
||||
public class ResolveFunctionEventArgs : EventArgs
|
||||
{
|
||||
|
||||
private readonly string MyName;
|
||||
private readonly Type[] MyArgumentTypes;
|
||||
|
||||
private Type _myReturnType;
|
||||
internal ResolveFunctionEventArgs(string name, Type[] argumentTypes)
|
||||
{
|
||||
MyName = name;
|
||||
MyArgumentTypes = argumentTypes;
|
||||
}
|
||||
|
||||
public string FunctionName
|
||||
{
|
||||
get { return MyName; }
|
||||
}
|
||||
|
||||
public Type[] ArgumentTypes
|
||||
{
|
||||
get { return MyArgumentTypes; }
|
||||
}
|
||||
|
||||
public Type ReturnType
|
||||
{
|
||||
get { return _myReturnType; }
|
||||
set { _myReturnType = value; }
|
||||
}
|
||||
}
|
||||
|
||||
public class InvokeFunctionEventArgs : EventArgs
|
||||
{
|
||||
|
||||
private readonly string _myName;
|
||||
private readonly object[] _myArguments;
|
||||
|
||||
private object _myFunctionResult;
|
||||
internal InvokeFunctionEventArgs(string name, object[] arguments)
|
||||
{
|
||||
_myName = name;
|
||||
_myArguments = arguments;
|
||||
}
|
||||
|
||||
public string FunctionName
|
||||
{
|
||||
get { return _myName; }
|
||||
}
|
||||
|
||||
public object[] Arguments
|
||||
{
|
||||
get { return _myArguments; }
|
||||
}
|
||||
|
||||
public object Result
|
||||
{
|
||||
get { return _myFunctionResult; }
|
||||
set { _myFunctionResult = value; }
|
||||
}
|
||||
}
|
||||
|
||||
public enum RealLiteralDataType
|
||||
{
|
||||
Single,
|
||||
Double,
|
||||
Decimal
|
||||
}
|
||||
}
|
||||
404
PublicTypes/VariableCollection.cs
Normal file
404
PublicTypes/VariableCollection.cs
Normal file
@@ -0,0 +1,404 @@
|
||||
using Flee.InternalTypes;
|
||||
using Flee.Resources;
|
||||
using System.ComponentModel;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Flee.PublicTypes
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public sealed class VariableCollection : IDictionary<string, object>
|
||||
{
|
||||
private IDictionary<string, IVariable> _myVariables;
|
||||
private readonly ExpressionContext _myContext;
|
||||
|
||||
public event EventHandler<ResolveVariableTypeEventArgs> ResolveVariableType;
|
||||
|
||||
public event EventHandler<ResolveVariableValueEventArgs> ResolveVariableValue;
|
||||
|
||||
public event EventHandler<ResolveFunctionEventArgs> ResolveFunction;
|
||||
|
||||
public event EventHandler<InvokeFunctionEventArgs> InvokeFunction;
|
||||
|
||||
internal VariableCollection(ExpressionContext context)
|
||||
{
|
||||
_myContext = context;
|
||||
this.CreateDictionary();
|
||||
this.HookOptions();
|
||||
}
|
||||
|
||||
#region "Methods - Non Public"
|
||||
|
||||
private void HookOptions()
|
||||
{
|
||||
_myContext.Options.CaseSensitiveChanged += OnOptionsCaseSensitiveChanged;
|
||||
}
|
||||
|
||||
private void CreateDictionary()
|
||||
{
|
||||
_myVariables = new Dictionary<string, IVariable>(_myContext.Options.StringComparer);
|
||||
}
|
||||
|
||||
private void OnOptionsCaseSensitiveChanged(object sender, EventArgs e)
|
||||
{
|
||||
this.CreateDictionary();
|
||||
}
|
||||
|
||||
internal void Copy(VariableCollection dest)
|
||||
{
|
||||
dest.CreateDictionary();
|
||||
dest.HookOptions();
|
||||
|
||||
foreach (KeyValuePair<string, IVariable> pair in _myVariables)
|
||||
{
|
||||
IVariable copyVariable = pair.Value.Clone();
|
||||
dest._myVariables.Add(pair.Key, copyVariable);
|
||||
}
|
||||
}
|
||||
|
||||
internal void DefineVariableInternal(string name, Type variableType, object variableValue)
|
||||
{
|
||||
Utility.AssertNotNull(variableType, "variableType");
|
||||
|
||||
if (_myVariables.ContainsKey(name) == true)
|
||||
{
|
||||
string msg = Utility.GetGeneralErrorMessage(GeneralErrorResourceKeys.VariableWithNameAlreadyDefined, name);
|
||||
throw new ArgumentException(msg);
|
||||
}
|
||||
|
||||
IVariable v = this.CreateVariable(variableType, variableValue);
|
||||
_myVariables.Add(name, v);
|
||||
}
|
||||
|
||||
internal Type GetVariableTypeInternal(string name)
|
||||
{
|
||||
IVariable value = null;
|
||||
bool success = _myVariables.TryGetValue(name, out value);
|
||||
|
||||
if (success == true)
|
||||
{
|
||||
return value.VariableType;
|
||||
}
|
||||
|
||||
ResolveVariableTypeEventArgs args = new ResolveVariableTypeEventArgs(name);
|
||||
ResolveVariableType?.Invoke(this, args);
|
||||
|
||||
return args.VariableType;
|
||||
}
|
||||
|
||||
private IVariable GetVariable(string name, bool throwOnNotFound)
|
||||
{
|
||||
IVariable value = null;
|
||||
bool success = _myVariables.TryGetValue(name, out value);
|
||||
|
||||
if (success == false & throwOnNotFound == true)
|
||||
{
|
||||
string msg = Utility.GetGeneralErrorMessage(GeneralErrorResourceKeys.UndefinedVariable, name);
|
||||
throw new ArgumentException(msg);
|
||||
}
|
||||
else
|
||||
{
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
private IVariable CreateVariable(Type variableValueType, object variableValue)
|
||||
{
|
||||
Type variableType = default(Type);
|
||||
|
||||
// Is the variable value an expression?
|
||||
IExpression expression = variableValue as IExpression;
|
||||
ExpressionOptions options = null;
|
||||
|
||||
if (expression != null)
|
||||
{
|
||||
options = expression.Context.Options;
|
||||
// Get its result type
|
||||
variableValueType = options.ResultType;
|
||||
|
||||
// Create a variable that wraps the expression
|
||||
|
||||
if (options.IsGeneric == false)
|
||||
{
|
||||
variableType = typeof(DynamicExpressionVariable<>);
|
||||
}
|
||||
else
|
||||
{
|
||||
variableType = typeof(GenericExpressionVariable<>);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Create a variable for a regular value
|
||||
_myContext.AssertTypeIsAccessible(variableValueType);
|
||||
variableType = typeof(GenericVariable<>);
|
||||
}
|
||||
|
||||
// Create the generic variable instance
|
||||
variableType = variableType.MakeGenericType(variableValueType);
|
||||
IVariable v = (IVariable)Activator.CreateInstance(variableType);
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
internal Type ResolveOnDemandFunction(string name, Type[] argumentTypes)
|
||||
{
|
||||
ResolveFunctionEventArgs args = new ResolveFunctionEventArgs(name, argumentTypes);
|
||||
ResolveFunction?.Invoke(this, args);
|
||||
return args.ReturnType;
|
||||
}
|
||||
|
||||
private static T ReturnGenericValue<T>(object value)
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
return default(T);
|
||||
}
|
||||
else
|
||||
{
|
||||
return (T)value;
|
||||
}
|
||||
}
|
||||
|
||||
private static void ValidateSetValueType(Type requiredType, object value)
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
// Can always assign null value
|
||||
return;
|
||||
}
|
||||
|
||||
Type valueType = value.GetType();
|
||||
|
||||
if (requiredType.IsAssignableFrom(valueType) == false)
|
||||
{
|
||||
string msg = Utility.GetGeneralErrorMessage(GeneralErrorResourceKeys.VariableValueNotAssignableToType, valueType.Name, requiredType.Name);
|
||||
throw new ArgumentException(msg);
|
||||
}
|
||||
}
|
||||
|
||||
internal static MethodInfo GetVariableLoadMethod(Type variableType)
|
||||
{
|
||||
MethodInfo mi = typeof(VariableCollection).GetMethod("GetVariableValueInternal", BindingFlags.Public | BindingFlags.Instance);
|
||||
mi = mi.MakeGenericMethod(variableType);
|
||||
return mi;
|
||||
}
|
||||
|
||||
internal static MethodInfo GetFunctionInvokeMethod(Type returnType)
|
||||
{
|
||||
MethodInfo mi = typeof(VariableCollection).GetMethod("GetFunctionResultInternal", BindingFlags.Public | BindingFlags.Instance);
|
||||
mi = mi.MakeGenericMethod(returnType);
|
||||
return mi;
|
||||
}
|
||||
|
||||
internal static MethodInfo GetVirtualPropertyLoadMethod(Type returnType)
|
||||
{
|
||||
MethodInfo mi = typeof(VariableCollection).GetMethod("GetVirtualPropertyValueInternal", BindingFlags.Public | BindingFlags.Instance);
|
||||
mi = mi.MakeGenericMethod(returnType);
|
||||
return mi;
|
||||
}
|
||||
|
||||
private Dictionary<string, object> GetNameValueDictionary()
|
||||
{
|
||||
Dictionary<string, object> dict = new Dictionary<string, object>();
|
||||
|
||||
foreach (KeyValuePair<string, IVariable> pair in _myVariables)
|
||||
{
|
||||
dict.Add(pair.Key, pair.Value.ValueAsObject);
|
||||
}
|
||||
|
||||
return dict;
|
||||
}
|
||||
|
||||
#endregion "Methods - Non Public"
|
||||
|
||||
#region "Methods - Public"
|
||||
|
||||
public Type GetVariableType(string name)
|
||||
{
|
||||
IVariable v = this.GetVariable(name, true);
|
||||
return v.VariableType;
|
||||
}
|
||||
|
||||
public void DefineVariable(string name, Type variableType)
|
||||
{
|
||||
this.DefineVariableInternal(name, variableType, null);
|
||||
}
|
||||
|
||||
public T GetVariableValueInternal<T>(string name)
|
||||
{
|
||||
if (_myVariables.TryGetValue(name, out IVariable variable))
|
||||
{
|
||||
if (variable is IGenericVariable<T> generic)
|
||||
{
|
||||
return (T)generic.GetValue();
|
||||
}
|
||||
}
|
||||
|
||||
GenericVariable<T> result = new GenericVariable<T>();
|
||||
GenericVariable<T> vTemp = new GenericVariable<T>();
|
||||
ResolveVariableValueEventArgs args = new ResolveVariableValueEventArgs(name, typeof(T));
|
||||
ResolveVariableValue?.Invoke(this, args);
|
||||
|
||||
ValidateSetValueType(typeof(T), args.VariableValue);
|
||||
vTemp.ValueAsObject = args.VariableValue;
|
||||
result = vTemp;
|
||||
return (T)result.GetValue();
|
||||
}
|
||||
|
||||
public T GetVirtualPropertyValueInternal<T>(string name, object component)
|
||||
{
|
||||
PropertyDescriptorCollection coll = TypeDescriptor.GetProperties(component);
|
||||
PropertyDescriptor pd = coll.Find(name, true);
|
||||
|
||||
object value = pd.GetValue(component);
|
||||
ValidateSetValueType(typeof(T), value);
|
||||
return ReturnGenericValue<T>(value);
|
||||
}
|
||||
|
||||
public T GetFunctionResultInternal<T>(string name, object[] arguments)
|
||||
{
|
||||
InvokeFunctionEventArgs args = new InvokeFunctionEventArgs(name, arguments);
|
||||
if (InvokeFunction != null)
|
||||
{
|
||||
InvokeFunction(this, args);
|
||||
}
|
||||
|
||||
object result = args.Result;
|
||||
ValidateSetValueType(typeof(T), result);
|
||||
|
||||
return ReturnGenericValue<T>(result);
|
||||
}
|
||||
|
||||
#endregion "Methods - Public"
|
||||
|
||||
#region "IDictionary Implementation"
|
||||
|
||||
private void Add1(System.Collections.Generic.KeyValuePair<string, object> item)
|
||||
{
|
||||
this.Add(item.Key, item.Value);
|
||||
}
|
||||
|
||||
void System.Collections.Generic.ICollection<System.Collections.Generic.KeyValuePair<string, object>>.Add(System.Collections.Generic.KeyValuePair<string, object> item)
|
||||
{
|
||||
Add1(item);
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
_myVariables.Clear();
|
||||
}
|
||||
|
||||
private bool Contains1(System.Collections.Generic.KeyValuePair<string, object> item)
|
||||
{
|
||||
return this.ContainsKey(item.Key);
|
||||
}
|
||||
|
||||
bool System.Collections.Generic.ICollection<System.Collections.Generic.KeyValuePair<string, object>>.Contains(System.Collections.Generic.KeyValuePair<string, object> item)
|
||||
{
|
||||
return Contains1(item);
|
||||
}
|
||||
|
||||
private void CopyTo(System.Collections.Generic.KeyValuePair<string, object>[] array, int arrayIndex)
|
||||
{
|
||||
Dictionary<string, object> dict = this.GetNameValueDictionary();
|
||||
ICollection<KeyValuePair<string, object>> coll = dict;
|
||||
coll.CopyTo(array, arrayIndex);
|
||||
}
|
||||
|
||||
private bool Remove1(System.Collections.Generic.KeyValuePair<string, object> item)
|
||||
{
|
||||
return this.Remove(item.Key);
|
||||
}
|
||||
|
||||
bool System.Collections.Generic.ICollection<System.Collections.Generic.KeyValuePair<string, object>>.Remove(System.Collections.Generic.KeyValuePair<string, object> item)
|
||||
{
|
||||
return Remove1(item);
|
||||
}
|
||||
|
||||
public void Add(string name, object value)
|
||||
{
|
||||
Utility.AssertNotNull(value, "value");
|
||||
this.DefineVariableInternal(name, value.GetType(), value);
|
||||
this[name] = value;
|
||||
}
|
||||
|
||||
public bool ContainsKey(string name)
|
||||
{
|
||||
return _myVariables.ContainsKey(name);
|
||||
}
|
||||
|
||||
public bool Remove(string name)
|
||||
{
|
||||
return _myVariables.Remove(name);
|
||||
}
|
||||
|
||||
public bool TryGetValue(string key, out object value)
|
||||
{
|
||||
IVariable v = this.GetVariable(key, false);
|
||||
value = v?.ValueAsObject;
|
||||
return v != null;
|
||||
}
|
||||
|
||||
public System.Collections.Generic.IEnumerator<System.Collections.Generic.KeyValuePair<string, object>> GetEnumerator()
|
||||
{
|
||||
Dictionary<string, object> dict = this.GetNameValueDictionary();
|
||||
return dict.GetEnumerator();
|
||||
}
|
||||
|
||||
private System.Collections.IEnumerator GetEnumerator1()
|
||||
{
|
||||
return this.GetEnumerator();
|
||||
}
|
||||
|
||||
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator1();
|
||||
}
|
||||
|
||||
public int Count => _myVariables.Count;
|
||||
|
||||
public bool IsReadOnly => false;
|
||||
|
||||
public object this[string name]
|
||||
{
|
||||
get
|
||||
{
|
||||
IVariable v = this.GetVariable(name, true);
|
||||
return v.ValueAsObject;
|
||||
}
|
||||
set
|
||||
{
|
||||
IVariable v = null;
|
||||
|
||||
if (_myVariables.TryGetValue(name, out v) == true)
|
||||
{
|
||||
v.ValueAsObject = value;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.Add(name, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public System.Collections.Generic.ICollection<string> Keys => _myVariables.Keys;
|
||||
|
||||
public System.Collections.Generic.ICollection<object> Values
|
||||
{
|
||||
get
|
||||
{
|
||||
Dictionary<string, object> dict = this.GetNameValueDictionary();
|
||||
return dict.Values;
|
||||
}
|
||||
}
|
||||
|
||||
void ICollection<KeyValuePair<string, object>>.CopyTo(KeyValuePair<string, object>[] array, int arrayIndex)
|
||||
{
|
||||
CopyTo(array, arrayIndex);
|
||||
}
|
||||
|
||||
#endregion "IDictionary Implementation"
|
||||
}
|
||||
}
|
||||
48
README.md
Normal file
48
README.md
Normal file
@@ -0,0 +1,48 @@
|
||||
# Flee (Supports Net6.0, Net5.0, Netstandard2.1, Netstandard2.0)
|
||||
Fast Lightweight Expression Evaluator.
|
||||
Convert this project vb.net to c#.
|
||||
|
||||
## Project Description
|
||||
Flee is an expression parser and evaluator for the .NET framework. It allows you to compute the value of string expressions such as sqrt(a^2 + b^2) at runtime. It uses a custom compiler, strongly-typed expression language, and lightweight codegen to compile expressions directly to IL. This means that expression evaluation is extremely fast and efficient.
|
||||
|
||||
## Features
|
||||
* Fast and efficient expression evaluation
|
||||
* Small, lightweight library
|
||||
* Compiles expressions to IL using a custom compiler, lightweight codegen, and the DynamicMethod class
|
||||
* Expressions (and the IL generated for them) are garbage-collected when no longer used
|
||||
* Does not create any dynamic assemblies that stay in memory
|
||||
* Backed by a comprehensive suite of unit tests
|
||||
* Culture-sensitive decimal point
|
||||
* Fine-grained control of what types an expression can use
|
||||
* Supports all arithmetic operations including the power (^) operator
|
||||
* Supports string, char, boolean, and floating-point literals
|
||||
* Supports 32/64 bit, signed/unsigned, and hex integer literals
|
||||
* Features a true conditional operator
|
||||
* Supports short-circuited logical operations
|
||||
* Supports arithmetic, comparison, implicit, and explicit overloaded operators
|
||||
* Variables of any type can be dynamically defined and used in expressions
|
||||
* CalculationEngine: Reference other expressions in an expression and recalculate in natural order
|
||||
* Expressions can index arrays and collections, access fields and properties, and call functions on various types
|
||||
* Generated IL can be saved to an assembly and viewed with a disassembler
|
||||
|
||||
### Installing Flee
|
||||
|
||||
You should install [Flee with NuGet](https://www.nuget.org/packages/Flee):
|
||||
|
||||
Install-Package Flee
|
||||
|
||||
Or via the .NET Core command line interface:
|
||||
|
||||
dotnet add package Flee
|
||||
|
||||
## NuGet Packages
|
||||
|
||||
| Name | NuGet |
|
||||
| :--- | :--- |
|
||||
| [Flee](https://www.nuget.org/packages/Flee) | [](https://www.nuget.org/packages/Flee)
|
||||
|
||||
## More information
|
||||
* [Examples](https://github.com/mparlak/Flee/wiki/Examples) to learn how to create and evaluate expressions.
|
||||
|
||||
## License
|
||||
Flee is licensed under the LGPL. This means that as long as you dynamically link (ie: add a reference) to the officially released assemblies, you can use it in commercial and non-commercial applications.
|
||||
Reference in New Issue
Block a user