diff --git a/src/Generator/Library.cs b/src/Generator/Library.cs index 8317cede..a720e382 100644 --- a/src/Generator/Library.cs +++ b/src/Generator/Library.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Text.RegularExpressions; +using CodingSeb.ExpressionEvaluator; using CppSharp.AST; using CppSharp.Generators; using CppSharp.Passes; diff --git a/src/Generator/Utils/ExpressionEvaluator.cs b/src/Generator/Utils/ExpressionEvaluator.cs index b0fdb8bb..4e6937a0 100644 --- a/src/Generator/Utils/ExpressionEvaluator.cs +++ b/src/Generator/Utils/ExpressionEvaluator.cs @@ -1,1468 +1,4250 @@ -using System; +/****************************************************************************************************** + Title : ExpressionEvaluator (https://github.com/codingseb/ExpressionEvaluator) + Version : 1.4.16.0 + (if last digit (the forth) is not a zero, the version is an intermediate version and can be unstable) + + Author : Coding Seb + Licence : MIT (https://github.com/codingseb/ExpressionEvaluator/blob/master/LICENSE.md) +*******************************************************************************************************/ + +using System; +using System.Collections; using System.Collections.Generic; using System.ComponentModel; +using System.Dynamic; using System.Globalization; using System.Linq; using System.Reflection; -using System.Runtime.Serialization; -using System.Security.Permissions; +using System.Runtime.InteropServices; +using System.Text; using System.Text.RegularExpressions; -/// -/// This class allow to evaluate a string math or pseudo C# expression -/// -internal class ExpressionEvaluator +namespace CodingSeb.ExpressionEvaluator { - private static Regex varOrFunctionRegEx = new Regex(@"^(?(?[?])?\.)?(?[a-zA-Z_][a-zA-Z0-9_]*)\s*(?[(])?", RegexOptions.IgnoreCase); - private static Regex numberRegex = new Regex(@"^(?[+-])?\d+(?\.?\d+(e[+-]?\d+)?)?(?ul|[fdulm])?", RegexOptions.IgnoreCase); - private static Regex stringBeginningRegex = new Regex("^(?[$])?(?[@])?[\"]"); - private static Regex castRegex = new Regex(@"^\(\s*(?[a-zA-Z_][a-zA-Z0-9_\.\[\]<>]*[?]?)\s*\)"); - private static Regex indexingBeginningRegex = new Regex(@"^[?]?\["); - private static Regex primaryTypesRegex = new Regex(@"(?<=^|[^a-zA-Z_])(?object|string|bool[?]?|byte[?]?|char[?]?|decimal[?]?|double[?]?|short[?]?|int[?]?|long[?]?|sbyte[?]?|float[?]?|ushort[?]?|uint[?]?|void)(?=[^a-zA-Z_]|$)"); - private static Regex endOfStringWithDollar = new Regex("^[^\"{]*[\"{]"); - private static Regex endOfStringWithoutDollar = new Regex("^[^\"]*[\"]"); - private static Regex endOfStringInterpolationRegex = new Regex("^[^}\"]*[}\"]"); - private static Regex stringBeginningForEndBlockRegex = new Regex("[$]?[@]?[\"]$"); - private static Regex lambdaExpressionRegex = new Regex(@"^\s*(?(\s*[(]\s*([a-zA-Z_][a-zA-Z0-9_]*\s*([,]\s*[a-zA-Z_][a-zA-Z0-9_]*\s*)*)?[)])|[a-zA-Z_][a-zA-Z0-9_]*)\s*=>(?.*)$"); - private static Regex lambdaArgRegex = new Regex(@"[a-zA-Z_][a-zA-Z0-9_]*"); - - private static BindingFlags instanceBindingFlag = (BindingFlags.Default | BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance); - private static BindingFlags staticBindingFlag = (BindingFlags.Default | BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Static); - - private static Dictionary PrimaryTypesDict = new Dictionary() - { - { "object", typeof(object) }, - { "string", typeof(string) }, - { "bool", typeof(bool) }, - { "bool?", typeof(bool?) }, - { "byte", typeof(byte) }, - { "byte?", typeof(byte?) }, - { "char", typeof(char) }, - { "char?", typeof(char?) }, - { "decimal", typeof(decimal) }, - { "decimal?", typeof(decimal?) }, - { "double", typeof(double) }, - { "double?", typeof(double?) }, - { "short", typeof(short) }, - { "short?", typeof(short?) }, - { "int", typeof(int) }, - { "int?", typeof(int?) }, - { "long", typeof(long) }, - { "long?", typeof(long?) }, - { "sbyte", typeof(sbyte) }, - { "sbyte?", typeof(sbyte?) }, - { "float", typeof(float) }, - { "float?", typeof(float?) }, - { "ushort", typeof(ushort) }, - { "ushort?", typeof(ushort?) }, - { "uint", typeof(uint) }, - { "uint?", typeof(uint?) }, - { "ulong", typeof(ulong) }, - { "ulong?", typeof(ulong?) }, - { "void", typeof(void) } - }; - - private static Dictionary> numberSuffixToParse = new Dictionary>() - { - { "f", number => float.Parse(number, NumberStyles.Any, CultureInfo.InvariantCulture) }, - { "d", number => double.Parse(number, NumberStyles.Any, CultureInfo.InvariantCulture) }, - { "u", number => uint.Parse(number, NumberStyles.Any, CultureInfo.InvariantCulture) }, - { "l", number => long.Parse(number, NumberStyles.Any, CultureInfo.InvariantCulture) }, - { "ul", number => ulong.Parse(number, NumberStyles.Any, CultureInfo.InvariantCulture) }, - { "m", number => decimal.Parse(number, NumberStyles.Any, CultureInfo.InvariantCulture) } - }; - - private static Dictionary stringEscapedCharDict = new Dictionary() - { - { '\\', @"\" }, - { '"', "\"" }, - { '0', "\0" }, - { 'a', "\a" }, - { 'b', "\b" }, - { 'f', "\f" }, - { 'n', "\n" }, - { 'r', "\r" }, - { 't', "\t" }, - { 'v', "\v" } - }; - - private enum ExpressionOperator - { - Plus, - Minus, - Multiply, - Divide, - Modulo, - Lower, - Greater, - Equal, - LowerOrEqual, - GreaterOrEqual, - Is, - NotEqual, - LogicalNegation, - ConditionalAnd, - ConditionalOr, - LogicalAnd, - LogicalOr, - LogicalXor, - ShiftBitsLeft, - ShiftBitsRight, - NullCoalescing, - Cast, - Indexing, - IndexingWithNullConditional, - } - - private static Dictionary operatorsDictionary = new Dictionary(StringComparer.OrdinalIgnoreCase) - { - { "+", ExpressionOperator.Plus }, - { "-", ExpressionOperator.Minus }, - { "*", ExpressionOperator.Multiply }, - { "/", ExpressionOperator.Divide }, - { "%", ExpressionOperator.Modulo }, - { "<", ExpressionOperator.Lower }, - { ">", ExpressionOperator.Greater }, - { "<=", ExpressionOperator.LowerOrEqual }, - { ">=", ExpressionOperator.GreaterOrEqual }, - { "is", ExpressionOperator.Is }, - { "==", ExpressionOperator.Equal }, - { "<>", ExpressionOperator.NotEqual }, - { "!=", ExpressionOperator.NotEqual }, - { "&&", ExpressionOperator.ConditionalAnd }, - { "||", ExpressionOperator.ConditionalOr }, - { "!", ExpressionOperator.LogicalNegation }, - { "&", ExpressionOperator.LogicalAnd }, - { "|", ExpressionOperator.LogicalOr }, - { "^", ExpressionOperator.LogicalXor }, - { "<<", ExpressionOperator.ShiftBitsLeft }, - { ">>", ExpressionOperator.ShiftBitsRight }, - { "??", ExpressionOperator.NullCoalescing }, - }; - - private static Dictionary leftOperandOnlyOperatorsEvaluationDictionary = new Dictionary() + /// + /// This class allow to evaluate a string math or pseudo C# expression + /// + public partial class ExpressionEvaluator { - }; + #region Regex declarations + + protected const string primaryTypesRegexPattern = @"(?<=^|[^\p{L}_])(?object|string|bool[?]?|byte[?]?|char[?]?|decimal[?]?|double[?]?|short[?]?|int[?]?|long[?]?|sbyte[?]?|float[?]?|ushort[?]?|uint[?]?|ulong[?]?|void)(?=[^a-zA-Z_]|$)"; + + protected static readonly Regex varOrFunctionRegEx = new Regex(@"^((?[+-])|(?[+][+]|--)|(?var)\s+|(?dynamic)\s+|(?(?[?])?\.)?)(?[\p{L}_](?>[\p{L}_0-9]*))(?>\s*)((?(?[+\-*/%&|^]|<<|>>|\?\?)?=(?![=>]))|(?([+][+]|--)(?![\p{L}_0-9]))|((?[<](?>([\p{L}_](?>[\p{L}_0-9]*)|(?>\s+)|[,\.])+|(?[<])|(?<-gentag>[>]))*(?(gentag)(?!))[>])?(?[(])?))", RegexOptions.IgnoreCase | RegexOptions.Compiled); + + protected const string numberRegexOrigPattern = @"^(?[+-])?([0-9][0-9_{1}]*[0-9]|\d)(?{0}?([0-9][0-9_]*[0-9]|\d)(e[+-]?([0-9][0-9_]*[0-9]|\d))?)?(?ul|[fdulm])?"; + protected string numberRegexPattern; + + protected static readonly Regex otherBasesNumberRegex = new Regex("^(?[+-])?(?0(?x)([0-9a-f][0-9a-f_]*[0-9a-f]|[0-9a-f])|0(?b)([01][01_]*[01]|[01]))", RegexOptions.IgnoreCase | RegexOptions.Compiled); + protected static readonly Regex stringBeginningRegex = new Regex("^(?[$])?(?[@])?[\"]", RegexOptions.Compiled); + protected static readonly Regex internalCharRegex = new Regex(@"^['](\\[\\'0abfnrtv]|[^'])[']", RegexOptions.Compiled); + protected static readonly Regex indexingBeginningRegex = new Regex(@"^[?]?\[", RegexOptions.Compiled); + protected static readonly Regex assignationOrPostFixOperatorRegex = new Regex(@"^(?>\s*)((?[+\-*/%&|^]|<<|>>|\?\?)?=(?![=>])|(?([+][+]|--)(?![\p{L}_0-9])))"); + protected static readonly Regex genericsDecodeRegex = new Regex("(?[^,<>]+)(?[<](?>[^<>]+|(?[<])|(?<-gentag>[>]))*(?(gentag)(?!))[>])?", RegexOptions.Compiled); + protected static readonly Regex genericsEndOnlyOneTrim = new Regex(@"(?>\s*)[>](?>\s*)$", RegexOptions.Compiled); + + protected static readonly Regex endOfStringWithDollar = new Regex("^([^\"{\\\\]|\\\\[\\\\\"0abfnrtv])*[\"{]", RegexOptions.Compiled); + protected static readonly Regex endOfStringWithoutDollar = new Regex("^([^\"\\\\]|\\\\[\\\\\"0abfnrtv])*[\"]", RegexOptions.Compiled); + protected static readonly Regex endOfStringWithDollarWithAt = new Regex("^[^\"{]*[\"{]", RegexOptions.Compiled); + protected static readonly Regex endOfStringWithoutDollarWithAt = new Regex("^[^\"]*[\"]", RegexOptions.Compiled); + protected static readonly Regex endOfStringInterpolationRegex = new Regex("^('\"'|[^}\"])*[}\"]", RegexOptions.Compiled); + protected static readonly Regex stringBeginningForEndBlockRegex = new Regex("[$]?[@]?[\"]$", RegexOptions.Compiled); + protected static readonly Regex lambdaExpressionRegex = new Regex(@"^(?>\s*)(?((?>\s*)[(](?>\s*)([\p{L}_](?>[\p{L}_0-9]*)(?>\s*)([,](?>\s*)[\p{L}_][\p{L}_0-9]*(?>\s*))*)?[)])|[\p{L}_](?>[\p{L}_0-9]*))(?>\s*)=>(?.*)$", RegexOptions.Singleline | RegexOptions.Compiled); + protected static readonly Regex lambdaArgRegex = new Regex(@"[\p{L}_](?>[\p{L}_0-9]*)", RegexOptions.Compiled); + protected static readonly Regex initInNewBeginningRegex = new Regex(@"^(?>\s*){", RegexOptions.Compiled); + + // Depending on OptionInlineNamespacesEvaluationActive. Initialized in constructor + protected string InstanceCreationWithNewKeywordRegexPattern { get { return @"^new(?>\s*)((?[{{])|((?[\p{L}_][\p{L}_0-9"+ (OptionInlineNamespacesEvaluationActive ? @"\." : string.Empty) + @"]*)(?>\s*)(?[<](?>[^<>]+|(?[<])|(?<-gentag>[>]))*(?(gentag)(?!))[>])?(?>\s*)((?[(])|(?\[)|(?[{{]))?))"; } } + protected string CastRegexPattern { get { return @"^\((?>\s*)(?[\p{L}_][\p{L}_0-9"+ (OptionInlineNamespacesEvaluationActive ? @"\." : string.Empty) + @"\[\]<>]*[?]?)(?>\s*)\)"; } } + + // To remove comments in scripts based on https://stackoverflow.com/questions/3524317/regex-to-strip-line-comments-from-c-sharp/3524689#3524689 + protected const string blockComments = @"/\*(.*?)\*/"; + protected const string lineComments = @"//[^\r\n]*"; + protected const string stringsIgnore = @"""((\\[^\n]|[^""\n])*)"""; + protected const string verbatimStringsIgnore = @"@(""[^""]*"")+"; + protected static readonly Regex removeCommentsRegex = new Regex($"{blockComments}|{lineComments}|{stringsIgnore}|{verbatimStringsIgnore}", RegexOptions.Singleline | RegexOptions.Compiled); + protected static readonly Regex newLineCharsRegex = new Regex(@"\r\n|\r|\n", RegexOptions.Compiled); + + // For script only + protected static readonly Regex blockKeywordsBeginningRegex = new Regex(@"^(?>\s*)(?while|for|foreach|if|else(?>\s*)if|catch)(?>\s*)[(]", RegexOptions.IgnoreCase | RegexOptions.Compiled); + protected static readonly Regex foreachParenthisEvaluationRegex = new Regex(@"^(?>\s*)(?[\p{L}_](?>[\p{L}_0-9]*))(?>\s*)(?in)(?>\s*)(?.*)", RegexOptions.IgnoreCase | RegexOptions.Compiled); + protected static readonly Regex blockKeywordsWithoutParenthesesBeginningRegex = new Regex(@"^(?>\s*)(?else|do|try|finally)(?![\p{L}_0-9])", RegexOptions.IgnoreCase | RegexOptions.Compiled); + protected static readonly Regex blockBeginningRegex = new Regex(@"^(?>\s*)[{]", RegexOptions.Compiled); + protected static readonly Regex returnKeywordRegex = new Regex(@"^return((?>\s*)|\()", RegexOptions.IgnoreCase | RegexOptions.Singleline | RegexOptions.Compiled); + protected static readonly Regex nextIsEndOfExpressionRegex = new Regex(@"^(?>\s*)[;]", RegexOptions.Compiled); + + #endregion + + #region enums (if else blocks states) + + protected enum IfBlockEvaluatedState + { + NoBlockEvaluated, + If, + ElseIf + } - private static Dictionary rightOperandOnlyOperatorsEvaluationDictionary = new Dictionary() - { - {ExpressionOperator.LogicalNegation, true } - }; + protected enum TryBlockEvaluatedState + { + NoBlockEvaluated, + Try, + Catch + } - private static List>> operatorsEvaluations = - new List>>() - { - new Dictionary>() - { - {ExpressionOperator.Indexing, (dynamic left, dynamic right) => left[right] }, - {ExpressionOperator.IndexingWithNullConditional, (dynamic left, dynamic right) => left?[right] }, - }, - new Dictionary>() - { - {ExpressionOperator.LogicalNegation, (dynamic left, dynamic right) => !right }, - {ExpressionOperator.Cast, (dynamic left, dynamic right) => ChangeType(right, left) }, - }, - new Dictionary>() - { - {ExpressionOperator.Multiply, (dynamic left, dynamic right) => left * right }, - {ExpressionOperator.Divide, (dynamic left, dynamic right) => left / right }, - {ExpressionOperator.Modulo, (dynamic left, dynamic right) => left % right }, - }, - new Dictionary>() - { - {ExpressionOperator.Plus, (dynamic left, dynamic right) => left + right }, - {ExpressionOperator.Minus, (dynamic left, dynamic right) => left - right }, - }, - new Dictionary>() - { - {ExpressionOperator.ShiftBitsLeft, (dynamic left, dynamic right) => left << right }, - {ExpressionOperator.ShiftBitsRight, (dynamic left, dynamic right) => left >> right }, - }, - new Dictionary>() - { - {ExpressionOperator.Lower, (dynamic left, dynamic right) => left < right }, - {ExpressionOperator.Greater, (dynamic left, dynamic right) => left > right }, - {ExpressionOperator.LowerOrEqual, (dynamic left, dynamic right) => left <= right }, - {ExpressionOperator.GreaterOrEqual, (dynamic left, dynamic right) => left >= right }, - {ExpressionOperator.Is, (dynamic left, dynamic right) => ((Type)right).IsAssignableFrom(left.GetType()) }, - }, - new Dictionary>() - { - {ExpressionOperator.Equal, (dynamic left, dynamic right) => left == right }, - {ExpressionOperator.NotEqual, (dynamic left, dynamic right) => left != right }, - }, - new Dictionary>() - { - {ExpressionOperator.LogicalAnd, (dynamic left, dynamic right) => left & right }, - }, - new Dictionary>() - { - {ExpressionOperator.LogicalXor, (dynamic left, dynamic right) => left ^ right }, - }, - new Dictionary>() - { - {ExpressionOperator.LogicalOr, (dynamic left, dynamic right) => left | right }, - }, - new Dictionary>() - { - {ExpressionOperator.ConditionalAnd, (dynamic left, dynamic right) => left && right }, - }, - new Dictionary>() - { - {ExpressionOperator.ConditionalOr, (dynamic left, dynamic right) => left || right }, - }, - new Dictionary>() - { - {ExpressionOperator.NullCoalescing, (dynamic left, dynamic right) => left ?? right }, - }, - }; - - private static Dictionary defaultVariables = new Dictionary(StringComparer.OrdinalIgnoreCase) - { - { "pi", Math.PI }, - { "e", Math.E }, - { "null", null}, - { "true", true }, - { "false", false }, - }; - - private static Dictionary> simpleDoubleMathFuncsDictionary = new Dictionary>() - { - { "abs", Math.Abs }, - { "acos", Math.Acos }, - { "asin", Math.Asin }, - { "atan", Math.Atan }, - { "ceiling", Math.Ceiling }, - { "cos", Math.Cos }, - { "cosh", Math.Cosh }, - { "exp", Math.Exp }, - { "floor", Math.Floor }, - { "log10", Math.Log10 }, - { "sin", Math.Sin }, - { "sinh", Math.Sinh }, - { "sqrt", Math.Sqrt }, - { "tan", Math.Tan }, - { "tanh", Math.Tanh }, - { "truncate", Math.Truncate }, - }; - - private static Dictionary> doubleDoubleMathFuncsDictionary = new Dictionary>() - { - { "atan2", Math.Atan2 }, - { "ieeeremainder", Math.IEEERemainder }, - { "log", Math.Log }, - { "pow", Math.Pow }, - }; + #endregion - private static Dictionary, object>> complexStandardFuncsDictionary = new Dictionary, object>>() - { - { "array", (self, args) => args.ConvertAll(arg => self.Evaluate(arg)).ToArray() }, - { "avg", (self, args) => args.ConvertAll(arg => Convert.ToDouble(self.Evaluate(arg))).Sum() / args.Count }, - { "default", (self, args) => { Type type = (self.Evaluate(args[0]) as Type); - return(type != null && type.IsValueType ? Activator.CreateInstance(type): null); } }, - { "if", (self, args) => (bool)self.Evaluate(args[0]) ? self.Evaluate(args[1]) : self.Evaluate(args[2]) }, - { "in", (self, args) => args.Skip(1).ToList().ConvertAll(arg => self.Evaluate(arg)).Contains(self.Evaluate(args[0])) }, - { "list", (self, args) => args.ConvertAll(arg => self.Evaluate(arg)) }, - { "max", (self, args) => args.ConvertAll(arg => Convert.ToDouble(self.Evaluate(arg))).Max() }, - { "min", (self, args) => args.ConvertAll(arg => Convert.ToDouble(self.Evaluate(arg))).Min() }, - { "new", (self, args) => { List cArgs = args.ConvertAll(arg => self.Evaluate(arg)); - return Activator.CreateInstance(cArgs[0] as Type, cArgs.Skip(1).ToArray());}}, - { "round", (self, args) => { return args.Count > 1 ? Math.Round(Convert.ToDouble(self.Evaluate(args[0])), (int)self.Evaluate(args[1])) : Math.Round(Convert.ToDouble(self.Evaluate(args[0]))); } }, - { "sign", (self, args) => Math.Sign(Convert.ToDouble(self.Evaluate(args[0]))) }, - }; + #region Dictionaries declarations (Primary types, number suffix, escaped chars, operators management, default vars and functions) - /// - /// All assemblies needed to resolves Types - /// - public List ReferencedAssemblies { get; set; } = typeof(ExpressionEvaluator).Assembly.GetReferencedAssemblies().ToList(); + protected static readonly IDictionary primaryTypesDict = new Dictionary() + { + { "object", typeof(object) }, + { "string", typeof(string) }, + { "bool", typeof(bool) }, + { "bool?", typeof(bool?) }, + { "byte", typeof(byte) }, + { "byte?", typeof(byte?) }, + { "char", typeof(char) }, + { "char?", typeof(char?) }, + { "decimal", typeof(decimal) }, + { "decimal?", typeof(decimal?) }, + { "double", typeof(double) }, + { "double?", typeof(double?) }, + { "short", typeof(short) }, + { "short?", typeof(short?) }, + { "int", typeof(int) }, + { "int?", typeof(int?) }, + { "long", typeof(long) }, + { "long?", typeof(long?) }, + { "sbyte", typeof(sbyte) }, + { "sbyte?", typeof(sbyte?) }, + { "float", typeof(float) }, + { "float?", typeof(float?) }, + { "ushort", typeof(ushort) }, + { "ushort?", typeof(ushort?) }, + { "uint", typeof(uint) }, + { "uint?", typeof(uint?) }, + { "ulong", typeof(ulong) }, + { "ulong?", typeof(ulong?) }, + { "void", typeof(void) } + }; + + protected static readonly IDictionary> numberSuffixToParse = new Dictionary>(StringComparer.OrdinalIgnoreCase) // Always Case insensitive, like in C# + { + { "f", (number, culture) => float.Parse(number, NumberStyles.Any, culture) }, + { "d", (number, culture) => double.Parse(number, NumberStyles.Any, culture) }, + { "u", (number, culture) => uint.Parse(number, NumberStyles.Any, culture) }, + { "l", (number, culture) => long.Parse(number, NumberStyles.Any, culture) }, + { "ul", (number, culture) => ulong.Parse(number, NumberStyles.Any, culture) }, + { "m", (number, culture) => decimal.Parse(number, NumberStyles.Any, culture) } + }; + + protected static readonly IDictionary stringEscapedCharDict = new Dictionary() + { + { '\\', @"\" }, + { '"', "\"" }, + { '0', "\0" }, + { 'a', "\a" }, + { 'b', "\b" }, + { 'f', "\f" }, + { 'n', "\n" }, + { 'r', "\r" }, + { 't', "\t" }, + { 'v', "\v" } + }; + + protected static readonly IDictionary charEscapedCharDict = new Dictionary() + { + { '\\', '\\' }, + { '\'', '\'' }, + { '0', '\0' }, + { 'a', '\a' }, + { 'b', '\b' }, + { 'f', '\f' }, + { 'n', '\n' }, + { 'r', '\r' }, + { 't', '\t' }, + { 'v', '\v' } + }; + + /// + /// OperatorsDictionaryInit() for values + /// + protected IDictionary operatorsDictionary = new Dictionary(StringComparer.Ordinal) + { + { "+", ExpressionOperator.Plus }, + { "-", ExpressionOperator.Minus }, + { "*", ExpressionOperator.Multiply }, + { "/", ExpressionOperator.Divide }, + { "%", ExpressionOperator.Modulo }, + { "<", ExpressionOperator.Lower }, + { ">", ExpressionOperator.Greater }, + { "<=", ExpressionOperator.LowerOrEqual }, + { ">=", ExpressionOperator.GreaterOrEqual }, + { "is", ExpressionOperator.Is }, + { "==", ExpressionOperator.Equal }, + { "!=", ExpressionOperator.NotEqual }, + { "&&", ExpressionOperator.ConditionalAnd }, + { "||", ExpressionOperator.ConditionalOr }, + { "!", ExpressionOperator.LogicalNegation }, + { "~", ExpressionOperator.BitwiseComplement }, + { "&", ExpressionOperator.LogicalAnd }, + { "|", ExpressionOperator.LogicalOr }, + { "^", ExpressionOperator.LogicalXor }, + { "<<", ExpressionOperator.ShiftBitsLeft }, + { ">>", ExpressionOperator.ShiftBitsRight }, + { "??", ExpressionOperator.NullCoalescing }, + }; + + protected static readonly IList leftOperandOnlyOperatorsEvaluationDictionary = new List(); + + protected static readonly IList rightOperandOnlyOperatorsEvaluationDictionary = new List() + { + ExpressionOperator.LogicalNegation, + ExpressionOperator.BitwiseComplement, + ExpressionOperator.UnaryPlus, + ExpressionOperator.UnaryMinus + }; - /// - /// All Namespaces Where to find types - /// - public List Namespaces { get; set; } = new List - { - "System", - "System.Linq", - "System.IO", - "System.Text", - "System.Text.RegularExpressions", - "System.ComponentModel", - "System.Collections", - "System.Collections.Generic", - "System.Collections.Specialized", - "System.Globalization" - }; + protected virtual IList LeftOperandOnlyOperatorsEvaluationDictionary => leftOperandOnlyOperatorsEvaluationDictionary; + protected virtual IList RightOperandOnlyOperatorsEvaluationDictionary => rightOperandOnlyOperatorsEvaluationDictionary; + protected virtual IList>> OperatorsEvaluations => operatorsEvaluations; - /// - /// A list of statics types where to find extensions methods - /// - public List StaticTypesForExtensionsMethods { get; set; } = new List() - { - typeof(Enumerable) // For Linq extension methods - }; + protected static object IndexingOperatorFunc(dynamic left, dynamic right) + { + if (left is NullConditionalNullValue) + { + return left; + } + else if (left is BubbleExceptionContainer) + { + return left; + } + Type type = ((object)left).GetType(); - /// - /// The Values of the variable use in the expressions - /// - public Dictionary Variables { get; set; } = new Dictionary(StringComparer.OrdinalIgnoreCase); + if (left is IDictionary dictionaryLeft) + { + return dictionaryLeft[right]; + } + else if (type.GetMethod("Item", new Type[] { ((object)right).GetType() }) is MethodInfo methodInfo) + { + return methodInfo.Invoke(left, new object[] { right }); + } - /// - /// If true Evaluate function is callables in an expression. If false Evaluate is not callable. - /// By default : false for security - /// - public bool IsEvaluateFunctionActivated { get; set; } = false; + return left[right]; + } - /// - /// Default Constructor - /// - public ExpressionEvaluator() - { } + protected static readonly IList>> operatorsEvaluations = + new List>>() + { + new Dictionary>() + { + {ExpressionOperator.Indexing, IndexingOperatorFunc}, + {ExpressionOperator.IndexingWithNullConditional, (dynamic left, dynamic right) => + { + if(left == null) + return new NullConditionalNullValue(); - /// - /// Constructor with variable initialize - /// - /// The Values of the variable use in the expressions - public ExpressionEvaluator(Dictionary variables) - { - this.Variables = variables; - } + return IndexingOperatorFunc(left, right); + } + }, + }, + new Dictionary>() + { + {ExpressionOperator.UnaryPlus, (dynamic _, dynamic right) => +right }, + {ExpressionOperator.UnaryMinus, (dynamic _, dynamic right) => -right }, + {ExpressionOperator.LogicalNegation, (dynamic _, dynamic right) => !right }, + {ExpressionOperator.BitwiseComplement, (dynamic _, dynamic right) => ~right }, + {ExpressionOperator.Cast, (dynamic left, dynamic right) => ChangeType(right, left) }, + }, + new Dictionary>() + { + {ExpressionOperator.Multiply, (dynamic left, dynamic right) => left * right }, + {ExpressionOperator.Divide, (dynamic left, dynamic right) => left / right }, + {ExpressionOperator.Modulo, (dynamic left, dynamic right) => left % right }, + }, + new Dictionary>() + { + {ExpressionOperator.Plus, (dynamic left, dynamic right) => left + right }, + {ExpressionOperator.Minus, (dynamic left, dynamic right) => left - right }, + }, + new Dictionary>() + { + {ExpressionOperator.ShiftBitsLeft, (dynamic left, dynamic right) => left << right }, + {ExpressionOperator.ShiftBitsRight, (dynamic left, dynamic right) => left >> right }, + }, + new Dictionary>() + { + {ExpressionOperator.Lower, (dynamic left, dynamic right) => left < right }, + {ExpressionOperator.Greater, (dynamic left, dynamic right) => left > right }, + {ExpressionOperator.LowerOrEqual, (dynamic left, dynamic right) => left <= right }, + {ExpressionOperator.GreaterOrEqual, (dynamic left, dynamic right) => left >= right }, + {ExpressionOperator.Is, (dynamic left, dynamic right) => left != null && (((ClassOrEnumType)right).Type).IsAssignableFrom(left.GetType()) }, + }, + new Dictionary>() + { + {ExpressionOperator.Equal, (dynamic left, dynamic right) => left == right }, + {ExpressionOperator.NotEqual, (dynamic left, dynamic right) => left != right }, + }, + new Dictionary>() + { + {ExpressionOperator.LogicalAnd, (dynamic left, dynamic right) => left & right }, + }, + new Dictionary>() + { + {ExpressionOperator.LogicalXor, (dynamic left, dynamic right) => left ^ right }, + }, + new Dictionary>() + { + {ExpressionOperator.LogicalOr, (dynamic left, dynamic right) => left | right }, + }, + new Dictionary>() + { + {ExpressionOperator.ConditionalAnd, (dynamic left, dynamic right) => { + if ( left is BubbleExceptionContainer leftExceptionContainer) + { + throw leftExceptionContainer.Exception; + } + else if (!left) + { + return false; + } + else if (right is BubbleExceptionContainer rightExceptionContainer) + { + throw rightExceptionContainer.Exception; + } + else + { + return left && right; + } + } }, + }, + new Dictionary>() + { + {ExpressionOperator.ConditionalOr, (dynamic left, dynamic right) => { + if ( left is BubbleExceptionContainer leftExceptionContainer) + { + throw leftExceptionContainer.Exception; + } + else if (left) + { + return true; + } + else if (right is BubbleExceptionContainer rightExceptionContainer) + { + throw rightExceptionContainer.Exception; + } + else + { + return left || right; + } + } }, + }, + new Dictionary>() + { + {ExpressionOperator.NullCoalescing, (dynamic left, dynamic right) => left ?? right }, + }, + }; - /// - /// Is Fired when no internal variable is found for a variable name. - /// Allow to define a variable and the corresponding value on the fly. - /// - public event EventHandler EvaluateVariable; + protected IDictionary defaultVariables = new Dictionary(StringComparer.Ordinal) + { + { "Pi", Math.PI }, + { "E", Math.E }, + { "null", null}, + { "true", true }, + { "false", false }, + }; + + protected IDictionary> simpleDoubleMathFuncsDictionary = new Dictionary>(StringComparer.Ordinal) + { + { "Abs", Math.Abs }, + { "Acos", Math.Acos }, + { "Asin", Math.Asin }, + { "Atan", Math.Atan }, + { "Ceiling", Math.Ceiling }, + { "Cos", Math.Cos }, + { "Cosh", Math.Cosh }, + { "Exp", Math.Exp }, + { "Floor", Math.Floor }, + { "Log10", Math.Log10 }, + { "Sin", Math.Sin }, + { "Sinh", Math.Sinh }, + { "Sqrt", Math.Sqrt }, + { "Tan", Math.Tan }, + { "Tanh", Math.Tanh }, + { "Truncate", Math.Truncate }, + }; + + protected IDictionary> doubleDoubleMathFuncsDictionary = new Dictionary>(StringComparer.Ordinal) + { + { "Atan2", Math.Atan2 }, + { "IEEERemainder", Math.IEEERemainder }, + { "Log", Math.Log }, + { "Pow", Math.Pow }, + }; - /// - /// Is Fired when no internal function is found for a variable name. - /// Allow to define a function and the corresponding value on the fly. - /// - public event EventHandler EvaluateFunction; + protected IDictionary, object>> complexStandardFuncsDictionary = new Dictionary, object>>(StringComparer.Ordinal) + { + { "Array", (self, args) => args.ConvertAll(self.Evaluate).ToArray() }, + { "ArrayOfType", (self, args) => + { + Array sourceArray = args.Skip(1).Select(self.Evaluate).ToArray(); + Array typedArray = Array.CreateInstance((Type)self.Evaluate(args[0]), sourceArray.Length); + Array.Copy(sourceArray, typedArray, sourceArray.Length); - /// - /// Evaluate the specified math or pseudo C# expression - /// - /// the math or pseudo C# expression to evaluate - /// The result of the operation if syntax is correct - public object Evaluate(string expr) - { - expr = expr.Trim(); + return typedArray; + } + }, + { "Avg", (self, args) => args.ConvertAll(arg => Convert.ToDouble(self.Evaluate(arg))).Sum() / args.Count }, + { "default", (self, args) => + { + object argValue = self.Evaluate(args[0]); - Stack stack = new Stack(); + if (argValue is ClassOrEnumType classOrTypeName) + return Activator.CreateInstance(classOrTypeName.Type); + else + return null; + } + }, + { "in", (self, args) => args.Skip(1).ToList().ConvertAll(self.Evaluate).Contains(self.Evaluate(args[0])) }, + { "List", (self, args) => args.ConvertAll(self.Evaluate) }, + { "ListOfType", (self, args) => + { + Type type = (Type)self.Evaluate(args[0]); + Array sourceArray = args.Skip(1).Select(self.Evaluate).ToArray(); + Array typedArray = Array.CreateInstance(type, sourceArray.Length); + Array.Copy(sourceArray, typedArray, sourceArray.Length); - if (GetLambdaExpression(expr, stack)) - return stack.Pop(); + Type typeOfList = typeof(List<>).MakeGenericType(type); - for (int i = 0; i < expr.Length; i++) - { - string restOfExpression = expr.Substring(i, expr.Length - i); + object list = Activator.CreateInstance(typeOfList); - if (!(EvaluateCast(restOfExpression, stack, ref i) - || EvaluateNumber(restOfExpression, stack, ref i) - || EvaluateVarOrFunc(expr, restOfExpression, stack, ref i) - || EvaluateTwoCharsOperators(expr, stack, ref i))) - { - string s = expr.Substring(i, 1); + typeOfList.GetMethod("AddRange").Invoke(list, new object[]{ typedArray }); - if (EvaluateParenthis(expr, s, stack, ref i) - || EvaluateIndexing(expr, s, stack, ref i) - || EvaluateString(expr, s, restOfExpression, stack, ref i)) - { } - else if (operatorsDictionary.ContainsKey(s)) + return list; + } + }, + { "Max", (self, args) => args.ConvertAll(arg => Convert.ToDouble(self.Evaluate(arg))).Max() }, + { "Min", (self, args) => args.ConvertAll(arg => Convert.ToDouble(self.Evaluate(arg))).Min() }, + { "new", (self, args) => + { + List cArgs = args.ConvertAll(self.Evaluate); + return cArgs[0] is ClassOrEnumType classOrEnumType ? Activator.CreateInstance(classOrEnumType.Type, cArgs.Skip(1).ToArray()) : null; + } + }, + { "Round", (self, args) => { - stack.Push(operatorsDictionary[s]); + if(args.Count == 3) + { + return Math.Round(Convert.ToDouble(self.Evaluate(args[0])), Convert.ToInt32(self.Evaluate(args[1])), (MidpointRounding)self.Evaluate(args[2])); + } + else if(args.Count == 2) + { + object arg2 = self.Evaluate(args[1]); + + if(arg2 is MidpointRounding midpointRounding) + return Math.Round(Convert.ToDouble(self.Evaluate(args[0])), midpointRounding); + else + return Math.Round(Convert.ToDouble(self.Evaluate(args[0])), Convert.ToInt32(arg2)); + } + else if(args.Count == 1) { return Math.Round(Convert.ToDouble(self.Evaluate(args[0]))); } + else + { + throw new ArgumentException(); + } } - else if (!s.Trim().Equals(string.Empty)) + }, + { "Sign", (self, args) => Math.Sign(Convert.ToDouble(self.Evaluate(args[0]))) }, + { "sizeof", (self, args) => { - throw new ExpressionEvaluatorSyntaxErrorException("Invalid character."); + Type type = ((ClassOrEnumType)self.Evaluate(args[0])).Type; + + if(type == typeof(bool)) + return 1; + else if(type == typeof(char)) + return 2; + else + return Marshal.SizeOf(type); } + }, + { "typeof", (self, args) => ((ClassOrEnumType)self.Evaluate(args[0])).Type }, + }; + + #endregion + + #region Caching + + /// + /// if set to true use a cache for types that were resolved to resolve faster next time. + /// if set to false the cache of types resolution is not use for this instance of ExpressionEvaluator. + /// Default : false + /// the cache is the static Dictionary TypesResolutionCaching (so it is shared by all instances of ExpressionEvaluator that have CacheTypesResolutions enabled) + /// + public bool CacheTypesResolutions { get; set; } + + /// + /// A shared cache for types resolution. + /// + public static IDictionary TypesResolutionCaching { get; set; } = new Dictionary(); + + /// + /// Clear all ExpressionEvaluator caches + /// + public static void ClearAllCaches() + { + TypesResolutionCaching.Clear(); + } + + #endregion + + #region Assemblies, Namespaces and types lists + + private static IList staticAssemblies; + private IList assemblies; + + /// + /// All assemblies needed to resolves Types + /// by default all Assemblies loaded in the current AppDomain + /// + public virtual IList Assemblies + { + get { return assemblies ?? (assemblies = staticAssemblies) ?? (assemblies = staticAssemblies = AppDomain.CurrentDomain.GetAssemblies().ToList()); } + set { assemblies = value; } + } + + /// + /// All Namespaces Where to find types + /// + public virtual IList Namespaces { get; set; } = new List() + { + "System", + "System.Linq", + "System.IO", + "System.Text", + "System.Text.RegularExpressions", + "System.ComponentModel", + "System.Dynamic", + "System.Collections", + "System.Collections.Generic", + "System.Collections.Specialized", + "System.Globalization" + }; + + /// + /// To add or remove specific types to manage in expression. + /// + public virtual IList Types { get; set; } = new List(); + + /// + /// A list of type to block an keep un usable in Expression Evaluation for security purpose + /// + public virtual IList TypesToBlock { get; set; } = new List(); + + /// + /// A list of statics types where to find extensions methods + /// + public virtual IList StaticTypesForExtensionsMethods { get; set; } = new List() + { + typeof(Enumerable) // For Linq extension methods + }; + + #endregion + + #region Options + + private bool optionCaseSensitiveEvaluationActive = true; + + /// + /// If true all evaluation are case sensitives. + /// If false evaluations are case insensitive. + /// By default = true + /// + public bool OptionCaseSensitiveEvaluationActive + { + get { return optionCaseSensitiveEvaluationActive; } + set + { + optionCaseSensitiveEvaluationActive = value; + StringComparisonForCasing = optionCaseSensitiveEvaluationActive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase; + Variables = Variables; + operatorsDictionary = new Dictionary(operatorsDictionary, StringComparerForCasing); + defaultVariables = new Dictionary(defaultVariables, StringComparerForCasing); + simpleDoubleMathFuncsDictionary = new Dictionary>(simpleDoubleMathFuncsDictionary, StringComparerForCasing); + doubleDoubleMathFuncsDictionary = new Dictionary>(doubleDoubleMathFuncsDictionary, StringComparerForCasing); + complexStandardFuncsDictionary = new Dictionary, object>>(complexStandardFuncsDictionary, StringComparerForCasing); } } - return ProcessStack(stack); - } + private StringComparison StringComparisonForCasing { get; set; } = StringComparison.Ordinal; - private bool EvaluateCast(string restOfExpression, Stack stack, ref int i) - { - Match castMatch = castRegex.Match(restOfExpression); + protected StringComparer StringComparerForCasing + { + get + { + return OptionCaseSensitiveEvaluationActive ? StringComparer.Ordinal : StringComparer.OrdinalIgnoreCase; + } + } - if (castMatch.Success) + /// + /// If true all numbers without decimal and suffixes evaluations will be done as double + /// If false Integers values without decimal and suffixes will be evaluate as int as in C# (Warning some operation can round values) + /// By default = false + /// + public bool OptionForceIntegerNumbersEvaluationsAsDoubleByDefault { get; set; } + + private CultureInfo cultureInfoForNumberParsing = CultureInfo.InvariantCulture.Clone() as CultureInfo; + + /// + /// The culture used to evaluate numbers + /// Synchronized with OptionNumberParsingDecimalSeparator and OptionNumberParsingThousandSeparator. + /// So always set a full CultureInfo object and do not change CultureInfoForNumberParsing.NumberFormat.NumberDecimalSeparator and CultureInfoForNumberParsing.NumberFormat.NumberGroupSeparator properties directly. + /// Warning if using comma in separators change also OptionFunctionArgumentsSeparator and OptionInitializersSeparator otherwise it will create conflicts + /// + public CultureInfo CultureInfoForNumberParsing { - string typeName = castMatch.Groups["typeName"].Value; - Type type = GetTypeByFriendlyName(typeName); + get + { + return cultureInfoForNumberParsing; + } - if (type != null) + set { - i += castMatch.Length - 1; - stack.Push(type); - stack.Push(ExpressionOperator.Cast); - return true; + cultureInfoForNumberParsing = value; + + OptionNumberParsingDecimalSeparator = cultureInfoForNumberParsing.NumberFormat.NumberDecimalSeparator; + OptionNumberParsingThousandSeparator = cultureInfoForNumberParsing.NumberFormat.NumberGroupSeparator; } } - return false; - } + private string optionNumberParsingDecimalSeparator = "."; - private bool EvaluateNumber(string restOfExpression, Stack stack, ref int i) - { - Match numberMatch = numberRegex.Match(restOfExpression); + /// + /// Allow to change the decimal separator of numbers when parsing expressions. + /// By default "." + /// Warning if using comma change also OptionFunctionArgumentsSeparator and OptionInitializersSeparator otherwise it will create conflicts. + /// Modify CultureInfoForNumberParsing. + /// + public string OptionNumberParsingDecimalSeparator + { + get + { + return optionNumberParsingDecimalSeparator; + } + + set + { + optionNumberParsingDecimalSeparator = value ?? "."; + CultureInfoForNumberParsing.NumberFormat.NumberDecimalSeparator = optionNumberParsingDecimalSeparator; + + numberRegexPattern = string.Format(numberRegexOrigPattern, + optionNumberParsingDecimalSeparator != null ? Regex.Escape(optionNumberParsingDecimalSeparator) : ".", + optionNumberParsingThousandSeparator != null ? Regex.Escape(optionNumberParsingThousandSeparator) : ""); + } + } - if (numberMatch.Success - && (!numberMatch.Groups["sign"].Success - || stack.Count == 0 - || stack.Peek() is ExpressionOperator)) + private string optionNumberParsingThousandSeparator = string.Empty; + + /// + /// Allow to change the thousand separator of numbers when parsing expressions. + /// By default string.Empty + /// Warning if using comma change also OptionFunctionArgumentsSeparator and OptionInitializersSeparator otherwise it will create conflicts. + /// Modify CultureInfoForNumberParsing. + /// + public string OptionNumberParsingThousandSeparator { - i += numberMatch.Length; - i--; + get + { + return optionNumberParsingThousandSeparator; + } - if (numberMatch.Groups["type"].Success) + set { - string type = numberMatch.Groups["type"].Value.ToLower(); - string numberNoType = numberMatch.Value.Replace(type, string.Empty); + optionNumberParsingThousandSeparator = value ?? string.Empty; + CultureInfoForNumberParsing.NumberFormat.NumberGroupSeparator = value; - Func parseFunc = null; - if (numberSuffixToParse.TryGetValue(type, out parseFunc)) - { - stack.Push(parseFunc(numberNoType)); - } + numberRegexPattern = string.Format(numberRegexOrigPattern, + optionNumberParsingDecimalSeparator != null ? Regex.Escape(optionNumberParsingDecimalSeparator) : ".", + optionNumberParsingThousandSeparator != null ? Regex.Escape(optionNumberParsingThousandSeparator) : ""); } - else + } + + /// + /// Allow to change the separator of functions arguments. + /// By default "," + /// Warning must to be changed if OptionNumberParsingDecimalSeparator = "," otherwise it will create conflicts + /// + public string OptionFunctionArgumentsSeparator { get; set; } = ","; + + /// + /// Allow to change the separator of Object and collections Initialization between { and } after the keyword new. + /// By default "," + /// Warning must to be changed if OptionNumberParsingDecimalSeparator = "," otherwise it will create conflicts + /// + public string OptionInitializersSeparator { get; set; } = ","; + + /// + /// if true allow to add the prefix Fluid or Fluent before void methods names to return back the instance on which the method is call. + /// if false unactive this functionality. + /// By default : true + /// + public bool OptionFluidPrefixingActive { get; set; } = true; + + /// + /// if true allow the use of inline namespace (Can be slow, and is less secure). + /// if false unactive inline namespace (only namespaces in Namespaces list are available). + /// By default : true + /// + public bool OptionInlineNamespacesEvaluationActive { get; set; } = true; + + private Func, object> newMethodMem; + + /// + /// if true allow to create instance of object with the Default function new(ClassNam,...). + /// if false unactive this functionality. + /// By default : true + /// + public bool OptionNewFunctionEvaluationActive + { + get + { + return complexStandardFuncsDictionary.ContainsKey("new"); + } + set { - if (numberMatch.Groups["hasdecimal"].Success) + if (value && !complexStandardFuncsDictionary.ContainsKey("new")) { - stack.Push(double.Parse(numberMatch.Value, NumberStyles.Any, CultureInfo.InvariantCulture)); + complexStandardFuncsDictionary["new"] = newMethodMem; } - else + else if (!value && complexStandardFuncsDictionary.ContainsKey("new")) { - stack.Push(int.Parse(numberMatch.Value, NumberStyles.Any, CultureInfo.InvariantCulture)); + newMethodMem = complexStandardFuncsDictionary["new"]; + complexStandardFuncsDictionary.Remove("new"); } } + } + + /// + /// if true allow to create instance of object with the C# syntax new ClassName(...). + /// if false unactive this functionality. + /// By default : true + /// + public bool OptionNewKeywordEvaluationActive { get; set; } = true; + + /// + /// if true allow to call static methods on classes. + /// if false unactive this functionality. + /// By default : true + /// + public bool OptionStaticMethodsCallActive { get; set; } = true; + + /// + /// if true allow to get static properties on classes + /// if false unactive this functionality. + /// By default : true + /// + public bool OptionStaticPropertiesGetActive { get; set; } = true; + + /// + /// if true allow to call instance methods on objects. + /// if false unactive this functionality. + /// By default : true + /// + public bool OptionInstanceMethodsCallActive { get; set; } = true; + + /// + /// if true allow to get instance properties on objects + /// if false unactive this functionality. + /// By default : true + /// + public bool OptionInstancePropertiesGetActive { get; set; } = true; + + /// + /// if true allow to get object at index or key like IndexedObject[indexOrKey] + /// if false unactive this functionality. + /// By default : true + /// + public bool OptionIndexingActive { get; set; } = true; + + /// + /// if true allow string interpretation with "" + /// if false unactive this functionality. + /// By default : true + /// + public bool OptionStringEvaluationActive { get; set; } = true; + + /// + /// if true allow char interpretation with '' + /// if false unactive this functionality. + /// By default : true + /// + public bool OptionCharEvaluationActive { get; set; } = true; + + /// + /// If true Evaluate function is callables in an expression. If false Evaluate is not callable. + /// By default : true + /// if set to false for security (also ensure that ExpressionEvaluator type is in TypesToBlock list) + /// + public bool OptionEvaluateFunctionActive { get; set; } = true; + + /// + /// If true allow to assign a value to a variable in the Variable disctionary with (=, +=, -=, *=, /=, %=, &=, |=, ^=, <<=, >>=, ++ or --) + /// If false unactive this functionality + /// By default : true + /// + public bool OptionVariableAssignationActive { get; set; } = true; + + /// + /// If true allow to set/modify a property or a field value with (=, +=, -=, *=, /=, %=, &=, |=, ^=, <<=, >>=, ++ or --) + /// If false unactive this functionality + /// By default : true + /// + public bool OptionPropertyOrFieldSetActive { get; set; } = true; + + /// + /// If true allow to assign a indexed element like Collections, List, Arrays and Dictionaries with (=, +=, -=, *=, /=, %=, &=, |=, ^=, <<=, >>=, ++ or --) + /// If false unactive this functionality + /// By default : true + /// + public bool OptionIndexingAssignationActive { get; set; } = true; + + /// + /// If true ScriptEvaluate function is callables in an expression. If false Evaluate is not callable. + /// By default : true + /// if set to false for security (also ensure that ExpressionEvaluator type is in TypesToBlock list) + /// + public bool OptionScriptEvaluateFunctionActive { get; set; } = true; + + /// + /// If ReturnAutomaticallyLastEvaluatedExpression ScriptEvaluate return automatically the last evaluated expression if no return keyword is met. + /// If ReturnNull return null if no return keyword is met. + /// If ThrowSyntaxException a exception is throw if no return keyword is met. + /// By default : ReturnAutomaticallyLastEvaluatedExpression; + /// + public OptionOnNoReturnKeywordFoundInScriptAction OptionOnNoReturnKeywordFoundInScriptAction { get; set; } + + /// + /// If true ScriptEvaluate need to have a semicolon [;] after each expression. + /// If false Allow to omit the semicolon for the last expression of the script. + /// Default : true + /// + public bool OptionScriptNeedSemicolonAtTheEndOfLastExpression { get; set; } = true; + + /// + /// If true Allow to access fields, properties and methods that are not declared public. (private, protected and internal) + /// If false Allow to access only to public members. + /// Default : false + /// Warning : This clearly break the encapsulation principle use this only if you know what you do. + /// + public bool OptionAllowNonPublicMembersAccess { get; set; } + + #endregion + + #region Reflection flags + + protected virtual BindingFlags InstanceBindingFlag + { + get + { + BindingFlags flag = BindingFlags.Default | BindingFlags.Public | BindingFlags.Instance; - return true; + if (!OptionCaseSensitiveEvaluationActive) + flag |= BindingFlags.IgnoreCase; + if (OptionAllowNonPublicMembersAccess) + flag |= BindingFlags.NonPublic; + + return flag; + } } - else + + protected virtual BindingFlags StaticBindingFlag { - return false; + get + { + BindingFlags flag = BindingFlags.Default | BindingFlags.Public | BindingFlags.Static; + + if (!OptionCaseSensitiveEvaluationActive) + flag |= BindingFlags.IgnoreCase; + if (OptionAllowNonPublicMembersAccess) + flag |= BindingFlags.NonPublic; + + return flag; + } } - } - private bool EvaluateVarOrFunc(string expr, string restOfExpression, Stack stack, ref int i) - { - Match varFuncMatch = varOrFunctionRegEx.Match(restOfExpression); + #endregion + + #region Custom and on the fly variables and methods + + /// + /// If set, this object is used to use it's fields, properties and methods as global variables and functions + /// + public object Context { get; set; } + + private IDictionary variables = new Dictionary(StringComparer.Ordinal); + + /// + /// Counts stack initialisations to determine if the expression enty point was reached. In that case the transported exception should be thrown. + /// + private int evaluationStackCount; + + /// + /// The Values of the variable use in the expressions + /// + public IDictionary Variables + { + get { return variables; } + set { variables = value == null ? new Dictionary(StringComparerForCasing) : new Dictionary(value, StringComparerForCasing); } + } + + /// + /// Is Fired before a variable, field or property resolution. + /// Allow to define a variable and the corresponding value on the fly. + /// Allow also to cancel the evaluation of this variable (consider it does'nt exists) + /// + public event EventHandler PreEvaluateVariable; + + /// + /// Is Fired before a function or method resolution. + /// Allow to define a function or method and the corresponding value on the fly. + /// Allow also to cancel the evaluation of this function (consider it does'nt exists) + /// + public event EventHandler PreEvaluateFunction; + + /// + /// Is Fired if no variable, field or property were found + /// Allow to define a variable and the corresponding value on the fly. + /// + public event EventHandler EvaluateVariable; + + /// + /// Is Fired if no function or method when were found. + /// Allow to define a function or method and the corresponding value on the fly. + /// + public event EventHandler EvaluateFunction; + + #endregion + + #region Constructors and overridable Inits methods + + /// + /// Default Constructor + /// + public ExpressionEvaluator() + { + DefaultDecimalSeparatorInit(); + + Init(); + } + + /// + /// Constructor with variables initialize + /// + /// The Values of variables use in the expressions + public ExpressionEvaluator(IDictionary variables) : this() + { + Variables = variables; + } + + /// + /// Constructor with context initialize + /// + /// the context that propose it's fields, properties and methods to the evaluation + public ExpressionEvaluator(object context) : this() + { + Context = context; + } + + /// + /// Constructor with variables and context initialize + /// + /// the context that propose it's fields, properties and methods to the evaluation + /// The Values of variables use in the expressions + public ExpressionEvaluator(object context, IDictionary variables) : this() + { + Context = context; + Variables = variables; + } + + protected virtual void DefaultDecimalSeparatorInit() + { + numberRegexPattern = string.Format(numberRegexOrigPattern, @"\.", string.Empty); - if (varFuncMatch.Success && !operatorsDictionary.ContainsKey(varFuncMatch.Value.Trim())) + CultureInfoForNumberParsing.NumberFormat.NumberDecimalSeparator = "."; + } + + protected virtual void Init() + { } + + #endregion + + #region Main evaluate methods (Expressions and scripts ==> public) + + #region Scripts + + protected bool inScript; + + /// + /// Evaluate a script (multiple expressions separated by semicolon) + /// Support Assignation with [=] (for simple variable write in the Variables dictionary) + /// support also if, else if, else while and for keywords + /// + /// The type in which to cast the result of the expression + /// the script to evaluate + /// The result of the last evaluated expression + public virtual T ScriptEvaluate(string script) + { + return (T)ScriptEvaluate(script); + } + + /// + /// Evaluate a script (multiple expressions separated by semicolon) + /// Support Assignation with [=] (for simple variable write in the Variables dictionary) + /// support also if, else if, else while and for keywords + /// + /// the script to evaluate + /// The result of the last evaluated expression + public virtual object ScriptEvaluate(string script) { - i += varFuncMatch.Length; + inScript = true; + try + { + bool isReturn = false; + bool isBreak = false; + bool isContinue = false; + + object result = ScriptEvaluate(script, ref isReturn, ref isBreak, ref isContinue); + + if (isBreak) + throw new ExpressionEvaluatorSyntaxErrorException("[break] keyword executed outside a loop"); + else if (isContinue) + throw new ExpressionEvaluatorSyntaxErrorException("[continue] keyword executed outside a loop"); + else + return result; + } + finally + { + inScript = false; + } + } - if (varFuncMatch.Groups["isfunction"].Success) + protected virtual object ScriptEvaluate(string script, ref bool valueReturned, ref bool breakCalled, ref bool continueCalled) + { + object lastResult = null; + bool isReturn = valueReturned; + bool isBreak = breakCalled; + bool isContinue = continueCalled; + int startOfExpression = 0; + IfBlockEvaluatedState ifBlockEvaluatedState = IfBlockEvaluatedState.NoBlockEvaluated; + TryBlockEvaluatedState tryBlockEvaluatedState = TryBlockEvaluatedState.NoBlockEvaluated; + List> ifElseStatementsList = new List>(); + List> tryStatementsList = new List>(); + + script = script.TrimEnd(); + + object ManageJumpStatementsOrExpressionEval(string expression) { - List funcArgs = GetExpressionsBetweenParenthis(expr, ref i, true); - object funcResult = null; - if (varFuncMatch.Groups["inObject"].Success) + expression = expression.Trim(); + + if (expression.Equals("break", StringComparisonForCasing)) { - if (stack.Count == 0 || stack.Peek() is ExpressionOperator) - { - throw new ExpressionEvaluatorSyntaxErrorException($"[{varFuncMatch.Value})] must follow an object."); - } - else - { - string func = varFuncMatch.Groups["name"].Value.ToLower(); - object obj = stack.Pop(); - Type objType = null; + isBreak = true; + return lastResult; + } - try - { - if (varFuncMatch.Groups["nullConditional"].Success && obj == null) - { - stack.Push(null); - } - else - { - List oArgs = funcArgs.ConvertAll(arg => Evaluate(arg)); - BindingFlags flag = DetermineInstanceOrStatic(ref objType, ref obj); + if (expression.Equals("continue", StringComparisonForCasing)) + { + isContinue = true; + return lastResult; + } - // Standard Instance or public method find - MethodInfo methodInfo = GetRealMethod(ref objType, ref obj, func, flag, oArgs); + if (expression.StartsWith("throw ", StringComparisonForCasing)) + { + throw Evaluate(expression.Remove(0, 6)) as Exception; + } - // if not found try to Find extension methods. - if (methodInfo == null && obj != null) - { - oArgs.Insert(0, obj); - objType = obj.GetType(); - obj = null; - for (int e = 0; e < StaticTypesForExtensionsMethods.Count && methodInfo == null; e++) - { - Type type = StaticTypesForExtensionsMethods[e]; - methodInfo = GetRealMethod(ref type, ref obj, func, staticBindingFlag, oArgs); - } - } + expression = returnKeywordRegex.Replace(expression, match => + { + if (OptionCaseSensitiveEvaluationActive && !match.Value.StartsWith("return")) + return match.Value; - if (methodInfo != null) - { - stack.Push(methodInfo.Invoke(obj, oArgs.ToArray())); - } - else - { - throw new ExpressionEvaluatorSyntaxErrorException($"[{objType.ToString()}] object has no Method named \"{func}\"."); - } - } + isReturn = true; + return match.Value.Contains("(") ? "(" : string.Empty; + }); + return Evaluate(expression); + } + + object ScriptExpressionEvaluate(ref int index) + { + string expression = script.Substring(startOfExpression, index - startOfExpression); + + startOfExpression = index + 1; + + return ManageJumpStatementsOrExpressionEval(expression); + } + + bool TryParseStringAndParenthisAndCurlyBrackets(ref int index) + { + bool parsed = true; + Match internalStringMatch = stringBeginningRegex.Match(script.Substring(index)); + + if (internalStringMatch.Success) + { + string innerString = internalStringMatch.Value + GetCodeUntilEndOfString(script.Substring(index + internalStringMatch.Length), internalStringMatch); + index += innerString.Length - 1; + } + else if (script[index] == '(') + { + index++; + GetExpressionsBetweenParenthesesOrOtherImbricableBrackets(script, ref index, false); + } + else if (script[index] == '{') + { + index++; + GetScriptBetweenCurlyBrackets(script, ref index); + } + else + { + Match charMatch = internalCharRegex.Match(script.Substring(index)); + + if (charMatch.Success) + index += charMatch.Length; + + parsed = false; + } + + return parsed; + } + + void ExecuteIfList() + { + if (ifElseStatementsList.Count > 0) + { + string ifScript = ifElseStatementsList.Find(statement => (bool)ManageJumpStatementsOrExpressionEval(statement[0]))?[1]; + + if (!string.IsNullOrEmpty(ifScript)) + lastResult = ScriptEvaluate(ifScript, ref isReturn, ref isBreak, ref isContinue); + + ifElseStatementsList.Clear(); + } + } + + void ExecuteTryList() + { + if (tryStatementsList.Count > 0) + { + if (tryStatementsList.Count == 1) + { + throw new ExpressionEvaluatorSyntaxErrorException("a try statement need at least one catch or one finally statement."); + } + + try + { + lastResult = ScriptEvaluate(tryStatementsList[0][0], ref isReturn, ref isBreak, ref isContinue); + } + catch (Exception exception) + { + bool atLeasOneCatch = false; + + foreach (List catchStatement in tryStatementsList.Skip(1).TakeWhile(e => e[0].Equals("catch"))) + { + if (catchStatement[1] != null) + { + string[] exceptionVariable = catchStatement[1].Trim().Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); + + string exceptionName = exceptionVariable[0]; + + if (exceptionVariable.Length >= 2) + { + if (!((ClassOrEnumType)Evaluate(exceptionVariable[0])).Type.IsAssignableFrom(exception.GetType())) + continue; + + exceptionName = exceptionVariable[1]; + } + + Variables[exceptionName] = exception; + } + + lastResult = ScriptEvaluate(catchStatement[2], ref isReturn, ref isBreak, ref isContinue); + atLeasOneCatch = true; + break; } - catch (ExpressionEvaluatorSyntaxErrorException) + + if (!atLeasOneCatch) { throw; } - catch (Exception ex) + } + finally + { + if (tryStatementsList.Last()[0].Equals("finally")) + { + lastResult = ScriptEvaluate(tryStatementsList.Last()[1], ref isReturn, ref isBreak, ref isContinue); + } + } + + tryStatementsList.Clear(); + } + } + + void ExecuteBlocksStacks() + { + ExecuteTryList(); + ExecuteIfList(); + } + + int i = 0; + + while (!isReturn && !isBreak && !isContinue && i < script.Length) + { + Match blockKeywordsBeginingMatch = null; + Match blockKeywordsWithoutParenthesesBeginningMatch = null; + + if (script.Substring(startOfExpression, i - startOfExpression).Trim().Equals(string.Empty) + && ((blockKeywordsBeginingMatch = blockKeywordsBeginningRegex.Match(script.Substring(i))).Success + || (blockKeywordsWithoutParenthesesBeginningMatch = blockKeywordsWithoutParenthesesBeginningRegex.Match(script.Substring(i))).Success)) + { + i += blockKeywordsBeginingMatch.Success ? blockKeywordsBeginingMatch.Length : blockKeywordsWithoutParenthesesBeginningMatch.Length; + string keyword = blockKeywordsBeginingMatch.Success ? blockKeywordsBeginingMatch.Groups["keyword"].Value.Replace(" ", "").Replace("\t", "") : (blockKeywordsWithoutParenthesesBeginningMatch?.Groups["keyword"].Value ?? string.Empty); + List keywordAttributes = blockKeywordsBeginingMatch.Success ? GetExpressionsBetweenParenthesesOrOtherImbricableBrackets(script, ref i, true, ";") : null; + + if (blockKeywordsBeginingMatch.Success) + i++; + + Match blockBeginningMatch = blockBeginningRegex.Match(script.Substring(i)); + + string subScript = string.Empty; + + if (blockBeginningMatch.Success) + { + i += blockBeginningMatch.Length; + + subScript = GetScriptBetweenCurlyBrackets(script, ref i); + + i++; + } + else + { + bool continueExpressionParsing = true; + startOfExpression = i; + + while (i < script.Length && continueExpressionParsing) + { + if (TryParseStringAndParenthisAndCurlyBrackets(ref i)) { } + else if (script.Length - i > 2 && script.Substring(i, 3).Equals("';'")) + { + i += 2; + } + else if (script[i] == ';') + { + subScript = script.Substring(startOfExpression, i + 1 - startOfExpression); + continueExpressionParsing = false; + } + + i++; + } + + if (subScript.Trim().Equals(string.Empty)) + throw new ExpressionEvaluatorSyntaxErrorException($"No instruction after [{keyword}] statement."); + } + + if (keyword.Equals("elseif", StringComparisonForCasing)) + { + if (ifBlockEvaluatedState == IfBlockEvaluatedState.NoBlockEvaluated) + { + throw new ExpressionEvaluatorSyntaxErrorException("No corresponding [if] for [else if] statement."); + } + else + { + ifElseStatementsList.Add(new List() { keywordAttributes[0], subScript }); + ifBlockEvaluatedState = IfBlockEvaluatedState.ElseIf; + } + } + else if (keyword.Equals("else", StringComparisonForCasing)) + { + if (ifBlockEvaluatedState == IfBlockEvaluatedState.NoBlockEvaluated) + { + throw new ExpressionEvaluatorSyntaxErrorException("No corresponding [if] for [else] statement."); + } + else + { + ifElseStatementsList.Add(new List() { "true", subScript }); + ifBlockEvaluatedState = IfBlockEvaluatedState.NoBlockEvaluated; + } + } + else if (keyword.Equals("catch", StringComparisonForCasing)) + { + if (tryBlockEvaluatedState == TryBlockEvaluatedState.NoBlockEvaluated) + { + throw new ExpressionEvaluatorSyntaxErrorException("No corresponding [try] for [catch] statement."); + } + else + { + tryStatementsList.Add(new List() { "catch", keywordAttributes.Count > 0 ? keywordAttributes[0] : null, subScript }); + tryBlockEvaluatedState = TryBlockEvaluatedState.Catch; + } + } + else if (keyword.Equals("finally", StringComparisonForCasing)) + { + if (tryBlockEvaluatedState == TryBlockEvaluatedState.NoBlockEvaluated) + { + throw new ExpressionEvaluatorSyntaxErrorException("No corresponding [try] for [finally] statement."); + } + else + { + tryStatementsList.Add(new List() { "finally", subScript }); + tryBlockEvaluatedState = TryBlockEvaluatedState.NoBlockEvaluated; + } + } + else + { + ExecuteBlocksStacks(); + + if (keyword.Equals("if", StringComparisonForCasing)) + { + ifElseStatementsList.Add(new List() { keywordAttributes[0], subScript }); + ifBlockEvaluatedState = IfBlockEvaluatedState.If; + tryBlockEvaluatedState = TryBlockEvaluatedState.NoBlockEvaluated; + } + else if (keyword.Equals("try", StringComparisonForCasing)) + { + tryStatementsList.Add(new List() { subScript }); + ifBlockEvaluatedState = IfBlockEvaluatedState.NoBlockEvaluated; + tryBlockEvaluatedState = TryBlockEvaluatedState.Try; + } + else if (keyword.Equals("do", StringComparisonForCasing)) { - throw new ExpressionEvaluatorSyntaxErrorException($"The call of the method \"{func}\" on type [{objType.ToString()}] generate this error : {(ex.InnerException?.Message ?? ex.Message)}", ex); + if ((blockKeywordsBeginingMatch = blockKeywordsBeginningRegex.Match(script.Substring(i))).Success + && blockKeywordsBeginingMatch.Groups["keyword"].Value.Equals("while", StringComparisonForCasing)) + { + i += blockKeywordsBeginingMatch.Length; + keywordAttributes = GetExpressionsBetweenParenthesesOrOtherImbricableBrackets(script, ref i, true, ";"); + + i++; + + Match nextIsEndOfExpressionMatch = null; + + if ((nextIsEndOfExpressionMatch = nextIsEndOfExpressionRegex.Match(script.Substring(i))).Success) + { + i += nextIsEndOfExpressionMatch.Length; + + do + { + lastResult = ScriptEvaluate(subScript, ref isReturn, ref isBreak, ref isContinue); + + if (isBreak) + { + isBreak = false; + break; + } + if (isContinue) + { + isContinue = false; + } + } + while (!isReturn && (bool)ManageJumpStatementsOrExpressionEval(keywordAttributes[0])); + } + else + { + throw new ExpressionEvaluatorSyntaxErrorException("A [;] character is missing. (After the do while condition)"); + } + } + else + { + throw new ExpressionEvaluatorSyntaxErrorException("No [while] keyword afte the [do] keyword and block"); + } + } + else if (keyword.Equals("while", StringComparisonForCasing)) + { + while (!isReturn && (bool)ManageJumpStatementsOrExpressionEval(keywordAttributes[0])) + { + lastResult = ScriptEvaluate(subScript, ref isReturn, ref isBreak, ref isContinue); + + if (isBreak) + { + isBreak = false; + break; + } + if (isContinue) + { + isContinue = false; + } + } + } + else if (keyword.Equals("for", StringComparisonForCasing)) + { + void forAction(int index) + { + if (keywordAttributes.Count > index && !keywordAttributes[index].Trim().Equals(string.Empty)) + ManageJumpStatementsOrExpressionEval(keywordAttributes[index]); + } + + for (forAction(0); !isReturn && (bool)ManageJumpStatementsOrExpressionEval(keywordAttributes[1]); forAction(2)) + { + lastResult = ScriptEvaluate(subScript, ref isReturn, ref isBreak, ref isContinue); + + if (isBreak) + { + isBreak = false; + break; + } + if (isContinue) + { + isContinue = false; + } + } + } + else if (keyword.Equals("foreach", StringComparisonForCasing)) + { + Match foreachParenthisEvaluationMatch = foreachParenthisEvaluationRegex.Match(keywordAttributes[0]); + + if (!foreachParenthisEvaluationMatch.Success) + { + throw new ExpressionEvaluatorSyntaxErrorException("wrong foreach syntax"); + } + else if (!foreachParenthisEvaluationMatch.Groups["in"].Value.Equals("in", StringComparisonForCasing)) + { + throw new ExpressionEvaluatorSyntaxErrorException("no [in] keyword found in foreach"); + } + else + { + foreach (dynamic foreachValue in (dynamic)Evaluate(foreachParenthisEvaluationMatch.Groups["collection"].Value)) + { + Variables[foreachParenthisEvaluationMatch.Groups["variableName"].Value] = foreachValue; + + lastResult = ScriptEvaluate(subScript, ref isReturn, ref isBreak, ref isContinue); + + if (isBreak) + { + isBreak = false; + break; + } + if (isContinue) + { + isContinue = false; + } + } + } } + } + + startOfExpression = i; + } + else + { + ExecuteBlocksStacks(); + + bool executed = false; + + if (TryParseStringAndParenthisAndCurlyBrackets(ref i)) { } + else if (script.Length - i > 2 && script.Substring(i, 3).Equals("';'")) + { + i += 2; + } + else if (script[i] == ';') + { + lastResult = ScriptExpressionEvaluate(ref i); + executed = true; + } + + if (!OptionScriptNeedSemicolonAtTheEndOfLastExpression && i == script.Length - 1 && !executed) + { + i++; + lastResult = ScriptExpressionEvaluate(ref i); + startOfExpression--; + } + + ifBlockEvaluatedState = IfBlockEvaluatedState.NoBlockEvaluated; + tryBlockEvaluatedState = TryBlockEvaluatedState.NoBlockEvaluated; + + if (OptionScriptNeedSemicolonAtTheEndOfLastExpression || i < script.Length) + i++; + } + } + + if (!script.Substring(startOfExpression).Trim().Equals(string.Empty) && !isReturn && !isBreak && !isContinue && OptionScriptNeedSemicolonAtTheEndOfLastExpression) + throw new ExpressionEvaluatorSyntaxErrorException("A [;] character is missing."); + + ExecuteBlocksStacks(); + + valueReturned = isReturn; + breakCalled = isBreak; + continueCalled = isContinue; + + if (isReturn || OptionOnNoReturnKeywordFoundInScriptAction == OptionOnNoReturnKeywordFoundInScriptAction.ReturnAutomaticallyLastEvaluatedExpression) + return lastResult; + else if (OptionOnNoReturnKeywordFoundInScriptAction == OptionOnNoReturnKeywordFoundInScriptAction.ReturnNull) + return null; + else + throw new ExpressionEvaluatorSyntaxErrorException("No [return] keyword found"); + } + + #endregion + + #region Expressions + + /// + /// Evaluate the specified math or pseudo C# expression + /// + /// The type in which to cast the result of the expression + /// the math or pseudo C# expression to evaluate + /// The result of the operation if syntax is correct casted in the specified type + public T Evaluate(string expression) + { + return (T)Evaluate(expression); + } + + private IList parsingMethods; + + protected virtual IList ParsingMethods => parsingMethods ?? (parsingMethods = new List() + { + EvaluateCast, + EvaluateNumber, + EvaluateInstanceCreationWithNewKeyword, + EvaluateVarOrFunc, + EvaluateOperators, + EvaluateChar, + EvaluateParenthis, + EvaluateIndexing, + EvaluateString, + EvaluateTernaryConditionalOperator, + }); + + /// + /// Evaluate the specified math or pseudo C# expression + /// + /// the math or pseudo C# expression to evaluate + /// The result of the operation if syntax is correct + public object Evaluate(string expression) + { + expression = expression.Trim(); + + Stack stack = new Stack(); + evaluationStackCount++; + try + { + if (GetLambdaExpression(expression, stack)) + return stack.Pop(); + for (int i = 0; i < expression.Length; i++) + { + if (!ParsingMethods.Any(parsingMethod => parsingMethod(expression, stack, ref i))) + { + string s = expression.Substring(i, 1); + + if (!s.Trim().Equals(string.Empty)) + { + throw new ExpressionEvaluatorSyntaxErrorException($"Invalid character [{(int)s[0]}:{s}]"); + } } } - else if (DefaultFunctions(varFuncMatch.Groups["name"].Value.ToLower(), funcArgs, out funcResult)) + + return ProcessStack(stack); + } + finally + { + evaluationStackCount--; + } + } + + #endregion + + #endregion + + #region Sub parts evaluate methods (protected virtual) + + protected virtual bool EvaluateCast(string expression, Stack stack, ref int i) + { + Match castMatch = Regex.Match(expression.Substring(i), CastRegexPattern, optionCaseSensitiveEvaluationActive ? RegexOptions.None : RegexOptions.IgnoreCase); + + if (castMatch.Success) + { + string typeName = castMatch.Groups["typeName"].Value; + + Type type = GetTypeByFriendlyName(typeName); + + if (type != null) + { + i += castMatch.Length - 1; + stack.Push(type); + stack.Push(ExpressionOperator.Cast); + return true; + } + } + + return false; + } + + protected virtual bool EvaluateNumber(string expression, Stack stack, ref int i) + { + string restOfExpression = expression.Substring(i); + Match numberMatch = Regex.Match(restOfExpression, numberRegexPattern, RegexOptions.IgnoreCase); + Match otherBaseMatch = otherBasesNumberRegex.Match(restOfExpression); + + if (otherBaseMatch.Success + && (!otherBaseMatch.Groups["sign"].Success + || stack.Count == 0 + || stack.Peek() is ExpressionOperator)) + { + i += otherBaseMatch.Length; + i--; + + int baseValue = otherBaseMatch.Groups["type"].Value.Equals("b") ? 2 : 16; + + if (otherBaseMatch.Groups["sign"].Success) + { + string value = otherBaseMatch.Groups["value"].Value.Replace("_", "").Substring(2); + stack.Push(otherBaseMatch.Groups["sign"].Value.Equals("-") ? -Convert.ToInt32(value, baseValue) : Convert.ToInt32(value, baseValue)); + } + else + { + stack.Push(Convert.ToInt32(otherBaseMatch.Value.Replace("_", "").Substring(2), baseValue)); + } + + return true; + } + else if (numberMatch.Success + && (!numberMatch.Groups["sign"].Success + || stack.Count == 0 + || stack.Peek() is ExpressionOperator)) + { + i += numberMatch.Length; + i--; + + if (numberMatch.Groups["type"].Success) + { + string type = numberMatch.Groups["type"].Value; + string numberNoType = numberMatch.Value.Replace(type, string.Empty).Replace("_", ""); + + if (numberSuffixToParse.TryGetValue(type, out Func parseFunc)) + { + stack.Push(parseFunc(numberNoType, CultureInfoForNumberParsing)); + } + } + else if (OptionForceIntegerNumbersEvaluationsAsDoubleByDefault || numberMatch.Groups["hasdecimal"].Success) + { + stack.Push(double.Parse(numberMatch.Value.Replace("_", ""), NumberStyles.Any, CultureInfoForNumberParsing)); + } + else + { + stack.Push(int.Parse(numberMatch.Value.Replace("_", ""), NumberStyles.Any, CultureInfoForNumberParsing)); + } + + return true; + } + else + { + return false; + } + } + + protected virtual bool EvaluateInstanceCreationWithNewKeyword(string expression, Stack stack, ref int i) + { + if (!OptionNewKeywordEvaluationActive) + return false; + + Match instanceCreationMatch = Regex.Match(expression.Substring(i), InstanceCreationWithNewKeywordRegexPattern, optionCaseSensitiveEvaluationActive ? RegexOptions.None : RegexOptions.IgnoreCase); + + if (instanceCreationMatch.Success + && (stack.Count == 0 + || stack.Peek() is ExpressionOperator)) + { + void InitSimpleObjet(object element, List initArgs) + { + string variable = "V" + Guid.NewGuid().ToString().Replace("-", ""); + + Variables[variable] = element; + + initArgs.ForEach(subExpr => + { + if (subExpr.Contains("=")) + { + string trimmedSubExpr = subExpr.TrimStart(); + + Evaluate($"{variable}{(trimmedSubExpr.StartsWith("[") ? string.Empty : ".")}{trimmedSubExpr}"); + } + else + { + throw new ExpressionEvaluatorSyntaxErrorException($"A '=' char is missing in [{subExpr}]. It is in a object initializer. It must contains one."); + } + }); + + Variables.Remove(variable); + } + + i += instanceCreationMatch.Length; + + if (instanceCreationMatch.Groups["isAnonymous"].Success) + { + object element = new ExpandoObject(); + + List initArgs = GetExpressionsBetweenParenthesesOrOtherImbricableBrackets(expression, ref i, true, OptionInitializersSeparator, "{", "}"); + + InitSimpleObjet(element, initArgs); + + stack.Push(element); + } + else + { + string completeName = instanceCreationMatch.Groups["name"].Value; + string genericTypes = instanceCreationMatch.Groups["isgeneric"].Value; + Type type = GetTypeByFriendlyName(completeName, genericTypes); + + if (type == null) + throw new ExpressionEvaluatorSyntaxErrorException($"Type or class {completeName}{genericTypes} is unknown"); + + void Init(object element, List initArgs) + { + if (typeof(IEnumerable).IsAssignableFrom(type) + && !typeof(IDictionary).IsAssignableFrom(type) + && !typeof(ExpandoObject).IsAssignableFrom(type)) + { + MethodInfo methodInfo = type.GetMethod("Add", BindingFlags.Public | BindingFlags.Instance); + + initArgs.ForEach(subExpr => methodInfo.Invoke(element, new object[] { Evaluate(subExpr) })); + } + else if (typeof(IDictionary).IsAssignableFrom(type) + && initArgs.All(subExpr => subExpr.TrimStart().StartsWith("{")) + && !typeof(ExpandoObject).IsAssignableFrom(type)) + { + initArgs.ForEach(subExpr => + { + int subIndex = subExpr.IndexOf("{") + 1; + + List subArgs = GetExpressionsBetweenParenthesesOrOtherImbricableBrackets(subExpr, ref subIndex, true, OptionInitializersSeparator, "{", "}"); + + if (subArgs.Count == 2) + { + dynamic indexedObject = element; + dynamic index = Evaluate(subArgs[0]); + indexedObject[index] = (dynamic)Evaluate(subArgs[1]); + } + else + { + throw new ExpressionEvaluatorSyntaxErrorException($"Bad Number of args in initialization of [{subExpr}]"); + } + }); + } + else + { + InitSimpleObjet(element, initArgs); + } + } + + if (instanceCreationMatch.Groups["isfunction"].Success) + { + List constructorArgs = GetExpressionsBetweenParenthesesOrOtherImbricableBrackets(expression, ref i, true, OptionFunctionArgumentsSeparator); + i++; + + List cArgs = constructorArgs.ConvertAll(Evaluate); + + object element = Activator.CreateInstance(type, cArgs.ToArray()); + + Match blockBeginningMatch = blockBeginningRegex.Match(expression.Substring(i)); + + if (blockBeginningMatch.Success) + { + i += blockBeginningMatch.Length; + + List initArgs = GetExpressionsBetweenParenthesesOrOtherImbricableBrackets(expression, ref i, true, OptionInitializersSeparator, "{", "}"); + + Init(element, initArgs); + } + else + { + i--; + } + + stack.Push(element); + } + else if (instanceCreationMatch.Groups["isInit"].Success) + { + object element = Activator.CreateInstance(type, new object[0]); + + List initArgs = GetExpressionsBetweenParenthesesOrOtherImbricableBrackets(expression, ref i, true, OptionInitializersSeparator, "{", "}"); + + Init(element, initArgs); + + stack.Push(element); + } + else if (instanceCreationMatch.Groups["isArray"].Success) + { + List arrayArgs = GetExpressionsBetweenParenthesesOrOtherImbricableBrackets(expression, ref i, true, OptionInitializersSeparator, "[", "]"); + i++; + Array array = null; + + if (arrayArgs.Count > 0) + { + array = Array.CreateInstance(type, arrayArgs.ConvertAll(subExpression => Convert.ToInt32(Evaluate(subExpression))).ToArray()); + } + + Match initInNewBeginningMatch = initInNewBeginningRegex.Match(expression.Substring(i)); + + if (initInNewBeginningMatch.Success) + { + i += initInNewBeginningMatch.Length; + + List arrayElements = GetExpressionsBetweenParenthesesOrOtherImbricableBrackets(expression, ref i, true, OptionInitializersSeparator, "{", "}"); + + if (array == null) + array = Array.CreateInstance(type, arrayElements.Count); + + Array.Copy(arrayElements.ConvertAll(Evaluate).ToArray(), array, arrayElements.Count); + } + + stack.Push(array); + } + else + { + throw new ExpressionEvaluatorSyntaxErrorException($"A new expression requires that type be followed by (), [] or {{}}(Check : {instanceCreationMatch.Value})"); + } + } + + return true; + } + else + { + return false; + } + } + + protected virtual bool EvaluateVarOrFunc(string expression, Stack stack, ref int i) + { + Match varFuncMatch = varOrFunctionRegEx.Match(expression.Substring(i)); + + if (varFuncMatch.Groups["varKeyword"].Success + && !varFuncMatch.Groups["assignationOperator"].Success) + { + throw new ExpressionEvaluatorSyntaxErrorException("Implicit variables must be initialized. [var " + varFuncMatch.Groups["name"].Value + "]"); + } + + if (varFuncMatch.Success + && (!varFuncMatch.Groups["sign"].Success + || stack.Count == 0 + || stack.Peek() is ExpressionOperator) + && !operatorsDictionary.ContainsKey(varFuncMatch.Value.Trim()) + && (!operatorsDictionary.ContainsKey(varFuncMatch.Groups["name"].Value) || varFuncMatch.Groups["inObject"].Success)) + { + string varFuncName = varFuncMatch.Groups["name"].Value; + string genericsTypes = varFuncMatch.Groups["isgeneric"].Value; + bool inObject = varFuncMatch.Groups["inObject"].Success; + + i += varFuncMatch.Length; + + if (varFuncMatch.Groups["isfunction"].Success) + { + List funcArgs = GetExpressionsBetweenParenthesesOrOtherImbricableBrackets(expression, ref i, true, OptionFunctionArgumentsSeparator); + + if (inObject + || Context?.GetType() + .GetMethods(InstanceBindingFlag) + .Any(methodInfo => methodInfo.Name.Equals(varFuncName, StringComparisonForCasing)) == true) + { + if (inObject && (stack.Count == 0 || stack.Peek() is ExpressionOperator)) + throw new ExpressionEvaluatorSyntaxErrorException($"[{varFuncMatch.Value})] must follow an object."); + + object obj = inObject ? stack.Pop() : Context; + object keepObj = obj; + Type objType = null; + Type[] inferedGenericsTypes = obj?.GetType().GenericTypeArguments; + ValueTypeNestingTrace valueTypeNestingTrace = null; + + if (obj != null && TypesToBlock.Contains(obj.GetType())) + throw new ExpressionEvaluatorSecurityException($"{obj.GetType().FullName} type is blocked"); + else if (obj is Type staticType && TypesToBlock.Contains(staticType)) + throw new ExpressionEvaluatorSecurityException($"{staticType.FullName} type is blocked"); + else if (obj is ClassOrEnumType classOrType && TypesToBlock.Contains(classOrType.Type)) + throw new ExpressionEvaluatorSecurityException($"{classOrType.Type} type is blocked"); + + try + { + if (obj is NullConditionalNullValue) + { + stack.Push(obj); + } + else if (varFuncMatch.Groups["nullConditional"].Success && obj == null) + { + stack.Push(new NullConditionalNullValue()); + } + else if (obj is BubbleExceptionContainer) + { + stack.Push(obj); + return true; + } + else + { + FunctionPreEvaluationEventArg functionPreEvaluationEventArg = new FunctionPreEvaluationEventArg(varFuncName, Evaluate, funcArgs, this, obj, genericsTypes, GetConcreteTypes); + + PreEvaluateFunction?.Invoke(this, functionPreEvaluationEventArg); + + if (functionPreEvaluationEventArg.CancelEvaluation) + { + throw new ExpressionEvaluatorSyntaxErrorException($"[{objType}] object has no Method named \"{varFuncName}\"."); + } + else if (functionPreEvaluationEventArg.FunctionReturnedValue) + { + stack.Push(functionPreEvaluationEventArg.Value); + } + else + { + List oArgs = funcArgs.ConvertAll(Evaluate); + BindingFlags flag = DetermineInstanceOrStatic(ref objType, ref obj, ref valueTypeNestingTrace); + + if (!OptionStaticMethodsCallActive && (flag & BindingFlags.Static) != 0) + throw new ExpressionEvaluatorSyntaxErrorException($"[{objType}] object has no Method named \"{varFuncName}\"."); + if (!OptionInstanceMethodsCallActive && (flag & BindingFlags.Instance) != 0) + throw new ExpressionEvaluatorSyntaxErrorException($"[{objType}] object has no Method named \"{varFuncName}\"."); + + // Standard Instance or public method find + MethodInfo methodInfo = GetRealMethod(ref objType, ref obj, varFuncName, flag, oArgs, genericsTypes, inferedGenericsTypes); + + // if not found check if obj is an expandoObject or similar + if (obj is IDynamicMetaObjectProvider + && obj is IDictionary dictionaryObject + && (dictionaryObject[varFuncName] is InternalDelegate || dictionaryObject[varFuncName] is Delegate)) + { + if (dictionaryObject[varFuncName] is InternalDelegate internalDelegate) + stack.Push(internalDelegate(oArgs.ToArray())); + else if (dictionaryObject[varFuncName] is Delegate del) + stack.Push(del.DynamicInvoke(oArgs.ToArray())); + } + else if (objType.GetProperty(varFuncName, InstanceBindingFlag) is PropertyInfo instancePropertyInfo + && (instancePropertyInfo.PropertyType.IsSubclassOf(typeof(Delegate)) || instancePropertyInfo.PropertyType == typeof(Delegate)) + && instancePropertyInfo.GetValue(obj) is Delegate del) + { + stack.Push(del.DynamicInvoke(oArgs.ToArray())); + } + else + { + bool isExtention = false; + + // if not found try to Find extension methods. + if (methodInfo == null && obj != null) + { + oArgs.Insert(0, obj); + objType = obj.GetType(); + //obj = null; + object extentionObj = null; + for (int e = 0; e < StaticTypesForExtensionsMethods.Count && methodInfo == null; e++) + { + Type type = StaticTypesForExtensionsMethods[e]; + methodInfo = GetRealMethod(ref type, ref extentionObj, varFuncName, StaticBindingFlag, oArgs, genericsTypes, inferedGenericsTypes); + isExtention = methodInfo != null; + } + } + + if (methodInfo != null) + { + stack.Push(methodInfo.Invoke(isExtention ? null : obj, oArgs.ToArray())); + } + else if (objType.GetProperty(varFuncName, StaticBindingFlag) is PropertyInfo staticPropertyInfo + && (staticPropertyInfo.PropertyType.IsSubclassOf(typeof(Delegate)) || staticPropertyInfo.PropertyType == typeof(Delegate)) + && staticPropertyInfo.GetValue(obj) is Delegate del2) + { + stack.Push(del2.DynamicInvoke(oArgs.ToArray())); + } + else + { + FunctionEvaluationEventArg functionEvaluationEventArg = new FunctionEvaluationEventArg(varFuncName, Evaluate, funcArgs, this, obj ?? keepObj, genericsTypes, GetConcreteTypes); + + EvaluateFunction?.Invoke(this, functionEvaluationEventArg); + + if (functionEvaluationEventArg.FunctionReturnedValue) + { + stack.Push(functionEvaluationEventArg.Value); + } + else + { + throw new ExpressionEvaluatorSyntaxErrorException($"[{objType}] object has no Method named \"{varFuncName}\"."); + } + } + } + } + } + } + catch (ExpressionEvaluatorSecurityException) + { + throw; + } + catch (ExpressionEvaluatorSyntaxErrorException) + { + throw; + } + catch (Exception ex) + { + //Transport the exception in stack. + stack.Push(new BubbleExceptionContainer() + { + Exception = new ExpressionEvaluatorSyntaxErrorException($"The call of the method \"{varFuncName}\" on type [{objType}] generate this error : {ex.InnerException?.Message ?? ex.Message}", ex) + }); + return true; //Signals an error to the parsing method array call + } + } + else + { + FunctionPreEvaluationEventArg functionPreEvaluationEventArg = new FunctionPreEvaluationEventArg(varFuncName, Evaluate, funcArgs, this, null, genericsTypes, GetConcreteTypes); + + PreEvaluateFunction?.Invoke(this, functionPreEvaluationEventArg); + + if (functionPreEvaluationEventArg.CancelEvaluation) + { + throw new ExpressionEvaluatorSyntaxErrorException($"Function [{varFuncName}] unknown in expression : [{expression.Replace("\r", "").Replace("\n", "")}]"); + } + else if (functionPreEvaluationEventArg.FunctionReturnedValue) + { + stack.Push(functionPreEvaluationEventArg.Value); + } + else if (DefaultFunctions(varFuncName, funcArgs, out object funcResult)) + { + stack.Push(funcResult); + } + else if (Variables.TryGetValue(varFuncName, out object o) && o is InternalDelegate lambdaExpression) + { + stack.Push(lambdaExpression.Invoke(funcArgs.ConvertAll(Evaluate).ToArray())); + } + else if (Variables.TryGetValue(varFuncName, out o) && o is Delegate delegateVar) + { + stack.Push(delegateVar.DynamicInvoke(funcArgs.ConvertAll(Evaluate).ToArray())); + } + else + { + FunctionEvaluationEventArg functionEvaluationEventArg = new FunctionEvaluationEventArg(varFuncName, Evaluate, funcArgs, this, genericTypes: genericsTypes, evaluateGenericTypes: GetConcreteTypes); + + EvaluateFunction?.Invoke(this, functionEvaluationEventArg); + + if (functionEvaluationEventArg.FunctionReturnedValue) + { + stack.Push(functionEvaluationEventArg.Value); + } + else + { + throw new ExpressionEvaluatorSyntaxErrorException($"Function [{varFuncName}] unknown in expression : [{expression.Replace("\r", "").Replace("\n", "")}]"); + } + } + } + } + else + { + if (inObject + || Context?.GetType() + .GetProperties(InstanceBindingFlag) + .Any(propInfo => propInfo.Name.Equals(varFuncName, StringComparisonForCasing)) == true + || Context?.GetType() + .GetFields(InstanceBindingFlag) + .Any(fieldInfo => fieldInfo.Name.Equals(varFuncName, StringComparisonForCasing)) == true) + { + if (inObject && (stack.Count == 0 || stack.Peek() is ExpressionOperator)) + throw new ExpressionEvaluatorSyntaxErrorException($"[{varFuncMatch.Value}] must follow an object."); + + object obj = inObject ? stack.Pop() : Context; + object keepObj = obj; + Type objType = null; + ValueTypeNestingTrace valueTypeNestingTrace = null; + + if (obj != null && TypesToBlock.Contains(obj.GetType())) + throw new ExpressionEvaluatorSecurityException($"{obj.GetType().FullName} type is blocked"); + else if (obj is Type staticType && TypesToBlock.Contains(staticType)) + throw new ExpressionEvaluatorSecurityException($"{staticType.FullName} type is blocked"); + else if (obj is ClassOrEnumType classOrType && TypesToBlock.Contains(classOrType.Type)) + throw new ExpressionEvaluatorSecurityException($"{classOrType.Type} type is blocked"); + + try + { + if (obj is NullConditionalNullValue) + { + stack.Push(obj); + } + else if (varFuncMatch.Groups["nullConditional"].Success && obj == null) + { + stack.Push(new NullConditionalNullValue()); + } + else if (obj is BubbleExceptionContainer) + { + stack.Push(obj); + return true; + } + else + { + VariablePreEvaluationEventArg variablePreEvaluationEventArg = new VariablePreEvaluationEventArg(varFuncName, this, obj, genericsTypes, GetConcreteTypes); + + PreEvaluateVariable?.Invoke(this, variablePreEvaluationEventArg); + + if (variablePreEvaluationEventArg.CancelEvaluation) + { + throw new ExpressionEvaluatorSyntaxErrorException($"[{objType}] object has no public Property or Member named \"{varFuncName}\".", new Exception("Variable evaluation canceled")); + } + else if (variablePreEvaluationEventArg.HasValue) + { + stack.Push(variablePreEvaluationEventArg.Value); + } + else + { + BindingFlags flag = DetermineInstanceOrStatic(ref objType, ref obj, ref valueTypeNestingTrace); + + if (!OptionStaticPropertiesGetActive && (flag & BindingFlags.Static) != 0) + throw new ExpressionEvaluatorSyntaxErrorException($"[{objType}] object has no public Property or Field named \"{varFuncName}\"."); + if (!OptionInstancePropertiesGetActive && (flag & BindingFlags.Instance) != 0) + throw new ExpressionEvaluatorSyntaxErrorException($"[{objType}] object has no public Property or Field named \"{varFuncName}\"."); + + bool isDynamic = (flag & BindingFlags.Instance) != 0 && obj is IDynamicMetaObjectProvider && obj is IDictionary; + IDictionary dictionaryObject = obj as IDictionary; + + MemberInfo member = isDynamic ? null : objType?.GetProperty(varFuncName, flag); + dynamic varValue = null; + bool assign = true; + + if (member == null && !isDynamic) + member = objType.GetField(varFuncName, flag); + + bool pushVarValue = true; + + if (isDynamic) + { + if (!varFuncMatch.Groups["assignationOperator"].Success || varFuncMatch.Groups["assignmentPrefix"].Success) + varValue = dictionaryObject.ContainsKey(varFuncName) ? dictionaryObject[varFuncName] : null; + else + pushVarValue = false; + } + + if (member == null && pushVarValue) + { + VariableEvaluationEventArg variableEvaluationEventArg = new VariableEvaluationEventArg(varFuncName, this, obj ?? keepObj, genericsTypes, GetConcreteTypes); + + EvaluateVariable?.Invoke(this, variableEvaluationEventArg); + + if (variableEvaluationEventArg.HasValue) + { + varValue = variableEvaluationEventArg.Value; + } + } + + if (!isDynamic && varValue == null && pushVarValue) + { + varValue = ((dynamic)member).GetValue(obj); + + if (varValue is ValueType) + { + stack.Push(valueTypeNestingTrace = new ValueTypeNestingTrace + { + Container = valueTypeNestingTrace ?? obj, + Member = member, + Value = varValue + }); + + pushVarValue = false; + } + } + + if (pushVarValue) + { + stack.Push(varValue); + } + + if (OptionPropertyOrFieldSetActive) + { + if (varFuncMatch.Groups["assignationOperator"].Success) + { + if (stack.Count > 1) + throw new ExpressionEvaluatorSyntaxErrorException("The left part of an assignation must be a variable, a property or an indexer."); + + string rightExpression = expression.Substring(i); + i = expression.Length; + + if (rightExpression.Trim().Equals(string.Empty)) + throw new ExpressionEvaluatorSyntaxErrorException("Right part is missing in assignation"); + + if (varFuncMatch.Groups["assignmentPrefix"].Success) + { + ExpressionOperator op = operatorsDictionary[varFuncMatch.Groups["assignmentPrefix"].Value]; + + varValue = OperatorsEvaluations.ToList().Find(dict => dict.ContainsKey(op))[op](varValue, Evaluate(rightExpression)); + } + else + { + varValue = Evaluate(rightExpression); + } + + stack.Clear(); + stack.Push(varValue); + } + else if (varFuncMatch.Groups["postfixOperator"].Success) + { + varValue = varFuncMatch.Groups["postfixOperator"].Value.Equals("++") ? varValue + 1 : varValue - 1; + } + else + { + assign = false; + } + + if (assign) + { + if (isDynamic) + { + dictionaryObject[varFuncName] = varValue; + } + else if (valueTypeNestingTrace != null) + { + valueTypeNestingTrace.Value = varValue; + valueTypeNestingTrace.AssignValue(); + } + else + { + ((dynamic)member).SetValue(obj, varValue); + } + } + } + else if (varFuncMatch.Groups["assignationOperator"].Success) + { + i -= varFuncMatch.Groups["assignationOperator"].Length; + } + else if (varFuncMatch.Groups["postfixOperator"].Success) + { + i -= varFuncMatch.Groups["postfixOperator"].Length; + } + } + } + } + catch (ExpressionEvaluatorSecurityException) + { + throw; + } + catch (ExpressionEvaluatorSyntaxErrorException) + { + throw; + } + catch (Exception ex) + { + //Transport the exception in stack. + stack.Push(new BubbleExceptionContainer() + { + Exception = new ExpressionEvaluatorSyntaxErrorException($"[{objType}] object has no public Property or Member named \"{varFuncName}\".", ex) + }); + i--; + return true; //Signals an error to the parsing method array call + } + } + else + { + VariablePreEvaluationEventArg variablePreEvaluationEventArg = new VariablePreEvaluationEventArg(varFuncName, this, genericTypes: genericsTypes, evaluateGenericTypes: GetConcreteTypes); + + PreEvaluateVariable?.Invoke(this, variablePreEvaluationEventArg); + + if (variablePreEvaluationEventArg.CancelEvaluation) + { + throw new ExpressionEvaluatorSyntaxErrorException($"Variable [{varFuncName}] unknown in expression : [{expression}]"); + } + else if (variablePreEvaluationEventArg.HasValue) + { + stack.Push(variablePreEvaluationEventArg.Value); + } + else if (defaultVariables.TryGetValue(varFuncName, out object varValueToPush)) + { + stack.Push(varValueToPush); + } + else if ((Variables.TryGetValue(varFuncName, out object cusVarValueToPush) + || varFuncMatch.Groups["assignationOperator"].Success + || (stack.Count == 1 && stack.Peek() is ClassOrEnumType && string.IsNullOrWhiteSpace(expression.Substring(i)))) + && (cusVarValueToPush == null || !TypesToBlock.Contains(cusVarValueToPush.GetType()))) + { + if (stack.Count == 1 && stack.Peek() is ClassOrEnumType classOrEnum) + { + if (Variables.ContainsKey(varFuncName)) + throw new ExpressionEvaluatorSyntaxErrorException($"Can not declare a new variable named [{varFuncName}]. A variable with this name already exists"); + else if (varFuncMatch.Groups["varKeyword"].Success) + throw new ExpressionEvaluatorSyntaxErrorException("Can not declare a variable with type and var keyword."); + else if (varFuncMatch.Groups["dynamicKeyword"].Success) + throw new ExpressionEvaluatorSyntaxErrorException("Can not declare a variable with type and dynamic keyword."); + + stack.Pop(); + + Variables[varFuncName] = new StronglyTypedVariable + { + Type = classOrEnum.Type, + Value = !varFuncMatch.Groups["assignationOperator"].Success && classOrEnum.Type.IsValueType ? Activator.CreateInstance(classOrEnum.Type) : null, + }; + } + + if (cusVarValueToPush is StronglyTypedVariable typedVariable) + cusVarValueToPush = typedVariable.Value; + + stack.Push(cusVarValueToPush); + + if (OptionVariableAssignationActive) + { + bool assign = true; + + if (varFuncMatch.Groups["assignationOperator"].Success) + { + if (stack.Count > 1) + throw new ExpressionEvaluatorSyntaxErrorException("The left part of an assignation must be a variable, a property or an indexer."); + + string rightExpression = expression.Substring(i); + i = expression.Length; + + if (rightExpression.Trim().Equals(string.Empty)) + throw new ExpressionEvaluatorSyntaxErrorException("Right part is missing in assignation"); + + if (varFuncMatch.Groups["assignmentPrefix"].Success) + { + if (!Variables.ContainsKey(varFuncName)) + throw new ExpressionEvaluatorSyntaxErrorException($"The variable[{varFuncName}] do not exists."); + + ExpressionOperator op = operatorsDictionary[varFuncMatch.Groups["assignmentPrefix"].Value]; + + cusVarValueToPush = OperatorsEvaluations.ToList().Find(dict => dict.ContainsKey(op))[op](cusVarValueToPush, Evaluate(rightExpression)); + } + else + { + cusVarValueToPush = Evaluate(rightExpression); + } + + stack.Clear(); + stack.Push(cusVarValueToPush); + } + else if (varFuncMatch.Groups["postfixOperator"].Success) + { + cusVarValueToPush = varFuncMatch.Groups["postfixOperator"].Value.Equals("++") ? (dynamic)cusVarValueToPush + 1 : (dynamic)cusVarValueToPush - 1; + } + else if (varFuncMatch.Groups["prefixOperator"].Success) + { + stack.Pop(); + cusVarValueToPush = varFuncMatch.Groups["prefixOperator"].Value.Equals("++") ? (dynamic)cusVarValueToPush + 1 : (dynamic)cusVarValueToPush - 1; + stack.Push(cusVarValueToPush); + } + else + { + assign = false; + } + + if (assign) + { + if (Variables.ContainsKey(varFuncName) && Variables[varFuncName] is StronglyTypedVariable stronglyTypedVariable) + { + if (cusVarValueToPush == null && stronglyTypedVariable.Type.IsValueType && Nullable.GetUnderlyingType(stronglyTypedVariable.Type) == null) + { + throw new ExpressionEvaluatorSyntaxErrorException($"Can not cast null to {stronglyTypedVariable.Type} because it's not a nullable valueType"); + } + + Type typeToAssign = cusVarValueToPush?.GetType(); + if (typeToAssign == null || stronglyTypedVariable.Type.IsAssignableFrom(typeToAssign)) + { + stronglyTypedVariable.Value = cusVarValueToPush; + } + else + { + throw new InvalidCastException($"A object of type {typeToAssign} can not be cast implicitely in {stronglyTypedVariable.Type}"); + } + } + else + { + Variables[varFuncName] = cusVarValueToPush; + } + } + } + else if (varFuncMatch.Groups["assignationOperator"].Success) + { + i -= varFuncMatch.Groups["assignationOperator"].Length; + } + else if (varFuncMatch.Groups["postfixOperator"].Success) + { + i -= varFuncMatch.Groups["postfixOperator"].Length; + } + } + else + { + string typeName = $"{varFuncName}{((i < expression.Length && expression.Substring(i)[0] == '?') ? "?" : "") }"; + Type staticType = GetTypeByFriendlyName(typeName, genericsTypes); + + if (staticType == null && OptionInlineNamespacesEvaluationActive) + { + int subIndex = 0; + Match namespaceMatch = varOrFunctionRegEx.Match(expression.Substring(i + subIndex)); + + while (staticType == null + && namespaceMatch.Success + && !namespaceMatch.Groups["sign"].Success + && !namespaceMatch.Groups["assignationOperator"].Success + && !namespaceMatch.Groups["postfixOperator"].Success + && !namespaceMatch.Groups["isfunction"].Success + && i + subIndex < expression.Length + && !typeName.EndsWith("?")) + { + subIndex += namespaceMatch.Length; + typeName += $".{namespaceMatch.Groups["name"].Value}{((i + subIndex < expression.Length && expression.Substring(i + subIndex)[0] == '?') ? "?" : "") }"; + + staticType = GetTypeByFriendlyName(typeName, namespaceMatch.Groups["isgeneric"].Value); + + if (staticType != null) + { + i += subIndex; + break; + } + + namespaceMatch = varOrFunctionRegEx.Match(expression.Substring(i + subIndex)); + } + } + + if (typeName.EndsWith("?") && staticType != null) + i++; + + if (staticType != null) + { + stack.Push(new ClassOrEnumType() { Type = staticType }); + } + else + { + VariableEvaluationEventArg variableEvaluationEventArg = new VariableEvaluationEventArg(varFuncName, this, genericTypes: genericsTypes, evaluateGenericTypes: GetConcreteTypes); + + EvaluateVariable?.Invoke(this, variableEvaluationEventArg); + + if (variableEvaluationEventArg.HasValue) + { + stack.Push(variableEvaluationEventArg.Value); + } + else + { + throw new ExpressionEvaluatorSyntaxErrorException($"Variable [{varFuncName}] unknown in expression : [{expression}]"); + } + } + } + } + + i--; + } + + if (varFuncMatch.Groups["sign"].Success) + { + object temp = stack.Pop(); + stack.Push(varFuncMatch.Groups["sign"].Value.Equals("+") ? ExpressionOperator.UnaryPlus : ExpressionOperator.UnaryMinus); + stack.Push(temp); + } + + return true; + } + else + { + return false; + } + } + + protected virtual bool EvaluateChar(string expression, Stack stack, ref int i) + { + if (!OptionCharEvaluationActive) + return false; + + string s = expression.Substring(i, 1); + + if (s.Equals("'")) + { + i++; + + if (expression.Substring(i, 1).Equals(@"\")) + { + i++; + char escapedChar = expression[i]; + + if (charEscapedCharDict.ContainsKey(escapedChar)) + { + stack.Push(charEscapedCharDict[escapedChar]); + i++; + } + else + { + throw new ExpressionEvaluatorSyntaxErrorException("Not known escape sequence in literal character"); + } + } + else if (expression.Substring(i, 1).Equals("'")) + { + throw new ExpressionEvaluatorSyntaxErrorException("Empty literal character is not valid"); + } + else + { + stack.Push(expression[i]); + i++; + } + + if (expression.Substring(i, 1).Equals("'")) + { + return true; + } + else + { + throw new ExpressionEvaluatorSyntaxErrorException("Too much characters in the literal character"); + } + } + else + { + return false; + } + } + + protected virtual bool EvaluateOperators(string expression, Stack stack, ref int i) + { + string regexPattern = "^(" + string.Join("|", operatorsDictionary + .Keys + .OrderByDescending(key => key.Length) + .Select(Regex.Escape)) + ")"; + + Match match = Regex.Match(expression.Substring(i), regexPattern, optionCaseSensitiveEvaluationActive ? RegexOptions.None : RegexOptions.IgnoreCase); + + if (match.Success) + { + string op = match.Value; + stack.Push(operatorsDictionary[op]); + i += op.Length - 1; + return true; + } + + return false; + } + + protected virtual bool EvaluateTernaryConditionalOperator(string expression, Stack stack, ref int i) + { + if (expression.Substring(i, 1).Equals("?")) + { + bool condition = (bool)ProcessStack(stack); + + string restOfExpression = expression.Substring(i + 1); + + for (int j = 0; j < restOfExpression.Length; j++) + { + string s2 = restOfExpression.Substring(j, 1); + + Match internalStringMatch = stringBeginningRegex.Match(restOfExpression.Substring(j)); + + if (internalStringMatch.Success) + { + string innerString = internalStringMatch.Value + GetCodeUntilEndOfString(restOfExpression.Substring(j + internalStringMatch.Length), internalStringMatch); + j += innerString.Length - 1; + } + else if (s2.Equals("(")) + { + j++; + GetExpressionsBetweenParenthesesOrOtherImbricableBrackets(restOfExpression, ref j, false); + } + else if (s2.Equals(":")) + { + stack.Clear(); + + stack.Push(condition ? Evaluate(restOfExpression.Substring(0, j)) : Evaluate(restOfExpression.Substring(j + 1))); + + i = expression.Length; + + return true; + } + } + } + + return false; + } + + protected virtual bool EvaluateParenthis(string expression, Stack stack, ref int i) + { + string s = expression.Substring(i, 1); + + if (s.Equals(")")) + throw new Exception($"To much ')' characters are defined in expression : [{expression}] : no corresponding '(' fund."); + + if (s.Equals("(")) + { + i++; + + if (stack.Count > 0 && stack.Peek() is InternalDelegate) + { + List expressionsInParenthis = GetExpressionsBetweenParenthesesOrOtherImbricableBrackets(expression, ref i, true); + + InternalDelegate lambdaDelegate = stack.Pop() as InternalDelegate; + + stack.Push(lambdaDelegate(expressionsInParenthis.ConvertAll(Evaluate).ToArray())); + } + else + { + CorrectStackWithUnaryPlusOrMinusBeforeParenthisIfNecessary(stack); + + List expressionsInParenthis = GetExpressionsBetweenParenthesesOrOtherImbricableBrackets(expression, ref i, false); + + stack.Push(Evaluate(expressionsInParenthis[0])); + } + + return true; + } + + return false; + } + + protected virtual void CorrectStackWithUnaryPlusOrMinusBeforeParenthisIfNecessary(Stack stack) + { + if (stack.Count > 0 && stack.Peek() is ExpressionOperator op && (op == ExpressionOperator.Plus || op == ExpressionOperator.Minus)) + { + stack.Pop(); + + if (stack.Count == 0 || stack.Peek() is ExpressionOperator) + { + stack.Push(op == ExpressionOperator.Plus ? ExpressionOperator.UnaryPlus : ExpressionOperator.UnaryMinus); + } + else + { + stack.Push(op); + } + } + } + + protected virtual bool EvaluateIndexing(string expression, Stack stack, ref int i) + { + if (!OptionIndexingActive) + return false; + + Match indexingBeginningMatch = indexingBeginningRegex.Match(expression.Substring(i)); + + if (indexingBeginningMatch.Success) + { + StringBuilder innerExp = new StringBuilder(); + i += indexingBeginningMatch.Length; + int bracketCount = 1; + for (; i < expression.Length; i++) + { + Match internalStringMatch = stringBeginningRegex.Match(expression.Substring(i)); + + if (internalStringMatch.Success) + { + string innerString = internalStringMatch.Value + GetCodeUntilEndOfString(expression.Substring(i + internalStringMatch.Length), internalStringMatch); + innerExp.Append(innerString); + i += innerString.Length - 1; + } + else + { + string s = expression.Substring(i, 1); + + if (s.Equals("[")) bracketCount++; + + if (s.Equals("]")) + { + bracketCount--; + if (bracketCount == 0) break; + } + innerExp.Append(s); + } + } + + if (bracketCount > 0) + { + string beVerb = bracketCount == 1 ? "is" : "are"; + throw new Exception($"{bracketCount} ']' character {beVerb} missing in expression : [{expression}]"); + } + + dynamic left = stack.Pop(); + + if (left is NullConditionalNullValue) + { + stack.Push(left); + return true; + } + else if (left is BubbleExceptionContainer) + { + stack.Push(left); + return true; + } + + dynamic right = Evaluate(innerExp.ToString()); + ExpressionOperator op = indexingBeginningMatch.Length == 2 ? ExpressionOperator.IndexingWithNullConditional : ExpressionOperator.Indexing; + + if (OptionForceIntegerNumbersEvaluationsAsDoubleByDefault && right is double && Regex.IsMatch(innerExp.ToString(), @"^\d+$")) + right = (int)right; + + Match assignationOrPostFixOperatorMatch = null; + + dynamic valueToPush = null; + + if (OptionIndexingAssignationActive && (assignationOrPostFixOperatorMatch = assignationOrPostFixOperatorRegex.Match(expression.Substring(i + 1))).Success) + { + i += assignationOrPostFixOperatorMatch.Length + 1; + + bool postFixOperator = assignationOrPostFixOperatorMatch.Groups["postfixOperator"].Success; + string exceptionContext = postFixOperator ? "++ or -- operator" : "an assignation"; + + if (stack.Count > 1) + throw new ExpressionEvaluatorSyntaxErrorException($"The left part of {exceptionContext} must be a variable, a property or an indexer."); + + if (op == ExpressionOperator.IndexingWithNullConditional) + throw new ExpressionEvaluatorSyntaxErrorException($"Null conditional is not usable left to {exceptionContext}"); + + if (postFixOperator) + { + if (left is IDictionary dictionaryLeft) + valueToPush = assignationOrPostFixOperatorMatch.Groups["postfixOperator"].Value.Equals("++") ? dictionaryLeft[right]++ : dictionaryLeft[right]--; + else + valueToPush = assignationOrPostFixOperatorMatch.Groups["postfixOperator"].Value.Equals("++") ? left[right]++ : left[right]--; + } + else + { + string rightExpression = expression.Substring(i); + i = expression.Length; + + if (rightExpression.Trim().Equals(string.Empty)) + throw new ExpressionEvaluatorSyntaxErrorException("Right part is missing in assignation"); + + if (assignationOrPostFixOperatorMatch.Groups["assignmentPrefix"].Success) + { + ExpressionOperator prefixOp = operatorsDictionary[assignationOrPostFixOperatorMatch.Groups["assignmentPrefix"].Value]; + + valueToPush = OperatorsEvaluations[0][op](left, right); + + valueToPush = OperatorsEvaluations.ToList().Find(dict => dict.ContainsKey(prefixOp))[prefixOp](valueToPush, Evaluate(rightExpression)); + } + else + { + valueToPush = Evaluate(rightExpression); + } + + if (left is IDictionary dictionaryLeft) + dictionaryLeft[right] = valueToPush; + else + left[right] = valueToPush; + + stack.Clear(); + } + } + else + { + valueToPush = OperatorsEvaluations[0][op](left, right); + } + + stack.Push(valueToPush); + + return true; + } + + return false; + } + + protected virtual bool EvaluateString(string expression, Stack stack, ref int i) + { + if (!OptionStringEvaluationActive) + return false; + + Match stringBeginningMatch = stringBeginningRegex.Match(expression.Substring(i)); + + if (stringBeginningMatch.Success) + { + bool isEscaped = stringBeginningMatch.Groups["escaped"].Success; + bool isInterpolated = stringBeginningMatch.Groups["interpolated"].Success; + + i += stringBeginningMatch.Length; + + Regex stringRegexPattern = new Regex($"^[^{(isEscaped ? "" : @"\\")}{(isInterpolated ? "{}" : "")}\"]*"); + + bool endOfString = false; + + StringBuilder resultString = new StringBuilder(); + + while (!endOfString && i < expression.Length) + { + Match stringMatch = stringRegexPattern.Match(expression.Substring(i, expression.Length - i)); + + resultString.Append(stringMatch.Value); + i += stringMatch.Length; + + if (expression.Substring(i)[0] == '"') + { + endOfString = true; + stack.Push(resultString.ToString()); + } + else if (expression.Substring(i)[0] == '{') + { + i++; + + if (expression.Substring(i)[0] == '{') + { + resultString.Append("{"); + i++; + } + else + { + StringBuilder innerExp = new StringBuilder(); + int bracketCount = 1; + for (; i < expression.Length; i++) + { + if (i + 3 <= expression.Length && expression.Substring(i, 3).Equals("'\"'")) + { + innerExp.Append("'\"'"); + i += 2; + } + else + { + Match internalStringMatch = stringBeginningRegex.Match(expression.Substring(i)); + + if (internalStringMatch.Success) + { + string innerString = internalStringMatch.Value + GetCodeUntilEndOfString(expression.Substring(i + internalStringMatch.Length), internalStringMatch); + innerExp.Append(innerString); + i += innerString.Length - 1; + } + else + { + string s = expression.Substring(i, 1); + + if (s.Equals("{")) bracketCount++; + + if (s.Equals("}")) + { + bracketCount--; + i++; + if (bracketCount == 0) break; + } + innerExp.Append(s); + } + } + } + + if (bracketCount > 0) + { + string beVerb = bracketCount == 1 ? "is" : "are"; + throw new Exception($"{bracketCount} '}}' character {beVerb} missing in expression : [{expression}]"); + } + resultString.Append(Evaluate(innerExp.ToString())); + } + } + else if (expression.Substring(i, expression.Length - i)[0] == '}') + { + i++; + + if (expression.Substring(i, expression.Length - i)[0] == '}') + { + resultString.Append("}"); + i++; + } + else + { + throw new ExpressionEvaluatorSyntaxErrorException("A character '}' must be escaped in a interpolated string."); + } + } + else if (expression.Substring(i, expression.Length - i)[0] == '\\') + { + i++; + + if (stringEscapedCharDict.TryGetValue(expression.Substring(i, expression.Length - i)[0], out string escapedString)) + { + resultString.Append(escapedString); + i++; + } + else + { + throw new ExpressionEvaluatorSyntaxErrorException("There is no corresponding escaped character for \\" + expression.Substring(i, 1)); + } + } + } + + if (!endOfString) + throw new ExpressionEvaluatorSyntaxErrorException("A \" character is missing."); + + return true; + } + + return false; + } + + #endregion + + #region ProcessStack + + protected virtual object ProcessStack(Stack stack) + { + List list = stack + .Select(e => e is ValueTypeNestingTrace valueTypeNestingTrace ? valueTypeNestingTrace.Value : e) + .Select(e => e is SubExpression subExpression ? Evaluate(subExpression.Expression) : e) + .Select(e => e is NullConditionalNullValue ? null : e) + .ToList(); + + OperatorsEvaluations.ToList().ForEach((IDictionary> operatorEvalutationsDict) => + { + for (int i = list.Count - 1; i >= 0; i--) + { + for (int opi = 0; opi < operatorEvalutationsDict.Keys.ToList().Count; opi++) + { + ExpressionOperator eOp = operatorEvalutationsDict.Keys.ToList()[opi]; + + if ((list[i] as ExpressionOperator) == eOp) + { + if (RightOperandOnlyOperatorsEvaluationDictionary.Contains(eOp)) + { + try + { + list[i] = operatorEvalutationsDict[eOp](null, (dynamic)list[i - 1]); + } + catch (Exception ex) + { + var right = (dynamic)list[i - 1]; + if (right is BubbleExceptionContainer) + { + list[i] = right;//Bubble up the causing error + } + else + { + list[i] = new BubbleExceptionContainer() { Exception = ex }; //Transport the processing error + } + } + list.RemoveAt(i - 1); + break; + } + else if (LeftOperandOnlyOperatorsEvaluationDictionary.Contains(eOp)) + { + try + { + list[i] = operatorEvalutationsDict[eOp]((dynamic)list[i + 1], null); + } + catch (Exception ex) + { + var left = (dynamic)list[i + 1]; + if (left is BubbleExceptionContainer) + { + list[i] = left; //Bubble up the causing error + } + else + { + list[i] = new BubbleExceptionContainer() { Exception = ex }; //Transport the processing error + } + } + list.RemoveAt(i + 1); + break; + } + else + { + try + { + list[i] = operatorEvalutationsDict[eOp]((dynamic)list[i + 1], (dynamic)list[i - 1]); + } + catch (Exception ex) + { + var left = (dynamic)list[i + 1]; + var right = (dynamic)list[i - 1]; + if (left is BubbleExceptionContainer) + { + list[i] = left; //Bubble up the causing error + } + else if (right is BubbleExceptionContainer) + { + list[i] = right; //Bubble up the causing error + } + else + { + list[i] = new BubbleExceptionContainer() { Exception = ex }; //Transport the processing error + } + } + list.RemoveAt(i + 1); + list.RemoveAt(i - 1); + i--; + break; + } + } + } + } + }); + + stack.Clear(); + for (int i = 0; i < list.Count; i++) + { + stack.Push(list[i]); + } + + if (stack.Count > 1) + { + foreach (var item in stack) + { + if (item is BubbleExceptionContainer bubbleExceptionContainer) + { + throw bubbleExceptionContainer.Exception; //Throw the first occuring error + } + } + throw new ExpressionEvaluatorSyntaxErrorException("Syntax error. Check that no operator is missing"); + } + else if (evaluationStackCount == 1 && stack.Peek() is BubbleExceptionContainer bubbleExceptionContainer) + { + //We reached the top level of the evaluation. So we want to throw the resulting exception. + throw bubbleExceptionContainer.Exception; + } + + return stack.Pop(); + } + + #endregion + + #region Remove comments + + /// + /// remove all line and blocks comments of the specified C# script. (Manage in strings comment syntax ignore) + /// based on https://stackoverflow.com/questions/3524317/regex-to-strip-line-comments-from-c-sharp/3524689#3524689 + /// + /// The C# code with comments to remove + /// The same C# code without comments + public string RemoveComments(string scriptWithComments) + { + return removeCommentsRegex.Replace(scriptWithComments, + match => + { + if (match.Value.StartsWith("/")) + { + Match newLineCharsMatch = newLineCharsRegex.Match(match.Value); + + if (match.Value.StartsWith("/*") && newLineCharsMatch.Success) + { + return newLineCharsMatch.Value; + } + else + { + return " "; + } + } + else + { + return match.Value; + } + }); + } + + #endregion + + #region Utils methods for parsing and interpretation + + protected delegate bool ParsingMethodDelegate(string expression, Stack stack, ref int i); + + protected delegate dynamic InternalDelegate(params dynamic[] args); + + protected virtual bool GetLambdaExpression(string expression, Stack stack) + { + Match lambdaExpressionMatch = lambdaExpressionRegex.Match(expression); + + if (lambdaExpressionMatch.Success) + { + List argsNames = lambdaArgRegex + .Matches(lambdaExpressionMatch.Groups["args"].Value) + .Cast().ToList() + .ConvertAll(argMatch => argMatch.Value); + + stack.Push(new InternalDelegate((object[] args) => + { + var vars = new Dictionary(variables); + + for (int a = 0; a < argsNames.Count || a < args.Length; a++) + { + vars[argsNames[a]] = args[a]; + } + + var savedVars = variables; + Variables = vars; + + string lambdaBody = lambdaExpressionMatch.Groups["expression"].Value.Trim(); + + object result = null; + + if (inScript && lambdaBody.StartsWith("{") && lambdaBody.EndsWith("}")) + { + result = ScriptEvaluate(lambdaBody.Substring(1, lambdaBody.Length - 2)); + inScript = true; + } + else + { + result = Evaluate(lambdaExpressionMatch.Groups["expression"].Value); + } + + variables = savedVars; + + return result; + })); + + return true; + } + else + { + return false; + } + } + + protected virtual MethodInfo GetRealMethod(ref Type type, ref object obj, string func, BindingFlags flag, List args, string genericsTypes, Type[] inferedGenericsTypes) + { + MethodInfo methodInfo = null; + List modifiedArgs = new List(args); + + if (OptionFluidPrefixingActive + && (func.StartsWith("Fluid", StringComparisonForCasing) + || func.StartsWith("Fluent", StringComparisonForCasing))) + { + methodInfo = GetRealMethod(ref type, ref obj, func.Substring(func.StartsWith("Fluid", StringComparisonForCasing) ? 5 : 6), flag, modifiedArgs, genericsTypes, inferedGenericsTypes); + if (methodInfo != null) + { + if (methodInfo.ReturnType == typeof(void)) + { + obj = new DelegateEncaps(obj, methodInfo); + + methodInfo = typeof(DelegateEncaps).GetMethod("CallFluidMethod"); + + args.Clear(); + args.Add(modifiedArgs.ToArray()); + } + + return methodInfo; + } + } + + if (args.Contains(null)) + { + methodInfo = type.GetMethod(func, flag); + } + else + { + methodInfo = type.GetMethod(func, flag, null, args.ConvertAll(arg => arg.GetType()).ToArray(), null); + } + + if (methodInfo != null) + { + methodInfo = MakeConcreteMethodIfGeneric(methodInfo, genericsTypes, inferedGenericsTypes); + } + else + { + List methodInfos = type.GetMethods(flag) + .Where(m => m.Name.Equals(func, StringComparisonForCasing) && m.GetParameters().Length == modifiedArgs.Count) + .ToList(); + + // For Linq methods that are overloaded and implement possibly lambda arguments + try + { + if (methodInfos.Count > 1 + && type == typeof(Enumerable) + && args.Count == 2 + && args[1] is InternalDelegate internalDelegate + && args[0] is IEnumerable enumerable + && enumerable.GetEnumerator() is IEnumerator enumerator + && enumerator.MoveNext() + && methodInfos.Any(m => m.GetParameters().Any(p => p.ParameterType.Name.StartsWith("Func")))) + { + Type lambdaResultType = internalDelegate.Invoke(enumerator.Current).GetType(); + + methodInfo = methodInfos.Find(m => + { + ParameterInfo[] parameterInfos = m.GetParameters(); + + return parameterInfos.Length == 2 + && parameterInfos[1].ParameterType.Name.StartsWith("Func") + && parameterInfos[1].ParameterType.GenericTypeArguments is Type[] genericTypesArgs + && genericTypesArgs.Length == 2 + && genericTypesArgs[1] == lambdaResultType; + }); + + if (methodInfo != null) + { + methodInfo = TryToCastMethodParametersToMakeItCallable(methodInfo, modifiedArgs, genericsTypes, inferedGenericsTypes); + } + } + } + catch { } + + for (int m = 0; m < methodInfos.Count && methodInfo == null; m++) + { + modifiedArgs = new List(args); + + methodInfo = TryToCastMethodParametersToMakeItCallable(methodInfos[m], modifiedArgs, genericsTypes, inferedGenericsTypes); + } + + if (methodInfo != null) + { + args.Clear(); + args.AddRange(modifiedArgs); + } + } + + return methodInfo; + } + + protected virtual MethodInfo TryToCastMethodParametersToMakeItCallable(MethodInfo methodInfoToCast, List modifiedArgs, string genericsTypes, Type[] inferedGenericsTypes) + { + MethodInfo methodInfo = null; + + methodInfoToCast = MakeConcreteMethodIfGeneric(methodInfoToCast, genericsTypes, inferedGenericsTypes); + + bool parametersCastOK = true; + + for (int a = 0; a < modifiedArgs.Count; a++) + { + Type parameterType = methodInfoToCast.GetParameters()[a].ParameterType; + string paramTypeName = parameterType.Name; + + if (paramTypeName.StartsWith("Predicate") + && modifiedArgs[a] is InternalDelegate) + { + InternalDelegate led = modifiedArgs[a] as InternalDelegate; + modifiedArgs[a] = new Predicate(o => (bool)(led(new object[] { o }))); + } + else if (paramTypeName.StartsWith("Func") + && modifiedArgs[a] is InternalDelegate) + { + InternalDelegate led = modifiedArgs[a] as InternalDelegate; + DelegateEncaps de = new DelegateEncaps(led); + MethodInfo encapsMethod = de.GetType() + .GetMethod($"Func{parameterType.GetGenericArguments().Length - 1}") + .MakeGenericMethod(parameterType.GetGenericArguments()); + modifiedArgs[a] = Delegate.CreateDelegate(parameterType, de, encapsMethod); + } + else if (paramTypeName.StartsWith("Action") + && modifiedArgs[a] is InternalDelegate) + { + InternalDelegate led = modifiedArgs[a] as InternalDelegate; + DelegateEncaps de = new DelegateEncaps(led); + MethodInfo encapsMethod = de.GetType() + .GetMethod($"Action{parameterType.GetGenericArguments().Length}") + .MakeGenericMethod(parameterType.GetGenericArguments()); + modifiedArgs[a] = Delegate.CreateDelegate(parameterType, de, encapsMethod); + } + else if (paramTypeName.StartsWith("Converter") + && modifiedArgs[a] is InternalDelegate) { - stack.Push(funcResult); + InternalDelegate led = modifiedArgs[a] as InternalDelegate; + modifiedArgs[a] = new Converter(o => led(new object[] { o })); } else { - FunctionEvaluationEventArg functionEvaluationEventArg = new FunctionEvaluationEventArg(varFuncMatch.Groups["name"].Value, Evaluate, funcArgs); - - EvaluateFunction?.Invoke(this, functionEvaluationEventArg); - - if (functionEvaluationEventArg.FunctionReturnedValue) + try { - stack.Push(functionEvaluationEventArg.Value); + if (!methodInfoToCast.GetParameters()[a].ParameterType.IsAssignableFrom(modifiedArgs[a].GetType())) + { + modifiedArgs[a] = Convert.ChangeType(modifiedArgs[a], methodInfoToCast.GetParameters()[a].ParameterType); + } } - else + catch { - throw new ExpressionEvaluatorSyntaxErrorException($"Function [{varFuncMatch.Groups["name"].Value}] unknown in expression : [{expr}]"); + parametersCastOK = false; } } } - else - { - string completeVar = varFuncMatch.Groups["name"].Value; - string var = completeVar.ToLower(); - object varValueToPush = null; - object cusVarValueToPush = null; - if (defaultVariables.TryGetValue(var, out varValueToPush)) - { - stack.Push(varValueToPush); - } - else if (Variables.TryGetValue(var, out cusVarValueToPush)) - { - stack.Push(cusVarValueToPush); - } - else - { - VariableEvaluationEventArg variableEvaluationEventArg = new VariableEvaluationEventArg(completeVar); + if (parametersCastOK) + methodInfo = methodInfoToCast; - EvaluateVariable?.Invoke(this, variableEvaluationEventArg); + return methodInfo; + } - if (variableEvaluationEventArg.HasValue) + protected virtual MethodInfo MakeConcreteMethodIfGeneric(MethodInfo methodInfo, string genericsTypes, Type[] inferedGenericsTypes) + { + if (methodInfo.IsGenericMethod) + { + if (genericsTypes.Equals(string.Empty)) + { + if (inferedGenericsTypes != null && inferedGenericsTypes.Length == methodInfo.GetGenericArguments().Length) { - stack.Push(variableEvaluationEventArg.Value); + return methodInfo.MakeGenericMethod(inferedGenericsTypes); } else { - if (varFuncMatch.Groups["inObject"].Success) - { - if (stack.Count == 0 || stack.Peek() is ExpressionOperator) - throw new ExpressionEvaluatorSyntaxErrorException($"[{varFuncMatch.Value}] must follow an object."); - - object obj = stack.Pop(); - Type objType = null; - - try - { - if (varFuncMatch.Groups["nullConditional"].Success && obj == null) - { - stack.Push(null); - } - else - { - BindingFlags flag = DetermineInstanceOrStatic(ref objType, ref obj); - - object varValue = objType?.GetProperty(var, flag)?.GetValue(obj); - if (varValue == null) - varValue = objType.GetField(var, flag).GetValue(obj); - - stack.Push(varValue); - } - } - catch (Exception ex) - { - throw new ExpressionEvaluatorSyntaxErrorException($"[{objType.ToString()}] object has no public Property or Member named \"{var}\".", ex); - } - } - else - { - string typeName = $"{completeVar}{((i < expr.Length && expr.Substring(i)[0] == '?') ? "?" : "") }"; - Type staticType = GetTypeByFriendlyName(typeName); - - if (typeName.EndsWith("?") && staticType != null) - i++; - - if (staticType != null) - { - stack.Push(staticType); - } - else - { - throw new ExpressionEvaluatorSyntaxErrorException($"Variable [{var}] unknown in expression : [{expr}]"); - } - } + return methodInfo.MakeGenericMethod(Enumerable.Repeat(typeof(object), methodInfo.GetGenericArguments().Length).ToArray()); } } - - i--; + else + { + return methodInfo.MakeGenericMethod(GetConcreteTypes(genericsTypes)); + } } - return true; - } - else - { - return false; + return methodInfo; } - } - private bool EvaluateTwoCharsOperators(string expr, Stack stack, ref int i) - { - if (i < expr.Length - 1) + protected virtual Type[] GetConcreteTypes(string genericsTypes) { - String op = expr.Substring(i, 2); - if (operatorsDictionary.ContainsKey(op)) - { - stack.Push(operatorsDictionary[op]); - i++; - return true; - } + return genericsDecodeRegex + .Matches(genericsEndOnlyOneTrim.Replace(genericsTypes.TrimStart(' ', '<'), "")) + .Cast() + .Select(match => GetTypeByFriendlyName(match.Groups["name"].Value, match.Groups["isgeneric"].Value, true)) + .ToArray(); } - return false; - } - - private bool EvaluateParenthis(string expr, string s, Stack stack, ref int i) - { - if (s.Equals(")")) - throw new Exception($"To much ')' characters are defined in expression : [{expr}] : no corresponding '(' fund."); - - if (s.Equals("(")) + protected virtual BindingFlags DetermineInstanceOrStatic(ref Type objType, ref object obj, ref ValueTypeNestingTrace valueTypeNestingTrace) { - i++; + valueTypeNestingTrace = obj as ValueTypeNestingTrace; - if (stack.Count > 0 && stack.Peek() is lambdaExpressionDelegate) + if (valueTypeNestingTrace != null) { - List expressionsInParenthis = GetExpressionsBetweenParenthis(expr, ref i, true); - - lambdaExpressionDelegate lambdaDelegate = stack.Pop() as lambdaExpressionDelegate; + obj = valueTypeNestingTrace.Value; + } - stack.Push(lambdaDelegate(expressionsInParenthis.ConvertAll(arg => Evaluate(arg)).ToArray())); + if (obj is ClassOrEnumType classOrTypeName) + { + objType = classOrTypeName.Type; + obj = null; + return StaticBindingFlag; } else { - List expressionsInParenthis = GetExpressionsBetweenParenthis(expr, ref i, false); - - stack.Push(Evaluate(expressionsInParenthis[0])); + objType = obj.GetType(); + return InstanceBindingFlag; } - - return true; } - return false; - } - - private bool EvaluateIndexing(string expr, string s, Stack stack, ref int i) - { - Match indexingBeginningMatch = indexingBeginningRegex.Match(expr.Substring(i)); - - if (indexingBeginningMatch.Success) + protected virtual string GetScriptBetweenCurlyBrackets(string parentScript, ref int index) { - string innerExp = ""; - i += indexingBeginningMatch.Length; + string s; + string currentScript = string.Empty; int bracketCount = 1; - for (; i < expr.Length; i++) + for (; index < parentScript.Length; index++) { - Match internalStringMatch = stringBeginningRegex.Match(expr.Substring(i)); + Match internalStringMatch = stringBeginningRegex.Match(parentScript.Substring(index)); + Match internalCharMatch = internalCharRegex.Match(parentScript.Substring(index)); if (internalStringMatch.Success) { - string innerString = internalStringMatch.Value + GetCodeUntilEndOfString(expr.Substring(i + internalStringMatch.Length), internalStringMatch); - innerExp += innerString; - i += innerString.Length - 1; + string innerString = internalStringMatch.Value + GetCodeUntilEndOfString(parentScript.Substring(index + internalStringMatch.Length), internalStringMatch); + currentScript += innerString; + index += innerString.Length - 1; + } + else if (internalCharMatch.Success) + { + currentScript += internalCharMatch.Value; + index += internalCharMatch.Length - 1; } else { - s = expr.Substring(i, 1); + s = parentScript.Substring(index, 1); - if (s.Equals("[")) bracketCount++; + if (s.Equals("{")) bracketCount++; - if (s.Equals("]")) + if (s.Equals("}")) { bracketCount--; - if (bracketCount == 0) break; + if (bracketCount == 0) + break; } - innerExp += s; + + currentScript += s; } } if (bracketCount > 0) { string beVerb = bracketCount == 1 ? "is" : "are"; - throw new Exception($"{bracketCount} ']' character {beVerb} missing in expression : [{expr}]"); + throw new Exception($"{bracketCount} '" + "}" + $"' character {beVerb} missing in script at : [{index}]"); } - stack.Push(indexingBeginningMatch.Length == 2 ? ExpressionOperator.IndexingWithNullConditional : ExpressionOperator.Indexing); - stack.Push(Evaluate(innerExp)); - - dynamic right = stack.Pop(); - ExpressionOperator op = (ExpressionOperator)stack.Pop(); - dynamic left = stack.Pop(); - stack.Push(operatorsEvaluations[0][op](left, right)); - - return true; + return currentScript; } - return false; - } - - private bool EvaluateString(string expr, string s, string restOfExpression, Stack stack, ref int i) - { - Match stringBeginningMatch = stringBeginningRegex.Match(restOfExpression); - - if (stringBeginningMatch.Success) + protected virtual List GetExpressionsBetweenParenthesesOrOtherImbricableBrackets(string expression, ref int i, bool checkSeparator, string separator = ",", string startChar = "(", string endChar = ")") { - bool isEscaped = stringBeginningMatch.Groups["escaped"].Success; - bool isInterpolated = stringBeginningMatch.Groups["interpolated"].Success; - - i += stringBeginningMatch.Length; - - Regex stringRegexPattern = new Regex($"^[^{(isEscaped ? "" : @"\\")}{(isInterpolated ? "{}" : "")}\"]*"); - - bool endOfString = false; + List expressionsList = new List(); - string resultString = string.Empty; - - while (!endOfString && i < expr.Length) + string s; + string currentExpression = string.Empty; + int bracketCount = 1; + for (; i < expression.Length; i++) { - Match stringMatch = stringRegexPattern.Match(expr.Substring(i, expr.Length - i)); + string subExpr = expression.Substring(i); + Match internalStringMatch = stringBeginningRegex.Match(subExpr); + Match internalCharMatch = internalCharRegex.Match(subExpr); - resultString += stringMatch.Value; - i += stringMatch.Length; - - if (expr.Substring(i)[0] == '"') + if (OptionStringEvaluationActive && internalStringMatch.Success) + { + string innerString = internalStringMatch.Value + GetCodeUntilEndOfString(expression.Substring(i + internalStringMatch.Length), internalStringMatch); + currentExpression += innerString; + i += innerString.Length - 1; + } + else if (OptionCharEvaluationActive && internalCharMatch.Success) { - endOfString = true; - stack.Push(resultString); + currentExpression += internalCharMatch.Value; + i += internalCharMatch.Length - 1; } - else if (expr.Substring(i)[0] == '{') + else { - i++; + s = expression.Substring(i, 1); - if (expr.Substring(i)[0] == '{') + if (s.Equals(startChar)) + { + bracketCount++; + } + else if (s.Equals("(")) { - resultString += @"{"; i++; + currentExpression += "(" + GetExpressionsBetweenParenthesesOrOtherImbricableBrackets(expression, ref i, false, ",", "(", ")").SingleOrDefault() + ")"; + continue; } - else + else if (s.Equals("{")) { - string innerExp = ""; - int bracketCount = 1; - for (; i < expr.Length; i++) - { - Match internalStringMatch = stringBeginningRegex.Match(expr.Substring(i)); - - if (internalStringMatch.Success) - { - string innerString = internalStringMatch.Value + GetCodeUntilEndOfString(expr.Substring(i + internalStringMatch.Length), internalStringMatch); - innerExp += innerString; - i += innerString.Length - 1; - } - else - { - - s = expr.Substring(i, 1); - - if (s.Equals("{")) bracketCount++; - - if (s.Equals("}")) - { - bracketCount--; - i++; - if (bracketCount == 0) break; - } - innerExp += s; - } - } - - if (bracketCount > 0) - { - string beVerb = bracketCount == 1 ? "is" : "are"; - throw new Exception($"{bracketCount} '}}' character {beVerb} missing in expression : [{expr}]"); - } - resultString += Evaluate(innerExp).ToString(); + i++; + currentExpression += "{" + GetExpressionsBetweenParenthesesOrOtherImbricableBrackets(expression, ref i, false, ",", "{", "}").SingleOrDefault() + "}"; + continue; } - } - else if (expr.Substring(i, expr.Length - i)[0] == '}') - { - i++; - - if (expr.Substring(i, expr.Length - i)[0] == '}') + else if (s.Equals("[")) { - resultString += @"}"; i++; + currentExpression += "[" + GetExpressionsBetweenParenthesesOrOtherImbricableBrackets(expression, ref i, false, ",", "[", "]").SingleOrDefault() + "]"; + continue; } - else + + if (s.Equals(endChar)) { - throw new ExpressionEvaluatorSyntaxErrorException("A character '}' must be escaped in a interpolated string."); + bracketCount--; + if (bracketCount == 0) + { + if (!currentExpression.Trim().Equals(string.Empty)) + expressionsList.Add(currentExpression); + break; + } } - } - else if (expr.Substring(i, expr.Length - i)[0] == '\\') - { - i++; - string escapedString = null; - if (stringEscapedCharDict.TryGetValue(expr.Substring(i, expr.Length - i)[0], out escapedString)) + if (checkSeparator && s.Equals(separator) && bracketCount == 1) { - resultString += escapedString; - i++; + expressionsList.Add(currentExpression); + currentExpression = string.Empty; } else { - throw new ExpressionEvaluatorSyntaxErrorException("There is no corresponding escaped character for \\" + expr.Substring(i, 1)); + currentExpression += s; } } } - if (!endOfString) - throw new ExpressionEvaluatorSyntaxErrorException("A \" character is missing."); + if (bracketCount > 0) + { + string beVerb = bracketCount == 1 ? "is" : "are"; + throw new Exception($"{bracketCount} '{endChar}' character {beVerb} missing in expression : [{expression}]"); + } - return true; + return expressionsList; } - return false; - } + protected virtual bool DefaultFunctions(string name, List args, out object result) + { + bool functionExists = true; - private object ProcessStack(Stack stack) - { - List list = stack.ToList(); + if (simpleDoubleMathFuncsDictionary.TryGetValue(name, out Func func)) + { + result = func(Convert.ToDouble(Evaluate(args[0]))); + } + else if (doubleDoubleMathFuncsDictionary.TryGetValue(name, out Func func2)) + { + result = func2(Convert.ToDouble(Evaluate(args[0])), Convert.ToDouble(Evaluate(args[1]))); + } + else if (complexStandardFuncsDictionary.TryGetValue(name, out Func, object> complexFunc)) + { + result = complexFunc(this, args); + } + else if (OptionEvaluateFunctionActive && name.Equals("Evaluate", StringComparisonForCasing)) + { + result = Evaluate((string)Evaluate(args[0])); + } + else if (OptionScriptEvaluateFunctionActive && name.Equals("ScriptEvaluate", StringComparisonForCasing)) + { + bool oldInScript = inScript; + result = ScriptEvaluate((string)Evaluate(args[0])); + inScript = oldInScript; + } + else + { + result = null; + functionExists = false; + } + + return functionExists; + } - operatorsEvaluations.ForEach(delegate (Dictionary> operatorEvalutationsDict) + protected virtual Type GetTypeByFriendlyName(string typeName, string genericTypes = "", bool throwExceptionIfNotFound = false) { - for (int i = list.Count - 1; i >= 0; i--) + Type result = null; + string formatedGenericTypes = string.Empty; + bool isCached = false; + try { - for (int opi = 0; opi < operatorEvalutationsDict.Keys.ToList().Count; opi++) + typeName = typeName.Replace(" ", "").Replace("\t", "").Replace("\r", "").Replace("\n", ""); + genericTypes = genericTypes.Replace(" ", "").Replace("\t", "").Replace("\r", "").Replace("\n", ""); + + if (CacheTypesResolutions && (TypesResolutionCaching?.ContainsKey(typeName + genericTypes) ?? false)) { - ExpressionOperator eOp = operatorEvalutationsDict.Keys.ToList()[opi]; + result = TypesResolutionCaching[typeName + genericTypes]; + isCached = true; + } - if ((list[i] as ExpressionOperator?) == eOp) + if (result == null) + { + if (!genericTypes.Equals(string.Empty)) { - if (rightOperandOnlyOperatorsEvaluationDictionary.ContainsKey(eOp)) - { - list[i] = operatorEvalutationsDict[eOp](null, (dynamic)list[i - 1]); - list.RemoveAt(i - 1); - break; - } - else if (leftOperandOnlyOperatorsEvaluationDictionary.ContainsKey(eOp)) - { - list[i] = operatorEvalutationsDict[eOp]((dynamic)list[i + 1], null); - list.RemoveAt(i + 1); - break; - } - else + Type[] types = GetConcreteTypes(genericTypes); + formatedGenericTypes = $"`{types.Length}[{ string.Join(", ", types.Select(type => "[" + type.AssemblyQualifiedName + "]"))}]"; + } + + result = Type.GetType(typeName + formatedGenericTypes, false, !OptionCaseSensitiveEvaluationActive); + } + + if (result == null) + { + typeName = Regex.Replace(typeName, primaryTypesRegexPattern, + (Match match) => primaryTypesDict[OptionCaseSensitiveEvaluationActive ? match.Value : match.Value.ToLower()].ToString(), optionCaseSensitiveEvaluationActive ? RegexOptions.None : RegexOptions.IgnoreCase); + + result = Type.GetType(typeName, false, !OptionCaseSensitiveEvaluationActive); + } + + if (result == null) + { + result = Types.ToList().Find(type => type.Name.Equals(typeName, StringComparisonForCasing) || type.FullName.StartsWith(typeName + ",")); + } + + for (int a = 0; a < Assemblies.Count && result == null; a++) + { + if (typeName.Contains(".")) + { + result = Type.GetType($"{typeName}{formatedGenericTypes},{Assemblies[a].FullName}", false, !OptionCaseSensitiveEvaluationActive); + } + else + { + for (int i = 0; i < Namespaces.Count && result == null; i++) { - list[i] = operatorEvalutationsDict[eOp]((dynamic)list[i + 1], (dynamic)list[i - 1]); - list.RemoveAt(i + 1); - list.RemoveAt(i - 1); - i -= 1; - break; + result = Type.GetType($"{Namespaces[i]}.{typeName}{formatedGenericTypes},{Assemblies[a].FullName}", false, !OptionCaseSensitiveEvaluationActive); } } } } - }); + catch (ExpressionEvaluatorSyntaxErrorException) + { + throw; + } + catch { } - stack.Clear(); - for (int i = 0; i < list.Count; i++) - { - stack.Push(list[i]); - } + if (result != null && TypesToBlock.Contains(result)) + result = null; - if (stack.Count > 1) - throw new ExpressionEvaluatorSyntaxErrorException("Syntax error. Check that no operator is missing"); + if (result == null && throwExceptionIfNotFound) + throw new ExpressionEvaluatorSyntaxErrorException($"Type or class {typeName}{genericTypes} is unknown"); - return stack.Pop(); - } + if (CacheTypesResolutions && (result != null) && !isCached) + TypesResolutionCaching[typeName + genericTypes] = result; - private delegate dynamic lambdaExpressionDelegate(params dynamic[] args); - private bool GetLambdaExpression(string expr, Stack stack) - { - Match lambdaExpressionMatch = lambdaExpressionRegex.Match(expr); + return result; + } - if (lambdaExpressionMatch.Success) + protected static object ChangeType(object value, Type conversionType) { - List argsNames = lambdaArgRegex - .Matches(lambdaExpressionMatch.Groups["args"].Value) - .Cast().ToList() - .ConvertAll(argMatch => argMatch.Value); - - stack.Push(new lambdaExpressionDelegate(delegate (object[] args) + if (conversionType == null) { - Dictionary vars = new Dictionary(Variables); - - for (int a = 0; a < argsNames.Count || a < args.Length; a++) + throw new ArgumentNullException(nameof(conversionType)); + } + if (conversionType.IsGenericType && conversionType.GetGenericTypeDefinition().Equals(typeof(Nullable<>))) + { + if (value == null) { - vars[argsNames[a]] = args[a]; + return null; } + NullableConverter nullableConverter = new NullableConverter(conversionType); + conversionType = nullableConverter.UnderlyingType; + } + return Convert.ChangeType(value, conversionType); + } - ExpressionEvaluator expressionEvaluator = new ExpressionEvaluator(vars); + protected virtual string GetCodeUntilEndOfString(string subExpr, Match stringBeginningMatch) + { + StringBuilder stringBuilder = new StringBuilder(); - return expressionEvaluator.Evaluate(lambdaExpressionMatch.Groups["expression"].Value); - })); + GetCodeUntilEndOfString(subExpr, stringBeginningMatch, ref stringBuilder); - return true; + return stringBuilder.ToString(); } - else + + protected virtual void GetCodeUntilEndOfString(string subExpr, Match stringBeginningMatch, ref StringBuilder stringBuilder) { - return false; - } - } + Match codeUntilEndOfStringMatch = stringBeginningMatch.Value.Contains("$") ? + (stringBeginningMatch.Value.Contains("@") ? endOfStringWithDollarWithAt.Match(subExpr) : endOfStringWithDollar.Match(subExpr)) : + (stringBeginningMatch.Value.Contains("@") ? endOfStringWithoutDollarWithAt.Match(subExpr) : endOfStringWithoutDollar.Match(subExpr)); - private MethodInfo GetRealMethod(ref Type type, ref object obj, string func, BindingFlags flag, List args) - { - MethodInfo methodInfo = null; - List modifiedArgs = new List(args); + if (codeUntilEndOfStringMatch.Success) + { + if (codeUntilEndOfStringMatch.Value.EndsWith("\"")) + { + stringBuilder.Append(codeUntilEndOfStringMatch.Value); + } + else if (codeUntilEndOfStringMatch.Value.EndsWith("{") && codeUntilEndOfStringMatch.Length < subExpr.Length) + { + if (subExpr[codeUntilEndOfStringMatch.Length] == '{') + { + stringBuilder.Append(codeUntilEndOfStringMatch.Value); + stringBuilder.Append("{"); + GetCodeUntilEndOfString(subExpr.Substring(codeUntilEndOfStringMatch.Length + 1), stringBeginningMatch, ref stringBuilder); + } + else + { + string interpolation = GetCodeUntilEndOfStringInterpolation(subExpr.Substring(codeUntilEndOfStringMatch.Length)); + stringBuilder.Append(codeUntilEndOfStringMatch.Value); + stringBuilder.Append(interpolation); + GetCodeUntilEndOfString(subExpr.Substring(codeUntilEndOfStringMatch.Length + interpolation.Length), stringBeginningMatch, ref stringBuilder); + } + } + else + { + stringBuilder.Append(subExpr); + } + } + else + { + stringBuilder.Append(subExpr); + } + } - if (func.StartsWith("fluid") || func.StartsWith("fluent")) + protected virtual string GetCodeUntilEndOfStringInterpolation(string subExpr) { - methodInfo = GetRealMethod(ref type, ref obj, func.Substring(func.StartsWith("fluid") ? 5 : 6), flag, modifiedArgs); - if (methodInfo != null) + Match endOfStringInterpolationMatch = endOfStringInterpolationRegex.Match(subExpr); + string result = subExpr; + + if (endOfStringInterpolationMatch.Success) { - if (methodInfo.ReturnType == typeof(void)) + if (endOfStringInterpolationMatch.Value.EndsWith("}")) + { + result = endOfStringInterpolationMatch.Value; + } + else { - obj = new DelegateEncaps(obj, methodInfo); + Match stringBeginningForEndBlockMatch = stringBeginningForEndBlockRegex.Match(endOfStringInterpolationMatch.Value); - methodInfo = typeof(DelegateEncaps).GetMethod("CallFluidMethod"); + string subString = GetCodeUntilEndOfString(subExpr.Substring(endOfStringInterpolationMatch.Length), stringBeginningForEndBlockMatch); - args.Clear(); - args.Add(modifiedArgs.ToArray()); + result = endOfStringInterpolationMatch.Value + subString + + GetCodeUntilEndOfStringInterpolation(subExpr.Substring(endOfStringInterpolationMatch.Length + subString.Length)); } - - return methodInfo; } - } - if (args.Contains(null)) - { - methodInfo = type.GetMethod(func, flag); - } - else - { - methodInfo = type.GetMethod(func, flag, null, args.ConvertAll(arg => arg.GetType()).ToArray(), null); + return result; } - if (methodInfo != null) - { - methodInfo = MakeConcreteMethodIfGeneric(methodInfo); - } - else - { - List methodInfos = type.GetMethods(flag) - .Where(m => - { - return m.Name.ToLower().Equals(func) && m.GetParameters().Length == modifiedArgs.Count; - }) - .ToList(); + #endregion - for (int m = 0; m < methodInfos.Count && methodInfo == null; m++) - { - methodInfos[m] = MakeConcreteMethodIfGeneric(methodInfos[m]); + #region Utils protected sub classes for parsing and interpretation - bool parametersCastOK = true; + protected class ValueTypeNestingTrace + { + public object Container { get; set; } - modifiedArgs = new List(args); + public MemberInfo Member { get; set; } - for (int a = 0; a < modifiedArgs.Count; a++) - { - Type parameterType = methodInfos[m].GetParameters()[a].ParameterType; - string paramTypeName = parameterType.Name; + public object Value { get; set; } - if (paramTypeName.StartsWith("Predicate") - && modifiedArgs[a] is lambdaExpressionDelegate) - { - lambdaExpressionDelegate led = modifiedArgs[a] as lambdaExpressionDelegate; - modifiedArgs[a] = new Predicate(o => (bool)(led(new object[] { o }))); - } - else if (paramTypeName.StartsWith("Func") - && modifiedArgs[a] is lambdaExpressionDelegate) - { - lambdaExpressionDelegate led = modifiedArgs[a] as lambdaExpressionDelegate; - DelegateEncaps de = new DelegateEncaps(led); - MethodInfo encapsMethod = de.GetType() - .GetMethod($"Func{parameterType.GetGenericArguments().Length - 1}") - .MakeGenericMethod(parameterType.GetGenericArguments()); - modifiedArgs[a] = Delegate.CreateDelegate(parameterType, de, encapsMethod); - } - else if (paramTypeName.StartsWith("Converter") - && modifiedArgs[a] is lambdaExpressionDelegate) - { - lambdaExpressionDelegate led = modifiedArgs[a] as lambdaExpressionDelegate; - modifiedArgs[a] = new Converter(o => (led(new object[] { o }))); - } - else - { - try - { - if (!methodInfos[m].GetParameters()[a].ParameterType.IsAssignableFrom(modifiedArgs[a].GetType())) - { - modifiedArgs[a] = Convert.ChangeType(modifiedArgs[a], methodInfos[m].GetParameters()[a].ParameterType); - } - } - catch - { - parametersCastOK = false; - } - } + public void AssignValue() + { + if (Container is ValueTypeNestingTrace valueTypeNestingTrace) + { + ((dynamic)Member).SetValue(valueTypeNestingTrace.Value, Value); + valueTypeNestingTrace.AssignValue(); + } + else + { + ((dynamic)Member).SetValue(Container, Value); } + } + } + + protected class NullConditionalNullValue + { } + + protected class DelegateEncaps + { + private readonly InternalDelegate lambda; + + private readonly MethodInfo methodInfo; + private readonly object target; - if (parametersCastOK) - methodInfo = methodInfos[m]; + public DelegateEncaps(InternalDelegate lambda) + { + this.lambda = lambda; } - if (methodInfo != null) + public DelegateEncaps(object target, MethodInfo methodInfo) { - args.Clear(); - args.AddRange(modifiedArgs); + this.target = target; + this.methodInfo = methodInfo; } - } - return methodInfo; - } + public object CallFluidMethod(params object[] args) + { + methodInfo.Invoke(target, args); + return target; + } - private MethodInfo MakeConcreteMethodIfGeneric(MethodInfo methodInfo) - { - if (methodInfo.IsGenericMethod) - { - return methodInfo.MakeGenericMethod(Enumerable.Repeat(typeof(object), methodInfo.GetGenericArguments().Count()).ToArray()); - } + public void Action0() + { + lambda(); + } - return methodInfo; - } + public void Action1(T1 arg1) + { + lambda(arg1); + } - private BindingFlags DetermineInstanceOrStatic(ref Type objType, ref object obj) - { - if (obj is Type) - { - objType = obj as Type; - obj = null; - return staticBindingFlag; - } - else - { - objType = obj.GetType(); - return instanceBindingFlag; - } - } + public void Action2(T1 arg1, T2 arg2) + { + lambda(arg1, arg2); + } - private List GetExpressionsBetweenParenthis(string expr, ref int i, bool checkComas) - { - List expressionsList = new List(); + public void Action3(T1 arg1, T2 arg2, T3 arg3) + { + lambda(arg1, arg2, arg3); + } - string s; - string currentExpression = string.Empty; - int bracketCount = 1; - for (; i < expr.Length; i++) - { - Match internalStringMatch = stringBeginningRegex.Match(expr.Substring(i)); + public void Action4(T1 arg1, T2 arg2, T3 arg3, T4 arg4) + { + lambda(arg1, arg2, arg3, arg4); + } - if (internalStringMatch.Success) + public void Action5(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5) { - string innerString = internalStringMatch.Value + GetCodeUntilEndOfString(expr.Substring(i + internalStringMatch.Length), internalStringMatch); - currentExpression += innerString; - i += innerString.Length - 1; + lambda(arg1, arg2, arg3, arg4, arg5); } - else + + public void Action6(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6) { - s = expr.Substring(i, 1); + lambda(arg1, arg2, arg3, arg4, arg5, arg6); + } - if (s.Equals("(")) bracketCount++; + public void Action7(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7) + { + lambda(arg1, arg2, arg3, arg4, arg5, arg6, arg7); + } - if (s.Equals(")")) - { - bracketCount--; - if (bracketCount == 0) - { - if (!currentExpression.Trim().Equals(string.Empty)) - expressionsList.Add(currentExpression); - break; - } - } + public void Action8(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8) + { + lambda(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8); + } - if (checkComas && s.Equals(",") && bracketCount == 1) - { - expressionsList.Add(currentExpression); - currentExpression = string.Empty; - } - else - currentExpression += s; + public void Action9(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9) + { + lambda(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9); } - } - if (bracketCount > 0) - { - string beVerb = bracketCount == 1 ? "is" : "are"; - throw new Exception($"{bracketCount} ')' character {beVerb} missing in expression : [{expr}]"); - } + public void Action10(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10) + { + lambda(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10); + } - return expressionsList; - } + public void Action11(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11) + { + lambda(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11); + } - private bool DefaultFunctions(string name, List args, out object result) - { - bool functionExists = true; + public void Action12(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11, T12 arg12) + { + lambda(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12); + } - Func func = null; - Func func2 = null; - Func, object> complexFunc = null; - if (simpleDoubleMathFuncsDictionary.TryGetValue(name, out func)) - { - result = func(Convert.ToDouble(Evaluate(args[0]))); - } - else if (doubleDoubleMathFuncsDictionary.TryGetValue(name, out func2)) - { - result = func2(Convert.ToDouble(Evaluate(args[0])), Convert.ToDouble(Evaluate(args[1]))); - } - else if (complexStandardFuncsDictionary.TryGetValue(name, out complexFunc)) - { - result = complexFunc(this, args); - } - else if (IsEvaluateFunctionActivated && name.Equals("evaluate")) - { - result = Evaluate((string)Evaluate(args[0])); - } - else - { - result = null; - functionExists = false; - } + public void Action13(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11, T12 arg12, T13 arg13) + { + lambda(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13); + } - return functionExists; - } + public void Action14(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11, T12 arg12, T13 arg13, T14 arg14) + { + lambda(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14); + } - private Type GetTypeByFriendlyName(string typeName) - { - Type result = null; - try - { - result = Type.GetType(typeName, false, true); + public void Action15(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11, T12 arg12, T13 arg13, T14 arg14, T15 arg15) + { + lambda(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15); + } - if (result == null) + public void Action16(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11, T12 arg12, T13 arg13, T14 arg14, T15 arg15, T16 arg16) { - typeName = primaryTypesRegex.Replace(typeName, delegate (Match match) - { - return PrimaryTypesDict[match.Value].ToString(); - }); + lambda(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16); + } - result = Type.GetType(typeName, false, true); + public TResult Func0() + { + return (TResult)lambda(); } - for (int a = 0; a < ReferencedAssemblies.Count && result == null; a++) + public TResult Func1(T arg) { - for (int i = 0; i < Namespaces.Count && result == null; i++) - { - result = Type.GetType($"{Namespaces[i]}.{typeName},{ReferencedAssemblies[a].FullName}", false, true); - } + return (TResult)lambda(arg); } - } - catch { } - return result; - } + public TResult Func2(T1 arg1, T2 arg2) + { + return (TResult)lambda(arg1, arg2); + } - private static object ChangeType(object value, Type conversionType) - { - if (conversionType == null) - { - throw new ArgumentNullException("conversionType"); - } - if (conversionType.IsGenericType && conversionType.GetGenericTypeDefinition().Equals(typeof(Nullable<>))) - { - if (value == null) + public TResult Func3(T1 arg1, T2 arg2, T3 arg3) { - return null; + return (TResult)lambda(arg1, arg2, arg3); } - NullableConverter nullableConverter = new NullableConverter(conversionType); - conversionType = nullableConverter.UnderlyingType; - } - return Convert.ChangeType(value, conversionType); - } - private string GetCodeUntilEndOfString(string subExpr, Match stringBeginningMatch) - { - Match codeUntilEndOfStringMatch = stringBeginningMatch.Value.Contains("$") ? endOfStringWithDollar.Match(subExpr) : endOfStringWithoutDollar.Match(subExpr); - string result = subExpr; + public TResult Func4(T1 arg1, T2 arg2, T3 arg3, T4 arg4) + { + return (TResult)lambda(arg1, arg2, arg3, arg4); + } - if (codeUntilEndOfStringMatch.Success) - { - if (codeUntilEndOfStringMatch.Value.EndsWith("\"")) + public TResult Func5(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5) { - result = codeUntilEndOfStringMatch.Value; + return (TResult)lambda(arg1, arg2, arg3, arg4, arg5); } - else if (codeUntilEndOfStringMatch.Value.EndsWith("{") && codeUntilEndOfStringMatch.Length < subExpr.Length) + + public TResult Func6(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6) { - if (subExpr[codeUntilEndOfStringMatch.Length] == '{') - { - result = codeUntilEndOfStringMatch.Value + "{" - + GetCodeUntilEndOfString(subExpr.Substring(codeUntilEndOfStringMatch.Length + 1), stringBeginningMatch); - } - else - { - string interpolation = GetCodeUntilEndOfStringInterpolation(subExpr.Substring(codeUntilEndOfStringMatch.Length)); - result = codeUntilEndOfStringMatch.Value + interpolation - + GetCodeUntilEndOfString(subExpr.Substring(codeUntilEndOfStringMatch.Length + interpolation.Length), stringBeginningMatch); - } + return (TResult)lambda(arg1, arg2, arg3, arg4, arg5, arg6); } - } - return result; - } + public TResult Func7(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7) + { + return (TResult)lambda(arg1, arg2, arg3, arg4, arg5, arg6, arg7); + } - private string GetCodeUntilEndOfStringInterpolation(string subExpr) - { - Match endOfStringInterpolationMatch = endOfStringInterpolationRegex.Match(subExpr); - string result = subExpr; + public TResult Func8(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8) + { + return (TResult)lambda(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8); + } - if (endOfStringInterpolationMatch.Success) - { - if (endOfStringInterpolationMatch.Value.EndsWith("}")) + public TResult Func9(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9) { - result = endOfStringInterpolationMatch.Value; + return (TResult)lambda(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9); } - else + + public TResult Func10(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10) + { + return (TResult)lambda(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10); + } + + public TResult Func11(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11) + { + return (TResult)lambda(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11); + } + + public TResult Func12(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11, T12 arg12) + { + return (TResult)lambda(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12); + } + + public TResult Func13(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11, T12 arg12, T13 arg13) + { + return (TResult)lambda(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13); + } + + public TResult Func14(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11, T12 arg12, T13 arg13, T14 arg14) { - Match stringBeginningForEndBlockMatch = stringBeginningForEndBlockRegex.Match(endOfStringInterpolationMatch.Value); + return (TResult)lambda(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14); + } - string subString = GetCodeUntilEndOfString(subExpr.Substring(endOfStringInterpolationMatch.Length), stringBeginningForEndBlockMatch); + public TResult Func15(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11, T12 arg12, T13 arg13, T14 arg14, T15 arg15) + { + return (TResult)lambda(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15); + } - result = endOfStringInterpolationMatch.Value + subString - + GetCodeUntilEndOfStringInterpolation(subExpr.Substring(endOfStringInterpolationMatch.Length + subString.Length)); + public TResult Func16(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11, T12 arg12, T13 arg13, T14 arg14, T15 arg15, T16 arg16) + { + return (TResult)lambda(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16); } } - return result; + #endregion } - private class DelegateEncaps + #region linked enums + + public enum OptionOnNoReturnKeywordFoundInScriptAction { - private lambdaExpressionDelegate lambda; + ReturnAutomaticallyLastEvaluatedExpression, + ReturnNull, + ThrowSyntaxException + } - private MethodInfo methodInfo; - private object target; + #endregion - public DelegateEncaps(lambdaExpressionDelegate lambda) - { - this.lambda = lambda; - } - public DelegateEncaps(object target, MethodInfo methodInfo) - { - this.target = target; - this.methodInfo = methodInfo; - } + #region ExpressionEvaluator linked public classes (specific Exceptions, EventArgs and Operators) - public object CallFluidMethod(params object[] args) - { - methodInfo.Invoke(target, args); - return target; - } + #region Operators Management - public TResult Func0() - { - return (TResult)lambda(); - } - public TResult Func1(T arg) - { - return (TResult)lambda(arg); - } - public TResult Func2(T1 arg1, T2 arg2) - { - return (TResult)lambda(arg1, arg2); - } - public TResult Func3(T1 arg1, T2 arg2, T3 arg3) - { - return (TResult)lambda(arg1, arg2, arg3); - } - public TResult Func4(T1 arg1, T2 arg2, T3 arg3, T4 arg4) + public partial class ExpressionOperator : IEquatable + { + protected static uint indexer = 0; + + protected ExpressionOperator() { - return (TResult)lambda(arg1, arg2, arg3, arg4); + indexer++; + OperatorValue = indexer; } - public TResult Func5(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5) + + protected uint OperatorValue { get; } + + public static readonly ExpressionOperator Plus = new ExpressionOperator(); + public static readonly ExpressionOperator Minus = new ExpressionOperator(); + public static readonly ExpressionOperator UnaryPlus = new ExpressionOperator(); + public static readonly ExpressionOperator UnaryMinus = new ExpressionOperator(); + public static readonly ExpressionOperator Multiply = new ExpressionOperator(); + public static readonly ExpressionOperator Divide = new ExpressionOperator(); + public static readonly ExpressionOperator Modulo = new ExpressionOperator(); + public static readonly ExpressionOperator Lower = new ExpressionOperator(); + public static readonly ExpressionOperator Greater = new ExpressionOperator(); + public static readonly ExpressionOperator Equal = new ExpressionOperator(); + public static readonly ExpressionOperator LowerOrEqual = new ExpressionOperator(); + public static readonly ExpressionOperator GreaterOrEqual = new ExpressionOperator(); + public static readonly ExpressionOperator Is = new ExpressionOperator(); + public static readonly ExpressionOperator NotEqual = new ExpressionOperator(); + public static readonly ExpressionOperator LogicalNegation = new ExpressionOperator(); + public static readonly ExpressionOperator BitwiseComplement = new ExpressionOperator(); + public static readonly ExpressionOperator ConditionalAnd = new ExpressionOperator(); + public static readonly ExpressionOperator ConditionalOr = new ExpressionOperator(); + public static readonly ExpressionOperator LogicalAnd = new ExpressionOperator(); + public static readonly ExpressionOperator LogicalOr = new ExpressionOperator(); + public static readonly ExpressionOperator LogicalXor = new ExpressionOperator(); + public static readonly ExpressionOperator ShiftBitsLeft = new ExpressionOperator(); + public static readonly ExpressionOperator ShiftBitsRight = new ExpressionOperator(); + public static readonly ExpressionOperator NullCoalescing = new ExpressionOperator(); + public static readonly ExpressionOperator Cast = new ExpressionOperator(); + public static readonly ExpressionOperator Indexing = new ExpressionOperator(); + public static readonly ExpressionOperator IndexingWithNullConditional = new ExpressionOperator(); + + public override bool Equals(object obj) { - return (TResult)lambda(arg1, arg2, arg3, arg4, arg5); + if (obj is ExpressionOperator otherOperator) + return Equals(otherOperator); + else + return OperatorValue.Equals(obj); } - public TResult Func6(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6) + + public override int GetHashCode() { - return (TResult)lambda(arg1, arg2, arg3, arg4, arg5, arg6); + return OperatorValue.GetHashCode(); } - public TResult Func7(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7) + + public bool Equals(ExpressionOperator otherOperator) { - return (TResult)lambda(arg1, arg2, arg3, arg4, arg5, arg6, arg7); + return otherOperator != null && OperatorValue == otherOperator.OperatorValue; } - public TResult Func8(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8) + } + + public static partial class OperatorsEvaluationsExtensions + { + public static IList>> Copy(this IList>> operatorsEvaluations) { - return (TResult)lambda(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8); + return operatorsEvaluations + .Select(dic => (IDictionary>)new Dictionary>(dic)) + .ToList(); } - public TResult Func9(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9) + + public static IList>> AddOperatorEvaluationAtLevelOf(this IList>> operatorsEvaluations, ExpressionOperator operatorToAdd, Func evaluation, ExpressionOperator levelOfThisOperator) { - return (TResult)lambda(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9); + operatorsEvaluations + .First(dict => dict.ContainsKey(levelOfThisOperator)) + .Add(operatorToAdd, evaluation); + + return operatorsEvaluations; } - public TResult Func10(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10) + + public static IList>> AddOperatorEvaluationAtLevel(this IList>> operatorsEvaluations, ExpressionOperator operatorToAdd, Func evaluation, int level) { - return (TResult)lambda(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10); + operatorsEvaluations[level] + .Add(operatorToAdd, evaluation); + + return operatorsEvaluations; } - public TResult Func11(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11) + + public static IList>> AddOperatorEvaluationAtNewLevelAfter(this IList>> operatorsEvaluations, ExpressionOperator operatorToAdd, Func evaluation, ExpressionOperator levelOfThisOperator) { - return (TResult)lambda(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11); + int level = operatorsEvaluations + .IndexOf(operatorsEvaluations.First(dict => dict.ContainsKey(levelOfThisOperator))); + + operatorsEvaluations + .Insert(level + 1, new Dictionary> { { operatorToAdd, evaluation } }); + + return operatorsEvaluations; } - public TResult Func12(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11, T12 arg12) + + public static IList>> AddOperatorEvaluationAtNewLevelBefore(this IList>> operatorsEvaluations, ExpressionOperator operatorToAdd, Func evaluation, ExpressionOperator levelOfThisOperator) { - return (TResult)lambda(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12); + int level = operatorsEvaluations + .IndexOf(operatorsEvaluations.First(dict => dict.ContainsKey(levelOfThisOperator))); + + operatorsEvaluations + .Insert(level, new Dictionary> { { operatorToAdd, evaluation } }); + + return operatorsEvaluations; } - public TResult Func13(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11, T12 arg12, T13 arg13) + + public static IList>> AddOperatorEvaluationAtNewLevel(this IList>> operatorsEvaluations, ExpressionOperator operatorToAdd, Func evaluation, int level) { - return (TResult)lambda(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13); + operatorsEvaluations + .Insert(level, new Dictionary> { { operatorToAdd, evaluation } }); + + return operatorsEvaluations; } - public TResult Func14(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11, T12 arg12, T13 arg13, T14 arg14) + + public static IList>> RemoveOperatorEvaluation(this IList>> operatorsEvaluations, ExpressionOperator operatorToRemove) { - return (TResult)lambda(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14); + operatorsEvaluations.First(dict => dict.ContainsKey(operatorToRemove)).Remove(operatorToRemove); + + return operatorsEvaluations; } - public TResult Func15(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11, T12 arg12, T13 arg13, T14 arg14, T15 arg15) + + public static IList FluidAdd(this IList listOfOperator, ExpressionOperator operatorToAdd) { - return (TResult)lambda(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15); + listOfOperator.Add(operatorToAdd); + + return listOfOperator; } - public TResult Func16(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11, T12 arg12, T13 arg13, T14 arg14, T15 arg15, T16 arg16) + + public static IList FluidRemove(this IList listOfOperator, ExpressionOperator operatorToRemove) { - return (TResult)lambda(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16); + listOfOperator.Remove(operatorToRemove); + + return listOfOperator; } } -} -[Serializable] -public class ExpressionEvaluatorSyntaxErrorException : Exception -{ - public ExpressionEvaluatorSyntaxErrorException() : base() - { } + #endregion - public ExpressionEvaluatorSyntaxErrorException(string message) : base(message) - { } - public ExpressionEvaluatorSyntaxErrorException(string message, Exception innerException) : base(message, innerException) - { } + public partial class ClassOrEnumType + { + public Type Type { get; set; } + } - protected ExpressionEvaluatorSyntaxErrorException(SerializationInfo info, StreamingContext context) - : base(info, context) + public partial class StronglyTypedVariable { + public Type Type { get; set; } + + public object Value { get; set; } } - [SecurityPermission(SecurityAction.LinkDemand, SerializationFormatter = true)] - public override void GetObjectData(SerializationInfo info, StreamingContext context) + public partial class SubExpression { - base.GetObjectData(info, context); + public string Expression { get; set; } + + public SubExpression(string expression) + { + Expression = expression; + } } -} -internal class VariableEvaluationEventArg : EventArgs -{ - /// - /// - /// - /// The name of the variable to Evaluate - public VariableEvaluationEventArg(string name) + public partial class BubbleExceptionContainer { - Name = name; + public Exception Exception { get; set; } } - /// - /// The name of the variable to Evaluate - /// - public string Name { get; private set; } + public partial class ExpressionEvaluatorSyntaxErrorException : Exception + { + public ExpressionEvaluatorSyntaxErrorException() + { } - private object varValue; + public ExpressionEvaluatorSyntaxErrorException(string message) : base(message) + { } - /// - /// To set a value to this variable - /// - public object Value + public ExpressionEvaluatorSyntaxErrorException(string message, Exception innerException) : base(message, innerException) + { } + } + + public partial class ExpressionEvaluatorSecurityException : Exception { - get { return varValue; } - set + public ExpressionEvaluatorSecurityException() + { } + + public ExpressionEvaluatorSecurityException(string message) : base(message) + { } + + public ExpressionEvaluatorSecurityException(string message, Exception innerException) : base(message, innerException) + { } + } + + public partial class VariableEvaluationEventArg : EventArgs + { + private readonly Func evaluateGenericTypes; + private readonly string genericTypes; + + /// + /// Constructor of the VariableEvaluationEventArg + /// + /// The name of the variable to Evaluate + public VariableEvaluationEventArg(string name, ExpressionEvaluator evaluator = null, object onInstance = null, string genericTypes = null, Func evaluateGenericTypes = null) { - varValue = value; - HasValue = true; + Name = name; + This = onInstance; + Evaluator = evaluator; + this.genericTypes = genericTypes; + this.evaluateGenericTypes = evaluateGenericTypes; } - } - /// - /// if true the variable is affected, if false it means that the variable does not exist. - /// - public bool HasValue { get; set; } = false; -} -internal class FunctionEvaluationEventArg : EventArgs -{ - private Func evaluateFunc = null; + /// + /// The name of the variable to Evaluate + /// + public string Name { get; } - public FunctionEvaluationEventArg(string name, Func evaluateFunc, List args = null) - { - Name = name; - Args = args ?? new List(); - this.evaluateFunc = evaluateFunc; - } + private object varValue; - /// - /// The not evaluated args of the function - /// - public List Args { get; private set; } = new List(); + /// + /// To set a value to this variable + /// + public object Value + { + get { return varValue; } + set + { + varValue = value; + HasValue = true; + } + } - /// - /// Get the values of the function's args. - /// - /// - public object[] EvaluateArgs() - { - return Args.ConvertAll(arg => evaluateFunc(arg)).ToArray(); + /// + /// if true the variable is affected, if false it means that the variable does not exist. + /// + public bool HasValue { get; set; } + + /// + /// In the case of on the fly instance property definition the instance of the object on which this Property is called. + /// Otherwise is set to null. + /// + public object This { get; } + + /// + /// A reference on the current expression evaluator. + /// + public ExpressionEvaluator Evaluator { get; } + + /// + /// Is true if some generic types are specified with <>. + /// false otherwise + /// + public bool HasGenericTypes + { + get + { + return !string.IsNullOrEmpty(genericTypes); + } + } + + /// + /// In the case where generic types are specified with <> + /// Evaluate all types and return an array of types + /// + public Type[] EvaluateGenericTypes() + { + return evaluateGenericTypes?.Invoke(genericTypes) ?? new Type[0]; + } } - /// - /// Get the value of the function's arg at the specified index - /// - /// The index of the function's arg to evaluate - /// The evaluated arg - public object EvaluateArg(int index) + public partial class VariablePreEvaluationEventArg : VariableEvaluationEventArg { - return evaluateFunc(Args[index]); + public VariablePreEvaluationEventArg(string name, ExpressionEvaluator evaluator = null, object onInstance = null, string genericTypes = null, Func evaluateGenericTypes = null) + : base(name, evaluator, onInstance, genericTypes, evaluateGenericTypes) + { } + + /// + /// If set to true cancel the evaluation of the current variable, field or property and throw an exception it does not exists + /// + public bool CancelEvaluation { get; set; } } - /// - /// The name of the variable to Evaluate - /// - public string Name { get; private set; } + public partial class FunctionEvaluationEventArg : EventArgs + { + private readonly Func evaluateFunc; + private readonly Func evaluateGenericTypes; + private readonly string genericTypes; - private object returnValue = null; + public FunctionEvaluationEventArg(string name, Func evaluateFunc, List args = null, ExpressionEvaluator evaluator = null, object onInstance = null, string genericTypes = null, Func evaluateGenericTypes = null) + { + Name = name; + Args = args ?? new List(); + this.evaluateFunc = evaluateFunc; + This = onInstance; + Evaluator = evaluator; + this.genericTypes = genericTypes; + this.evaluateGenericTypes = evaluateGenericTypes; + } - /// - /// To set the return value of the function - /// - public object Value - { - get { return returnValue; } - set + /// + /// The not evaluated args of the function + /// + public List Args { get; } = new List(); + + /// + /// Get the values of the function's args. + /// + /// + public object[] EvaluateArgs() + { + return Args.ConvertAll(arg => evaluateFunc(arg)).ToArray(); + } + + /// + /// Get the value of the function's arg at the specified index + /// + /// The index of the function's arg to evaluate + /// The evaluated arg + public object EvaluateArg(int index) + { + return evaluateFunc(Args[index]); + } + + /// + /// Get the value of the function's arg at the specified index + /// + /// The type of the result to get. (Do a cast) + /// The index of the function's arg to evaluate + /// The evaluated arg casted in the specified type + public T EvaluateArg(int index) + { + return (T)evaluateFunc(Args[index]); + } + + /// + /// The name of the variable to Evaluate + /// + public string Name { get; } + + private object returnValue; + + /// + /// To set the return value of the function + /// + public object Value + { + get { return returnValue; } + set + { + returnValue = value; + FunctionReturnedValue = true; + } + } + + /// + /// if true the function evaluation has been done, if false it means that the function does not exist. + /// + public bool FunctionReturnedValue { get; set; } + + /// + /// In the case of on the fly instance method definition the instance of the object on which this method (function) is called. + /// Otherwise is set to null. + /// + public object This { get; } + + /// + /// A reference on the current expression evaluator. + /// + public ExpressionEvaluator Evaluator { get; } + + /// + /// Is true if some generic types are specified with <>. + /// false otherwise + /// + public bool HasGenericTypes + { + get + { + return !string.IsNullOrEmpty(genericTypes); + } + } + + /// + /// In the case where generic types are specified with <> + /// Evaluate all types and return an array of types + /// + public Type[] EvaluateGenericTypes() { - returnValue = value; - FunctionReturnedValue = true; + return evaluateGenericTypes?.Invoke(genericTypes) ?? new Type[0]; } } - /// - /// if true the function evaluation has been done, if false it means that the function does not exist. - /// - public bool FunctionReturnedValue { get; set; } = false; -} + public partial class FunctionPreEvaluationEventArg : FunctionEvaluationEventArg + { + public FunctionPreEvaluationEventArg(string name, Func evaluateFunc, List args = null, ExpressionEvaluator evaluator = null, object onInstance = null, string genericTypes = null, Func evaluateGenericTypes = null) + : base(name, evaluateFunc, args, evaluator, onInstance, genericTypes, evaluateGenericTypes) + { } + + /// + /// If set to true cancel the evaluation of the current function or method and throw an exception that the function does not exists + /// + public bool CancelEvaluation { get; set; } + } + + #endregion +} \ No newline at end of file