From 284e7643450b274126eb845de3af36617fb66328 Mon Sep 17 00:00:00 2001 From: ninemine <1371605831@qq.com> Date: Wed, 8 Oct 2025 09:49:37 +0800 Subject: [PATCH] Flee --- CalcEngine/InternalTypes/DependencyManager.cs | 350 +++++ .../InternalTypes/IdentifierAnalyzer.cs | 105 ++ CalcEngine/InternalTypes/Miscellaneous.cs | 114 ++ CalcEngine/PublicTypes/BatchLoader.cs | 69 + CalcEngine/PublicTypes/CalculationEngine.cs | 360 +++++ CalcEngine/PublicTypes/Exceptions.cs | 54 + CalcEngine/PublicTypes/SimpleCalcEngine.cs | 121 ++ ExpressionElements/Arithmetic.cs | 327 ++++ ExpressionElements/Base/Binary.cs | 160 ++ ExpressionElements/Base/ExpressionElement.cs | 55 + ExpressionElements/Base/Literals/Integral.cs | 118 ++ .../Base/Literals/LiteralElement.cs | 107 ++ ExpressionElements/Base/Literals/Real.cs | 130 ++ ExpressionElements/Base/Member.cs | 358 +++++ ExpressionElements/Base/Unary.cs | 28 + ExpressionElements/Cast.cs | 513 ++++++ ExpressionElements/Compare.cs | 279 ++++ ExpressionElements/Conditional.cs | 75 + ExpressionElements/In.cs | 195 +++ ExpressionElements/Literals/Boolean.cs | 22 + ExpressionElements/Literals/Char.cs | 24 + ExpressionElements/Literals/DateTime.cs | 42 + ExpressionElements/Literals/Integral/Int32.cs | 84 + ExpressionElements/Literals/Integral/Int64.cs | 82 + .../Literals/Integral/UInt32.cs | 34 + .../Literals/Integral/UInt64.cs | 34 + ExpressionElements/Literals/Null.cs | 16 + ExpressionElements/Literals/Real/Decimal.cs | 76 + ExpressionElements/Literals/Real/Double.cs | 46 + ExpressionElements/Literals/Real/Single.cs | 45 + ExpressionElements/Literals/String.cs | 23 + ExpressionElements/Literals/TimeSpan.cs | 38 + ExpressionElements/LogicalBitwise/AndOr.cs | 348 ++++ ExpressionElements/LogicalBitwise/Not.cs | 46 + ExpressionElements/LogicalBitwise/Xor.cs | 44 + .../MemberElements/ArgumentList.cs | 59 + .../MemberElements/FunctionCall.cs | 409 +++++ .../MemberElements/Identifier.cs | 492 ++++++ ExpressionElements/MemberElements/Indexer.cs | 181 +++ .../MemberElements/InvocationList.cs | 120 ++ .../MemberElements/Miscellaneous.cs | 38 + ExpressionElements/Negate.cs | 56 + ExpressionElements/Root.cs | 45 + ExpressionElements/Shift.cs | 136 ++ InternalTypes/BranchManager.cs | 285 ++++ InternalTypes/Expression.cs | 215 +++ InternalTypes/FleeILGenerator.cs | 269 ++++ InternalTypes/ImplicitConversions.cs | 573 +++++++ InternalTypes/Miscellaneous.cs | 562 +++++++ InternalTypes/Utility.cs | 350 +++++ InternalTypes/VariableTypes.cs | 100 ++ Parsing/AlternativeElement.cs | 60 + Parsing/Analyzer.cs | 240 +++ Parsing/Automaton.cs | 111 ++ Parsing/CharacterSetElement.cs | 267 ++++ Parsing/CombineElement.cs | 58 + Parsing/CustomExpressionAnalyzer.cs | 596 +++++++ Parsing/CustomTokenPatterns.cs | 49 + Parsing/Element.cs | 19 + Parsing/Expression.grammar | 133 ++ Parsing/ExpressionAnalyzer.cs | 1395 +++++++++++++++++ Parsing/ExpressionConstants.cs | 78 + Parsing/ExpressionParser.cs | 460 ++++++ Parsing/ExpressionTokenizer.cs | 153 ++ Parsing/LookAheadReader.cs | 226 +++ Parsing/LookAheadSet.cs | 589 +++++++ Parsing/Matcher.cs | 107 ++ Parsing/Node.cs | 240 +++ Parsing/ParseException.cs | 250 +++ Parsing/Parser.cs | 492 ++++++ Parsing/ParserCreationException.cs | 216 +++ Parsing/ParserLogException.cs | 55 + Parsing/Production.cs | 70 + Parsing/ProductionPattern.cs | 213 +++ Parsing/ProductionPatternAlternative.cs | 211 +++ Parsing/ProductionPatternElement.cs | 138 ++ Parsing/ReaderBuffer.cs | 180 +++ Parsing/RecursiveDescentParser.cs | 648 ++++++++ Parsing/RegExp.cs | 505 ++++++ Parsing/RegExpException.cs | 113 ++ Parsing/RepeatElement.cs | 239 +++ Parsing/StackParser.cs | 761 +++++++++ Parsing/StringElement.cs | 64 + Parsing/Token.cs | 168 ++ Parsing/TokenMatch.cs | 31 + Parsing/TokenNFA.cs | 825 ++++++++++ Parsing/TokenPattern.cs | 303 ++++ Parsing/TokenRegExpParser.cs | 545 +++++++ Parsing/TokenStringDFA.cs | 213 +++ Parsing/Tokenizer.cs | 444 ++++++ PublicTypes/Exceptions.cs | 67 + PublicTypes/ExpressionContext.cs | 251 +++ PublicTypes/ExpressionImports.cs | 195 +++ PublicTypes/ExpressionOptions.cs | 207 +++ PublicTypes/ExpressionParserOptions.cs | 99 ++ PublicTypes/ImportTypes.cs | 417 +++++ PublicTypes/Miscellaneous.cs | 177 +++ PublicTypes/VariableCollection.cs | 404 +++++ README.md | 48 + 99 files changed, 21742 insertions(+) create mode 100644 CalcEngine/InternalTypes/DependencyManager.cs create mode 100644 CalcEngine/InternalTypes/IdentifierAnalyzer.cs create mode 100644 CalcEngine/InternalTypes/Miscellaneous.cs create mode 100644 CalcEngine/PublicTypes/BatchLoader.cs create mode 100644 CalcEngine/PublicTypes/CalculationEngine.cs create mode 100644 CalcEngine/PublicTypes/Exceptions.cs create mode 100644 CalcEngine/PublicTypes/SimpleCalcEngine.cs create mode 100644 ExpressionElements/Arithmetic.cs create mode 100644 ExpressionElements/Base/Binary.cs create mode 100644 ExpressionElements/Base/ExpressionElement.cs create mode 100644 ExpressionElements/Base/Literals/Integral.cs create mode 100644 ExpressionElements/Base/Literals/LiteralElement.cs create mode 100644 ExpressionElements/Base/Literals/Real.cs create mode 100644 ExpressionElements/Base/Member.cs create mode 100644 ExpressionElements/Base/Unary.cs create mode 100644 ExpressionElements/Cast.cs create mode 100644 ExpressionElements/Compare.cs create mode 100644 ExpressionElements/Conditional.cs create mode 100644 ExpressionElements/In.cs create mode 100644 ExpressionElements/Literals/Boolean.cs create mode 100644 ExpressionElements/Literals/Char.cs create mode 100644 ExpressionElements/Literals/DateTime.cs create mode 100644 ExpressionElements/Literals/Integral/Int32.cs create mode 100644 ExpressionElements/Literals/Integral/Int64.cs create mode 100644 ExpressionElements/Literals/Integral/UInt32.cs create mode 100644 ExpressionElements/Literals/Integral/UInt64.cs create mode 100644 ExpressionElements/Literals/Null.cs create mode 100644 ExpressionElements/Literals/Real/Decimal.cs create mode 100644 ExpressionElements/Literals/Real/Double.cs create mode 100644 ExpressionElements/Literals/Real/Single.cs create mode 100644 ExpressionElements/Literals/String.cs create mode 100644 ExpressionElements/Literals/TimeSpan.cs create mode 100644 ExpressionElements/LogicalBitwise/AndOr.cs create mode 100644 ExpressionElements/LogicalBitwise/Not.cs create mode 100644 ExpressionElements/LogicalBitwise/Xor.cs create mode 100644 ExpressionElements/MemberElements/ArgumentList.cs create mode 100644 ExpressionElements/MemberElements/FunctionCall.cs create mode 100644 ExpressionElements/MemberElements/Identifier.cs create mode 100644 ExpressionElements/MemberElements/Indexer.cs create mode 100644 ExpressionElements/MemberElements/InvocationList.cs create mode 100644 ExpressionElements/MemberElements/Miscellaneous.cs create mode 100644 ExpressionElements/Negate.cs create mode 100644 ExpressionElements/Root.cs create mode 100644 ExpressionElements/Shift.cs create mode 100644 InternalTypes/BranchManager.cs create mode 100644 InternalTypes/Expression.cs create mode 100644 InternalTypes/FleeILGenerator.cs create mode 100644 InternalTypes/ImplicitConversions.cs create mode 100644 InternalTypes/Miscellaneous.cs create mode 100644 InternalTypes/Utility.cs create mode 100644 InternalTypes/VariableTypes.cs create mode 100644 Parsing/AlternativeElement.cs create mode 100644 Parsing/Analyzer.cs create mode 100644 Parsing/Automaton.cs create mode 100644 Parsing/CharacterSetElement.cs create mode 100644 Parsing/CombineElement.cs create mode 100644 Parsing/CustomExpressionAnalyzer.cs create mode 100644 Parsing/CustomTokenPatterns.cs create mode 100644 Parsing/Element.cs create mode 100644 Parsing/Expression.grammar create mode 100644 Parsing/ExpressionAnalyzer.cs create mode 100644 Parsing/ExpressionConstants.cs create mode 100644 Parsing/ExpressionParser.cs create mode 100644 Parsing/ExpressionTokenizer.cs create mode 100644 Parsing/LookAheadReader.cs create mode 100644 Parsing/LookAheadSet.cs create mode 100644 Parsing/Matcher.cs create mode 100644 Parsing/Node.cs create mode 100644 Parsing/ParseException.cs create mode 100644 Parsing/Parser.cs create mode 100644 Parsing/ParserCreationException.cs create mode 100644 Parsing/ParserLogException.cs create mode 100644 Parsing/Production.cs create mode 100644 Parsing/ProductionPattern.cs create mode 100644 Parsing/ProductionPatternAlternative.cs create mode 100644 Parsing/ProductionPatternElement.cs create mode 100644 Parsing/ReaderBuffer.cs create mode 100644 Parsing/RecursiveDescentParser.cs create mode 100644 Parsing/RegExp.cs create mode 100644 Parsing/RegExpException.cs create mode 100644 Parsing/RepeatElement.cs create mode 100644 Parsing/StackParser.cs create mode 100644 Parsing/StringElement.cs create mode 100644 Parsing/Token.cs create mode 100644 Parsing/TokenMatch.cs create mode 100644 Parsing/TokenNFA.cs create mode 100644 Parsing/TokenPattern.cs create mode 100644 Parsing/TokenRegExpParser.cs create mode 100644 Parsing/TokenStringDFA.cs create mode 100644 Parsing/Tokenizer.cs create mode 100644 PublicTypes/Exceptions.cs create mode 100644 PublicTypes/ExpressionContext.cs create mode 100644 PublicTypes/ExpressionImports.cs create mode 100644 PublicTypes/ExpressionOptions.cs create mode 100644 PublicTypes/ExpressionParserOptions.cs create mode 100644 PublicTypes/ImportTypes.cs create mode 100644 PublicTypes/Miscellaneous.cs create mode 100644 PublicTypes/VariableCollection.cs create mode 100644 README.md diff --git a/CalcEngine/InternalTypes/DependencyManager.cs b/CalcEngine/InternalTypes/DependencyManager.cs new file mode 100644 index 0000000..55b5f97 --- /dev/null +++ b/CalcEngine/InternalTypes/DependencyManager.cs @@ -0,0 +1,350 @@ +using Flee.CalcEngine.PublicTypes; + +namespace Flee.CalcEngine.InternalTypes +{ + + /// + /// Keeps track of our dependencies + /// + /// + internal class DependencyManager + { + + /// + /// Map of a node and the nodes that depend on it + /// + private readonly Dictionary> _myDependentsMap; + private readonly IEqualityComparer _myEqualityComparer; + + /// + /// Map of a node and the number of nodes that point to it + /// + private readonly Dictionary _myPrecedentsMap; + public DependencyManager(IEqualityComparer comparer) + { + _myEqualityComparer = comparer; + _myDependentsMap = new Dictionary>(_myEqualityComparer); + _myPrecedentsMap = new Dictionary(_myEqualityComparer); + } + + private IDictionary CreateInnerDictionary() + { + return new Dictionary(_myEqualityComparer); + } + + private IDictionary GetInnerDictionary(T tail) + { + Dictionary 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 CloneDependents(T[] tails) + { + IDictionary seenNodes = this.CreateInnerDictionary(); + DependencyManager copy = new DependencyManager(_myEqualityComparer); + + foreach (T tail in tails) + { + this.CloneDependentsInternal(tail, copy, seenNodes); + } + + return copy; + } + + private void CloneDependentsInternal(T tail, DependencyManager target, IDictionary 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 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 value = _myDependentsMap[old]; + + _myDependentsMap.Remove(old); + _myDependentsMap.Add(replaceWith, value); + + foreach (Dictionary 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)this.CreateInnerDictionary()); + } + } + + public void AddDepedency(T tail, T head) + { + IDictionary innerDict = this.GetInnerDictionary(tail); + + if (innerDict.ContainsKey(head) == false) + { + innerDict.Add(head, head); + this.AddPrecedent(head); + } + } + + public void RemoveDependency(T tail, T head) + { + IDictionary innerDict = this.GetInnerDictionary(tail); + this.RemoveHead(head, innerDict); + } + + private void RemoveHead(T head, IDictionary dict) + { + if (dict.Remove(head) == true) + { + this.RemovePrecedent(head); + } + } + + public void Remove(T[] tails) + { + foreach (Dictionary 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 dest) + { + Dictionary innerDict = (Dictionary)this.GetInnerDictionary(tail); + dest.AddRange(innerDict.Keys); + } + + public T[] GetDependents(T tail) + { + Dictionary dependents = (Dictionary)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 dependents) + { + dependents[tail] = null; + Dictionary directDependents = (Dictionary)this.GetInnerDictionary(tail); + + foreach (T pair in directDependents.Keys) + { + this.GetDependentsRecursive(pair, dependents); + } + } + + public void GetDirectPrecedents(T head, IList dest) + { + foreach (T tail in _myDependentsMap.Keys) + { + Dictionary innerDict = (Dictionary)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 innerDict = (Dictionary)this.GetInnerDictionary(tail); + return innerDict.Count > 0; + } + + private string FormatValues(ICollection 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 ""; + } + else + { + return string.Join(",", strings); + } + } + + /// + /// Add all nodes that don't have any incoming edges into a queue + /// + /// + /// + public Queue GetSources(T[] rootTails) + { + Queue q = new Queue(); + + foreach (T rootTail in rootTails) + { + if (this.HasPrecedents(rootTail) == false) + { + q.Enqueue(rootTail); + } + } + + return q; + } + + public IList TopologicalSort(Queue sources) + { + IList output = new List(); + List directDependents = new List(); + + 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 list = new List(); + + foreach (KeyValuePair 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> 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; + } + +} + diff --git a/CalcEngine/InternalTypes/IdentifierAnalyzer.cs b/CalcEngine/InternalTypes/IdentifierAnalyzer.cs new file mode 100644 index 0000000..50c872f --- /dev/null +++ b/CalcEngine/InternalTypes/IdentifierAnalyzer.cs @@ -0,0 +1,105 @@ +using Flee.Parsing; +using Flee.PublicTypes; + +namespace Flee.CalcEngine.InternalTypes +{ + internal class IdentifierAnalyzer : Analyzer + { + + private readonly IDictionary _myIdentifiers; + private int _myMemberExpressionCount; + + private bool _myInFieldPropertyExpression; + public IdentifierAnalyzer() + { + _myIdentifiers = new Dictionary(); + } + + 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 GetIdentifiers(ExpressionContext context) + { + Dictionary dict = new Dictionary(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; + } + } +} diff --git a/CalcEngine/InternalTypes/Miscellaneous.cs b/CalcEngine/InternalTypes/Miscellaneous.cs new file mode 100644 index 0000000..0ff7f43 --- /dev/null +++ b/CalcEngine/InternalTypes/Miscellaneous.cs @@ -0,0 +1,114 @@ +using Flee.PublicTypes; + +namespace Flee.CalcEngine.InternalTypes +{ + internal class PairEqualityComparer : EqualityComparer + { + 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 : 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; + } + +} + diff --git a/CalcEngine/PublicTypes/BatchLoader.cs b/CalcEngine/PublicTypes/BatchLoader.cs new file mode 100644 index 0000000..9eafd7b --- /dev/null +++ b/CalcEngine/PublicTypes/BatchLoader.cs @@ -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 _myNameInfoMap; + + private readonly DependencyManager _myDependencies; + internal BatchLoader() + { + _myNameInfoMap = new Dictionary(StringComparer.OrdinalIgnoreCase); + _myDependencies = new DependencyManager(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 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 sources = _myDependencies.GetSources(tails); + + IList 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 GetReferences(string expression, ExpressionContext context) + { + IdentifierAnalyzer analyzer = context.ParseIdentifiers(expression); + + return analyzer.GetIdentifiers(context); + } + } + +} + diff --git a/CalcEngine/PublicTypes/CalculationEngine.cs b/CalcEngine/PublicTypes/CalculationEngine.cs new file mode 100644 index 0000000..838cc11 --- /dev/null +++ b/CalcEngine/PublicTypes/CalculationEngine.cs @@ -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 _myDependencies; + /// + /// Map of name to node + /// + private readonly Dictionary _myNameNodeMap; + #endregion + + #region "Events" + public event EventHandler NodeRecalculated; + #endregion + + #region "Constructor" + public CalculationEngine() + { + _myDependencies = new DependencyManager(new PairEqualityComparer()); + _myNameNodeMap = new Dictionary(StringComparer.OrdinalIgnoreCase); + } + #endregion + + #region "Methods - Private" + private void AddTemporaryHead(string headName) + { + GenericExpressionResultPair pair = new GenericExpressionResultPair(); + 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 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(); + } + + /// + /// Called by an expression when it references another expression in the engine + /// + /// + /// + 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(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 actualTail = (GenericExpressionResultPair)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 dependents = new List(); + + if ((pair != null)) + { + _myDependencies.GetDirectDependents(pair, dependents); + } + + return this.GetNames(dependents); + } + + public string[] GetPrecedents(string name) + { + ExpressionResultPair pair = this.GetTail(name); + List dependents = new List(); + + 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 tempDependents = _myDependencies.CloneDependents(rootTails); + // Get the sources (ie: nodes with no incoming edges) since that's what the sort requires + Queue sources = tempDependents.GetSources(rootTails); + // Do the topological sort + IList 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 + } + +} diff --git a/CalcEngine/PublicTypes/Exceptions.cs b/CalcEngine/PublicTypes/Exceptions.cs new file mode 100644 index 0000000..427f1e1 --- /dev/null +++ b/CalcEngine/PublicTypes/Exceptions.cs @@ -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; + } + +} + diff --git a/CalcEngine/PublicTypes/SimpleCalcEngine.cs b/CalcEngine/PublicTypes/SimpleCalcEngine.cs new file mode 100644 index 0000000..311cab8 --- /dev/null +++ b/CalcEngine/PublicTypes/SimpleCalcEngine.cs @@ -0,0 +1,121 @@ +using Flee.CalcEngine.InternalTypes; +using Flee.PublicTypes; + +namespace Flee.CalcEngine.PublicTypes +{ + public class SimpleCalcEngine + { + + #region "Fields" + + private readonly IDictionary _myExpressions; + + private ExpressionContext _myContext; + #endregion + + #region "Constructor" + + public SimpleCalcEngine() + { + _myExpressions = new Dictionary(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(string expressionName, string expression) + { + ExpressionContext linkedContext = this.ParseAndLink(expressionName, expression); + IExpression e = linkedContext.CompileGeneric(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 + } + +} diff --git a/ExpressionElements/Arithmetic.cs b/ExpressionElements/Arithmetic.cs new file mode 100644 index 0000000..26cb9ce --- /dev/null +++ b/ExpressionElements/Arithmetic.cs @@ -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)); + } + + /// + /// Emit an arithmetic operation with handling for unsigned and checked contexts + /// + /// + /// + /// + 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); + } + } + + /// + /// Emit a string concatenation + /// + /// + /// + 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; + } + } + } +} diff --git a/ExpressionElements/Base/Binary.cs b/ExpressionElements/Base/Binary.cs new file mode 100644 index 0000000..90fc4e3 --- /dev/null +++ b/ExpressionElements/Base/Binary.cs @@ -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() + { + } + + /// + /// Converts a list of binary elements into a binary tree + /// + /// + /// + /// + 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); + } + + /// + /// Set the left and right operands, get the operation, and get the result type + /// + /// + /// + /// + 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; + } +} diff --git a/ExpressionElements/Base/ExpressionElement.cs b/ExpressionElements/Base/ExpressionElement.cs new file mode 100644 index 0000000..3134d55 --- /dev/null +++ b/ExpressionElements/Base/ExpressionElement.cs @@ -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() + { + } + + /// + /// // All expression elements must be able to emit their IL + /// + /// + /// + public abstract void Emit(FleeILGenerator ilg, IServiceProvider services); + /// + /// All expression elements must expose the Type they evaluate to + /// + 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; + } + } + } +} diff --git a/ExpressionElements/Base/Literals/Integral.cs b/ExpressionElements/Base/Literals/Integral.cs new file mode 100644 index 0000000..51925ad --- /dev/null +++ b/ExpressionElements/Base/Literals/Integral.cs @@ -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() + { + } + + /// + /// Attempt to find the first type of integer that a number can fit into + /// + /// + /// + /// + /// + /// + 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); + } + } + } +} diff --git a/ExpressionElements/Base/Literals/LiteralElement.cs b/ExpressionElements/Base/Literals/LiteralElement.cs new file mode 100644 index 0000000..63bdef7 --- /dev/null +++ b/ExpressionElements/Base/Literals/LiteralElement.cs @@ -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); + } + } +} diff --git a/ExpressionElements/Base/Literals/Real.cs b/ExpressionElements/Base/Literals/Real.cs new file mode 100644 index 0000000..08ab7e0 --- /dev/null +++ b/ExpressionElements/Base/Literals/Real.cs @@ -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; + } + } + } +} diff --git a/ExpressionElements/Base/Member.cs b/ExpressionElements/Base/Member.cs new file mode 100644 index 0000000..c0bcd89 --- /dev/null +++ b/ExpressionElements/Base/Member.cs @@ -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); + } + + /// + /// Handles a call emit for static, instance methods of reference/value types + /// + /// + /// + 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); + } + + /// + /// Emit a function call for a value type + /// + /// + /// + 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); + } + } + + /// + /// Determine if a field, property, or method is public + /// + /// + /// + 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 accessible = new List(); + + // 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; + } + } + + /// + /// Find members in the default namespace + /// + /// + /// + /// + 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; + } +} diff --git a/ExpressionElements/Base/Unary.cs b/ExpressionElements/Base/Unary.cs new file mode 100644 index 0000000..240aa28 --- /dev/null +++ b/ExpressionElements/Base/Unary.cs @@ -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; + } + +} diff --git a/ExpressionElements/Cast.cs b/ExpressionElements/Cast.cs new file mode 100644 index 0000000..6e8be33 --- /dev/null +++ b/ExpressionElements/Cast.cs @@ -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; + } + + /// + /// Resolve the type we are casting to + /// + /// + /// + /// + 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; + } +} diff --git a/ExpressionElements/Compare.cs b/ExpressionElements/Compare.cs new file mode 100644 index 0000000..b33f5c6 --- /dev/null +++ b/ExpressionElements/Compare.cs @@ -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); + } + + /// + /// Emit the actual compare + /// + /// + /// + 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; + } + } + + /// + /// Get the correct greater/less than opcode + /// + /// + /// + 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; + } + } + } +} diff --git a/ExpressionElements/Conditional.cs b/ExpressionElements/Conditional.cs new file mode 100644 index 0000000..d32ce52 --- /dev/null +++ b/ExpressionElements/Conditional.cs @@ -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; + } +} diff --git a/ExpressionElements/In.cs b/ExpressionElements/In.cs new file mode 100644 index 0000000..e521b28 --- /dev/null +++ b/ExpressionElements/In.cs @@ -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 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(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); + } +} diff --git a/ExpressionElements/Literals/Boolean.cs b/ExpressionElements/Literals/Boolean.cs new file mode 100644 index 0000000..fdf8330 --- /dev/null +++ b/ExpressionElements/Literals/Boolean.cs @@ -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); + } +} diff --git a/ExpressionElements/Literals/Char.cs b/ExpressionElements/Literals/Char.cs new file mode 100644 index 0000000..847803c --- /dev/null +++ b/ExpressionElements/Literals/Char.cs @@ -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); + } +} diff --git a/ExpressionElements/Literals/DateTime.cs b/ExpressionElements/Literals/DateTime.cs new file mode 100644 index 0000000..c9fb66c --- /dev/null +++ b/ExpressionElements/Literals/DateTime.cs @@ -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); + } +} diff --git a/ExpressionElements/Literals/Integral/Int32.cs b/ExpressionElements/Literals/Integral/Int32.cs new file mode 100644 index 0000000..e25b362 --- /dev/null +++ b/ExpressionElements/Literals/Integral/Int32.cs @@ -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; + } +} diff --git a/ExpressionElements/Literals/Integral/Int64.cs b/ExpressionElements/Literals/Integral/Int64.cs new file mode 100644 index 0000000..975c28b --- /dev/null +++ b/ExpressionElements/Literals/Integral/Int64.cs @@ -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); + } +} diff --git a/ExpressionElements/Literals/Integral/UInt32.cs b/ExpressionElements/Literals/Integral/UInt32.cs new file mode 100644 index 0000000..53fb9e6 --- /dev/null +++ b/ExpressionElements/Literals/Integral/UInt32.cs @@ -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); + } +} diff --git a/ExpressionElements/Literals/Integral/UInt64.cs b/ExpressionElements/Literals/Integral/UInt64.cs new file mode 100644 index 0000000..8718839 --- /dev/null +++ b/ExpressionElements/Literals/Integral/UInt64.cs @@ -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); + } +} diff --git a/ExpressionElements/Literals/Null.cs b/ExpressionElements/Literals/Null.cs new file mode 100644 index 0000000..d074e3b --- /dev/null +++ b/ExpressionElements/Literals/Null.cs @@ -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); + } +} diff --git a/ExpressionElements/Literals/Real/Decimal.cs b/ExpressionElements/Literals/Real/Decimal.cs new file mode 100644 index 0000000..e55a036 --- /dev/null +++ b/ExpressionElements/Literals/Real/Decimal.cs @@ -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); + } +} diff --git a/ExpressionElements/Literals/Real/Double.cs b/ExpressionElements/Literals/Real/Double.cs new file mode 100644 index 0000000..3bc9109 --- /dev/null +++ b/ExpressionElements/Literals/Real/Double.cs @@ -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); + } +} diff --git a/ExpressionElements/Literals/Real/Single.cs b/ExpressionElements/Literals/Real/Single.cs new file mode 100644 index 0000000..5cb53f7 --- /dev/null +++ b/ExpressionElements/Literals/Real/Single.cs @@ -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); + } +} diff --git a/ExpressionElements/Literals/String.cs b/ExpressionElements/Literals/String.cs new file mode 100644 index 0000000..20c53f9 --- /dev/null +++ b/ExpressionElements/Literals/String.cs @@ -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); + } +} diff --git a/ExpressionElements/Literals/TimeSpan.cs b/ExpressionElements/Literals/TimeSpan.cs new file mode 100644 index 0000000..4186b0f --- /dev/null +++ b/ExpressionElements/Literals/TimeSpan.cs @@ -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); + } +} diff --git a/ExpressionElements/LogicalBitwise/AndOr.cs b/ExpressionElements/LogicalBitwise/AndOr.cs new file mode 100644 index 0000000..80adca6 --- /dev/null +++ b/ExpressionElements/LogicalBitwise/AndOr.cs @@ -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); + } + + /// + /// 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. + /// + /// + /// + /// + 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); + } + + /// + /// Emit a sequence of and/or expressions with short-circuiting + /// + /// + /// + /// + 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); + } + + + /// + /// Get the label for a short-circuit + /// + /// + /// + /// + /// + 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(); + } + } + + /// + /// Recursively pop operators and operands + /// + /// + /// + 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); + } + + /// + /// Emit the end cases for a short-circuit + /// + /// + /// + /// + 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()); + } + + /// + /// Visit the nodes of the tree (right then left) and populate some data structures + /// + /// + 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); + } + } + } +} diff --git a/ExpressionElements/LogicalBitwise/Not.cs b/ExpressionElements/LogicalBitwise/Not.cs new file mode 100644 index 0000000..dfdb400 --- /dev/null +++ b/ExpressionElements/LogicalBitwise/Not.cs @@ -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; + } + } + } +} diff --git a/ExpressionElements/LogicalBitwise/Xor.cs b/ExpressionElements/LogicalBitwise/Xor.cs new file mode 100644 index 0000000..399d849 --- /dev/null +++ b/ExpressionElements/LogicalBitwise/Xor.cs @@ -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) + { + } + } +} diff --git a/ExpressionElements/MemberElements/ArgumentList.cs b/ExpressionElements/MemberElements/ArgumentList.cs new file mode 100644 index 0000000..88887b4 --- /dev/null +++ b/ExpressionElements/MemberElements/ArgumentList.cs @@ -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 _myElements; + public ArgumentList(ICollection elements) + { + ExpressionElement[] arr = new ExpressionElement[elements.Count]; + elements.CopyTo(arr, 0); + _myElements = arr; + } + + private string[] GetArgumentTypeNames() + { + List l = new List(); + + foreach (ExpressionElement e in _myElements) + { + l.Add(e.ResultType.Name); + } + + return l.ToArray(); + } + + public Type[] GetArgumentTypes() + { + List l = new List(); + + 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; + } +} diff --git a/ExpressionElements/MemberElements/FunctionCall.cs b/ExpressionElements/MemberElements/FunctionCall.cs new file mode 100644 index 0000000..f98936f --- /dev/null +++ b/ExpressionElements/MemberElements/FunctionCall.cs @@ -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 _myMethods; + private CustomMethodInfo _myTargetMethodInfo; + + private Type _myOnDemandFunctionReturnType; + public FunctionCallElement(string name, ArgumentList arguments) + { + this.MyName = name; + _myArguments = arguments; + } + + internal FunctionCallElement(string name, ICollection 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 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); + } + + /// + /// Try to find a match from a set of methods + /// + /// + /// + /// + private void BindToMethod(ICollection methods, MemberElement previous, Type[] argTypes) + { + List customInfos = new List(); + + // 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); + } + } + + /// + /// Find the best match from a set of overloaded methods + /// + /// + /// + /// + 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(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 accessible = new List(); + + foreach (CustomMethodInfo cmi in infos) + { + if (cmi.IsAccessible(this) == true) + { + accessible.Add(cmi); + } + } + + return accessible.ToArray(); + } + + /// + /// Handle case where we have overloads with the same score + /// + /// + private void DetectAmbiguousMatches(CustomMethodInfo[] infos) + { + List sameScores = new List(); + CustomMethodInfo first = infos[0]; + + // Find all matches with the same score as the best match + foreach (CustomMethodInfo cmi in infos) + { + if (((IEquatable)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); + } + + /// + /// Emit elements into an array + /// + /// + /// + /// + /// + 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"); + } + } + + /// + /// Emit the arguments to a regular method call + /// + /// + /// + /// + /// + 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"); + } + } + + /// + /// The method info we will be calling + /// + 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; + } +} diff --git a/ExpressionElements/MemberElements/Identifier.cs b/ExpressionElements/MemberElements/Identifier.cs new file mode 100644 index 0000000..b53382c --- /dev/null +++ b/ExpressionElements/MemberElements/Identifier.cs @@ -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); + } + } + } + + /// + /// 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. + /// + /// + /// + /// + 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); + } + + /// + /// Load a PropertyDescriptor based property + /// + /// + 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; + } +} diff --git a/ExpressionElements/MemberElements/Indexer.cs b/ExpressionElements/MemberElements/Indexer.cs new file mode 100644 index 0000000..3b79edd --- /dev/null +++ b/ExpressionElements/MemberElements/Indexer.cs @@ -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 methods = new List(); + + // 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; + } +} diff --git a/ExpressionElements/MemberElements/InvocationList.cs b/ExpressionElements/MemberElements/InvocationList.cs new file mode 100644 index 0000000..15aa609 --- /dev/null +++ b/ExpressionElements/MemberElements/InvocationList.cs @@ -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]; + } + + /// + /// Arrange elements as a linked list + /// + /// + 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; + } +} diff --git a/ExpressionElements/MemberElements/Miscellaneous.cs b/ExpressionElements/MemberElements/Miscellaneous.cs new file mode 100644 index 0000000..d183ea2 --- /dev/null +++ b/ExpressionElements/MemberElements/Miscellaneous.cs @@ -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; + } +} diff --git a/ExpressionElements/Negate.cs b/ExpressionElements/Negate.cs new file mode 100644 index 0000000..17127eb --- /dev/null +++ b/ExpressionElements/Negate.cs @@ -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); + } + } + } +} diff --git a/ExpressionElements/Root.cs b/ExpressionElements/Root.cs new file mode 100644 index 0000000..26b991b --- /dev/null +++ b/ExpressionElements/Root.cs @@ -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); + } +} diff --git a/ExpressionElements/Shift.cs b/ExpressionElements/Shift.cs new file mode 100644 index 0000000..aece3c6 --- /dev/null +++ b/ExpressionElements/Shift.cs @@ -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); + } + } +} diff --git a/InternalTypes/BranchManager.cs b/InternalTypes/BranchManager.cs new file mode 100644 index 0000000..2bbde86 --- /dev/null +++ b/InternalTypes/BranchManager.cs @@ -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 MyBranchInfos; + + public BranchManager() + { + MyBranchInfos = new List(); + } + + /// + /// check if any long branches exist + /// + /// + public bool HasLongBranches() + { + foreach (BranchInfo bi in MyBranchInfos) + { + if (bi.ComputeIsLongBranch()) return true; + } + return false; + } + + /// + /// Determine whether to use short or long branches. + /// This advances the ilg offset with No-op to adjust + /// for the long branches needed. + /// + /// + 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); + } + + + /// + /// Determine if a branch from a point to a label will be long + /// + /// + /// + /// + 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; + } + + /// + /// Add a branch from a location to a target label + /// + /// + /// + /// + 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); + } + + + /// + /// Set the position for a label + /// + /// + /// + /// + 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, IComparable + { + private int _myPosition; + + /// + /// ' Long branch is 5 bytes; short branch is 2; so we adjust by the difference + /// + private const int LongBranchAdjust = 3; + + /// + /// Length of the Br_s opcode + /// + private const int BrSLength = 2; + + public ILLocation() + { + } + + public ILLocation(int position) + { + _myPosition = position; + } + + public void SetPosition(int position) + { + _myPosition = position; + } + + /// + /// Adjust our position by a certain amount of long branches + /// + /// + /// + public void AdjustForLongBranch(int longBranchCount) + { + _myPosition += longBranchCount * LongBranchAdjust; + } + + /// + /// Determine if this branch is long + /// + /// + /// + /// + 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.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); + } + } + + /// + /// 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. + /// + /// + /// + public bool Equals(ILLocation start) + { + return _myStart.Equals1(start); + } + + public override string ToString() + { + return $"{_myStart} -> {_myEnd} (L={_myStart.IsLongBranch(_myEnd)})"; + } + + public bool IsLongBranch => _myIsLongBranch; + } +} diff --git a/InternalTypes/Expression.cs b/InternalTypes/Expression.cs new file mode 100644 index 0000000..6e25935 --- /dev/null +++ b/InternalTypes/Expression.cs @@ -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 : IExpression, IDynamicExpression, IGenericExpression + { + private readonly string _myExpression; + private ExpressionContext _myContext; + private ExpressionOptions _myOptions; + private readonly ExpressionInfo _myInfo; + private ExpressionEvaluator _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)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); + } + + /// + /// Emit to an assembly. We've already computed long branches at this point, + /// so we emit as a second pass + /// + /// + /// + /// + 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.Evaluate() + { + return EvaluateGeneric(); + } + + public IExpression Clone() + { + Expression copy = (Expression)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; + } +} diff --git a/InternalTypes/FleeILGenerator.cs b/InternalTypes/FleeILGenerator.cs new file mode 100644 index 0000000..1f24002 --- /dev/null +++ b/InternalTypes/FleeILGenerator.cs @@ -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 _localBuilderTemp; + private int _myPass; + private int _brContext; + private BranchManager _bm; + + public FleeILGenerator(ILGenerator ilg) + { + _myIlGenerator = ilg; + _localBuilderTemp = new Dictionary(); + _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; + } + + /// + /// after first pass, check for long branches. + /// If any, we need to generate again. + /// + /// + public bool NeedsSecondPass() + { + return _bm.HasLongBranches(); + } + + /// + /// need a new ILGenerator for 2nd pass. This can also + /// get called for a 3rd pass when emitting to assembly. + /// + /// + 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); + } +} diff --git a/InternalTypes/ImplicitConversions.cs b/InternalTypes/ImplicitConversions.cs new file mode 100644 index 0000000..dadac42 --- /dev/null +++ b/InternalTypes/ImplicitConversions.cs @@ -0,0 +1,573 @@ +using System.Diagnostics; +using System.Reflection.Emit; +using System.Reflection; + +namespace Flee.InternalTypes +{ + internal class ImplicitConverter + { + /// + /// Table of results for binary operations using primitives + /// + private static readonly Type[,] OurBinaryResultTable; + + /// + /// Primitive types we support + /// + 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); + } + + /// + ///Emit an implicit conversion (if the ilg is not null) and returns a value that determines whether the implicit conversion + /// succeeded + /// + /// + /// + /// + /// + 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); + } + } + + /// + /// Get the result type for a binary operation + /// + /// + /// + /// + 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; + } + } +} diff --git a/InternalTypes/Miscellaneous.cs b/InternalTypes/Miscellaneous.cs new file mode 100644 index 0000000..3fcdf35 --- /dev/null +++ b/InternalTypes/Miscellaneous.cs @@ -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(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, IEquatable + { + /// + /// Method we are wrapping + /// + private readonly MethodInfo _myTarget; + /// + /// The rating of how close the method matches the given arguments (0 is best) + /// + 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); + } + } + + /// + /// Compute a score showing how close our method matches the given argument types (for extension methods) + /// + /// + /// + /// + 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; + } + + /// + /// Compute a score showing how close our method matches the given argument types + /// + /// + /// + /// + 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); + } + + /// + /// Is the given MethodInfo usable as an overload? + /// + /// + /// + 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.Equals(CustomMethodInfo other) + { + return Equals1(other); + } + + public MethodInfo Target => _myTarget; + } + + internal class ShortCircuitInfo + { + + public Stack Operands; + public Stack Operators; + private Dictionary Labels; + + public ShortCircuitInfo() + { + this.Operands = new Stack(); + this.Operators = new Stack(); + this.Labels = new Dictionary(); + } + + 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 _myProperties; + public PropertyDictionary() + { + _myProperties = new Dictionary(StringComparer.OrdinalIgnoreCase); + } + + public PropertyDictionary Clone() + { + PropertyDictionary copy = new PropertyDictionary(); + + foreach (KeyValuePair pair in _myProperties) + { + copy.SetValue(pair.Key, pair.Value); + } + + return copy; + } + + public T GetValue(string name) + { + object value = default(T); + if (_myProperties.TryGetValue(name, out value) == false) + { + Debug.Fail($"Unknown property '{name}'"); + } + return (T)value; + } + + public void SetToDefault(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); + } + } +} diff --git a/InternalTypes/Utility.cs b/InternalTypes/Utility.cs new file mode 100644 index 0000000..2063bcd --- /dev/null +++ b/InternalTypes/Utility.cs @@ -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); + } + } + + /// + /// Find a simple (unary) overloaded operator + /// + /// The name of the operator + /// The type to convert from + /// The type to convert to (can be null if it's not known beforehand) + /// The operator's method or null of no match is found + 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]; + } + } + + /// + /// Matches simple overloaded operators + /// + /// + /// + /// + /// + 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); + } + } +} diff --git a/InternalTypes/VariableTypes.cs b/InternalTypes/VariableTypes.cs new file mode 100644 index 0000000..9721bcc --- /dev/null +++ b/InternalTypes/VariableTypes.cs @@ -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 + { + object GetValue(); + } + + internal class DynamicExpressionVariable : IVariable, IGenericVariable + { + private IDynamicExpression _myExpression; + public IVariable Clone() + { + DynamicExpressionVariable copy = new DynamicExpressionVariable(); + 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 : IVariable, IGenericVariable + { + private IGenericExpression _myExpression; + public IVariable Clone() + { + GenericExpressionVariable copy = new GenericExpressionVariable(); + copy._myExpression = _myExpression; + return copy; + } + + public object GetValue() + { + return _myExpression.Evaluate(); + } + + public object ValueAsObject + { + get { return _myExpression; } + set { _myExpression = (IGenericExpression)value; } + } + + public System.Type VariableType => _myExpression.Context.Options.ResultType; + } + + internal class GenericVariable : IVariable, IGenericVariable + { + + + public object MyValue; + public IVariable Clone() + { + GenericVariable copy = new GenericVariable { 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; + } + } + } + } + +} diff --git a/Parsing/AlternativeElement.cs b/Parsing/AlternativeElement.cs new file mode 100644 index 0000000..67d8d76 --- /dev/null +++ b/Parsing/AlternativeElement.cs @@ -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 + " "); + } + } +} diff --git a/Parsing/Analyzer.cs b/Parsing/Analyzer.cs new file mode 100644 index 0000000..7d9b6af --- /dev/null +++ b/Parsing/Analyzer.cs @@ -0,0 +1,240 @@ +using System.Collections; + +namespace Flee.Parsing +{ + [Obsolete("Creates a new parse tree analyzer.")] + internal class Analyzer + { + public Analyzer() + { + } + + /// + /// Resets this analyzer when the parser is reset for another + ///input stream.The default implementation of this method does + /// nothing. + /// + 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; + } + } +} diff --git a/Parsing/Automaton.cs b/Parsing/Automaton.cs new file mode 100644 index 0000000..1116664 --- /dev/null +++ b/Parsing/Automaton.cs @@ -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); + } + } + } +} diff --git a/Parsing/CharacterSetElement.cs b/Parsing/CharacterSetElement.cs new file mode 100644 index 0000000..fa57d2f --- /dev/null +++ b/Parsing/CharacterSetElement.cs @@ -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; + } + } + } +} diff --git a/Parsing/CombineElement.cs b/Parsing/CombineElement.cs new file mode 100644 index 0000000..37bc135 --- /dev/null +++ b/Parsing/CombineElement.cs @@ -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); + } + } +} diff --git a/Parsing/CustomExpressionAnalyzer.cs b/Parsing/CustomExpressionAnalyzer.cs new file mode 100644 index 0000000..295a171 --- /dev/null +++ b/Parsing/CustomExpressionAnalyzer.cs @@ -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 parts = new List(); + + 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; + } + } +} diff --git a/Parsing/CustomTokenPatterns.cs b/Parsing/CustomTokenPatterns.cs new file mode 100644 index 0000000..50e3f1c --- /dev/null +++ b/Parsing/CustomTokenPatterns.cs @@ -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()); + } + } +} diff --git a/Parsing/Element.cs b/Parsing/Element.cs new file mode 100644 index 0000000..53e6b78 --- /dev/null +++ b/Parsing/Element.cs @@ -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); + } +} diff --git a/Parsing/Expression.grammar b/Parsing/Expression.grammar new file mode 100644 index 0000000..e25b009 --- /dev/null +++ b/Parsing/Expression.grammar @@ -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 ")"; \ No newline at end of file diff --git a/Parsing/ExpressionAnalyzer.cs b/Parsing/ExpressionAnalyzer.cs new file mode 100644 index 0000000..bbb73c3 --- /dev/null +++ b/Parsing/ExpressionAnalyzer.cs @@ -0,0 +1,1395 @@ +namespace Flee.Parsing +{ + /// + /// A class providing callback methods for the parser. + /// + internal abstract class ExpressionAnalyzer : Analyzer + { + /// + /// Called when entering a parse tree node. + /// + /// + public override void Enter(Node node) + { + switch (node.Id) + { + case (int)ExpressionConstants.ADD: + EnterAdd((Token)node); + + break; + case (int)ExpressionConstants.SUB: + EnterSub((Token)node); + + break; + case (int)ExpressionConstants.MUL: + EnterMul((Token)node); + + break; + case (int)ExpressionConstants.DIV: + EnterDiv((Token)node); + + break; + case (int)ExpressionConstants.POWER: + EnterPower((Token)node); + + break; + case (int)ExpressionConstants.MOD: + EnterMod((Token)node); + + break; + case (int)ExpressionConstants.LEFT_PAREN: + EnterLeftParen((Token)node); + + break; + case (int)ExpressionConstants.RIGHT_PAREN: + EnterRightParen((Token)node); + + break; + case (int)ExpressionConstants.LEFT_BRACE: + EnterLeftBrace((Token)node); + + break; + case (int)ExpressionConstants.RIGHT_BRACE: + EnterRightBrace((Token)node); + + break; + case (int)ExpressionConstants.EQ: + EnterEq((Token)node); + + break; + case (int)ExpressionConstants.LT: + EnterLt((Token)node); + + break; + case (int)ExpressionConstants.GT: + EnterGt((Token)node); + + break; + case (int)ExpressionConstants.LTE: + EnterLte((Token)node); + + break; + case (int)ExpressionConstants.GTE: + EnterGte((Token)node); + + break; + case (int)ExpressionConstants.NE: + EnterNe((Token)node); + + break; + case (int)ExpressionConstants.AND: + EnterAnd((Token)node); + + break; + case (int)ExpressionConstants.OR: + EnterOr((Token)node); + + break; + case (int)ExpressionConstants.XOR: + EnterXor((Token)node); + + break; + case (int)ExpressionConstants.NOT: + EnterNot((Token)node); + + break; + case (int)ExpressionConstants.IN: + EnterIn((Token)node); + + break; + case (int)ExpressionConstants.DOT: + EnterDot((Token)node); + + break; + case (int)ExpressionConstants.ARGUMENT_SEPARATOR: + EnterArgumentSeparator((Token)node); + + break; + case (int)ExpressionConstants.ARRAY_BRACES: + EnterArrayBraces((Token)node); + + break; + case (int)ExpressionConstants.LEFT_SHIFT: + EnterLeftShift((Token)node); + + break; + case (int)ExpressionConstants.RIGHT_SHIFT: + EnterRightShift((Token)node); + + break; + case (int)ExpressionConstants.INTEGER: + EnterInteger((Token)node); + + break; + case (int)ExpressionConstants.REAL: + EnterReal((Token)node); + + break; + case (int)ExpressionConstants.STRING_LITERAL: + EnterStringLiteral((Token)node); + + break; + case (int)ExpressionConstants.CHAR_LITERAL: + EnterCharLiteral((Token)node); + + break; + case (int)ExpressionConstants.TRUE: + EnterTrue((Token)node); + + break; + case (int)ExpressionConstants.FALSE: + EnterFalse((Token)node); + + break; + case (int)ExpressionConstants.IDENTIFIER: + EnterIdentifier((Token)node); + + break; + case (int)ExpressionConstants.HEX_LITERAL: + EnterHexLiteral((Token)node); + + break; + case (int)ExpressionConstants.NULL_LITERAL: + EnterNullLiteral((Token)node); + + break; + case (int)ExpressionConstants.TIMESPAN: + EnterTimespan((Token)node); + + break; + case (int)ExpressionConstants.DATETIME: + EnterDatetime((Token)node); + + break; + case (int)ExpressionConstants.IF: + EnterIf((Token)node); + + break; + case (int)ExpressionConstants.CAST: + EnterCast((Token)node); + + break; + case (int)ExpressionConstants.EXPRESSION: + EnterExpression((Production)node); + + break; + case (int)ExpressionConstants.XOR_EXPRESSION: + EnterXorExpression((Production)node); + + break; + case (int)ExpressionConstants.OR_EXPRESSION: + EnterOrExpression((Production)node); + + break; + case (int)ExpressionConstants.AND_EXPRESSION: + EnterAndExpression((Production)node); + + break; + case (int)ExpressionConstants.NOT_EXPRESSION: + EnterNotExpression((Production)node); + + break; + case (int)ExpressionConstants.IN_EXPRESSION: + EnterInExpression((Production)node); + + break; + case (int)ExpressionConstants.IN_TARGET_EXPRESSION: + EnterInTargetExpression((Production)node); + + break; + case (int)ExpressionConstants.IN_LIST_TARGET_EXPRESSION: + EnterInListTargetExpression((Production)node); + + break; + case (int)ExpressionConstants.COMPARE_EXPRESSION: + EnterCompareExpression((Production)node); + + break; + case (int)ExpressionConstants.SHIFT_EXPRESSION: + EnterShiftExpression((Production)node); + + break; + case (int)ExpressionConstants.ADDITIVE_EXPRESSION: + EnterAdditiveExpression((Production)node); + + break; + case (int)ExpressionConstants.MULTIPLICATIVE_EXPRESSION: + EnterMultiplicativeExpression((Production)node); + + break; + case (int)ExpressionConstants.POWER_EXPRESSION: + EnterPowerExpression((Production)node); + + break; + case (int)ExpressionConstants.NEGATE_EXPRESSION: + EnterNegateExpression((Production)node); + + break; + case (int)ExpressionConstants.MEMBER_EXPRESSION: + EnterMemberExpression((Production)node); + + break; + case (int)ExpressionConstants.MEMBER_ACCESS_EXPRESSION: + EnterMemberAccessExpression((Production)node); + + break; + case (int)ExpressionConstants.BASIC_EXPRESSION: + EnterBasicExpression((Production)node); + + break; + case (int)ExpressionConstants.MEMBER_FUNCTION_EXPRESSION: + EnterMemberFunctionExpression((Production)node); + + break; + case (int)ExpressionConstants.FIELD_PROPERTY_EXPRESSION: + EnterFieldPropertyExpression((Production)node); + + break; + case (int)ExpressionConstants.SPECIAL_FUNCTION_EXPRESSION: + EnterSpecialFunctionExpression((Production)node); + + break; + case (int)ExpressionConstants.IF_EXPRESSION: + EnterIfExpression((Production)node); + + break; + case (int)ExpressionConstants.CAST_EXPRESSION: + EnterCastExpression((Production)node); + + break; + case (int)ExpressionConstants.CAST_TYPE_EXPRESSION: + EnterCastTypeExpression((Production)node); + + break; + case (int)ExpressionConstants.INDEX_EXPRESSION: + EnterIndexExpression((Production)node); + + break; + case (int)ExpressionConstants.FUNCTION_CALL_EXPRESSION: + EnterFunctionCallExpression((Production)node); + + break; + case (int)ExpressionConstants.ARGUMENT_LIST: + EnterArgumentList((Production)node); + + break; + case (int)ExpressionConstants.LITERAL_EXPRESSION: + EnterLiteralExpression((Production)node); + + break; + case (int)ExpressionConstants.BOOLEAN_LITERAL_EXPRESSION: + EnterBooleanLiteralExpression((Production)node); + + break; + case (int)ExpressionConstants.EXPRESSION_GROUP: + EnterExpressionGroup((Production)node); + + break; + } + } + + /// + /// Called when exiting a parse tree node.the node being exited the node to add to the parse tree, or null if no parse tree should be created< + /// + /// + /// + public override Node Exit(Node node) + { + switch (node.Id) + { + case (int)ExpressionConstants.ADD: + + return ExitAdd((Token)node); + case (int)ExpressionConstants.SUB: + + return ExitSub((Token)node); + case (int)ExpressionConstants.MUL: + + return ExitMul((Token)node); + case (int)ExpressionConstants.DIV: + + return ExitDiv((Token)node); + case (int)ExpressionConstants.POWER: + + return ExitPower((Token)node); + case (int)ExpressionConstants.MOD: + + return ExitMod((Token)node); + case (int)ExpressionConstants.LEFT_PAREN: + + return ExitLeftParen((Token)node); + case (int)ExpressionConstants.RIGHT_PAREN: + + return ExitRightParen((Token)node); + case (int)ExpressionConstants.LEFT_BRACE: + + return ExitLeftBrace((Token)node); + case (int)ExpressionConstants.RIGHT_BRACE: + + return ExitRightBrace((Token)node); + case (int)ExpressionConstants.EQ: + + return ExitEq((Token)node); + case (int)ExpressionConstants.LT: + + return ExitLt((Token)node); + case (int)ExpressionConstants.GT: + + return ExitGt((Token)node); + case (int)ExpressionConstants.LTE: + + return ExitLte((Token)node); + case (int)ExpressionConstants.GTE: + + return ExitGte((Token)node); + case (int)ExpressionConstants.NE: + + return ExitNe((Token)node); + case (int)ExpressionConstants.AND: + + return ExitAnd((Token)node); + case (int)ExpressionConstants.OR: + + return ExitOr((Token)node); + case (int)ExpressionConstants.XOR: + + return ExitXor((Token)node); + case (int)ExpressionConstants.NOT: + + return ExitNot((Token)node); + case (int)ExpressionConstants.IN: + + return ExitIn((Token)node); + case (int)ExpressionConstants.DOT: + + return ExitDot((Token)node); + case (int)ExpressionConstants.ARGUMENT_SEPARATOR: + + return ExitArgumentSeparator((Token)node); + case (int)ExpressionConstants.ARRAY_BRACES: + + return ExitArrayBraces((Token)node); + case (int)ExpressionConstants.LEFT_SHIFT: + + return ExitLeftShift((Token)node); + case (int)ExpressionConstants.RIGHT_SHIFT: + + return ExitRightShift((Token)node); + case (int)ExpressionConstants.INTEGER: + + return ExitInteger((Token)node); + case (int)ExpressionConstants.REAL: + + return ExitReal((Token)node); + case (int)ExpressionConstants.STRING_LITERAL: + + return ExitStringLiteral((Token)node); + case (int)ExpressionConstants.CHAR_LITERAL: + + return ExitCharLiteral((Token)node); + case (int)ExpressionConstants.TRUE: + + return ExitTrue((Token)node); + case (int)ExpressionConstants.FALSE: + + return ExitFalse((Token)node); + case (int)ExpressionConstants.IDENTIFIER: + + return ExitIdentifier((Token)node); + case (int)ExpressionConstants.HEX_LITERAL: + + return ExitHexliteral((Token)node); + case (int)ExpressionConstants.NULL_LITERAL: + + return ExitNullLiteral((Token)node); + case (int)ExpressionConstants.TIMESPAN: + + return ExitTimespan((Token)node); + case (int)ExpressionConstants.DATETIME: + + return ExitDatetime((Token)node); + case (int)ExpressionConstants.IF: + + return ExitIf((Token)node); + case (int)ExpressionConstants.CAST: + + return ExitCast((Token)node); + case (int)ExpressionConstants.EXPRESSION: + + return ExitExpression((Production)node); + case (int)ExpressionConstants.XOR_EXPRESSION: + + return ExitXorExpression((Production)node); + case (int)ExpressionConstants.OR_EXPRESSION: + + return ExitOrExpression((Production)node); + case (int)ExpressionConstants.AND_EXPRESSION: + + return ExitAndExpression((Production)node); + case (int)ExpressionConstants.NOT_EXPRESSION: + + return ExitNotExpression((Production)node); + case (int)ExpressionConstants.IN_EXPRESSION: + + return ExitInExpression((Production)node); + case (int)ExpressionConstants.IN_TARGET_EXPRESSION: + + return ExitInTargetExpression((Production)node); + case (int)ExpressionConstants.IN_LIST_TARGET_EXPRESSION: + + return ExitInListTargetExpression((Production)node); + case (int)ExpressionConstants.COMPARE_EXPRESSION: + + return ExitCompareExpression((Production)node); + case (int)ExpressionConstants.SHIFT_EXPRESSION: + + return ExitShiftExpression((Production)node); + case (int)ExpressionConstants.ADDITIVE_EXPRESSION: + + return ExitAdditiveExpression((Production)node); + case (int)ExpressionConstants.MULTIPLICATIVE_EXPRESSION: + + return ExitMultiplicativeExpression((Production)node); + case (int)ExpressionConstants.POWER_EXPRESSION: + + return ExitPowerExpression((Production)node); + case (int)ExpressionConstants.NEGATE_EXPRESSION: + + return ExitNegateExpression((Production)node); + case (int)ExpressionConstants.MEMBER_EXPRESSION: + + return ExitMemberExpression((Production)node); + case (int)ExpressionConstants.MEMBER_ACCESS_EXPRESSION: + + return ExitMemberAccessExpression((Production)node); + case (int)ExpressionConstants.BASIC_EXPRESSION: + + return ExitBasicExpression((Production)node); + case (int)ExpressionConstants.MEMBER_FUNCTION_EXPRESSION: + + return ExitMemberFunctionExpression((Production)node); + case (int)ExpressionConstants.FIELD_PROPERTY_EXPRESSION: + + return ExitFieldPropertyExpression((Production)node); + case (int)ExpressionConstants.SPECIAL_FUNCTION_EXPRESSION: + + return ExitSpecialFunctionExpression((Production)node); + case (int)ExpressionConstants.IF_EXPRESSION: + + return ExitIfExpression((Production)node); + case (int)ExpressionConstants.CAST_EXPRESSION: + + return ExitCastExpression((Production)node); + case (int)ExpressionConstants.CAST_TYPE_EXPRESSION: + + return ExitCastTypeExpression((Production)node); + case (int)ExpressionConstants.INDEX_EXPRESSION: + + return ExitIndexExpression((Production)node); + case (int)ExpressionConstants.FUNCTION_CALL_EXPRESSION: + + return ExitFunctionCallExpression((Production)node); + case (int)ExpressionConstants.ARGUMENT_LIST: + + return ExitArgumentList((Production)node); + case (int)ExpressionConstants.LITERAL_EXPRESSION: + + return ExitLiteralExpression((Production)node); + case (int)ExpressionConstants.BOOLEAN_LITERAL_EXPRESSION: + + return ExitBooleanLiteralExpression((Production)node); + case (int)ExpressionConstants.EXPRESSION_GROUP: + + return ExitExpressionGroup((Production)node); + } + return node; + } + + /// + /// Called when adding a child to a parse tree node. + /// + /// + /// + public override void Child(Production node, Node child) + { + switch (node.Id) + { + case (int)ExpressionConstants.EXPRESSION: + ChildExpression(node, child); + + break; + case (int)ExpressionConstants.XOR_EXPRESSION: + ChildXorExpression(node, child); + + break; + case (int)ExpressionConstants.OR_EXPRESSION: + ChildOrExpression(node, child); + + break; + case (int)ExpressionConstants.AND_EXPRESSION: + ChildAndExpression(node, child); + + break; + case (int)ExpressionConstants.NOT_EXPRESSION: + ChildNotExpression(node, child); + + break; + case (int)ExpressionConstants.IN_EXPRESSION: + ChildInExpression(node, child); + + break; + case (int)ExpressionConstants.IN_TARGET_EXPRESSION: + ChildInTargetExpression(node, child); + + break; + case (int)ExpressionConstants.IN_LIST_TARGET_EXPRESSION: + ChildInListTargetExpression(node, child); + + break; + case (int)ExpressionConstants.COMPARE_EXPRESSION: + ChildCompareExpression(node, child); + + break; + case (int)ExpressionConstants.SHIFT_EXPRESSION: + ChildShiftExpression(node, child); + + break; + case (int)ExpressionConstants.ADDITIVE_EXPRESSION: + ChildAdditiveExpression(node, child); + + break; + case (int)ExpressionConstants.MULTIPLICATIVE_EXPRESSION: + ChildMultiplicativeExpression(node, child); + + break; + case (int)ExpressionConstants.POWER_EXPRESSION: + ChildPowerExpression(node, child); + + break; + case (int)ExpressionConstants.NEGATE_EXPRESSION: + ChildNegateExpression(node, child); + + break; + case (int)ExpressionConstants.MEMBER_EXPRESSION: + ChildMemberExpression(node, child); + + break; + case (int)ExpressionConstants.MEMBER_ACCESS_EXPRESSION: + ChildMemberAccessExpression(node, child); + + break; + case (int)ExpressionConstants.BASIC_EXPRESSION: + ChildBasicExpression(node, child); + + break; + case (int)ExpressionConstants.MEMBER_FUNCTION_EXPRESSION: + ChildMemberFunctionExpression(node, child); + + break; + case (int)ExpressionConstants.FIELD_PROPERTY_EXPRESSION: + ChildFieldPropertyExpression(node, child); + + break; + case (int)ExpressionConstants.SPECIAL_FUNCTION_EXPRESSION: + ChildSpecialFunctionExpression(node, child); + + break; + case (int)ExpressionConstants.IF_EXPRESSION: + ChildIfExpression(node, child); + + break; + case (int)ExpressionConstants.CAST_EXPRESSION: + ChildCastExpression(node, child); + + break; + case (int)ExpressionConstants.CAST_TYPE_EXPRESSION: + ChildCastTypeExpression(node, child); + + break; + case (int)ExpressionConstants.INDEX_EXPRESSION: + ChildIndexExpression(node, child); + + break; + case (int)ExpressionConstants.FUNCTION_CALL_EXPRESSION: + ChildFunctionCallExpression(node, child); + + break; + case (int)ExpressionConstants.ARGUMENT_LIST: + ChildArgumentList(node, child); + + break; + case (int)ExpressionConstants.LITERAL_EXPRESSION: + ChildLiteralExpression(node, child); + + break; + case (int)ExpressionConstants.BOOLEAN_LITERAL_EXPRESSION: + ChildBooleanLiteralExpression(node, child); + + break; + case (int)ExpressionConstants.EXPRESSION_GROUP: + ChildExpressionGroup(node, child); + + break; + } + } + + public virtual void EnterAdd(Token node) + { + } + + public virtual Node ExitAdd(Token node) + { + return node; + } + + public virtual void EnterSub(Token node) + { + } + + + public virtual Node ExitSub(Token node) + { + return node; + } + + public virtual void EnterMul(Token node) + { + } + + public virtual Node ExitMul(Token node) + { + return node; + } + + public virtual void EnterDiv(Token node) + { + } + + public virtual Node ExitDiv(Token node) + { + return node; + } + + public virtual void EnterPower(Token node) + { + } + + public virtual Node ExitPower(Token node) + { + return node; + } + + public virtual void EnterMod(Token node) + { + } + + public virtual Node ExitMod(Token node) + { + return node; + } + + public virtual void EnterLeftParen(Token node) + { + } + + public virtual Node ExitLeftParen(Token node) + { + return node; + } + + public virtual void EnterRightParen(Token node) + { + } + + public virtual Node ExitRightParen(Token node) + { + return node; + } + + public virtual void EnterLeftBrace(Token node) + { + } + + public virtual Node ExitLeftBrace(Token node) + { + return node; + } + + public virtual void EnterRightBrace(Token node) + { + } + + public virtual Node ExitRightBrace(Token node) + { + return node; + } + + public virtual void EnterEq(Token node) + { + } + + public virtual Node ExitEq(Token node) + { + return node; + } + + public virtual void EnterLt(Token node) + { + } + + public virtual Node ExitLt(Token node) + { + return node; + } + + public virtual void EnterGt(Token node) + { + } + + public virtual Node ExitGt(Token node) + { + return node; + } + + public virtual void EnterLte(Token node) + { + } + + public virtual Node ExitLte(Token node) + { + return node; + } + + public virtual void EnterGte(Token node) + { + } + + public virtual Node ExitGte(Token node) + { + return node; + } + + public virtual void EnterNe(Token node) + { + } + + public virtual Node ExitNe(Token node) + { + return node; + } + + public virtual void EnterAnd(Token node) + { + } + + public virtual Node ExitAnd(Token node) + { + return node; + } + + public virtual void EnterOr(Token node) + { + } + + public virtual Node ExitOr(Token node) + { + return node; + } + + public virtual void EnterXor(Token node) + { + } + + public virtual Node ExitXor(Token node) + { + return node; + } + + public virtual void EnterNot(Token node) + { + } + + public virtual Node ExitNot(Token node) + { + return node; + } + + public virtual void EnterIn(Token node) + { + } + + public virtual Node ExitIn(Token node) + { + return node; + } + + public virtual void EnterDot(Token node) + { + } + + public virtual Node ExitDot(Token node) + { + return node; + } + + public virtual void EnterArgumentSeparator(Token node) + { + } + + public virtual Node ExitArgumentSeparator(Token node) + { + return node; + } + + public virtual void EnterArrayBraces(Token node) + { + } + + public virtual Node ExitArrayBraces(Token node) + { + return node; + } + + public virtual void EnterLeftShift(Token node) + { + } + + public virtual Node ExitLeftShift(Token node) + { + return node; + } + + public virtual void EnterRightShift(Token node) + { + } + + public virtual Node ExitRightShift(Token node) + { + return node; + } + + public virtual void EnterInteger(Token node) + { + } + + public virtual Node ExitInteger(Token node) + { + return node; + } + + public virtual void EnterReal(Token node) + { + } + + public virtual Node ExitReal(Token node) + { + return node; + } + + public virtual void EnterStringLiteral(Token node) + { + } + + public virtual Node ExitStringLiteral(Token node) + { + return node; + } + + public virtual void EnterCharLiteral(Token node) + { + } + + public virtual Node ExitCharLiteral(Token node) + { + return node; + } + + public virtual void EnterTrue(Token node) + { + } + + public virtual Node ExitTrue(Token node) + { + return node; + } + + public virtual void EnterFalse(Token node) + { + } + + public virtual Node ExitFalse(Token node) + { + return node; + } + + public virtual void EnterIdentifier(Token node) + { + } + + public virtual Node ExitIdentifier(Token node) + { + return node; + } + + public virtual void EnterHexLiteral(Token node) + { + } + + public virtual Node ExitHexliteral(Token node) + { + return node; + } + + public virtual void EnterNullLiteral(Token node) + { + } + + public virtual Node ExitNullLiteral(Token node) + { + return node; + } + + public virtual void EnterTimespan(Token node) + { + } + + public virtual Node ExitTimespan(Token node) + { + return node; + } + + public virtual void EnterDatetime(Token node) + { + } + + public virtual Node ExitDatetime(Token node) + { + return node; + } + + public virtual void EnterIf(Token node) + { + } + + public virtual Node ExitIf(Token node) + { + return node; + } + + public virtual void EnterCast(Token node) + { + } + + public virtual Node ExitCast(Token node) + { + return node; + } + + public virtual void EnterExpression(Production node) + { + } + + public virtual Node ExitExpression(Production node) + { + return node; + } + + public virtual void ChildExpression(Production node, Node child) + { + node.AddChild(child); + } + + public virtual void EnterXorExpression(Production node) + { + } + + public virtual Node ExitXorExpression(Production node) + { + return node; + } + + public virtual void ChildXorExpression(Production node, Node child) + { + node.AddChild(child); + } + + public virtual void EnterOrExpression(Production node) + { + } + + public virtual Node ExitOrExpression(Production node) + { + return node; + } + + public virtual void ChildOrExpression(Production node, Node child) + { + node.AddChild(child); + } + + public virtual void EnterAndExpression(Production node) + { + } + + public virtual Node ExitAndExpression(Production node) + { + return node; + } + + public virtual void ChildAndExpression(Production node, Node child) + { + node.AddChild(child); + } + + public virtual void EnterNotExpression(Production node) + { + } + + public virtual Node ExitNotExpression(Production node) + { + return node; + } + + public virtual void ChildNotExpression(Production node, Node child) + { + node.AddChild(child); + } + + public virtual void EnterInExpression(Production node) + { + } + + public virtual Node ExitInExpression(Production node) + { + return node; + } + + public virtual void ChildInExpression(Production node, Node child) + { + node.AddChild(child); + } + + public virtual void EnterInTargetExpression(Production node) + { + } + + public virtual Node ExitInTargetExpression(Production node) + { + return node; + } + + public virtual void ChildInTargetExpression(Production node, Node child) + { + node.AddChild(child); + } + + public virtual void EnterInListTargetExpression(Production node) + { + } + + public virtual Node ExitInListTargetExpression(Production node) + { + return node; + } + + public virtual void ChildInListTargetExpression(Production node, Node child) + { + node.AddChild(child); + } + + public virtual void EnterCompareExpression(Production node) + { + } + + public virtual Node ExitCompareExpression(Production node) + { + return node; + } + + public virtual void ChildCompareExpression(Production node, Node child) + { + node.AddChild(child); + } + + public virtual void EnterShiftExpression(Production node) + { + } + + public virtual Node ExitShiftExpression(Production node) + { + return node; + } + + public virtual void ChildShiftExpression(Production node, Node child) + { + node.AddChild(child); + } + + public virtual void EnterAdditiveExpression(Production node) + { + } + + public virtual Node ExitAdditiveExpression(Production node) + { + return node; + } + + public virtual void ChildAdditiveExpression(Production node, Node child) + { + node.AddChild(child); + } + + public virtual void EnterMultiplicativeExpression(Production node) + { + } + + public virtual Node ExitMultiplicativeExpression(Production node) + { + return node; + } + + + public virtual void ChildMultiplicativeExpression(Production node, Node child) + { + node.AddChild(child); + } + + public virtual void EnterPowerExpression(Production node) + { + } + + public virtual Node ExitPowerExpression(Production node) + { + return node; + } + + public virtual void ChildPowerExpression(Production node, Node child) + { + node.AddChild(child); + } + + public virtual void EnterNegateExpression(Production node) + { + } + + public virtual Node ExitNegateExpression(Production node) + { + return node; + } + + public virtual void ChildNegateExpression(Production node, Node child) + { + node.AddChild(child); + } + + public virtual void EnterMemberExpression(Production node) + { + } + + public virtual Node ExitMemberExpression(Production node) + { + return node; + } + + public virtual void ChildMemberExpression(Production node, Node child) + { + node.AddChild(child); + } + + public virtual void EnterMemberAccessExpression(Production node) + { + } + + public virtual Node ExitMemberAccessExpression(Production node) + { + return node; + } + + public virtual void ChildMemberAccessExpression(Production node, Node child) + { + node.AddChild(child); + } + + public virtual void EnterBasicExpression(Production node) + { + } + + public virtual Node ExitBasicExpression(Production node) + { + return node; + } + + public virtual void ChildBasicExpression(Production node, Node child) + { + node.AddChild(child); + } + + public virtual void EnterMemberFunctionExpression(Production node) + { + } + + public virtual Node ExitMemberFunctionExpression(Production node) + { + return node; + } + + public virtual void ChildMemberFunctionExpression(Production node, Node child) + { + node.AddChild(child); + } + + public virtual void EnterFieldPropertyExpression(Production node) + { + } + + public virtual Node ExitFieldPropertyExpression(Production node) + { + return node; + } + + public virtual void ChildFieldPropertyExpression(Production node, Node child) + { + node.AddChild(child); + } + + public virtual void EnterSpecialFunctionExpression(Production node) + { + } + + public virtual Node ExitSpecialFunctionExpression(Production node) + { + return node; + } + + public virtual void ChildSpecialFunctionExpression(Production node, Node child) + { + node.AddChild(child); + } + + public virtual void EnterIfExpression(Production node) + { + } + + public virtual Node ExitIfExpression(Production node) + { + return node; + } + + public virtual void ChildIfExpression(Production node, Node child) + { + node.AddChild(child); + } + + public virtual void EnterCastExpression(Production node) + { + } + + public virtual Node ExitCastExpression(Production node) + { + return node; + } + + public virtual void ChildCastExpression(Production node, Node child) + { + node.AddChild(child); + } + + public virtual void EnterCastTypeExpression(Production node) + { + } + + public virtual Node ExitCastTypeExpression(Production node) + { + return node; + } + + public virtual void ChildCastTypeExpression(Production node, Node child) + { + node.AddChild(child); + } + + public virtual void EnterIndexExpression(Production node) + { + } + + public virtual Node ExitIndexExpression(Production node) + { + return node; + } + + public virtual void ChildIndexExpression(Production node, Node child) + { + node.AddChild(child); + } + + public virtual void EnterFunctionCallExpression(Production node) + { + } + + public virtual Node ExitFunctionCallExpression(Production node) + { + return node; + } + + public virtual void ChildFunctionCallExpression(Production node, Node child) + { + node.AddChild(child); + } + + public virtual void EnterArgumentList(Production node) + { + } + + public virtual Node ExitArgumentList(Production node) + { + return node; + } + + public virtual void ChildArgumentList(Production node, Node child) + { + node.AddChild(child); + } + + public virtual void EnterLiteralExpression(Production node) + { + } + + public virtual Node ExitLiteralExpression(Production node) + { + return node; + } + + public virtual void ChildLiteralExpression(Production node, Node child) + { + node.AddChild(child); + } + + public virtual void EnterBooleanLiteralExpression(Production node) + { + } + + public virtual Node ExitBooleanLiteralExpression(Production node) + { + return node; + } + + public virtual void ChildBooleanLiteralExpression(Production node, Node child) + { + node.AddChild(child); + } + + public virtual void EnterExpressionGroup(Production node) + { + } + + public virtual Node ExitExpressionGroup(Production node) + { + return node; + } + + public virtual void ChildExpressionGroup(Production node, Node child) + { + node.AddChild(child); + } + } +} diff --git a/Parsing/ExpressionConstants.cs b/Parsing/ExpressionConstants.cs new file mode 100644 index 0000000..c5c7d0a --- /dev/null +++ b/Parsing/ExpressionConstants.cs @@ -0,0 +1,78 @@ +namespace Flee.Parsing +{ + /// + /// An enumeration with token and production node + ///constants. + 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 + } +} \ No newline at end of file diff --git a/Parsing/ExpressionParser.cs b/Parsing/ExpressionParser.cs new file mode 100644 index 0000000..122a1c8 --- /dev/null +++ b/Parsing/ExpressionParser.cs @@ -0,0 +1,460 @@ +using Flee.PublicTypes; + +namespace Flee.Parsing +{ + /// + /// A token stream parser. + /// + 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); + } + } +} diff --git a/Parsing/ExpressionTokenizer.cs b/Parsing/ExpressionTokenizer.cs new file mode 100644 index 0000000..34d7c62 --- /dev/null +++ b/Parsing/ExpressionTokenizer.cs @@ -0,0 +1,153 @@ +using Flee.PublicTypes; + + +namespace Flee.Parsing +{ + /// + /// A character stream tokenizer. + /// + 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); + } + } +} diff --git a/Parsing/LookAheadReader.cs b/Parsing/LookAheadReader.cs new file mode 100644 index 0000000..74a2110 --- /dev/null +++ b/Parsing/LookAheadReader.cs @@ -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; + } + } + } + } +} diff --git a/Parsing/LookAheadSet.cs b/Parsing/LookAheadSet.cs new file mode 100644 index 0000000..2b0e75d --- /dev/null +++ b/Parsing/LookAheadSet.cs @@ -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; + } + } + } +} diff --git a/Parsing/Matcher.cs b/Parsing/Matcher.cs new file mode 100644 index 0000000..9bb985c --- /dev/null +++ b/Parsing/Matcher.cs @@ -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; + } + } +} diff --git a/Parsing/Node.cs b/Parsing/Node.cs new file mode 100644 index 0000000..dd69676 --- /dev/null +++ b/Parsing/Node.cs @@ -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); + } + } + } +} diff --git a/Parsing/ParseException.cs b/Parsing/ParseException.cs new file mode 100644 index 0000000..7ab175f --- /dev/null +++ b/Parsing/ParseException.cs @@ -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; + + + /// + /// Creates a new parse exception. + /// + /// + /// + /// + /// + public ParseException(ErrorType type, + string info, + int line, + int column) + : this(type, info, null, line, column) + { + } + + /// + /// 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. + /// + /// + /// + /// + /// + /// + 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(); + } + } +} diff --git a/Parsing/Parser.cs b/Parsing/Parser.cs new file mode 100644 index 0000000..0d152da --- /dev/null +++ b/Parsing/Parser.cs @@ -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; + + /// + /// Creates a new parser. + /// + /// + internal Parser(TextReader input) : this(input, null) + { + } + + /// + /// Creates a new parser. + /// + /// + /// + 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); + } + } + } +} diff --git a/Parsing/ParserCreationException.cs b/Parsing/ParserCreationException.cs new file mode 100644 index 0000000..f68ac19 --- /dev/null +++ b/Parsing/ParserCreationException.cs @@ -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; + } + } +} diff --git a/Parsing/ParserLogException.cs b/Parsing/ParserLogException.cs new file mode 100644 index 0000000..bb2ed87 --- /dev/null +++ b/Parsing/ParserLogException.cs @@ -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; + } + } +} diff --git a/Parsing/Production.cs b/Parsing/Production.cs new file mode 100644 index 0000000..31b40f7 --- /dev/null +++ b/Parsing/Production.cs @@ -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 + ')'; + } + } +} diff --git a/Parsing/ProductionPattern.cs b/Parsing/ProductionPattern.cs new file mode 100644 index 0000000..8d6c93d --- /dev/null +++ b/Parsing/ProductionPattern.cs @@ -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(); + } + } +} diff --git a/Parsing/ProductionPatternAlternative.cs b/Parsing/ProductionPatternAlternative.cs new file mode 100644 index 0000000..221805a --- /dev/null +++ b/Parsing/ProductionPatternAlternative.cs @@ -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(); + } + } +} diff --git a/Parsing/ProductionPatternElement.cs b/Parsing/ProductionPatternElement.cs new file mode 100644 index 0000000..cc35d60 --- /dev/null +++ b/Parsing/ProductionPatternElement.cs @@ -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(); + } + } +} diff --git a/Parsing/ReaderBuffer.cs b/Parsing/ReaderBuffer.cs new file mode 100644 index 0000000..1bb94ef --- /dev/null +++ b/Parsing/ReaderBuffer.cs @@ -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); + } + } +} diff --git a/Parsing/RecursiveDescentParser.cs b/Parsing/RecursiveDescentParser.cs new file mode 100644 index 0000000..545ce9b --- /dev/null +++ b/Parsing/RecursiveDescentParser.cs @@ -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) { "" }; + 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); + } + } + } + } +} diff --git a/Parsing/RegExp.cs b/Parsing/RegExp.cs new file mode 100644 index 0000000..c08cff5 --- /dev/null +++ b/Parsing/RegExp.cs @@ -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; + } + } +} diff --git a/Parsing/RegExpException.cs b/Parsing/RegExpException.cs new file mode 100644 index 0000000..4e7ac22 --- /dev/null +++ b/Parsing/RegExpException.cs @@ -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(""); + } + + // Append position + buffer.Append(" at position "); + buffer.Append(_position); + + return buffer.ToString(); + } + } +} diff --git a/Parsing/RepeatElement.cs b/Parsing/RepeatElement.cs new file mode 100644 index 0000000..197df4f --- /dev/null +++ b/Parsing/RepeatElement.cs @@ -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 + " "); + } + } +} diff --git a/Parsing/StackParser.cs b/Parsing/StackParser.cs new file mode 100644 index 0000000..d3f8269 --- /dev/null +++ b/Parsing/StackParser.cs @@ -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) { "" }; + 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 + }; + } + + /// + /// 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. + /// + /// + /// + private Node ParsePatterns(ProductionPattern start) + { + Stack _stack = new Stack(); + _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); + } + } + } + } +} diff --git a/Parsing/StringElement.cs b/Parsing/StringElement.cs new file mode 100644 index 0000000..f1c525a --- /dev/null +++ b/Parsing/StringElement.cs @@ -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 + "'"); + } + } +} diff --git a/Parsing/Token.cs b/Parsing/Token.cs new file mode 100644 index 0000000..3386c9f --- /dev/null +++ b/Parsing/Token.cs @@ -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(); + } + } +} diff --git a/Parsing/TokenMatch.cs b/Parsing/TokenMatch.cs new file mode 100644 index 0000000..fadc90d --- /dev/null +++ b/Parsing/TokenMatch.cs @@ -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; + } + } + } +} diff --git a/Parsing/TokenNFA.cs b/Parsing/TokenNFA.cs new file mode 100644 index 0000000..7d7c470 --- /dev/null +++ b/Parsing/TokenNFA.cs @@ -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.

+ * + * 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; + } + } +} diff --git a/Parsing/TokenPattern.cs b/Parsing/TokenPattern.cs new file mode 100644 index 0000000..7213c97 --- /dev/null +++ b/Parsing/TokenPattern.cs @@ -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; + } + } +} diff --git a/Parsing/TokenRegExpParser.cs b/Parsing/TokenRegExpParser.cs new file mode 100644 index 0000000..061003f --- /dev/null +++ b/Parsing/TokenRegExpParser.cs @@ -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; + } + } + } +} diff --git a/Parsing/TokenStringDFA.cs b/Parsing/TokenStringDFA.cs new file mode 100644 index 0000000..710c7a2 --- /dev/null +++ b/Parsing/TokenStringDFA.cs @@ -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); + } + } +} diff --git a/Parsing/Tokenizer.cs b/Parsing/Tokenizer.cs new file mode 100644 index 0000000..82d6cb7 --- /dev/null +++ b/Parsing/Tokenizer.cs @@ -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; + } + } + } +} diff --git a/PublicTypes/Exceptions.cs b/PublicTypes/Exceptions.cs new file mode 100644 index 0000000..e484660 --- /dev/null +++ b/PublicTypes/Exceptions.cs @@ -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 + } + + ///

