From 1ddecb1dbdabb42b573ca5693eed52130bcd40b4 Mon Sep 17 00:00:00 2001 From: Joao Matos <joao@tritao.eu> Date: Sat, 2 Jan 2021 15:39:32 +0000 Subject: [PATCH] Update expression evaluator code from latest upstream. --- src/Generator/Library.cs | 1 + src/Generator/Utils/ExpressionEvaluator.cs | 5054 +++++++++++++++----- 2 files changed, 3919 insertions(+), 1136 deletions(-) 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; -/// <summary> -/// This class allow to evaluate a string math or pseudo C# expression -/// </summary> -internal class ExpressionEvaluator +namespace CodingSeb.ExpressionEvaluator { - private static Regex varOrFunctionRegEx = new Regex(@"^(?<inObject>(?<nullConditional>[?])?\.)?(?<name>[a-zA-Z_][a-zA-Z0-9_]*)\s*(?<isfunction>[(])?", RegexOptions.IgnoreCase); - private static Regex numberRegex = new Regex(@"^(?<sign>[+-])?\d+(?<hasdecimal>\.?\d+(e[+-]?\d+)?)?(?<type>ul|[fdulm])?", RegexOptions.IgnoreCase); - private static Regex stringBeginningRegex = new Regex("^(?<interpolated>[$])?(?<escaped>[@])?[\"]"); - private static Regex castRegex = new Regex(@"^\(\s*(?<typeName>[a-zA-Z_][a-zA-Z0-9_\.\[\]<>]*[?]?)\s*\)"); - private static Regex indexingBeginningRegex = new Regex(@"^[?]?\["); - private static Regex primaryTypesRegex = new Regex(@"(?<=^|[^a-zA-Z_])(?<primaryType>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*(?<args>(\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*=>(?<expression>.*)$"); - 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<string, Type> PrimaryTypesDict = new Dictionary<string, Type>() - { - { "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<string, Func<string, object>> numberSuffixToParse = new Dictionary<string, Func<string, object>>() - { - { "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<char, string> stringEscapedCharDict = new Dictionary<char, string>() - { - { '\\', @"\" }, - { '"', "\"" }, - { '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<string, ExpressionOperator> operatorsDictionary = new Dictionary<string, ExpressionOperator>(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<ExpressionOperator, bool> leftOperandOnlyOperatorsEvaluationDictionary = new Dictionary<ExpressionOperator, bool>() + /// <summary> + /// This class allow to evaluate a string math or pseudo C# expression + /// </summary> + public partial class ExpressionEvaluator { - }; + #region Regex declarations + + protected const string primaryTypesRegexPattern = @"(?<=^|[^\p{L}_])(?<primaryType>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(@"^((?<sign>[+-])|(?<prefixOperator>[+][+]|--)|(?<varKeyword>var)\s+|(?<dynamicKeyword>dynamic)\s+|(?<inObject>(?<nullConditional>[?])?\.)?)(?<name>[\p{L}_](?>[\p{L}_0-9]*))(?>\s*)((?<assignationOperator>(?<assignmentPrefix>[+\-*/%&|^]|<<|>>|\?\?)?=(?![=>]))|(?<postfixOperator>([+][+]|--)(?![\p{L}_0-9]))|((?<isgeneric>[<](?>([\p{L}_](?>[\p{L}_0-9]*)|(?>\s+)|[,\.])+|(?<gentag>[<])|(?<-gentag>[>]))*(?(gentag)(?!))[>])?(?<isfunction>[(])?))", RegexOptions.IgnoreCase | RegexOptions.Compiled); + + protected const string numberRegexOrigPattern = @"^(?<sign>[+-])?([0-9][0-9_{1}]*[0-9]|\d)(?<hasdecimal>{0}?([0-9][0-9_]*[0-9]|\d)(e[+-]?([0-9][0-9_]*[0-9]|\d))?)?(?<type>ul|[fdulm])?"; + protected string numberRegexPattern; + + protected static readonly Regex otherBasesNumberRegex = new Regex("^(?<sign>[+-])?(?<value>0(?<type>x)([0-9a-f][0-9a-f_]*[0-9a-f]|[0-9a-f])|0(?<type>b)([01][01_]*[01]|[01]))", RegexOptions.IgnoreCase | RegexOptions.Compiled); + protected static readonly Regex stringBeginningRegex = new Regex("^(?<interpolated>[$])?(?<escaped>[@])?[\"]", 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*)((?<assignmentPrefix>[+\-*/%&|^]|<<|>>|\?\?)?=(?![=>])|(?<postfixOperator>([+][+]|--)(?![\p{L}_0-9])))"); + protected static readonly Regex genericsDecodeRegex = new Regex("(?<name>[^,<>]+)(?<isgeneric>[<](?>[^<>]+|(?<gentag>[<])|(?<-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*)(?<args>((?>\s*)[(](?>\s*)([\p{L}_](?>[\p{L}_0-9]*)(?>\s*)([,](?>\s*)[\p{L}_][\p{L}_0-9]*(?>\s*))*)?[)])|[\p{L}_](?>[\p{L}_0-9]*))(?>\s*)=>(?<expression>.*)$", 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*)((?<isAnonymous>[{{])|((?<name>[\p{L}_][\p{L}_0-9"+ (OptionInlineNamespacesEvaluationActive ? @"\." : string.Empty) + @"]*)(?>\s*)(?<isgeneric>[<](?>[^<>]+|(?<gentag>[<])|(?<-gentag>[>]))*(?(gentag)(?!))[>])?(?>\s*)((?<isfunction>[(])|(?<isArray>\[)|(?<isInit>[{{]))?))"; } } + protected string CastRegexPattern { get { return @"^\((?>\s*)(?<typeName>[\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*)(?<keyword>while|for|foreach|if|else(?>\s*)if|catch)(?>\s*)[(]", RegexOptions.IgnoreCase | RegexOptions.Compiled); + protected static readonly Regex foreachParenthisEvaluationRegex = new Regex(@"^(?>\s*)(?<variableName>[\p{L}_](?>[\p{L}_0-9]*))(?>\s*)(?<in>in)(?>\s*)(?<collection>.*)", RegexOptions.IgnoreCase | RegexOptions.Compiled); + protected static readonly Regex blockKeywordsWithoutParenthesesBeginningRegex = new Regex(@"^(?>\s*)(?<keyword>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<ExpressionOperator, bool> rightOperandOnlyOperatorsEvaluationDictionary = new Dictionary<ExpressionOperator, bool>() - { - {ExpressionOperator.LogicalNegation, true } - }; + protected enum TryBlockEvaluatedState + { + NoBlockEvaluated, + Try, + Catch + } - private static List<Dictionary<ExpressionOperator, Func<dynamic, dynamic, object>>> operatorsEvaluations = - new List<Dictionary<ExpressionOperator, Func<dynamic, dynamic, object>>>() - { - new Dictionary<ExpressionOperator, Func<dynamic, dynamic, object>>() - { - {ExpressionOperator.Indexing, (dynamic left, dynamic right) => left[right] }, - {ExpressionOperator.IndexingWithNullConditional, (dynamic left, dynamic right) => left?[right] }, - }, - new Dictionary<ExpressionOperator, Func<dynamic, dynamic, object>>() - { - {ExpressionOperator.LogicalNegation, (dynamic left, dynamic right) => !right }, - {ExpressionOperator.Cast, (dynamic left, dynamic right) => ChangeType(right, left) }, - }, - new Dictionary<ExpressionOperator, Func<dynamic, dynamic, object>>() - { - {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, Func<dynamic, dynamic, object>>() - { - {ExpressionOperator.Plus, (dynamic left, dynamic right) => left + right }, - {ExpressionOperator.Minus, (dynamic left, dynamic right) => left - right }, - }, - new Dictionary<ExpressionOperator, Func<dynamic, dynamic, object>>() - { - {ExpressionOperator.ShiftBitsLeft, (dynamic left, dynamic right) => left << right }, - {ExpressionOperator.ShiftBitsRight, (dynamic left, dynamic right) => left >> right }, - }, - new Dictionary<ExpressionOperator, Func<dynamic, dynamic, object>>() - { - {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, Func<dynamic, dynamic, object>>() - { - {ExpressionOperator.Equal, (dynamic left, dynamic right) => left == right }, - {ExpressionOperator.NotEqual, (dynamic left, dynamic right) => left != right }, - }, - new Dictionary<ExpressionOperator, Func<dynamic, dynamic, object>>() - { - {ExpressionOperator.LogicalAnd, (dynamic left, dynamic right) => left & right }, - }, - new Dictionary<ExpressionOperator, Func<dynamic, dynamic, object>>() - { - {ExpressionOperator.LogicalXor, (dynamic left, dynamic right) => left ^ right }, - }, - new Dictionary<ExpressionOperator, Func<dynamic, dynamic, object>>() - { - {ExpressionOperator.LogicalOr, (dynamic left, dynamic right) => left | right }, - }, - new Dictionary<ExpressionOperator, Func<dynamic, dynamic, object>>() - { - {ExpressionOperator.ConditionalAnd, (dynamic left, dynamic right) => left && right }, - }, - new Dictionary<ExpressionOperator, Func<dynamic, dynamic, object>>() - { - {ExpressionOperator.ConditionalOr, (dynamic left, dynamic right) => left || right }, - }, - new Dictionary<ExpressionOperator, Func<dynamic, dynamic, object>>() - { - {ExpressionOperator.NullCoalescing, (dynamic left, dynamic right) => left ?? right }, - }, - }; - - private static Dictionary<string, object> defaultVariables = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase) - { - { "pi", Math.PI }, - { "e", Math.E }, - { "null", null}, - { "true", true }, - { "false", false }, - }; - - private static Dictionary<string, Func<double, double>> simpleDoubleMathFuncsDictionary = new Dictionary<string, Func<double, double>>() - { - { "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<string, Func<double, double, double>> doubleDoubleMathFuncsDictionary = new Dictionary<string, Func<double, double, double>>() - { - { "atan2", Math.Atan2 }, - { "ieeeremainder", Math.IEEERemainder }, - { "log", Math.Log }, - { "pow", Math.Pow }, - }; + #endregion - private static Dictionary<string, Func<ExpressionEvaluator, List<string>, object>> complexStandardFuncsDictionary = new Dictionary<string, Func<ExpressionEvaluator, List<string>, 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<object> 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) - /// <summary> - /// All assemblies needed to resolves Types - /// </summary> - public List<AssemblyName> ReferencedAssemblies { get; set; } = typeof(ExpressionEvaluator).Assembly.GetReferencedAssemblies().ToList(); + protected static readonly IDictionary<string, Type> primaryTypesDict = new Dictionary<string, Type>() + { + { "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<string, Func<string, CultureInfo, object>> numberSuffixToParse = new Dictionary<string, Func<string, CultureInfo, object>>(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<char, string> stringEscapedCharDict = new Dictionary<char, string>() + { + { '\\', @"\" }, + { '"', "\"" }, + { '0', "\0" }, + { 'a', "\a" }, + { 'b', "\b" }, + { 'f', "\f" }, + { 'n', "\n" }, + { 'r', "\r" }, + { 't', "\t" }, + { 'v', "\v" } + }; + + protected static readonly IDictionary<char, char> charEscapedCharDict = new Dictionary<char, char>() + { + { '\\', '\\' }, + { '\'', '\'' }, + { '0', '\0' }, + { 'a', '\a' }, + { 'b', '\b' }, + { 'f', '\f' }, + { 'n', '\n' }, + { 'r', '\r' }, + { 't', '\t' }, + { 'v', '\v' } + }; + + /// <summary> + /// OperatorsDictionaryInit() for values + /// </summary> + protected IDictionary<string, ExpressionOperator> operatorsDictionary = new Dictionary<string, ExpressionOperator>(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<ExpressionOperator> leftOperandOnlyOperatorsEvaluationDictionary = new List<ExpressionOperator>(); + + protected static readonly IList<ExpressionOperator> rightOperandOnlyOperatorsEvaluationDictionary = new List<ExpressionOperator>() + { + ExpressionOperator.LogicalNegation, + ExpressionOperator.BitwiseComplement, + ExpressionOperator.UnaryPlus, + ExpressionOperator.UnaryMinus + }; - /// <summary> - /// All Namespaces Where to find types - /// </summary> - public List<string> Namespaces { get; set; } = new List<string> - { - "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<ExpressionOperator> LeftOperandOnlyOperatorsEvaluationDictionary => leftOperandOnlyOperatorsEvaluationDictionary; + protected virtual IList<ExpressionOperator> RightOperandOnlyOperatorsEvaluationDictionary => rightOperandOnlyOperatorsEvaluationDictionary; + protected virtual IList<IDictionary<ExpressionOperator, Func<dynamic, dynamic, object>>> OperatorsEvaluations => operatorsEvaluations; - /// <summary> - /// A list of statics types where to find extensions methods - /// </summary> - public List<Type> StaticTypesForExtensionsMethods { get; set; } = new List<Type>() - { - 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(); - /// <summary> - /// The Values of the variable use in the expressions - /// </summary> - public Dictionary<string, object> Variables { get; set; } = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase); + if (left is IDictionary<string, object> dictionaryLeft) + { + return dictionaryLeft[right]; + } + else if (type.GetMethod("Item", new Type[] { ((object)right).GetType() }) is MethodInfo methodInfo) + { + return methodInfo.Invoke(left, new object[] { right }); + } - /// <summary> - /// If <c>true</c> Evaluate function is callables in an expression. If <c>false</c> Evaluate is not callable. - /// By default : false for security - /// </summary> - public bool IsEvaluateFunctionActivated { get; set; } = false; + return left[right]; + } - /// <summary> - /// Default Constructor - /// </summary> - public ExpressionEvaluator() - { } + protected static readonly IList<IDictionary<ExpressionOperator, Func<dynamic, dynamic, object>>> operatorsEvaluations = + new List<IDictionary<ExpressionOperator, Func<dynamic, dynamic, object>>>() + { + new Dictionary<ExpressionOperator, Func<dynamic, dynamic, object>>() + { + {ExpressionOperator.Indexing, IndexingOperatorFunc}, + {ExpressionOperator.IndexingWithNullConditional, (dynamic left, dynamic right) => + { + if(left == null) + return new NullConditionalNullValue(); - /// <summary> - /// Constructor with variable initialize - /// </summary> - /// <param name="variables">The Values of the variable use in the expressions</param> - public ExpressionEvaluator(Dictionary<string, object> variables) - { - this.Variables = variables; - } + return IndexingOperatorFunc(left, right); + } + }, + }, + new Dictionary<ExpressionOperator, Func<dynamic, dynamic, object>>() + { + {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, Func<dynamic, dynamic, object>>() + { + {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, Func<dynamic, dynamic, object>>() + { + {ExpressionOperator.Plus, (dynamic left, dynamic right) => left + right }, + {ExpressionOperator.Minus, (dynamic left, dynamic right) => left - right }, + }, + new Dictionary<ExpressionOperator, Func<dynamic, dynamic, object>>() + { + {ExpressionOperator.ShiftBitsLeft, (dynamic left, dynamic right) => left << right }, + {ExpressionOperator.ShiftBitsRight, (dynamic left, dynamic right) => left >> right }, + }, + new Dictionary<ExpressionOperator, Func<dynamic, dynamic, object>>() + { + {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, Func<dynamic, dynamic, object>>() + { + {ExpressionOperator.Equal, (dynamic left, dynamic right) => left == right }, + {ExpressionOperator.NotEqual, (dynamic left, dynamic right) => left != right }, + }, + new Dictionary<ExpressionOperator, Func<dynamic, dynamic, object>>() + { + {ExpressionOperator.LogicalAnd, (dynamic left, dynamic right) => left & right }, + }, + new Dictionary<ExpressionOperator, Func<dynamic, dynamic, object>>() + { + {ExpressionOperator.LogicalXor, (dynamic left, dynamic right) => left ^ right }, + }, + new Dictionary<ExpressionOperator, Func<dynamic, dynamic, object>>() + { + {ExpressionOperator.LogicalOr, (dynamic left, dynamic right) => left | right }, + }, + new Dictionary<ExpressionOperator, Func<dynamic, dynamic, object>>() + { + {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, Func<dynamic, dynamic, object>>() + { + {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, Func<dynamic, dynamic, object>>() + { + {ExpressionOperator.NullCoalescing, (dynamic left, dynamic right) => left ?? right }, + }, + }; - /// <summary> - /// Is Fired when no internal variable is found for a variable name. - /// Allow to define a variable and the corresponding value on the fly. - /// </summary> - public event EventHandler<VariableEvaluationEventArg> EvaluateVariable; + protected IDictionary<string, object> defaultVariables = new Dictionary<string, object>(StringComparer.Ordinal) + { + { "Pi", Math.PI }, + { "E", Math.E }, + { "null", null}, + { "true", true }, + { "false", false }, + }; + + protected IDictionary<string, Func<double, double>> simpleDoubleMathFuncsDictionary = new Dictionary<string, Func<double, double>>(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<string, Func<double, double, double>> doubleDoubleMathFuncsDictionary = new Dictionary<string, Func<double, double, double>>(StringComparer.Ordinal) + { + { "Atan2", Math.Atan2 }, + { "IEEERemainder", Math.IEEERemainder }, + { "Log", Math.Log }, + { "Pow", Math.Pow }, + }; - /// <summary> - /// Is Fired when no internal function is found for a variable name. - /// Allow to define a function and the corresponding value on the fly. - /// </summary> - public event EventHandler<FunctionEvaluationEventArg> EvaluateFunction; + protected IDictionary<string, Func<ExpressionEvaluator, List<string>, object>> complexStandardFuncsDictionary = new Dictionary<string, Func<ExpressionEvaluator, List<string>, 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); - /// <summary> - /// Evaluate the specified math or pseudo C# expression - /// </summary> - /// <param name="expr">the math or pseudo C# expression to evaluate</param> - /// <returns>The result of the operation if syntax is correct</returns> - 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<object> stack = new Stack<object>(); + 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<object> 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 + + /// <summary> + /// if set to <c>true</c> use a cache for types that were resolved to resolve faster next time. + /// if set to <c>false</c> 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) + /// </summary> + public bool CacheTypesResolutions { get; set; } + + /// <summary> + /// A shared cache for types resolution. + /// </summary> + public static IDictionary<string, Type> TypesResolutionCaching { get; set; } = new Dictionary<string, Type>(); + + /// <summary> + /// Clear all ExpressionEvaluator caches + /// </summary> + public static void ClearAllCaches() + { + TypesResolutionCaching.Clear(); + } + + #endregion + + #region Assemblies, Namespaces and types lists + + private static IList<Assembly> staticAssemblies; + private IList<Assembly> assemblies; + + /// <summary> + /// All assemblies needed to resolves Types + /// by default all Assemblies loaded in the current AppDomain + /// </summary> + public virtual IList<Assembly> Assemblies + { + get { return assemblies ?? (assemblies = staticAssemblies) ?? (assemblies = staticAssemblies = AppDomain.CurrentDomain.GetAssemblies().ToList()); } + set { assemblies = value; } + } + + /// <summary> + /// All Namespaces Where to find types + /// </summary> + public virtual IList<string> Namespaces { get; set; } = new List<string>() + { + "System", + "System.Linq", + "System.IO", + "System.Text", + "System.Text.RegularExpressions", + "System.ComponentModel", + "System.Dynamic", + "System.Collections", + "System.Collections.Generic", + "System.Collections.Specialized", + "System.Globalization" + }; + + /// <summary> + /// To add or remove specific types to manage in expression. + /// </summary> + public virtual IList<Type> Types { get; set; } = new List<Type>(); + + /// <summary> + /// A list of type to block an keep un usable in Expression Evaluation for security purpose + /// </summary> + public virtual IList<Type> TypesToBlock { get; set; } = new List<Type>(); + + /// <summary> + /// A list of statics types where to find extensions methods + /// </summary> + public virtual IList<Type> StaticTypesForExtensionsMethods { get; set; } = new List<Type>() + { + typeof(Enumerable) // For Linq extension methods + }; + + #endregion + + #region Options + + private bool optionCaseSensitiveEvaluationActive = true; + + /// <summary> + /// If <c>true</c> all evaluation are case sensitives. + /// If <c>false</c> evaluations are case insensitive. + /// By default = true + /// </summary> + public bool OptionCaseSensitiveEvaluationActive + { + get { return optionCaseSensitiveEvaluationActive; } + set + { + optionCaseSensitiveEvaluationActive = value; + StringComparisonForCasing = optionCaseSensitiveEvaluationActive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase; + Variables = Variables; + operatorsDictionary = new Dictionary<string, ExpressionOperator>(operatorsDictionary, StringComparerForCasing); + defaultVariables = new Dictionary<string, object>(defaultVariables, StringComparerForCasing); + simpleDoubleMathFuncsDictionary = new Dictionary<string, Func<double, double>>(simpleDoubleMathFuncsDictionary, StringComparerForCasing); + doubleDoubleMathFuncsDictionary = new Dictionary<string, Func<double, double, double>>(doubleDoubleMathFuncsDictionary, StringComparerForCasing); + complexStandardFuncsDictionary = new Dictionary<string, Func<ExpressionEvaluator, List<string>, object>>(complexStandardFuncsDictionary, StringComparerForCasing); } } - return ProcessStack(stack); - } + private StringComparison StringComparisonForCasing { get; set; } = StringComparison.Ordinal; - private bool EvaluateCast(string restOfExpression, Stack<object> stack, ref int i) - { - Match castMatch = castRegex.Match(restOfExpression); + protected StringComparer StringComparerForCasing + { + get + { + return OptionCaseSensitiveEvaluationActive ? StringComparer.Ordinal : StringComparer.OrdinalIgnoreCase; + } + } - if (castMatch.Success) + /// <summary> + /// If <c>true</c> all numbers without decimal and suffixes evaluations will be done as double + /// If <c>false</c> Integers values without decimal and suffixes will be evaluate as int as in C# (Warning some operation can round values) + /// By default = false + /// </summary> + public bool OptionForceIntegerNumbersEvaluationsAsDoubleByDefault { get; set; } + + private CultureInfo cultureInfoForNumberParsing = CultureInfo.InvariantCulture.Clone() as CultureInfo; + + /// <summary> + /// 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 + /// </summary> + 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<object> stack, ref int i) - { - Match numberMatch = numberRegex.Match(restOfExpression); + /// <summary> + /// 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. + /// </summary> + 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; + + /// <summary> + /// 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. + /// </summary> + 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<string, object> 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 + } + + /// <summary> + /// Allow to change the separator of functions arguments. + /// By default "," + /// Warning must to be changed if OptionNumberParsingDecimalSeparator = "," otherwise it will create conflicts + /// </summary> + public string OptionFunctionArgumentsSeparator { get; set; } = ","; + + /// <summary> + /// 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 + /// </summary> + public string OptionInitializersSeparator { get; set; } = ","; + + /// <summary> + /// if <c>true</c> allow to add the prefix Fluid or Fluent before void methods names to return back the instance on which the method is call. + /// if <c>false</c> unactive this functionality. + /// By default : true + /// </summary> + public bool OptionFluidPrefixingActive { get; set; } = true; + + /// <summary> + /// if <c>true</c> allow the use of inline namespace (Can be slow, and is less secure). + /// if <c>false</c> unactive inline namespace (only namespaces in Namespaces list are available). + /// By default : true + /// </summary> + public bool OptionInlineNamespacesEvaluationActive { get; set; } = true; + + private Func<ExpressionEvaluator, List<string>, object> newMethodMem; + + /// <summary> + /// if <c>true</c> allow to create instance of object with the Default function new(ClassNam,...). + /// if <c>false</c> unactive this functionality. + /// By default : true + /// </summary> + 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"); } } + } + + /// <summary> + /// if <c>true</c> allow to create instance of object with the C# syntax new ClassName(...). + /// if <c>false</c> unactive this functionality. + /// By default : true + /// </summary> + public bool OptionNewKeywordEvaluationActive { get; set; } = true; + + /// <summary> + /// if <c>true</c> allow to call static methods on classes. + /// if <c>false</c> unactive this functionality. + /// By default : true + /// </summary> + public bool OptionStaticMethodsCallActive { get; set; } = true; + + /// <summary> + /// if <c>true</c> allow to get static properties on classes + /// if <c>false</c> unactive this functionality. + /// By default : true + /// </summary> + public bool OptionStaticPropertiesGetActive { get; set; } = true; + + /// <summary> + /// if <c>true</c> allow to call instance methods on objects. + /// if <c>false</c> unactive this functionality. + /// By default : true + /// </summary> + public bool OptionInstanceMethodsCallActive { get; set; } = true; + + /// <summary> + /// if <c>true</c> allow to get instance properties on objects + /// if <c>false</c> unactive this functionality. + /// By default : true + /// </summary> + public bool OptionInstancePropertiesGetActive { get; set; } = true; + + /// <summary> + /// if <c>true</c> allow to get object at index or key like IndexedObject[indexOrKey] + /// if <c>false</c> unactive this functionality. + /// By default : true + /// </summary> + public bool OptionIndexingActive { get; set; } = true; + + /// <summary> + /// if <c>true</c> allow string interpretation with "" + /// if <c>false</c> unactive this functionality. + /// By default : true + /// </summary> + public bool OptionStringEvaluationActive { get; set; } = true; + + /// <summary> + /// if <c>true</c> allow char interpretation with '' + /// if <c>false</c> unactive this functionality. + /// By default : true + /// </summary> + public bool OptionCharEvaluationActive { get; set; } = true; + + /// <summary> + /// If <c>true</c> Evaluate function is callables in an expression. If <c>false</c> Evaluate is not callable. + /// By default : true + /// if set to false for security (also ensure that ExpressionEvaluator type is in TypesToBlock list) + /// </summary> + public bool OptionEvaluateFunctionActive { get; set; } = true; + + /// <summary> + /// If <c>true</c> allow to assign a value to a variable in the Variable disctionary with (=, +=, -=, *=, /=, %=, &=, |=, ^=, <<=, >>=, ++ or --) + /// If <c>false</c> unactive this functionality + /// By default : true + /// </summary> + public bool OptionVariableAssignationActive { get; set; } = true; + + /// <summary> + /// If <c>true</c> allow to set/modify a property or a field value with (=, +=, -=, *=, /=, %=, &=, |=, ^=, <<=, >>=, ++ or --) + /// If <c>false</c> unactive this functionality + /// By default : true + /// </summary> + public bool OptionPropertyOrFieldSetActive { get; set; } = true; + + /// <summary> + /// If <c>true</c> allow to assign a indexed element like Collections, List, Arrays and Dictionaries with (=, +=, -=, *=, /=, %=, &=, |=, ^=, <<=, >>=, ++ or --) + /// If <c>false</c> unactive this functionality + /// By default : true + /// </summary> + public bool OptionIndexingAssignationActive { get; set; } = true; + + /// <summary> + /// If <c>true</c> ScriptEvaluate function is callables in an expression. If <c>false</c> Evaluate is not callable. + /// By default : true + /// if set to false for security (also ensure that ExpressionEvaluator type is in TypesToBlock list) + /// </summary> + public bool OptionScriptEvaluateFunctionActive { get; set; } = true; + + /// <summary> + /// If <c>ReturnAutomaticallyLastEvaluatedExpression</c> ScriptEvaluate return automatically the last evaluated expression if no return keyword is met. + /// If <c>ReturnNull</c> return null if no return keyword is met. + /// If <c>ThrowSyntaxException</c> a exception is throw if no return keyword is met. + /// By default : ReturnAutomaticallyLastEvaluatedExpression; + /// </summary> + public OptionOnNoReturnKeywordFoundInScriptAction OptionOnNoReturnKeywordFoundInScriptAction { get; set; } + + /// <summary> + /// If <c>true</c> ScriptEvaluate need to have a semicolon [;] after each expression. + /// If <c>false</c> Allow to omit the semicolon for the last expression of the script. + /// Default : true + /// </summary> + public bool OptionScriptNeedSemicolonAtTheEndOfLastExpression { get; set; } = true; + + /// <summary> + /// If <c>true</c> Allow to access fields, properties and methods that are not declared public. (private, protected and internal) + /// If <c>false</c> 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. + /// </summary> + 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<object> stack, ref int i) - { - Match varFuncMatch = varOrFunctionRegEx.Match(restOfExpression); + #endregion + + #region Custom and on the fly variables and methods + + /// <summary> + /// If set, this object is used to use it's fields, properties and methods as global variables and functions + /// </summary> + public object Context { get; set; } + + private IDictionary<string, object> variables = new Dictionary<string, object>(StringComparer.Ordinal); + + /// <summary> + /// Counts stack initialisations to determine if the expression enty point was reached. In that case the transported exception should be thrown. + /// </summary> + private int evaluationStackCount; + + /// <summary> + /// The Values of the variable use in the expressions + /// </summary> + public IDictionary<string, object> Variables + { + get { return variables; } + set { variables = value == null ? new Dictionary<string, object>(StringComparerForCasing) : new Dictionary<string, object>(value, StringComparerForCasing); } + } + + /// <summary> + /// 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) + /// </summary> + public event EventHandler<VariablePreEvaluationEventArg> PreEvaluateVariable; + + /// <summary> + /// 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) + /// </summary> + public event EventHandler<FunctionPreEvaluationEventArg> PreEvaluateFunction; + + /// <summary> + /// Is Fired if no variable, field or property were found + /// Allow to define a variable and the corresponding value on the fly. + /// </summary> + public event EventHandler<VariableEvaluationEventArg> EvaluateVariable; + + /// <summary> + /// Is Fired if no function or method when were found. + /// Allow to define a function or method and the corresponding value on the fly. + /// </summary> + public event EventHandler<FunctionEvaluationEventArg> EvaluateFunction; + + #endregion + + #region Constructors and overridable Inits methods + + /// <summary> + /// Default Constructor + /// </summary> + public ExpressionEvaluator() + { + DefaultDecimalSeparatorInit(); + + Init(); + } + + /// <summary> + /// Constructor with variables initialize + /// </summary> + /// <param name="variables">The Values of variables use in the expressions</param> + public ExpressionEvaluator(IDictionary<string, object> variables) : this() + { + Variables = variables; + } + + /// <summary> + /// Constructor with context initialize + /// </summary> + /// <param name="context">the context that propose it's fields, properties and methods to the evaluation</param> + public ExpressionEvaluator(object context) : this() + { + Context = context; + } + + /// <summary> + /// Constructor with variables and context initialize + /// </summary> + /// <param name="context">the context that propose it's fields, properties and methods to the evaluation</param> + /// <param name="variables">The Values of variables use in the expressions</param> + public ExpressionEvaluator(object context, IDictionary<string, object> 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; + + /// <summary> + /// 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 + /// </summary> + /// <typeparam name="T">The type in which to cast the result of the expression</typeparam> + /// <param name="script">the script to evaluate</param> + /// <returns>The result of the last evaluated expression</returns> + public virtual T ScriptEvaluate<T>(string script) + { + return (T)ScriptEvaluate(script); + } + + /// <summary> + /// 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 + /// </summary> + /// <param name="script">the script to evaluate</param> + /// <returns>The result of the last evaluated expression</returns> + 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<List<string>> ifElseStatementsList = new List<List<string>>(); + List<List<string>> tryStatementsList = new List<List<string>>(); + + script = script.TrimEnd(); + + object ManageJumpStatementsOrExpressionEval(string expression) { - List<string> 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<object> 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<string> 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<string> 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<string>() { 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<string>() { "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<string>() { "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<string>() { "finally", subScript }); + tryBlockEvaluatedState = TryBlockEvaluatedState.NoBlockEvaluated; + } + } + else + { + ExecuteBlocksStacks(); + + if (keyword.Equals("if", StringComparisonForCasing)) + { + ifElseStatementsList.Add(new List<string>() { keywordAttributes[0], subScript }); + ifBlockEvaluatedState = IfBlockEvaluatedState.If; + tryBlockEvaluatedState = TryBlockEvaluatedState.NoBlockEvaluated; + } + else if (keyword.Equals("try", StringComparisonForCasing)) + { + tryStatementsList.Add(new List<string>() { 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 + + /// <summary> + /// Evaluate the specified math or pseudo C# expression + /// </summary> + /// <typeparam name="T">The type in which to cast the result of the expression</typeparam> + /// <param name="expression">the math or pseudo C# expression to evaluate</param> + /// <returns>The result of the operation if syntax is correct casted in the specified type</returns> + public T Evaluate<T>(string expression) + { + return (T)Evaluate(expression); + } + + private IList<ParsingMethodDelegate> parsingMethods; + + protected virtual IList<ParsingMethodDelegate> ParsingMethods => parsingMethods ?? (parsingMethods = new List<ParsingMethodDelegate>() + { + EvaluateCast, + EvaluateNumber, + EvaluateInstanceCreationWithNewKeyword, + EvaluateVarOrFunc, + EvaluateOperators, + EvaluateChar, + EvaluateParenthis, + EvaluateIndexing, + EvaluateString, + EvaluateTernaryConditionalOperator, + }); + + /// <summary> + /// Evaluate the specified math or pseudo C# expression + /// </summary> + /// <param name="expression">the math or pseudo C# expression to evaluate</param> + /// <returns>The result of the operation if syntax is correct</returns> + public object Evaluate(string expression) + { + expression = expression.Trim(); + + Stack<object> stack = new Stack<object>(); + 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<object> 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<object> 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<string, CultureInfo, object> 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<object> 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<string> 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<string> 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<string> 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<string> 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<string> constructorArgs = GetExpressionsBetweenParenthesesOrOtherImbricableBrackets(expression, ref i, true, OptionFunctionArgumentsSeparator); + i++; + + List<object> 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<string> 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<string> initArgs = GetExpressionsBetweenParenthesesOrOtherImbricableBrackets(expression, ref i, true, OptionInitializersSeparator, "{", "}"); + + Init(element, initArgs); + + stack.Push(element); + } + else if (instanceCreationMatch.Groups["isArray"].Success) + { + List<string> 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<string> 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<object> 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<string> 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<object> 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<string, object> 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<string, object>; + IDictionary<string, object> dictionaryObject = obj as IDictionary<string, object>; + + 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<object> 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<object> 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<object> 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<object> 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<string> expressionsInParenthis = GetExpressionsBetweenParenthesesOrOtherImbricableBrackets(expression, ref i, true); + + InternalDelegate lambdaDelegate = stack.Pop() as InternalDelegate; + + stack.Push(lambdaDelegate(expressionsInParenthis.ConvertAll(Evaluate).ToArray())); + } + else + { + CorrectStackWithUnaryPlusOrMinusBeforeParenthisIfNecessary(stack); + + List<string> expressionsInParenthis = GetExpressionsBetweenParenthesesOrOtherImbricableBrackets(expression, ref i, false); + + stack.Push(Evaluate(expressionsInParenthis[0])); + } + + return true; + } + + return false; + } + + protected virtual void CorrectStackWithUnaryPlusOrMinusBeforeParenthisIfNecessary(Stack<object> 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<object> 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<string, object> 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<string, object> 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<object> 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<object> stack) + { + List<object> 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<ExpressionOperator, Func<dynamic, dynamic, object>> 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 + + /// <summary> + /// 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 + /// </summary> + /// <param name="scriptWithComments">The C# code with comments to remove</param> + /// <returns>The same C# code without comments</returns> + 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<object> stack, ref int i); + + protected delegate dynamic InternalDelegate(params dynamic[] args); + + protected virtual bool GetLambdaExpression(string expression, Stack<object> stack) + { + Match lambdaExpressionMatch = lambdaExpressionRegex.Match(expression); + + if (lambdaExpressionMatch.Success) + { + List<string> argsNames = lambdaArgRegex + .Matches(lambdaExpressionMatch.Groups["args"].Value) + .Cast<Match>().ToList() + .ConvertAll(argMatch => argMatch.Value); + + stack.Push(new InternalDelegate((object[] args) => + { + var vars = new Dictionary<string, object>(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<object> args, string genericsTypes, Type[] inferedGenericsTypes) + { + MethodInfo methodInfo = null; + List<object> modifiedArgs = new List<object>(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<MethodInfo> 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<object>(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<object> 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<object>(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<object, object>(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<object> 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<Match>() + .Select(match => GetTypeByFriendlyName(match.Groups["name"].Value, match.Groups["isgeneric"].Value, true)) + .ToArray(); } - return false; - } - - private bool EvaluateParenthis(string expr, string s, Stack<object> 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<string> 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<string> 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<object> 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<object> stack, ref int i) - { - Match stringBeginningMatch = stringBeginningRegex.Match(restOfExpression); - - if (stringBeginningMatch.Success) + protected virtual List<string> 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<string> expressionsList = new List<string>(); - 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<string> args, out object result) + { + bool functionExists = true; - private object ProcessStack(Stack<object> stack) - { - List<object> list = stack.ToList<object>(); + if (simpleDoubleMathFuncsDictionary.TryGetValue(name, out Func<double, double> func)) + { + result = func(Convert.ToDouble(Evaluate(args[0]))); + } + else if (doubleDoubleMathFuncsDictionary.TryGetValue(name, out Func<double, double, double> func2)) + { + result = func2(Convert.ToDouble(Evaluate(args[0])), Convert.ToDouble(Evaluate(args[1]))); + } + else if (complexStandardFuncsDictionary.TryGetValue(name, out Func<ExpressionEvaluator, List<string>, 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<ExpressionOperator, Func<dynamic, dynamic, object>> 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<object> stack) - { - Match lambdaExpressionMatch = lambdaExpressionRegex.Match(expr); + return result; + } - if (lambdaExpressionMatch.Success) + protected static object ChangeType(object value, Type conversionType) { - List<string> argsNames = lambdaArgRegex - .Matches(lambdaExpressionMatch.Groups["args"].Value) - .Cast<Match>().ToList() - .ConvertAll(argMatch => argMatch.Value); - - stack.Push(new lambdaExpressionDelegate(delegate (object[] args) + if (conversionType == null) { - Dictionary<string, object> vars = new Dictionary<string, object>(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<object> args) - { - MethodInfo methodInfo = null; - List<object> modifiedArgs = new List<object>(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<MethodInfo> 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<object>(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<object>(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<object, object>(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>(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, T2>(T1 arg1, T2 arg2) + { + lambda(arg1, arg2); + } - private List<string> GetExpressionsBetweenParenthis(string expr, ref int i, bool checkComas) - { - List<string> expressionsList = new List<string>(); + public void Action3<T1, T2, T3>(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, T2, T3, T4>(T1 arg1, T2 arg2, T3 arg3, T4 arg4) + { + lambda(arg1, arg2, arg3, arg4); + } - if (internalStringMatch.Success) + public void Action5<T1, T2, T3, T4, T5>(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, T2, T3, T4, T5, T6>(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, T2, T3, T4, T5, T6, T7>(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, T2, T3, T4, T5, T6, T7, T8>(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, T2, T3, T4, T5, T6, T7, T8, T9>(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, T2, T3, T4, T5, T6, T7, T8, T9, T10>(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, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11>(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<string> args, out object result) - { - bool functionExists = true; + public void Action12<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12>(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<double, double> func = null; - Func<double, double, double> func2 = null; - Func<ExpressionEvaluator, List<string>, 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, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13>(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, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14>(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, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15>(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, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16>(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<TResult>() + { + return (TResult)lambda(); } - for (int a = 0; a < ReferencedAssemblies.Count && result == null; a++) + public TResult Func1<T, TResult>(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, T2, TResult>(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, T2, T3, TResult>(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, T2, T3, T4, TResult>(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, T2, T3, T4, T5, TResult>(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, T2, T3, T4, T5, T6, TResult>(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, T2, T3, T4, T5, T6, T7, TResult>(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, T2, T3, T4, T5, T6, T7, T8, TResult>(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, T2, T3, T4, T5, T6, T7, T8, T9, TResult>(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, T2, T3, T4, T5, T6, T7, T8, T9, T10, TResult>(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, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, TResult>(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, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, TResult>(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, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, TResult>(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, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, TResult>(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, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, TResult>(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, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, TResult>(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<TResult>() - { - return (TResult)lambda(); - } - public TResult Func1<T, TResult>(T arg) - { - return (TResult)lambda(arg); - } - public TResult Func2<T1, T2, TResult>(T1 arg1, T2 arg2) - { - return (TResult)lambda(arg1, arg2); - } - public TResult Func3<T1, T2, T3, TResult>(T1 arg1, T2 arg2, T3 arg3) - { - return (TResult)lambda(arg1, arg2, arg3); - } - public TResult Func4<T1, T2, T3, T4, TResult>(T1 arg1, T2 arg2, T3 arg3, T4 arg4) + public partial class ExpressionOperator : IEquatable<ExpressionOperator> + { + protected static uint indexer = 0; + + protected ExpressionOperator() { - return (TResult)lambda(arg1, arg2, arg3, arg4); + indexer++; + OperatorValue = indexer; } - public TResult Func5<T1, T2, T3, T4, T5, TResult>(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, T2, T3, T4, T5, T6, TResult>(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, T2, T3, T4, T5, T6, T7, TResult>(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, T2, T3, T4, T5, T6, T7, T8, TResult>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8) + } + + public static partial class OperatorsEvaluationsExtensions + { + public static IList<IDictionary<ExpressionOperator, Func<dynamic, dynamic, object>>> Copy(this IList<IDictionary<ExpressionOperator, Func<dynamic, dynamic, object>>> operatorsEvaluations) { - return (TResult)lambda(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8); + return operatorsEvaluations + .Select(dic => (IDictionary<ExpressionOperator, Func<dynamic, dynamic, object>>)new Dictionary<ExpressionOperator, Func<dynamic, dynamic, object>>(dic)) + .ToList(); } - public TResult Func9<T1, T2, T3, T4, T5, T6, T7, T8, T9, TResult>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9) + + public static IList<IDictionary<ExpressionOperator, Func<dynamic, dynamic, object>>> AddOperatorEvaluationAtLevelOf(this IList<IDictionary<ExpressionOperator, Func<dynamic, dynamic, object>>> operatorsEvaluations, ExpressionOperator operatorToAdd, Func<dynamic, dynamic, object> 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, T2, T3, T4, T5, T6, T7, T8, T9, T10, TResult>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10) + + public static IList<IDictionary<ExpressionOperator, Func<dynamic, dynamic, object>>> AddOperatorEvaluationAtLevel(this IList<IDictionary<ExpressionOperator, Func<dynamic, dynamic, object>>> operatorsEvaluations, ExpressionOperator operatorToAdd, Func<dynamic, dynamic, object> 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, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, TResult>(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<IDictionary<ExpressionOperator, Func<dynamic, dynamic, object>>> AddOperatorEvaluationAtNewLevelAfter(this IList<IDictionary<ExpressionOperator, Func<dynamic, dynamic, object>>> operatorsEvaluations, ExpressionOperator operatorToAdd, Func<dynamic, dynamic, object> 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<ExpressionOperator, Func<dynamic, dynamic, object>> { { operatorToAdd, evaluation } }); + + return operatorsEvaluations; } - public TResult Func12<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, TResult>(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<IDictionary<ExpressionOperator, Func<dynamic, dynamic, object>>> AddOperatorEvaluationAtNewLevelBefore(this IList<IDictionary<ExpressionOperator, Func<dynamic, dynamic, object>>> operatorsEvaluations, ExpressionOperator operatorToAdd, Func<dynamic, dynamic, object> 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<ExpressionOperator, Func<dynamic, dynamic, object>> { { operatorToAdd, evaluation } }); + + return operatorsEvaluations; } - public TResult Func13<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, TResult>(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<IDictionary<ExpressionOperator, Func<dynamic, dynamic, object>>> AddOperatorEvaluationAtNewLevel(this IList<IDictionary<ExpressionOperator, Func<dynamic, dynamic, object>>> operatorsEvaluations, ExpressionOperator operatorToAdd, Func<dynamic, dynamic, object> evaluation, int level) { - return (TResult)lambda(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13); + operatorsEvaluations + .Insert(level, new Dictionary<ExpressionOperator, Func<dynamic, dynamic, object>> { { operatorToAdd, evaluation } }); + + return operatorsEvaluations; } - public TResult Func14<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, TResult>(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<IDictionary<ExpressionOperator, Func<dynamic, dynamic, object>>> RemoveOperatorEvaluation(this IList<IDictionary<ExpressionOperator, Func<dynamic, dynamic, object>>> 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, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, TResult>(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<ExpressionOperator> FluidAdd(this IList<ExpressionOperator> 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, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, TResult>(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<ExpressionOperator> FluidRemove(this IList<ExpressionOperator> 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 -{ - /// <summary> - /// - /// </summary> - /// <param name="name">The name of the variable to Evaluate</param> - public VariableEvaluationEventArg(string name) + public partial class BubbleExceptionContainer { - Name = name; + public Exception Exception { get; set; } } - /// <summary> - /// The name of the variable to Evaluate - /// </summary> - public string Name { get; private set; } + public partial class ExpressionEvaluatorSyntaxErrorException : Exception + { + public ExpressionEvaluatorSyntaxErrorException() + { } - private object varValue; + public ExpressionEvaluatorSyntaxErrorException(string message) : base(message) + { } - /// <summary> - /// To set a value to this variable - /// </summary> - 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<string, Type[]> evaluateGenericTypes; + private readonly string genericTypes; + + /// <summary> + /// Constructor of the VariableEvaluationEventArg + /// </summary> + /// <param name="name">The name of the variable to Evaluate</param> + public VariableEvaluationEventArg(string name, ExpressionEvaluator evaluator = null, object onInstance = null, string genericTypes = null, Func<string, Type[]> evaluateGenericTypes = null) { - varValue = value; - HasValue = true; + Name = name; + This = onInstance; + Evaluator = evaluator; + this.genericTypes = genericTypes; + this.evaluateGenericTypes = evaluateGenericTypes; } - } - /// <summary> - /// if <c>true</c> the variable is affected, if <c>false</c> it means that the variable does not exist. - /// </summary> - public bool HasValue { get; set; } = false; -} -internal class FunctionEvaluationEventArg : EventArgs -{ - private Func<string, object> evaluateFunc = null; + /// <summary> + /// The name of the variable to Evaluate + /// </summary> + public string Name { get; } - public FunctionEvaluationEventArg(string name, Func<string, object> evaluateFunc, List<string> args = null) - { - Name = name; - Args = args ?? new List<string>(); - this.evaluateFunc = evaluateFunc; - } + private object varValue; - /// <summary> - /// The not evaluated args of the function - /// </summary> - public List<string> Args { get; private set; } = new List<string>(); + /// <summary> + /// To set a value to this variable + /// </summary> + public object Value + { + get { return varValue; } + set + { + varValue = value; + HasValue = true; + } + } - /// <summary> - /// Get the values of the function's args. - /// </summary> - /// <returns></returns> - public object[] EvaluateArgs() - { - return Args.ConvertAll(arg => evaluateFunc(arg)).ToArray(); + /// <summary> + /// if <c>true</c> the variable is affected, if <c>false</c> it means that the variable does not exist. + /// </summary> + public bool HasValue { get; set; } + + /// <summary> + /// 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. + /// </summary> + public object This { get; } + + /// <summary> + /// A reference on the current expression evaluator. + /// </summary> + public ExpressionEvaluator Evaluator { get; } + + /// <summary> + /// Is <c>true</c> if some generic types are specified with <>. + /// <c>false</c> otherwise + /// </summary> + public bool HasGenericTypes + { + get + { + return !string.IsNullOrEmpty(genericTypes); + } + } + + /// <summary> + /// In the case where generic types are specified with <> + /// Evaluate all types and return an array of types + /// </summary> + public Type[] EvaluateGenericTypes() + { + return evaluateGenericTypes?.Invoke(genericTypes) ?? new Type[0]; + } } - /// <summary> - /// Get the value of the function's arg at the specified index - /// </summary> - /// <param name="index">The index of the function's arg to evaluate</param> - /// <returns>The evaluated arg</returns> - 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<string, Type[]> evaluateGenericTypes = null) + : base(name, evaluator, onInstance, genericTypes, evaluateGenericTypes) + { } + + /// <summary> + /// If set to true cancel the evaluation of the current variable, field or property and throw an exception it does not exists + /// </summary> + public bool CancelEvaluation { get; set; } } - /// <summary> - /// The name of the variable to Evaluate - /// </summary> - public string Name { get; private set; } + public partial class FunctionEvaluationEventArg : EventArgs + { + private readonly Func<string, object> evaluateFunc; + private readonly Func<string, Type[]> evaluateGenericTypes; + private readonly string genericTypes; - private object returnValue = null; + public FunctionEvaluationEventArg(string name, Func<string, object> evaluateFunc, List<string> args = null, ExpressionEvaluator evaluator = null, object onInstance = null, string genericTypes = null, Func<string, Type[]> evaluateGenericTypes = null) + { + Name = name; + Args = args ?? new List<string>(); + this.evaluateFunc = evaluateFunc; + This = onInstance; + Evaluator = evaluator; + this.genericTypes = genericTypes; + this.evaluateGenericTypes = evaluateGenericTypes; + } - /// <summary> - /// To set the return value of the function - /// </summary> - public object Value - { - get { return returnValue; } - set + /// <summary> + /// The not evaluated args of the function + /// </summary> + public List<string> Args { get; } = new List<string>(); + + /// <summary> + /// Get the values of the function's args. + /// </summary> + /// <returns></returns> + public object[] EvaluateArgs() + { + return Args.ConvertAll(arg => evaluateFunc(arg)).ToArray(); + } + + /// <summary> + /// Get the value of the function's arg at the specified index + /// </summary> + /// <param name="index">The index of the function's arg to evaluate</param> + /// <returns>The evaluated arg</returns> + public object EvaluateArg(int index) + { + return evaluateFunc(Args[index]); + } + + /// <summary> + /// Get the value of the function's arg at the specified index + /// </summary> + /// <typeparam name="T">The type of the result to get. (Do a cast)</typeparam> + /// <param name="index">The index of the function's arg to evaluate</param> + /// <returns>The evaluated arg casted in the specified type</returns> + public T EvaluateArg<T>(int index) + { + return (T)evaluateFunc(Args[index]); + } + + /// <summary> + /// The name of the variable to Evaluate + /// </summary> + public string Name { get; } + + private object returnValue; + + /// <summary> + /// To set the return value of the function + /// </summary> + public object Value + { + get { return returnValue; } + set + { + returnValue = value; + FunctionReturnedValue = true; + } + } + + /// <summary> + /// if <c>true</c> the function evaluation has been done, if <c>false</c> it means that the function does not exist. + /// </summary> + public bool FunctionReturnedValue { get; set; } + + /// <summary> + /// 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. + /// </summary> + public object This { get; } + + /// <summary> + /// A reference on the current expression evaluator. + /// </summary> + public ExpressionEvaluator Evaluator { get; } + + /// <summary> + /// Is <c>true</c> if some generic types are specified with <>. + /// <c>false</c> otherwise + /// </summary> + public bool HasGenericTypes + { + get + { + return !string.IsNullOrEmpty(genericTypes); + } + } + + /// <summary> + /// In the case where generic types are specified with <> + /// Evaluate all types and return an array of types + /// </summary> + public Type[] EvaluateGenericTypes() { - returnValue = value; - FunctionReturnedValue = true; + return evaluateGenericTypes?.Invoke(genericTypes) ?? new Type[0]; } } - /// <summary> - /// if <c>true</c> the function evaluation has been done, if <c>false</c> it means that the function does not exist. - /// </summary> - public bool FunctionReturnedValue { get; set; } = false; -} + public partial class FunctionPreEvaluationEventArg : FunctionEvaluationEventArg + { + public FunctionPreEvaluationEventArg(string name, Func<string, object> evaluateFunc, List<string> args = null, ExpressionEvaluator evaluator = null, object onInstance = null, string genericTypes = null, Func<string, Type[]> evaluateGenericTypes = null) + : base(name, evaluateFunc, args, evaluator, onInstance, genericTypes, evaluateGenericTypes) + { } + + /// <summary> + /// If set to true cancel the evaluation of the current function or method and throw an exception that the function does not exists + /// </summary> + public bool CancelEvaluation { get; set; } + } + + #endregion +} \ No newline at end of file