+ /// + /// + [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; + } +} diff --git a/PublicTypes/ExpressionContext.cs b/PublicTypes/ExpressionContext.cs new file mode 100644 index 0000000..fcfe7bc --- /dev/null +++ b/PublicTypes/ExpressionContext.cs @@ -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("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(expression, this, false); + } + + public IGenericExpression CompileGeneric(string expression) + { + return new Flee.InternalTypes.Expression(expression, this, true); + } + + #endregion + + #region "Properties - Private" + + private ExpressionParser IdentifierParser + { + get + { + ExpressionParser parser = _myProperties.GetValue("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("NoClone"); } + set { _myProperties.SetValue("NoClone", value); } + } + + internal object ExpressionOwner => _myProperties.GetValue("ExpressionOwner"); + + internal string CalcEngineExpressionName => _myProperties.GetValue("CalcEngineExpressionName"); + + internal ExpressionParser Parser => _myProperties.GetValue("ExpressionParser"); + + #endregion + + #region "Properties - Public" + public ExpressionOptions Options => _myProperties.GetValue("Options"); + + public ExpressionImports Imports => _myProperties.GetValue("Imports"); + + public VariableCollection Variables => _myVariables; + + public CalculationEngine CalculationEngine => _myProperties.GetValue("CalculationEngine"); + + public ExpressionParserOptions ParserOptions => _myProperties.GetValue("ParserOptions"); + + #endregion + } +} diff --git a/PublicTypes/ExpressionImports.cs b/PublicTypes/ExpressionImports.cs new file mode 100644 index 0000000..2236b77 --- /dev/null +++ b/PublicTypes/ExpressionImports.cs @@ -0,0 +1,195 @@ +using System.Reflection; +using Flee.InternalTypes; +using Flee.Resources; + +namespace Flee.PublicTypes +{ + public sealed class ExpressionImports + { + + private static Dictionary OurBuiltinTypeMap = CreateBuiltinTypeMap(); + private NamespaceImport MyRootImport; + private TypeImport MyOwnerImport; + + private ExpressionContext MyContext; + internal ExpressionImports() + { + MyRootImport = new NamespaceImport("true"); + } + + private static Dictionary CreateBuiltinTypeMap() + { + Dictionary map = new Dictionary(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 pair in OurBuiltinTypeMap) + { + this.AddType(pair.Value, pair.Key); + } + } + #endregion + + #region "Properties - Public" + public NamespaceImport RootImport => MyRootImport; + + #endregion + } +} diff --git a/PublicTypes/ExpressionOptions.cs b/PublicTypes/ExpressionOptions.cs new file mode 100644 index 0000000..0093009 --- /dev/null +++ b/PublicTypes/ExpressionOptions.cs @@ -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("CaseSensitive"); + _myProperties.SetToDefault("Checked"); + _myProperties.SetToDefault("EmitToAssembly"); + _myProperties.SetToDefault("ResultType"); + _myProperties.SetToDefault("IsGeneric"); + _myProperties.SetToDefault("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("ResultType"); } + set + { + Utility.AssertNotNull(value, "value"); + _myProperties.SetValue("ResultType", value); + } + } + + public bool Checked + { + get { return _myProperties.GetValue("Checked"); } + set { _myProperties.SetValue("Checked", value); } + } + + public StringComparison StringComparison + { + get { return _myProperties.GetValue("StringComparison"); } + set { _myProperties.SetValue("StringComparison", value); } + } + + public bool EmitToAssembly + { + get { return _myProperties.GetValue("EmitToAssembly"); } + set { _myProperties.SetValue("EmitToAssembly", value); } + } + + public BindingFlags OwnerMemberAccess + { + get { return _myProperties.GetValue("OwnerMemberAccess"); } + set { _myProperties.SetValue("OwnerMemberAccess", value); } + } + + public bool CaseSensitive + { + get { return _myProperties.GetValue("CaseSensitive"); } + set + { + if (this.CaseSensitive != value) + { + _myProperties.SetValue("CaseSensitive", value); + if (CaseSensitiveChanged != null) + { + CaseSensitiveChanged(this, EventArgs.Empty); + } + } + } + } + + public bool IntegersAsDoubles + { + get { return _myProperties.GetValue("IntegersAsDoubles"); } + set { _myProperties.SetValue("IntegersAsDoubles", value); } + } + + public CultureInfo ParseCulture + { + get { return _myProperties.GetValue("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"); } + set { _myProperties.SetValue("RealLiteralDataType", value); } + } + #endregion + + #region "Properties - Non Public" + internal IEqualityComparer 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("IsGeneric"); } + set { _myProperties.SetValue("IsGeneric", value); } + } + #endregion + } +} diff --git a/PublicTypes/ExpressionParserOptions.cs b/PublicTypes/ExpressionParserOptions.cs new file mode 100644 index 0000000..5d857c8 --- /dev/null +++ b/PublicTypes/ExpressionParserOptions.cs @@ -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("DateTimeFormat"); } + set { _myProperties.SetValue("DateTimeFormat", value); } + } + + public bool RequireDigitsBeforeDecimalPoint + { + get { return _myProperties.GetValue("RequireDigitsBeforeDecimalPoint"); } + set { _myProperties.SetValue("RequireDigitsBeforeDecimalPoint", value); } + } + + public char DecimalSeparator + { + get { return _myProperties.GetValue("DecimalSeparator"); } + set + { + _myProperties.SetValue("DecimalSeparator", value); + _myParseCulture.NumberFormat.NumberDecimalSeparator = value.ToString(); + } + } + + public char FunctionArgumentSeparator + { + get { return _myProperties.GetValue("FunctionArgumentSeparator"); } + set { _myProperties.SetValue("FunctionArgumentSeparator", value); } + } + + #endregion + } +} diff --git a/PublicTypes/ImportTypes.cs b/PublicTypes/ImportTypes.cs new file mode 100644 index 0000000..08b379f --- /dev/null +++ b/PublicTypes/ImportTypes.cs @@ -0,0 +1,417 @@ +using System.Reflection; +using Flee.InternalTypes; +using Flee.Resources; + +namespace Flee.PublicTypes +{ + public abstract class ImportBase : IEnumerable, IEquatable + { + 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 dest); + protected abstract void AddMembers(MemberTypes memberType, ICollection dest); + + internal ImportBase Clone() + { + return (ImportBase)this.MemberwiseClone(); + } + + protected static void AddImportMembers(ImportBase import, string memberName, MemberTypes memberType, ICollection dest) + { + import.AddMembers(memberName, memberType, dest); + } + + protected static void AddImportMembers(ImportBase import, MemberTypes memberType, ICollection dest) + { + import.AddMembers(memberType, dest); + } + + protected static void AddMemberRange(ICollection members, ICollection 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 found = new List(); + this.AddMembers(memberName, memberType, found); + return found.ToArray(); + } + #endregion + + #region "Methods - Public" + public MemberInfo[] GetMembers(MemberTypes memberType) + { + List found = new List(); + this.AddMembers(memberType, found); + return found.ToArray(); + } + #endregion + + #region "IEnumerable Implementation" + public virtual System.Collections.Generic.IEnumerator GetEnumerator() + { + List coll = new List(); + 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 dest) + { + MemberInfo[] members = _myType.FindMembers(memberType, _myBindFlags, this.Context.Options.MemberFilter, memberName); + ImportBase.AddMemberRange(members, dest); + } + + protected override void AddMembers(MemberTypes memberType, ICollection 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 GetEnumerator() + { + if (_myUseTypeNameAsNamespace == true) + { + List coll = new List(); + 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 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 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 + { + private readonly string _myNamespace; + private readonly List _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(); + } + + 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 dest) + { + foreach (ImportBase import in this.NonContainerImports) + { + AddImportMembers(import, memberName, memberType, dest); + } + } + + protected override void AddMembers(MemberTypes memberType, ICollection 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 NonContainerImports + { + get + { + List found = new List(); + + 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 GetEnumerator() + { + return _myImports.GetEnumerator(); + } + + public int Count => _myImports.Count; + + public bool IsReadOnly => false; + + #endregion + } +} diff --git a/PublicTypes/Miscellaneous.cs b/PublicTypes/Miscellaneous.cs new file mode 100644 index 0000000..a9c01fa --- /dev/null +++ b/PublicTypes/Miscellaneous.cs @@ -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 : IExpression + { + T Evaluate(); + } + + public sealed class ExpressionInfo + { + + + private readonly IDictionary _myData; + internal ExpressionInfo() + { + _myData = new Dictionary + { + {"ReferencedVariables", new Dictionary(StringComparer.OrdinalIgnoreCase)} + }; + } + + internal void AddReferencedVariable(string name) + { + IDictionary dict = (IDictionary)_myData["ReferencedVariables"]; + dict[name] = name; + } + + public string[] GetReferencedVariables() + { + IDictionary dict = (IDictionary)_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 + } +} diff --git a/PublicTypes/VariableCollection.cs b/PublicTypes/VariableCollection.cs new file mode 100644 index 0000000..930dd0d --- /dev/null +++ b/PublicTypes/VariableCollection.cs @@ -0,0 +1,404 @@ +using Flee.InternalTypes; +using Flee.Resources; +using System.ComponentModel; +using System.Reflection; + +namespace Flee.PublicTypes +{ + /// + /// + /// + public sealed class VariableCollection : IDictionary + { + private IDictionary _myVariables; + private readonly ExpressionContext _myContext; + + public event EventHandler ResolveVariableType; + + public event EventHandler ResolveVariableValue; + + public event EventHandler ResolveFunction; + + public event EventHandler 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(_myContext.Options.StringComparer); + } + + private void OnOptionsCaseSensitiveChanged(object sender, EventArgs e) + { + this.CreateDictionary(); + } + + internal void Copy(VariableCollection dest) + { + dest.CreateDictionary(); + dest.HookOptions(); + + foreach (KeyValuePair 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(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 GetNameValueDictionary() + { + Dictionary dict = new Dictionary(); + + foreach (KeyValuePair 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(string name) + { + if (_myVariables.TryGetValue(name, out IVariable variable)) + { + if (variable is IGenericVariable generic) + { + return (T)generic.GetValue(); + } + } + + GenericVariable result = new GenericVariable(); + GenericVariable vTemp = new GenericVariable(); + 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(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(value); + } + + public T GetFunctionResultInternal(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(result); + } + + #endregion "Methods - Public" + + #region "IDictionary Implementation" + + private void Add1(System.Collections.Generic.KeyValuePair item) + { + this.Add(item.Key, item.Value); + } + + void System.Collections.Generic.ICollection>.Add(System.Collections.Generic.KeyValuePair item) + { + Add1(item); + } + + public void Clear() + { + _myVariables.Clear(); + } + + private bool Contains1(System.Collections.Generic.KeyValuePair item) + { + return this.ContainsKey(item.Key); + } + + bool System.Collections.Generic.ICollection>.Contains(System.Collections.Generic.KeyValuePair item) + { + return Contains1(item); + } + + private void CopyTo(System.Collections.Generic.KeyValuePair[] array, int arrayIndex) + { + Dictionary dict = this.GetNameValueDictionary(); + ICollection> coll = dict; + coll.CopyTo(array, arrayIndex); + } + + private bool Remove1(System.Collections.Generic.KeyValuePair item) + { + return this.Remove(item.Key); + } + + bool System.Collections.Generic.ICollection>.Remove(System.Collections.Generic.KeyValuePair 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> GetEnumerator() + { + Dictionary 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 Keys => _myVariables.Keys; + + public System.Collections.Generic.ICollection Values + { + get + { + Dictionary dict = this.GetNameValueDictionary(); + return dict.Values; + } + } + + void ICollection>.CopyTo(KeyValuePair[] array, int arrayIndex) + { + CopyTo(array, arrayIndex); + } + + #endregion "IDictionary Implementation" + } +} \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..c078ef5 --- /dev/null +++ b/README.md @@ -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) | [![Flee](https://img.shields.io/badge/nuget-v2.0.0-blue.svg)](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.