Decompiled source of Adletec Sonic v1.6.0
BepInEx/core/Adletec.Sonic/netstandard2.0/Adletec.Sonic.dll
Decompiled a month ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.Linq; using System.Linq.Expressions; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Text; using System.Threading; using Adletec.Sonic.Execution; using Adletec.Sonic.Operations; using Adletec.Sonic.Parsing; using Adletec.Sonic.Parsing.Tokenizing; using Adletec.Sonic.Util; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.0", FrameworkDisplayName = ".NET Standard 2.0")] [assembly: AssemblyCompany("adletec Software Engineering")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyDescription("\n sonic is a rapid evaluation engine for mathematical expressions. It can parse and execute strings containing mathematical expressions. It started as a fork of the dormant Jace.NET project.\n ")] [assembly: AssemblyFileVersion("1.6.0")] [assembly: AssemblyInformationalVersion("1.0.0+51c5ceee8d6280c77fcafed9938f957b5e0baa86")] [assembly: AssemblyProduct("sonic")] [assembly: AssemblyTitle("Adletec.Sonic")] [assembly: AssemblyMetadata("RepositoryUrl", "https://github.com/adletec/sonic")] [assembly: AssemblyVersion("1.6.0.0")] namespace Adletec.Sonic { public enum DataType { Integer, FloatingPoint } public delegate TResult DynamicFunc<T, TResult>(params T[] values); public class Evaluator : IEvaluator { private readonly TokenReader tokenReader; private readonly Optimizer optimizer; private readonly IExecutor executor; private readonly ExpressionValidator expressionValidator; private readonly VariableValidator variableValidator; private readonly MemoryCache<string, Func<IDictionary<string, double>, double>> executionFormulaCache; private readonly bool cacheEnabled; private readonly bool optimizerEnabled; private readonly bool guardedModeEnabled; private readonly bool validationEnabled; private readonly Random random; internal IFunctionRegistry FunctionRegistry { get; } internal IConstantRegistry ConstantRegistry { get; } public IEnumerable<FunctionInfo> Functions => FunctionRegistry; public IEnumerable<ConstantInfo> Constants => ConstantRegistry; public static Evaluator CreateWithDefaults() { return new EvaluatorBuilder().Build(); } public static EvaluatorBuilder Create() { return new EvaluatorBuilder(); } internal Evaluator(EvaluatorBuilder options) { bool caseSensitive = options.CaseSensitive; executionFormulaCache = new MemoryCache<string, Func<IDictionary<string, double>, double>>(options.CacheMaximumSize, options.CacheReductionSize); FunctionRegistry = new FunctionRegistry(caseSensitive, options.GuardedModeEnabled); ConstantRegistry = new ConstantRegistry(caseSensitive, options.GuardedModeEnabled); CultureInfo cultureInfo = options.CultureInfo; char argumentSeparator = options.ArgumentSeparator; tokenReader = new TokenReader(cultureInfo, argumentSeparator); cacheEnabled = options.CacheEnabled; optimizerEnabled = options.OptimizerEnabled; guardedModeEnabled = options.GuardedModeEnabled; validationEnabled = options.ValidationEnabled; random = new Random(); switch (options.ExecutionMode) { case ExecutionMode.Interpreted: executor = new Interpreter(caseSensitive, guardedModeEnabled); break; case ExecutionMode.Compiled: executor = new DynamicCompiler(caseSensitive, guardedModeEnabled); break; default: throw new ArgumentException($"Unsupported execution mode \"{options.ExecutionMode}\".", "ExecutionMode"); } optimizer = new Optimizer(new Interpreter()); if (options.DefaultConstants) { RegisterDefaultConstants(); } if (options.DefaultFunctions) { RegisterDefaultFunctions(); } if (options.Constants != null) { foreach (ConstantDraft constant in options.Constants) { if (guardedModeEnabled && FunctionRegistry.IsFunctionName(constant.Name)) { throw new ArgumentException("The constant name cannot be the same as a function name."); } ConstantRegistry.RegisterConstant(constant.Name, constant.Value); } } if (options.Functions != null) { foreach (FunctionDraft function in options.Functions) { if (guardedModeEnabled && ConstantRegistry.IsConstantName(function.Name)) { throw new ArgumentException("The function name cannot be the same as a constant name."); } FunctionRegistry.RegisterFunction(function.Name, function.Function, function.IsIdempotent); } } expressionValidator = new ExpressionValidator(FunctionRegistry, cultureInfo); variableValidator = new VariableValidator(); } public double Evaluate(string expression) { return Evaluate(expression, new Dictionary<string, double>()); } public double Evaluate(string expression, IDictionary<string, double> variables) { return CreateDelegate(expression)(variables); } public Func<IDictionary<string, double>, double> CreateDelegate(string expression) { if (string.IsNullOrEmpty(expression)) { throw new ArgumentNullException("expression"); } if (IsInFormulaCache(expression, out var function)) { return function; } Operation operation = BuildAbstractSyntaxTree(expression, ConstantRegistry, optimizerEnabled, validationEnabled); return BuildEvaluator(expression, operation); } public void Validate(string expression) { BuildAbstractSyntaxTree(expression, ConstantRegistry, optimize: false, validate: true); } public void Validate(string expression, IList<string> variables) { Operation operation = BuildAbstractSyntaxTree(expression, ConstantRegistry, optimizerEnabled, validate: true); variableValidator.Validate(operation, variables); } private void RegisterDefaultFunctions() { FunctionRegistry.RegisterFunction("sin", new Func<double, double>(Math.Sin), isIdempotent: true); FunctionRegistry.RegisterFunction("cos", new Func<double, double>(Math.Cos), isIdempotent: true); FunctionRegistry.RegisterFunction("csc", new Func<double, double>(MathUtil.Csc), isIdempotent: true); FunctionRegistry.RegisterFunction("sec", new Func<double, double>(MathUtil.Sec), isIdempotent: true); FunctionRegistry.RegisterFunction("asin", new Func<double, double>(Math.Asin), isIdempotent: true); FunctionRegistry.RegisterFunction("acos", new Func<double, double>(Math.Acos), isIdempotent: true); FunctionRegistry.RegisterFunction("tan", new Func<double, double>(Math.Tan), isIdempotent: true); FunctionRegistry.RegisterFunction("cot", new Func<double, double>(MathUtil.Cot), isIdempotent: true); FunctionRegistry.RegisterFunction("atan", new Func<double, double>(Math.Atan), isIdempotent: true); FunctionRegistry.RegisterFunction("acot", new Func<double, double>(MathUtil.Acot), isIdempotent: true); FunctionRegistry.RegisterFunction("loge", new Func<double, double>(Math.Log), isIdempotent: true); FunctionRegistry.RegisterFunction("log10", new Func<double, double>(Math.Log10), isIdempotent: true); FunctionRegistry.RegisterFunction("logn", new Func<double, double, double>(Math.Log), isIdempotent: true); FunctionRegistry.RegisterFunction("sqrt", new Func<double, double>(Math.Sqrt), isIdempotent: true); FunctionRegistry.RegisterFunction("abs", new Func<double, double>(Math.Abs), isIdempotent: true); FunctionRegistry.RegisterFunction("if", (Func<double, double, double, double>)((double a, double b, double c) => (a == 0.0) ? c : b), isIdempotent: true); FunctionRegistry.RegisterFunction("ifless", (Func<double, double, double, double, double>)((double a, double b, double c, double d) => (!(a < b)) ? d : c), isIdempotent: true); FunctionRegistry.RegisterFunction("ifmore", (Func<double, double, double, double, double>)((double a, double b, double c, double d) => (!(a > b)) ? d : c), isIdempotent: true); FunctionRegistry.RegisterFunction("ifequal", (Func<double, double, double, double, double>)((double a, double b, double c, double d) => (a != b) ? d : c), isIdempotent: true); FunctionRegistry.RegisterFunction("ceiling", new Func<double, double>(Math.Ceiling), isIdempotent: true); FunctionRegistry.RegisterFunction("floor", new Func<double, double>(Math.Floor), isIdempotent: true); FunctionRegistry.RegisterFunction("truncate", new Func<double, double>(Math.Truncate), isIdempotent: true); FunctionRegistry.RegisterFunction("round", new Func<double, double>(Math.Round), isIdempotent: true); FunctionRegistry.RegisterFunction("max", (DynamicFunc<double, double>)((double[] a) => a.Max()), isIdempotent: true); FunctionRegistry.RegisterFunction("min", (DynamicFunc<double, double>)((double[] a) => a.Min()), isIdempotent: true); FunctionRegistry.RegisterFunction("avg", (DynamicFunc<double, double>)((double[] a) => a.Average()), isIdempotent: true); FunctionRegistry.RegisterFunction("median", (DynamicFunc<double, double>)((double[] a) => a.Median()), isIdempotent: true); FunctionRegistry.RegisterFunction("sum", (DynamicFunc<double, double>)((double[] a) => a.Sum()), isIdempotent: true); FunctionRegistry.RegisterFunction("random", new Func<double>(random.NextDouble), isIdempotent: false); } private void RegisterDefaultConstants() { ConstantRegistry.RegisterConstant("e", Math.E); ConstantRegistry.RegisterConstant("pi", Math.PI); } private Operation BuildAbstractSyntaxTree(string expression, IConstantRegistry compiledConstants, bool optimize, bool validate) { List<Token> list = tokenReader.Read(expression); if (validate) { expressionValidator.Validate(list, expression); } Operation operation = new AstBuilder(FunctionRegistry, compiledConstants).Build(list); if (!optimize) { return operation; } return optimizer.Optimize(operation, FunctionRegistry, ConstantRegistry); } private Func<IDictionary<string, double>, double> BuildEvaluator(string formulaText, Operation operation) { if (!cacheEnabled) { return Evaluator(formulaText); } return executionFormulaCache.GetOrAdd(formulaText, Evaluator); Func<IDictionary<string, double>, double> Evaluator(string s) { Constant<double> constant = operation as Constant<double>; if (constant != null) { if (guardedModeEnabled) { return delegate(IDictionary<string, double> values) { VariableVerifier.VerifyVariableNames(values, ConstantRegistry, FunctionRegistry); return constant.Value; }; } return (IDictionary<string, double> _) => constant.Value; } return executor.BuildFormula(operation, FunctionRegistry, ConstantRegistry); } } private bool IsInFormulaCache(string formulaText, out Func<IDictionary<string, double>, double> function) { function = null; if (cacheEnabled) { return executionFormulaCache.TryGetValue(formulaText, out function); } return false; } } public class EvaluatorBuilder { private const int DefaultCacheMaximumSize = 500; private const int DefaultCacheReductionSize = 50; private static readonly List<char> IllegalArgumentSeparators = new List<char> { ' ', '+', '-', '*', '/', '^', '(', ')', '_', '%', '>', '<', '=', '&', '|', '≠', '≤', '≥' }; internal CultureInfo CultureInfo { get; private set; } = CultureInfo.CurrentCulture; internal ExecutionMode ExecutionMode { get; private set; } = ExecutionMode.Compiled; internal bool CacheEnabled { get; private set; } = true; internal bool OptimizerEnabled { get; private set; } = true; internal bool CaseSensitive { get; private set; } = true; internal bool DefaultFunctions { get; private set; } = true; internal bool DefaultConstants { get; private set; } = true; internal int CacheMaximumSize { get; private set; } = 500; internal int CacheReductionSize { get; private set; } = 50; internal bool GuardedModeEnabled { get; private set; } internal bool ValidationEnabled { get; private set; } = true; internal char ArgumentSeparator { get; private set; } = ','; private char? OverrideArgumentSeparator { get; set; } internal List<FunctionDraft> Functions { get; } internal List<ConstantDraft> Constants { get; } public EvaluatorBuilder() { Functions = new List<FunctionDraft>(); Constants = new List<ConstantDraft>(); } public EvaluatorBuilder(EvaluatorBuilder evaluatorBuilder) { CultureInfo = evaluatorBuilder.CultureInfo; ExecutionMode = evaluatorBuilder.ExecutionMode; CacheEnabled = evaluatorBuilder.CacheEnabled; OptimizerEnabled = evaluatorBuilder.OptimizerEnabled; CaseSensitive = evaluatorBuilder.CaseSensitive; DefaultConstants = evaluatorBuilder.DefaultConstants; DefaultFunctions = evaluatorBuilder.DefaultFunctions; CacheMaximumSize = evaluatorBuilder.CacheMaximumSize; CacheReductionSize = evaluatorBuilder.CacheReductionSize; GuardedModeEnabled = evaluatorBuilder.GuardedModeEnabled; ValidationEnabled = evaluatorBuilder.ValidationEnabled; ArgumentSeparator = evaluatorBuilder.ArgumentSeparator; OverrideArgumentSeparator = evaluatorBuilder.OverrideArgumentSeparator; Functions = new List<FunctionDraft>(evaluatorBuilder.Functions); Constants = new List<ConstantDraft>(evaluatorBuilder.Constants); } public EvaluatorBuilder UseCulture(CultureInfo cultureInfo) { CultureInfo = cultureInfo; return this; } public EvaluatorBuilder UseArgumentSeparator(char argumentSeparator) { if (char.IsLetter(argumentSeparator) || char.IsDigit(argumentSeparator) || IllegalArgumentSeparators.Contains(argumentSeparator)) { throw new ArgumentException("Illegal argument separator \"\". Character must be a symbol and must not be ", "argumentSeparator"); } OverrideArgumentSeparator = argumentSeparator; return this; } public EvaluatorBuilder UseExecutionMode(ExecutionMode executionMode) { ExecutionMode = executionMode; return this; } public EvaluatorBuilder EnableCache() { CacheEnabled = true; return this; } public EvaluatorBuilder DisableCache() { CacheEnabled = false; return this; } public EvaluatorBuilder EnableOptimizer() { OptimizerEnabled = true; return this; } public EvaluatorBuilder DisableOptimizer() { OptimizerEnabled = false; return this; } public EvaluatorBuilder EnableCaseSensitivity() { CaseSensitive = true; return this; } public EvaluatorBuilder DisableCaseSensitivity() { CaseSensitive = false; return this; } public EvaluatorBuilder EnableDefaultFunctions() { DefaultFunctions = true; return this; } public EvaluatorBuilder DisableDefaultFunctions() { DefaultFunctions = false; return this; } public EvaluatorBuilder EnableDefaultConstants() { DefaultConstants = true; return this; } public EvaluatorBuilder DisableDefaultConstants() { DefaultConstants = false; return this; } public EvaluatorBuilder EnableGuardedMode() { GuardedModeEnabled = true; return this; } public EvaluatorBuilder DisableGuardedMode() { GuardedModeEnabled = false; return this; } public EvaluatorBuilder EnableValidation() { ValidationEnabled = true; return this; } public EvaluatorBuilder DisableValidation() { ValidationEnabled = false; return this; } public EvaluatorBuilder UseCacheMaximumSize(int cacheMaximumSize) { CacheMaximumSize = cacheMaximumSize; return this; } public EvaluatorBuilder UseCacheReductionSize(int cacheReductionSize) { CacheReductionSize = cacheReductionSize; return this; } public EvaluatorBuilder AddFunction(string functionName, Func<double> function, bool isIdempotent = true) { Functions.Add(new FunctionDraft(functionName, isIdempotent, function)); return this; } public EvaluatorBuilder AddFunction(string functionName, Func<double, double> function, bool isIdempotent = true) { Functions.Add(new FunctionDraft(functionName, isIdempotent, function)); return this; } public EvaluatorBuilder AddFunction(string functionName, Func<double, double, double> function, bool isIdempotent = true) { Functions.Add(new FunctionDraft(functionName, isIdempotent, function)); return this; } public EvaluatorBuilder AddFunction(string functionName, Func<double, double, double, double> function, bool isIdempotent = true) { Functions.Add(new FunctionDraft(functionName, isIdempotent, function)); return this; } public EvaluatorBuilder AddFunction(string functionName, Func<double, double, double, double, double> function, bool isIdempotent = true) { Functions.Add(new FunctionDraft(functionName, isIdempotent, function)); return this; } public EvaluatorBuilder AddFunction(string functionName, Func<double, double, double, double, double, double> function, bool isIdempotent = true) { Functions.Add(new FunctionDraft(functionName, isIdempotent, function)); return this; } public EvaluatorBuilder AddFunction(string functionName, Func<double, double, double, double, double, double, double> function, bool isIdempotent = true) { Functions.Add(new FunctionDraft(functionName, isIdempotent, function)); return this; } public EvaluatorBuilder AddFunction(string functionName, Func<double, double, double, double, double, double, double, double> function, bool isIdempotent = true) { Functions.Add(new FunctionDraft(functionName, isIdempotent, function)); return this; } public EvaluatorBuilder AddFunction(string functionName, Func<double, double, double, double, double, double, double, double, double> function, bool isIdempotent = true) { Functions.Add(new FunctionDraft(functionName, isIdempotent, function)); return this; } public EvaluatorBuilder AddFunction(string functionName, Func<double, double, double, double, double, double, double, double, double, double> function, bool isIdempotent = true) { Functions.Add(new FunctionDraft(functionName, isIdempotent, function)); return this; } public EvaluatorBuilder AddFunction(string functionName, Func<double, double, double, double, double, double, double, double, double, double, double> function, bool isIdempotent = true) { Functions.Add(new FunctionDraft(functionName, isIdempotent, function)); return this; } public EvaluatorBuilder AddFunction(string functionName, Func<double, double, double, double, double, double, double, double, double, double, double, double> function, bool isIdempotent = true) { Functions.Add(new FunctionDraft(functionName, isIdempotent, function)); return this; } public EvaluatorBuilder AddFunction(string functionName, Func<double, double, double, double, double, double, double, double, double, double, double, double, double> function, bool isIdempotent = true) { Functions.Add(new FunctionDraft(functionName, isIdempotent, function)); return this; } public EvaluatorBuilder AddFunction(string functionName, Func<double, double, double, double, double, double, double, double, double, double, double, double, double, double> function, bool isIdempotent = true) { Functions.Add(new FunctionDraft(functionName, isIdempotent, function)); return this; } public EvaluatorBuilder AddFunction(string functionName, Func<double, double, double, double, double, double, double, double, double, double, double, double, double, double, double> function, bool isIdempotent = true) { Functions.Add(new FunctionDraft(functionName, isIdempotent, function)); return this; } public EvaluatorBuilder AddFunction(string functionName, Func<double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double> function, bool isIdempotent = true) { Functions.Add(new FunctionDraft(functionName, isIdempotent, function)); return this; } public EvaluatorBuilder AddFunction(string functionName, Func<double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double> function, bool isIdempotent = true) { Functions.Add(new FunctionDraft(functionName, isIdempotent, function)); return this; } public EvaluatorBuilder AddFunction(string functionName, DynamicFunc<double, double> functionDelegate, bool isIdempotent = true) { Functions.Add(new FunctionDraft(functionName, isIdempotent, functionDelegate)); return this; } public EvaluatorBuilder AddConstant(string constantName, double value) { Constants.Add(new ConstantDraft(constantName, value)); return this; } public Evaluator Build() { if (!OverrideArgumentSeparator.HasValue) { ArgumentSeparator = ((CultureInfo.NumberFormat.NumberDecimalSeparator[0] == ',') ? ';' : ','); } else { if (OverrideArgumentSeparator == CultureInfo.NumberFormat.NumberDecimalSeparator[0]) { throw new ArgumentException($"The list separator \"{ArgumentSeparator}\" is the same as the decimal separator \"{CultureInfo.NumberFormat.NumberDecimalSeparator}\". This is not allowed.", "ArgumentSeparator"); } ArgumentSeparator = OverrideArgumentSeparator.Value; } return new Evaluator(this); } protected bool Equals(EvaluatorBuilder other) { if (object.Equals(CultureInfo, other.CultureInfo) && ArgumentSeparator == other.ArgumentSeparator && OverrideArgumentSeparator == other.OverrideArgumentSeparator && ExecutionMode == other.ExecutionMode && CacheEnabled == other.CacheEnabled && OptimizerEnabled == other.OptimizerEnabled && CaseSensitive == other.CaseSensitive && DefaultFunctions == other.DefaultFunctions && DefaultConstants == other.DefaultConstants && CacheMaximumSize == other.CacheMaximumSize && CacheReductionSize == other.CacheReductionSize && Functions.SequenceEqual(other.Functions) && Constants.SequenceEqual(other.Constants) && GuardedModeEnabled == other.GuardedModeEnabled) { return ValidationEnabled == other.ValidationEnabled; } return false; } public override bool Equals(object obj) { if (obj == null) { return false; } if (this == obj) { return true; } if (obj.GetType() != GetType()) { return false; } return Equals((EvaluatorBuilder)obj); } public override int GetHashCode() { return (int)(((((((((((((((((((((((uint)(((((((CultureInfo != null) ? CultureInfo.GetHashCode() : 0) * 397) ^ ArgumentSeparator.GetHashCode()) * 397) ^ OverrideArgumentSeparator.GetHashCode()) * 397) ^ (uint)ExecutionMode) * 397) ^ (uint)CacheEnabled.GetHashCode()) * 397) ^ (uint)OptimizerEnabled.GetHashCode()) * 397) ^ (uint)CaseSensitive.GetHashCode()) * 397) ^ (uint)DefaultFunctions.GetHashCode()) * 397) ^ (uint)DefaultConstants.GetHashCode()) * 397) ^ (uint)CacheMaximumSize) * 397) ^ (uint)CacheReductionSize) * 397) ^ (uint)GuardedModeEnabled.GetHashCode()) * 397) ^ (uint)ValidationEnabled.GetHashCode()) * 397) ^ (uint)((Functions != null) ? Functions.GetHashCode() : 0)) * 397) ^ ((Constants != null) ? Constants.GetHashCode() : 0); } } internal class FunctionDraft { public string Name { get; } public bool IsIdempotent { get; } public Delegate Function { get; } public FunctionDraft(string name, bool isIdempotent, Delegate function) { Name = name; IsIdempotent = isIdempotent; Function = function; } protected bool Equals(FunctionDraft other) { if (Name == other.Name && IsIdempotent == other.IsIdempotent) { return object.Equals(Function, other.Function); } return false; } public override bool Equals(object obj) { if (obj == null) { return false; } if (this == obj) { return true; } if (obj.GetType() != GetType()) { return false; } return Equals((FunctionDraft)obj); } public override int GetHashCode() { return (((((Name != null) ? Name.GetHashCode() : 0) * 397) ^ IsIdempotent.GetHashCode()) * 397) ^ (((object)Function != null) ? Function.GetHashCode() : 0); } } internal class ConstantDraft { public string Name { get; } public double Value { get; } public ConstantDraft(string name, double value) { Name = name; Value = value; } protected bool Equals(ConstantDraft other) { if (Name == other.Name) { return Value.Equals(other.Value); } return false; } public override bool Equals(object obj) { if (obj == null) { return false; } if (this == obj) { return true; } if (obj.GetType() != GetType()) { return false; } return Equals((ConstantDraft)obj); } public override int GetHashCode() { return (((Name != null) ? Name.GetHashCode() : 0) * 397) ^ Value.GetHashCode(); } } public class FormulaContext { public IDictionary<string, double> Variables { get; private set; } public IFunctionRegistry FunctionRegistry { get; private set; } public IConstantRegistry ConstantRegistry { get; private set; } public FormulaContext(IDictionary<string, double> variables, IFunctionRegistry functionRegistry, IConstantRegistry constantRegistry) { Variables = variables; FunctionRegistry = functionRegistry; ConstantRegistry = constantRegistry; } } public interface IEvaluator { IEnumerable<FunctionInfo> Functions { get; } IEnumerable<ConstantInfo> Constants { get; } double Evaluate(string expression); double Evaluate(string expression, IDictionary<string, double> variables); Func<IDictionary<string, double>, double> CreateDelegate(string expression); void Validate(string expression); void Validate(string expression, IList<string> variables); } public class ParseException : Exception { public string Expression { get; } public ParseException(string message, string expression) : base(message) { Expression = expression; } } public class InvalidTokenParseException : ParseException { public int TokenPosition { get; } public string Token { get; } public InvalidTokenParseException(string message, string expression, int tokenPosition, string token) : base(message, expression) { TokenPosition = tokenPosition; Token = token; } } public class InvalidFloatingPointNumberParseException : ParseException { public int TokenPosition { get; } public string Token { get; } public InvalidFloatingPointNumberParseException(string message, string expression, int tokenPosition, string token) : base(message, expression) { TokenPosition = tokenPosition; Token = token; } } public class MissingLeftParenthesisParseException : ParseException { public int RightParenthesisPosition { get; } public MissingLeftParenthesisParseException(string message, string expression, int rightParenthesisPosition) : base(message, expression) { RightParenthesisPosition = rightParenthesisPosition; } } public class MissingRightParenthesisParseException : ParseException { public int LeftParenthesisPosition { get; } public MissingRightParenthesisParseException(string message, string expression, int leftParenthesisPosition) : base(message, expression) { LeftParenthesisPosition = leftParenthesisPosition; } } public class UnknownFunctionParseException : ParseException { public int FunctionNamePosition { get; } public string FunctionName { get; } public UnknownFunctionParseException(string message, string expression, int functionNamePosition, string functionName) : base(message, expression) { FunctionNamePosition = functionNamePosition; FunctionName = functionName; } } public class InvalidArgumentCountParseException : ParseException { public int FunctionNamePosition { get; } public string FunctionName { get; } public InvalidArgumentCountParseException(string message, string expression, int functionNamePosition, string functionName) : base(message, expression) { FunctionNamePosition = functionNamePosition; FunctionName = functionName; } } public class MissingOperandParseException : ParseException { public int OperatorPosition { get; } public string Operator { get; } public MissingOperandParseException(string message, string expression, int operatorPosition, string @operator) : base(message, expression) { OperatorPosition = operatorPosition; Operator = @operator; } } public class MissingQuoteParseException : ParseException { public int QuotePosition { get; } public MissingQuoteParseException(string message, string expression, int quotePosition) : base(message, expression) { QuotePosition = quotePosition; } } public class VariableNotDefinedException : Exception { public string VariableName { get; } public VariableNotDefinedException(string message, string variableName) : base(message) { VariableName = variableName; } public VariableNotDefinedException(string message, Exception innerException, string variableName) : base(message, innerException) { VariableName = variableName; } } } namespace Adletec.Sonic.Util { internal static class EngineUtil { internal static IDictionary<string, double> ConvertToCaseInsensitiveDictionary(IDictionary<string, double> variables) { return new Dictionary<string, double>(variables, StringComparer.OrdinalIgnoreCase); } } public class FuncAdapter { public Delegate Wrap(IEnumerable<Adletec.Sonic.Execution.ParameterInfo> parameters, Func<IDictionary<string, double>, double> function) { Adletec.Sonic.Execution.ParameterInfo[] parameterArray = parameters.ToArray(); return GenerateDelegate(parameterArray, function); } private Delegate GenerateDelegate(Adletec.Sonic.Execution.ParameterInfo[] parameterArray, Func<Dictionary<string, double>, double> function) { Type delegateType = GetDelegateType(parameterArray); Type typeFromHandle = typeof(Dictionary<string, double>); ParameterExpression parameterExpression = Expression.Variable(typeof(Dictionary<string, double>), "dictionary"); BinaryExpression item = Expression.Assign(parameterExpression, Expression.New(typeFromHandle)); ParameterExpression[] array = new ParameterExpression[parameterArray.Length]; List<Expression> list = new List<Expression>(); list.Add(item); for (int i = 0; i < parameterArray.Length; i++) { Type type = ((parameterArray[i].DataType == DataType.FloatingPoint) ? typeof(double) : typeof(int)); array[i] = Expression.Parameter(type, parameterArray[i].Name); list.Add(Expression.Call(parameterExpression, typeFromHandle.GetRuntimeMethod("Add", new Type[2] { typeof(string), typeof(double) }), Expression.Constant(parameterArray[i].Name), Expression.Convert(array[i], typeof(double)))); } InvocationExpression item2 = Expression.Invoke(Expression.Constant(function), parameterExpression); list.Add(item2); return Expression.Lambda(delegateType, Expression.Block(new ParameterExpression[1] { parameterExpression }, list), array).Compile(); } private Type GetDelegateType(IList<Adletec.Sonic.Execution.ParameterInfo> parameters) { string text = $"System.Func`{parameters.Count + 1}"; Type type = Type.GetType(text); if (type == null) { throw new InvalidOperationException("Couldn't get type of $" + text + "."); } Type[] array = new Type[parameters.Count + 1]; for (int i = 0; i < parameters.Count; i++) { array[i] = ((parameters[i].DataType == DataType.FloatingPoint) ? typeof(double) : typeof(int)); } array[^1] = typeof(double); return type.MakeGenericType(array); } } internal static class MathExtended { private static int Partition<T>(this IList<T> list, int start, int end, Random rnd = null) where T : IComparable<T> { if (rnd != null) { list.Swap(end, rnd.Next(start, end + 1)); } T other = list[end]; int num = start - 1; for (int i = start; i < end; i++) { if (list[i].CompareTo(other) <= 0) { list.Swap(i, ++num); } } list.Swap(end, ++num); return num; } public static T NthOrderStatistic<T>(this IList<T> list, int n, Random rnd = null) where T : IComparable<T> { return list.NthOrderStatistic(n, 0, list.Count - 1, rnd); } private static T NthOrderStatistic<T>(this IList<T> list, int n, int start, int end, Random rnd) where T : IComparable<T> { int num; while (true) { num = list.Partition(start, end, rnd); if (num == n) { break; } if (n < num) { end = num - 1; } else { start = num + 1; } } return list[num]; } public static void Swap<T>(this IList<T> list, int i, int j) { if (i != j) { T value = list[i]; list[i] = list[j]; list[j] = value; } } public static T Median<T>(this IList<T> list) where T : IComparable<T> { return list.NthOrderStatistic((list.Count - 1) / 2); } public static double Median<T>(this IEnumerable<T> sequence, Func<T, double> getValue) { List<double> list = sequence.Select(getValue).ToList(); int n = (list.Count - 1) / 2; return list.NthOrderStatistic(n); } } public static class MathUtil { public static double Cot(double a) { return 1.0 / Math.Tan(a); } public static double Acot(double d) { return Math.Atan(1.0 / d); } public static double Csc(double a) { return 1.0 / Math.Sin(a); } public static double Sec(double d) { return 1.0 / Math.Cos(d); } } public class MemoryCache<TKey, TValue> { private class CacheItem { private readonly MemoryCache<TKey, TValue> cache; public TValue Value { get; } public long LastAccessed { get; private set; } public CacheItem(MemoryCache<TKey, TValue> cache, TValue value) { this.cache = cache; Value = value; Accessed(); } public void Accessed() { LastAccessed = Interlocked.Increment(ref cache.counter); } } private readonly int maximumSize; private readonly int reductionSize; private long counter; private readonly ConcurrentDictionary<TKey, CacheItem> dictionary; public TValue this[TKey key] { get { CacheItem cacheItem = dictionary[key]; cacheItem.Accessed(); return cacheItem.Value; } } public int Count => dictionary.Count; public MemoryCache(int maximumSize, int reductionSize) { if (maximumSize < 1) { throw new ArgumentOutOfRangeException("maximumSize", "The maximum allowed number of items in the cache must be at least one."); } if (reductionSize < 1) { throw new ArgumentOutOfRangeException("reductionSize", "The cache reduction size must be at least one."); } this.maximumSize = maximumSize; this.reductionSize = reductionSize; dictionary = new ConcurrentDictionary<TKey, CacheItem>(); } public bool ContainsKey(TKey key) { return dictionary.ContainsKey(key); } public bool TryGetValue(TKey key, out TValue result) { if (dictionary.TryGetValue(key, out var value)) { value.Accessed(); result = value.Value; return true; } result = default(TValue); return false; } public TValue GetOrAdd(TKey key, Func<TKey, TValue> valueFactory) { if (valueFactory == null) { throw new ArgumentNullException("valueFactory"); } return dictionary.GetOrAdd(key, delegate(TKey k) { EnsureCacheStorageAvailable(); return new CacheItem(this, valueFactory(k)); }).Value; } private void EnsureCacheStorageAvailable() { if (dictionary.Count < maximumSize) { return; } foreach (TKey item in (IEnumerable<TKey>)dictionary.ToArray().Where(delegate(KeyValuePair<TKey, CacheItem> p) { KeyValuePair<TKey, CacheItem> keyValuePair3 = p; if (keyValuePair3.Key != null) { keyValuePair3 = p; return keyValuePair3.Value != null; } return false; }).OrderBy(delegate(KeyValuePair<TKey, CacheItem> p) { KeyValuePair<TKey, CacheItem> keyValuePair2 = p; return keyValuePair2.Value.LastAccessed; }) .Select(delegate(KeyValuePair<TKey, CacheItem> p) { KeyValuePair<TKey, CacheItem> keyValuePair = p; return keyValuePair.Key; }) .Take(reductionSize) .ToList()) { dictionary.TryRemove(item, out var _); } } } } namespace Adletec.Sonic.Parsing { public class AstBuilder { private readonly IFunctionRegistry functionRegistry; private readonly IConstantRegistry constantRegistry; private readonly Dictionary<char, int> operationPrecedence = new Dictionary<char, int>(); private readonly Stack<Operation> resultStack = new Stack<Operation>(); private readonly Stack<Token> operatorStack = new Stack<Token>(); private readonly Stack<int> dynamicFunctionArgumentCountStack = new Stack<int>(); public AstBuilder(IFunctionRegistry functionRegistry, IConstantRegistry constantRegistry) { this.functionRegistry = functionRegistry ?? throw new ArgumentNullException("functionRegistry"); this.constantRegistry = constantRegistry ?? throw new ArgumentNullException("constantRegistry"); operationPrecedence.Add('(', 0); operationPrecedence.Add('&', 1); operationPrecedence.Add('|', 1); operationPrecedence.Add('<', 2); operationPrecedence.Add('>', 2); operationPrecedence.Add('≤', 2); operationPrecedence.Add('≥', 2); operationPrecedence.Add('≠', 2); operationPrecedence.Add('=', 2); operationPrecedence.Add('+', 3); operationPrecedence.Add('-', 3); operationPrecedence.Add('*', 4); operationPrecedence.Add('/', 4); operationPrecedence.Add('%', 4); operationPrecedence.Add('_', 5); operationPrecedence.Add('^', 5); } public Operation Build(IList<Token> tokens) { for (int i = 0; i < tokens.Count; i++) { Token token = tokens[i]; switch (token.TokenType) { case TokenType.Integer: { IntegerConstant item5 = new IntegerConstant((int)token.Value); resultStack.Push(item5); break; } case TokenType.FloatingPoint: { FloatingPointConstant item3 = new FloatingPointConstant((double)token.Value); resultStack.Push(item3); break; } case TokenType.Function: operatorStack.Push(token); dynamicFunctionArgumentCountStack.Push(1); break; case TokenType.Symbol: { string text = (string)token.Value; if (constantRegistry.IsConstantName(text)) { FloatingPointConstant item = new FloatingPointConstant(constantRegistry.GetConstantInfo(text).Value); resultStack.Push(item); } else { Variable item2 = new Variable(text); resultStack.Push(item2); } break; } case TokenType.LeftParenthesis: operatorStack.Push(token); break; case TokenType.RightParenthesis: PopOperations(); operatorStack.Pop(); if (operatorStack.Count >= 1 && operatorStack.Peek().TokenType == TokenType.Function) { Operation item4 = ConvertFunction(operatorStack.Pop()); resultStack.Push(item4); } break; case TokenType.ArgumentSeparator: PopOperations(); dynamicFunctionArgumentCountStack.Push(dynamicFunctionArgumentCountStack.Pop() + 1); break; case TokenType.Operation: { char c = (char)token.Value; while (operatorStack.Count > 0 && operatorStack.Peek().TokenType == TokenType.Operation) { Token operationToken = operatorStack.Peek(); char key = (char)operationToken.Value; if ((!IsLeftAssociativeOperation(c) || operationPrecedence[c] > operationPrecedence[key]) && operationPrecedence[c] >= operationPrecedence[key]) { break; } operatorStack.Pop(); resultStack.Push(ConvertOperation(operationToken)); } operatorStack.Push(token); break; } default: throw new ArgumentOutOfRangeException("token", string.Format("Unexpected value \"{0}\" for {1}.", token, "token")); } } PopOperations(); return resultStack.Pop(); } private void PopOperations() { while (operatorStack.Count > 0 && operatorStack.Peek().TokenType != TokenType.LeftParenthesis) { Token token = operatorStack.Pop(); switch (token.TokenType) { case TokenType.Operation: resultStack.Push(ConvertOperation(token)); break; case TokenType.Function: { Operation item = ConvertFunction(token); resultStack.Push(item); break; } default: throw new InvalidOperationException("Unexpected token type."); } } } private Operation ConvertOperation(Token operationToken) { switch ((char)operationToken.Value) { case '+': { Operation argument = resultStack.Pop(); Operation argument2 = resultStack.Pop(); return new Addition(RequiredDataType(argument2, argument), argument2, argument); } case '-': { Operation argument = resultStack.Pop(); Operation argument2 = resultStack.Pop(); return new Subtraction(RequiredDataType(argument2, argument), argument2, argument); } case '*': { Operation argument = resultStack.Pop(); Operation argument2 = resultStack.Pop(); return new Multiplication(RequiredDataType(argument2, argument), argument2, argument); } case '/': { Operation divisor = resultStack.Pop(); Operation dividend = resultStack.Pop(); return new Division(DataType.FloatingPoint, dividend, divisor); } case '%': { Operation divisor = resultStack.Pop(); Operation dividend = resultStack.Pop(); return new Modulo(DataType.FloatingPoint, dividend, divisor); } case '_': { Operation argument2 = resultStack.Pop(); return new UnaryMinus(argument2.DataType, argument2); } case '^': { Operation exponent = resultStack.Pop(); Operation @base = resultStack.Pop(); return new Exponentiation(DataType.FloatingPoint, @base, exponent); } case '&': { Operation argument = resultStack.Pop(); Operation argument2 = resultStack.Pop(); return new And(RequiredDataType(argument2, argument), argument2, argument); } case '|': { Operation argument = resultStack.Pop(); Operation argument2 = resultStack.Pop(); return new Or(RequiredDataType(argument2, argument), argument2, argument); } case '<': { Operation argument = resultStack.Pop(); Operation argument2 = resultStack.Pop(); return new LessThan(RequiredDataType(argument2, argument), argument2, argument); } case '≤': { Operation argument = resultStack.Pop(); Operation argument2 = resultStack.Pop(); return new LessOrEqualThan(RequiredDataType(argument2, argument), argument2, argument); } case '>': { Operation argument = resultStack.Pop(); Operation argument2 = resultStack.Pop(); return new GreaterThan(RequiredDataType(argument2, argument), argument2, argument); } case '≥': { Operation argument = resultStack.Pop(); Operation argument2 = resultStack.Pop(); return new GreaterOrEqualThan(RequiredDataType(argument2, argument), argument2, argument); } case '=': { Operation argument = resultStack.Pop(); Operation argument2 = resultStack.Pop(); return new Equal(RequiredDataType(argument2, argument), argument2, argument); } case '≠': { Operation argument = resultStack.Pop(); Operation argument2 = resultStack.Pop(); return new NotEqual(RequiredDataType(argument2, argument), argument2, argument); } default: throw new ArgumentException($"Unknown operation \"{operationToken}\".", "operationToken"); } } private Operation ConvertFunction(Token functionToken) { string functionName = (string)functionToken.Value; FunctionInfo functionInfo = functionRegistry.GetFunctionInfo(functionName); int num = dynamicFunctionArgumentCountStack.Pop(); int num2 = (functionInfo.IsDynamicFunc ? num : functionInfo.NumberOfParameters); List<Operation> list = new List<Operation>(); for (int i = 0; i < num2; i++) { list.Add(resultStack.Pop()); } list.Reverse(); return new Function(DataType.FloatingPoint, functionName, list, functionInfo.IsIdempotent); } private static bool IsLeftAssociativeOperation(char character) { if (character != '*' && character != '+' && character != '-') { return character == '/'; } return true; } private static DataType RequiredDataType(Operation argument1, Operation argument2) { if (argument1.DataType != DataType.FloatingPoint && argument2.DataType != DataType.FloatingPoint) { return DataType.Integer; } return DataType.FloatingPoint; } } public class ExpressionValidator { private class ValidationContext { public bool IsFunction { get; set; } public int ExpectedArgumentCount { get; set; } public int ActualArgumentCount { get; set; } public Token RootToken { get; set; } public bool IsDynamic { get; set; } } private readonly IFunctionRegistry functionRegistry; private readonly CultureInfo cultureInfo; public ExpressionValidator(IFunctionRegistry functionRegistry, CultureInfo cultureInfo) { this.functionRegistry = functionRegistry; this.cultureInfo = cultureInfo; } public void Validate(IList<Token> tokenList, string expression) { Stack<ValidationContext> stack = new Stack<ValidationContext>(); if (tokenList.Count == 0) { throw new ArgumentException("Token list cannot be empty", "tokenList"); } Token token = tokenList[0]; switch (token.TokenType) { case TokenType.Operation: if (IsBinaryOperation(token)) { ThrowMissingOperationArgumentParseException(token, expression, "There is no left argument."); } break; case TokenType.ArgumentSeparator: ThrowInvalidTokenParseException(token, expression); break; case TokenType.RightParenthesis: ThrowMissingLeftParenthesisParseException(token, expression); break; case TokenType.LeftParenthesis: stack.Push(new ValidationContext { IsFunction = false, ActualArgumentCount = 0, ExpectedArgumentCount = 0, RootToken = token }); break; } for (int i = 1; i < tokenList.Count; i++) { Token token2 = tokenList[i]; Token token3 = tokenList[i - 1]; switch (token2.TokenType) { case TokenType.Operation: if (!IsUnaryOperation(token2)) { switch (token3.TokenType) { case TokenType.LeftParenthesis: case TokenType.ArgumentSeparator: ThrowInvalidTokenParseException(token2, expression); break; case TokenType.Operation: ThrowMissingOperationArgumentParseException(token3, expression, "There is no right argument."); break; } break; } goto case TokenType.Integer; case TokenType.Integer: case TokenType.FloatingPoint: case TokenType.Symbol: case TokenType.Function: { TokenType tokenType = token3.TokenType; if ((uint)tokenType <= 3u || tokenType == TokenType.RightParenthesis) { ThrowInvalidTokenParseException(token2, expression); } if (IsStartOfArgument(stack, token3)) { stack.Peek().ActualArgumentCount++; } break; } case TokenType.ArgumentSeparator: switch (token3.TokenType) { case TokenType.LeftParenthesis: case TokenType.ArgumentSeparator: ThrowInvalidTokenParseException(token2, expression); break; case TokenType.Operation: if (IsBinaryOperation(token3)) { ThrowMissingOperationArgumentParseException(token2, expression, "There is no right argument."); } break; } if (stack.Count == 0) { ThrowInvalidTokenParseException(token2, expression, "Argument separator is outside of function."); } if (!stack.Peek().IsFunction) { ThrowInvalidTokenParseException(token2, expression, "Argument separator must be a direct child of a function."); } break; case TokenType.LeftParenthesis: { TokenType tokenType = token3.TokenType; if ((uint)tokenType <= 2u || tokenType == TokenType.RightParenthesis) { ThrowInvalidTokenParseException(token2, expression); } if (token3.TokenType == TokenType.Function) { FunctionInfo functionInfo = functionRegistry.GetFunctionInfo((string)token3.Value); if (functionInfo == null) { ThrowUnknownFunctionParseException(token3, expression); } stack.Push(new ValidationContext { IsFunction = true, IsDynamic = functionInfo.IsDynamicFunc, ExpectedArgumentCount = functionInfo.NumberOfParameters, ActualArgumentCount = 0, RootToken = token3 }); } else { if (IsStartOfArgument(stack, token3)) { stack.Peek().ActualArgumentCount++; } stack.Push(new ValidationContext { IsFunction = false, ActualArgumentCount = 0, ExpectedArgumentCount = 0, RootToken = token2 }); } break; } case TokenType.RightParenthesis: { switch (token3.TokenType) { case TokenType.Operation: ThrowMissingOperationArgumentParseException(token2, expression, "There is no right argument."); break; case TokenType.ArgumentSeparator: ThrowInvalidTokenParseException(token2, expression, "Argument separator without following argument."); break; case TokenType.LeftParenthesis: if (!IsFunctionOnTopOfStack(stack)) { ThrowInvalidTokenParseException(token2, expression, "Empty parentheses are not allowed."); } break; } if (stack.Count == 0) { ThrowMissingLeftParenthesisParseException(token2, expression); } ValidationContext validationContext = stack.Pop(); if (validationContext.IsDynamic) { if (validationContext.ActualArgumentCount < 1) { ThrowInvalidDynamicFunctionArgumentCountParseException(validationContext.RootToken, expression); } } else if (validationContext.ExpectedArgumentCount != validationContext.ActualArgumentCount) { ThrowInvalidFunctionArgumentCountParseException(validationContext.RootToken, expression, validationContext.ExpectedArgumentCount, validationContext.ActualArgumentCount); } break; } default: throw new ArgumentOutOfRangeException(); } } if (stack.Any()) { ThrowMissingRightParenthesisParseException(stack.Peek().RootToken, expression); } if (tokenList[tokenList.Count - 1].TokenType == TokenType.Operation) { ThrowMissingOperationArgumentParseException(tokenList.Last(), expression, "There is no right argument."); } } private void ThrowInvalidTokenParseException(Token token, string expression, string message = null) { string text = ((token.Value is double num) ? num.ToString(cultureInfo) : token.Value.ToString()); int startPosition = token.StartPosition; throw new InvalidTokenParseException($"Unexpected token at position {startPosition} in expression: \"{text}\". {message}", expression, startPosition, text); } private static void ThrowMissingOperationArgumentParseException(Token token, string expression, string message = null) { string text = token.Value.ToString(); int startPosition = token.StartPosition; throw new MissingOperandParseException($"Missing argument for operation \"{text}\" at position {startPosition}. {message}", expression, startPosition, text); } private static void ThrowInvalidDynamicFunctionArgumentCountParseException(Token rootToken, string expression, string message = null) { string text = rootToken.Value.ToString(); int startPosition = rootToken.StartPosition; throw new InvalidArgumentCountParseException($"Invalid argument count for dynamic function \"{text}\" at position {startPosition}. Expected to find at least one argument, but found none. {message}", expression, startPosition, text); } private static void ThrowInvalidFunctionArgumentCountParseException(Token rootToken, string expression, int expectedArguments, int foundArguments, string message = null) { string text = rootToken.Value.ToString(); int startPosition = rootToken.StartPosition; throw new InvalidArgumentCountParseException($"Invalid argument count for function \"{text}\" at position {startPosition}. Expected {expectedArguments}, but found {foundArguments}. {message}", expression, startPosition, text); } private static void ThrowMissingLeftParenthesisParseException(Token token, string expression, string message = null) { int startPosition = token.StartPosition; throw new MissingLeftParenthesisParseException($"Missing left parenthesis for right parenthesis at position {startPosition}. {message}", expression, startPosition); } private static void ThrowMissingRightParenthesisParseException(Token token, string expression, string message = null) { int startPosition = token.StartPosition; throw new MissingRightParenthesisParseException($"Missing right parenthesis for left parenthesis at position {startPosition}. {message}", expression, startPosition); } private static void ThrowUnknownFunctionParseException(Token token, string expression, string message = null) { string arg = token.Value.ToString(); int startPosition = token.StartPosition; throw new UnknownFunctionParseException($"Unknown function \"{arg}\" at position {startPosition}. {message}", expression, startPosition, token.Value.ToString()); } private static bool IsUnaryOperation(Token token) { if (token.TokenType == TokenType.Operation) { return (char)token.Value == '_'; } return false; } private static bool IsBinaryOperation(Token token) { if (token.TokenType == TokenType.Operation) { return (char)token.Value != '_'; } return false; } private static bool IsStartOfArgument(Stack<ValidationContext> contextStack, Token previousToken) { if (IsFunctionOnTopOfStack(contextStack)) { if (previousToken.TokenType != TokenType.ArgumentSeparator) { return previousToken.TokenType == TokenType.LeftParenthesis; } return true; } return false; } private static bool IsFunctionOnTopOfStack(Stack<ValidationContext> contextStack) { if (contextStack.Count == 0) { return false; } return contextStack.Peek().IsFunction; } } public class Optimizer { private readonly IExecutor executor; public Optimizer(IExecutor executor) { this.executor = executor; } public Operation Optimize(Operation operation, IFunctionRegistry functionRegistry, IConstantRegistry constantRegistry) { if (operation.GetType() == typeof(Addition)) { Addition addition = (Addition)operation; addition.Argument1 = Optimize(addition.Argument1, functionRegistry, constantRegistry); addition.Argument2 = Optimize(addition.Argument2, functionRegistry, constantRegistry); if (!addition.Argument1.DependsOnVariables && !addition.Argument2.DependsOnVariables) { addition.DependsOnVariables = false; } if (addition.Argument1.IsIdempotent && addition.Argument2.IsIdempotent) { addition.IsIdempotent = true; } } else if (operation.GetType() == typeof(Subtraction)) { Subtraction subtraction = (Subtraction)operation; subtraction.Argument1 = Optimize(subtraction.Argument1, functionRegistry, constantRegistry); subtraction.Argument2 = Optimize(subtraction.Argument2, functionRegistry, constantRegistry); if (!subtraction.Argument1.DependsOnVariables && !subtraction.Argument2.DependsOnVariables) { subtraction.DependsOnVariables = false; } if (subtraction.Argument1.IsIdempotent && subtraction.Argument2.IsIdempotent) { subtraction.IsIdempotent = true; } } else if (operation.GetType() == typeof(Multiplication)) { Multiplication multiplication = (Multiplication)operation; multiplication.Argument1 = Optimize(multiplication.Argument1, functionRegistry, constantRegistry); multiplication.Argument2 = Optimize(multiplication.Argument2, functionRegistry, constantRegistry); if (IsZero(multiplication.Argument1) || IsZero(multiplication.Argument2)) { return new FloatingPointConstant(0.0); } if (!multiplication.Argument1.DependsOnVariables && !multiplication.Argument2.DependsOnVariables) { multiplication.DependsOnVariables = false; } if (multiplication.Argument1.IsIdempotent && multiplication.Argument2.IsIdempotent) { multiplication.IsIdempotent = true; } } else if (operation.GetType() == typeof(Division)) { Division division = (Division)operation; division.Dividend = Optimize(division.Dividend, functionRegistry, constantRegistry); division.Divisor = Optimize(division.Divisor, functionRegistry, constantRegistry); if (IsZero(division.Dividend)) { return new FloatingPointConstant(0.0); } if (!division.Dividend.DependsOnVariables && !division.Divisor.DependsOnVariables) { division.DependsOnVariables = false; } if (division.Dividend.IsIdempotent && division.Divisor.IsIdempotent) { division.IsIdempotent = true; } } else if (operation.GetType() == typeof(Exponentiation)) { Exponentiation exponentiation = (Exponentiation)operation; exponentiation.Base = Optimize(exponentiation.Base, functionRegistry, constantRegistry); exponentiation.Exponent = Optimize(exponentiation.Exponent, functionRegistry, constantRegistry); if (IsZero(exponentiation.Exponent)) { return new FloatingPointConstant(1.0); } if (IsZero(exponentiation.Base)) { return new FloatingPointConstant(0.0); } if (!exponentiation.Base.DependsOnVariables && !exponentiation.Exponent.DependsOnVariables) { exponentiation.DependsOnVariables = false; } if (exponentiation.Base.IsIdempotent && exponentiation.Exponent.IsIdempotent) { exponentiation.IsIdempotent = true; } } else if (operation.GetType() == typeof(Function)) { Function function = (Function)operation; IList<Operation> list2 = (function.Arguments = function.Arguments.Select((Operation a) => Optimize(a, functionRegistry, constantRegistry)).ToList()); function.IsIdempotent = functionRegistry.GetFunctionInfo(function.FunctionName).IsIdempotent; for (int i = 0; i < list2.Count; i++) { if (!function.DependsOnVariables && list2[i].DependsOnVariables) { function.DependsOnVariables = true; } if (function.IsIdempotent && !list2[i].IsIdempotent) { function.IsIdempotent = false; } if (function.DependsOnVariables && !function.IsIdempotent) { break; } } function.DependsOnVariables = list2.Any((Operation a) => a.DependsOnVariables); } if (!operation.DependsOnVariables && operation.IsIdempotent && operation.GetType() != typeof(IntegerConstant) && operation.GetType() != typeof(FloatingPointConstant)) { return new FloatingPointConstant(executor.Execute(operation, functionRegistry, constantRegistry)); } return operation; } private bool IsZero(Operation operation) { if (operation.GetType() == typeof(FloatingPointConstant)) { return ((FloatingPointConstant)operation).Value == 0.0; } if (operation.GetType() == typeof(IntegerConstant)) { return ((IntegerConstant)operation).Value == 0; } return false; } } public class TokenReader { private readonly CultureInfo cultureInfo; private readonly char decimalSeparator; private readonly char argumentSeparator; public TokenReader() : this(CultureInfo.InvariantCulture, ',') { } public TokenReader(CultureInfo cultureInfo, char argumentSeparator) { this.cultureInfo = cultureInfo; decimalSeparator = cultureInfo.NumberFormat.NumberDecimalSeparator[0]; this.argumentSeparator = argumentSeparator; if (cultureInfo.NumberFormat.NumberDecimalSeparator.ToCharArray(0, 1)[0] == argumentSeparator) { throw new ArgumentException("argumentSeparator cannot be the same as NumberDecimalSeparator", "argumentSeparator"); } } public List<Token> Read(string expression) { if (string.IsNullOrEmpty(expression)) { throw new ArgumentNullException("expression"); } List<Token> list = new List<Token>(); char[] array = expression.ToCharArray(); bool isFormulaSubPart = true; bool flag = false; for (int i = 0; i < array.Length; i++) { if (IsPartOfNumeric(array[i], isFirstCharacter: true, afterMinus: false, isFormulaSubPart)) { if (array[i] == '-') { list.Add(new Token { TokenType = TokenType.Operation, Value = '_', StartPosition = i, Length = 1 }); continue; } StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append(array[i]); int num = i; while (++i < array.Length && IsPartOfNumeric(array[i], isFirstCharacter: false, array[i - 1] == '-', isFormulaSubPart)) { if (flag && IsScientificNotation(array[i])) { throw new InvalidTokenParseException($"Invalid token \"{array[i]}\" detected at position {i}.", expression, i, array[i].ToString()); } if (IsScientificNotation(array[i])) { flag = IsScientificNotation(array[i]); if (array.Length > i + 1 && array[i + 1] == '-') { stringBuilder.Append(array[i++]); } } stringBuilder.Append(array[i]); } if (int.TryParse(stringBuilder.ToString(), out var result)) { list.Add(new Token { TokenType = TokenType.Integer, Value = result, StartPosition = num, Length = i - num }); isFormulaSubPart = false; } else { if (!double.TryParse(stringBuilder.ToString(), NumberStyles.Float | NumberStyles.AllowThousands, cultureInfo, out var result2)) { throw new InvalidFloatingPointNumberParseException($"Invalid floating point number: {stringBuilder}", expression, num, stringBuilder.ToString()); } list.Add(new Token { TokenType = TokenType.FloatingPoint, Value = result2, StartPosition = num, Length = i - num }); flag = false; isFormulaSubPart = false; } if (i == array.Length) { continue; } } if (IsPartOfTextToken(array[i], isFirstCharacter: true)) { string text = array[i].ToString() ?? ""; int num2 = i; int j = i + 1; while (j < array.Length && IsPartOfTextToken(array[j], isFirstCharacter: false)) { text += array[++i]; j = i + 1; } int num3 = j; for (; array.Length > j && char.IsWhiteSpace(array[j]); j++) { } if (array.Length > j && array[j] == '(') { i = j; list.Add(new Token { TokenType = TokenType.Function, Value = text, StartPosition = num2, Length = num3 - num2 }); list.Add(new Token { TokenType = TokenType.LeftParenthesis, Value = '(', StartPosition = i, Length = 1 }); isFormulaSubPart = true; } else { list.Add(new Token { TokenType = TokenType.Symbol, Value = text, StartPosition = num2, Length = num3 - num2 }); isFormulaSubPart = false; } continue; } if (array[i] == argumentSeparator) { list.Add(new Token { TokenType = TokenType.ArgumentSeparator, Value = array[i], StartPosition = i, Length = 1 }); isFormulaSubPart = false; continue; } switch (array[i]) { case '%': case '*': case '+': case '/': case '^': case '≠': case '≤': case '≥': list.Add(new Token { TokenType = TokenType.Operation, Value = array[i], StartPosition = i, Length = 1 }); isFormulaSubPart = true; break; case '-': if (IsUnaryMinus(array[i], list)) { list.Add(new Token { TokenType = TokenType.Operation, Value = '_', StartPosition = i, Length = 1 }); } else { list.Add(new Token { TokenType = TokenType.Operation, Value = array[i], StartPosition = i, Length = 1 }); } isFormulaSubPart = true; break; case '(': list.Add(new Token { TokenType = TokenType.LeftParenthesis, Value = array[i], StartPosition = i, Length = 1 }); isFormulaSubPart = true; break; case ')': list.Add(new Token { TokenType = TokenType.RightParenthesis, Value = array[i], StartPosition = i, Length = 1 }); isFormulaSubPart = false; break; case '<': if (i + 1 < array.Length && array[i + 1] == '=') { list.Add(new Token { TokenType = TokenType.Operation, Value = '≤', StartPosition = i++, Length = 2 }); } else { list.Add(new Token { TokenType = TokenType.Operation, Value = '<', StartPosition = i, Length = 1 }); } isFormulaSubPart = false; break; case '>': if (i + 1 < array.Length && array[i + 1] == '=') { list.Add(new Token { TokenType = TokenType.Operation, Value = '≥', StartPosition = i++, Length = 2 }); } else { list.Add(new Token { TokenType = TokenType.Operation, Value = '>', StartPosition = i, Length = 1 }); } isFormulaSubPart = false; break; case '!': if (i + 1 < array.Length && array[i + 1] == '=') { list.Add(new Token { TokenType = TokenType.Operation, Value = '≠', StartPosition = i++, Length = 2 }); isFormulaSubPart = false; break; } throw new InvalidTokenParseException($"Invalid token \"{array[i]}\" detected at position {i}.", expression, i, array[i].ToString()); case '&': if (i + 1 < array.Length && array[i + 1] == '&') { list.Add(new Token { TokenType = TokenType.Operation, Value = '&', StartPosition = i++, Length = 2 }); isFormulaSubPart = false; break; } throw new InvalidTokenParseException($"Invalid token \"{array[i]}\" detected at position {i}.", expression, i, array[i].ToString()); case '|': if (i + 1 < array.Length && array[i + 1] == '|') { list.Add(new Token { TokenType = TokenType.Operation, Value = '|', StartPosition = i++, Length = 2 }); isFormulaSubPart = false; break; } throw new InvalidTokenParseException($"Invalid token \"{array[i]}\" detected at position {i}.", expression, i, array[i].ToString()); case '=': if (i + 1 < array.Length && array[i + 1] == '=') { list.Add(new Token { TokenType = TokenType.Operation, Value = '=', StartPosition = i++, Length = 2 }); isFormulaSubPart = false; break; } throw new InvalidTokenParseException($"Invalid token \"{array[i]}\" detected at position {i}.", expression, i, array[i].ToString()); case '\'': { string text2 = ""; int num4 = i; int num5 = i + 1; while (num5 < array.Length && array[num5] != '\'') { text2 += array[++i]; num5 = i + 1; } if (num5 == array.Length) { throw new MissingQuoteParseException($"Missing corresponding quote to quote sign at position {num4}.", expression, num4); } i++; int num6 = ++num5; for (; array.Length > num5 && char.IsWhiteSpace(array[num5]); num5++) { } if (array.Length > num5 && array[num5] == '(') { i = num5; list.Add(new Token { TokenType = TokenType.Function, Value = text2, StartPosition = num4, Length = num6 - num4 }); list.Add(new Token { TokenType = TokenType.LeftParenthesis, Value = '(', StartPosition = i, Length = 1 }); isFormulaSubPart = true; } else { list.Add(new Token { TokenType = TokenType.Symbol, Value = text2, StartPosition = num4, Length = num6 - num4 }); isFormulaSubPart = false; } break; } default: throw new InvalidTokenParseException($"Invalid token \"{array[i]}\" detected at position {i}.", expression, i, array[i].ToString()); case ' ': break; } } return list; } private bool IsPartOfNumeric(char character, bool isFirstCharacter, bool afterMinus, bool isFormulaSubPart) { if (character != decimalSeparator && (character < '0' || character > '9') && (!(isFormulaSubPart && isFirstCharacter) || character != '-') && (isFirstCharacter || afterMinus || character != 'e')) { if (!isFirstCharacter) { return character == 'E'; } return false; } return true; } private bool IsPartOfTextToken(char character, bool isFirstCharacter) { if ((character < 'a' || character > 'z') && (character < 'A' || character > 'Z') && (isFirstCharacter || character < '0' || character > '9')) { if (!isFirstCharacter) { return character == '_'; } return false; } return true; } private bool IsUnaryMinus(char currentToken, IList<Token> tokens) { if (currentToken != '-') { return false; } Token token = tokens[tokens.Count - 1]; if (token.TokenType != TokenType.FloatingPoint && token.TokenType != 0 && token.TokenType != TokenType.Symbol) { return token.TokenType != TokenType.RightParenthesis; } return false; } private bool IsScientificNotation(char currentToken) { if (currentToken != 'e') { return currentToken == 'E'; } return true; } } public enum TokenType { Integer, FloatingPoint, Symbol, Function, Operation, LeftParenthesis, RightParenthesis, ArgumentSeparator } public class VariableValidator { public void Validate(Operation operation, IList<string> variables) { Type type = operation.GetType(); if (type == typeof(Addition)) { Addition addition = (Addition)operation; Validate(addition.Argument1, variables); Validate(addition.Argument2, variables); } else if (type == typeof(Subtraction)) { Subtraction subtraction = (Subtraction)operation; Validate(subtraction.Argument1, variables); Validate(subtraction.Argument2, variables); } else if (type == typeof(Multiplication)) { Multiplication multiplication = (Multiplication)operation; Validate(multiplication.Argument1, variables); Validate(multiplication.Argument2, variables); } else if (type == typeof(Division)) { Division division = (Division)operation; Validate(division.Dividend, variables); Validate(division.Divisor, variables); } else if (type == typeof(Exponentiation)) { Exponentiation exponentiation = (Exponentiation)operation; Validate(exponentiation.Base, variables); Validate(exponentiation.Exponent, variables); } else if (type == typeof(Variable)) { Variable variable = (Variable)operation; if (!variables.Contains(variable.Name)) { throw new VariableNotDefinedException("Variable '" + variable.Name + "' is not defined.", variable.Name); } } else { if (!(type == typeof(Function))) { return; } foreach (Operation argument in ((Function)operation).Arguments) { Validate(argument, variables); } } } } } namespace Adletec.Sonic.Parsing.Tokenizing { public struct Token { public int StartPosition; public int Length; public TokenType TokenType; public object Value; } } namespace Adletec.Sonic.Operations { public class Addition : Operation { public Operation Argument1 { get; internal set; } public Operation Argument2 { get; internal set; } public Addition(DataType dataType, Operation argument1, Operation argument2) : base(dataType, argument1.DependsOnVariables || argument2.DependsOnVariables, argument1.IsIdempotent && argument2.IsIdempotent) { Argument1 = argument1; Argument2 = argument2; } } public class And : Operation { public Operation Argument1 { get; } public Operation Argument2 { get; } public And(DataType dataType, Operation argument1, Operation argument2) : base(dataType, argument1.DependsOnVariables || argument2.DependsOnVariables, argument1.IsIdempotent && argument2.IsIdempotent) { Argument1 = argument1; Argument2 = argument2; } } public abstract class Constant<T> : Operation { public T Value { get; } protected Constant(DataType dataType, T value) : base(dataType, dependsOnVariables: false, isIdempotent: true) { Value = value; } public override bool Equals(object obj) { if (obj is Constant<T> constant) { return Value.Equals(constant.Value); } return false; } public override int GetHashCode() { return Value.GetHashCode(); } } public class IntegerConstant : Constant<int> { public IntegerConstant(int value) : base(DataType.Integer, value) { } } public class FloatingPointConstant : Constant<double> { public FloatingPointConstant(double value) : base(DataType.FloatingPoint, value) { } } public class Division : Operation { public Operation Dividend { get; internal set; } public Operation Divisor { get; internal set; } public Division(DataType dataType, Operation dividend, Operation divisor) : base(dataType, dividend.DependsOnVariables || divisor.DependsOnVariables, dividend.IsIdempotent && divisor.IsIdempotent) { Dividend = dividend; Divisor = divisor; } } public class Equal : Operation { public Operation Argument1 { get; } public Operation Argument2 { get; } public Equal(DataType dataType, Operation argument1, Operation argument2) : base(dataType, argument1.DependsOnVariables || argument2.DependsOnVariables, argument1.IsIdempotent && argument2.IsIdempotent) { Argument1 = argument1; Argument2 = argument2; } } public class Exponentiation : Operation { public Operation Base { get; internal set; } public Operation Exponent { get; internal set; } public Exponentiation(DataType dataType, Operation @base, Operation exponent) : base(dataType, @base.DependsOnVariables || exponent.DependsOnVariables, @base.IsIdempotent && exponent.IsIdempotent) { Base = @base; Exponent = exponent; } } public class Function : Operation { private IList<Operation> arguments; public string FunctionName { get; private set; } public IList<Operation> Arguments { get { return arguments; } internal set { arguments = value; base.DependsOnVariables = arguments.FirstOrDefault((Operation o) => o.DependsOnVariables) != null; } } public Function(DataType dataType, string functionName, IList<Operation> arguments, bool isIdempotent) : base(dataType, arguments.FirstOrDefault((Operation o) => o.DependsOnVariables) != null, isIdempotent && arguments.All((Operation o) => o.IsIdempotent)) { FunctionName = functionName; this.arguments = arguments; } } public class GreaterOrEqualThan : Operation { public Operation Argument1 { get; } public Operation Argument2 { get; } public GreaterOrEqualThan(DataType dataType, Operation argument1, Operation argument2) : base(dataType, argument1.DependsOnVariables || argument2.DependsOnVariables, argument1.IsIdempotent && argument2.IsIdempotent) { Argument1 = argument1; Argument2 = argument2; } } public class GreaterThan : Operation { public Operation Argument1 { get; } public Operation Argument2 { get; } public GreaterThan(DataType dataType, Operation argument1, Operation argument2) : base(dataType, argument1.DependsOnVariables || argument2.DependsOnVariables, argument1.IsIdempotent && argument2.IsIdempotent) { Argument1 = argument1; Argument2 = argument2; } } public class LessOrEqualThan : Operation { public Operation Argument1 { get; } public Operation Argument2 { get; } public LessOrEqualThan(DataType dataType, Operation argument1, Operation argument2) : base(dataType, argument1.DependsOnVariables || argument2.DependsOnVariables, argument1.IsIdempotent && argument2.IsIdempotent) { Argument1 = argument1; Argument2 = argument2; } } public class LessThan : Operation { public Operation Argument1 { get; } public Operation Argument2 { get; } public LessThan(DataType dataType, Operation argument1, Operation argument2) : base(dataType, argument1.DependsOnVariables || argument2.DependsOnVariables, argument1.IsIdempotent && argument2.IsIdempotent) { Argument1 = argument1; Argument2 = argument2; } } public class Modulo : Operation { public Operation Dividend { get; } public Operation Divisor { get; } public Modulo(DataType dataType, Operation dividend, Operation divisor) : base(dataType, dividend.DependsOnVariables || divisor.DependsOnVariables, dividend.IsIdempotent && divisor.IsIdempotent) { Dividend = dividend; Divisor = divisor; } } public class Multiplication : Operation { public Operation Argument1 { get; internal set; } public Operation Argument2 { get; internal set; } public Multiplication(DataType dataType, Operation argument1, Operation argument2) : base(dataType, argument1.DependsOnVariables || argument2.DependsOnVariables, argument1.IsIdempotent && argument2.IsIdempotent) { Argument1 = argument1; Argument2 = argument2; } } public class NotEqual : Operation { public Operation Argument1 { get; } public Operation Argument2 { get; } public NotEqual(DataType dataType, Operation argument1, Operation argument2) : base(dataType, argument1.DependsOnVariables || argument2.DependsOnVariables, argument1.IsIdempotent && argument2.IsIdempotent) { Argument1 = argument1; Argument2 = argument2; } } public abstract class Operation { public DataType DataType { get; private set; } public bool DependsOnVariables { get; internal set; } public bool IsIdempotent { get; internal set; } protected Operation(DataType dataType, bool dependsOnVariables, bool isIdempotent) { DataType = dataType; DependsOnVariables = dependsOnVariables; IsIdempotent = isIdempotent; } } public class Or : Operation { public Operation Argument1 { get; } public Operation Argument2 { get; } public Or(DataType dataType, Operation argument1, Operation argument2) : base(dataType, argument1.DependsOnVariables || argument2.DependsOnVariables, argument1.IsIdempotent && argument2.IsIdempotent) { Argument1 = argument1; Argument2 = argument2; } } public class Subtraction : Operation { public Operation Argument1 { get; internal set; } public Operation Argument2 { get; internal set; } public Subtraction(DataType dataType, Operation argument1, Operation argument2) : base(dataType, argument1.DependsOnVariables || argument2.DependsOnVariables, argument1.IsIdempotent && argument2.IsIdempotent) { Argument1 = argument1; Argument2 = argument2; } } public class UnaryMinus : Operation { public Operation Argument { get; } public UnaryMinus(DataType dataType, Operation argument) : base(dataType, argument.DependsOnVariables, argument.IsIdempotent) { Argument = argument; } } public class Variable : Operation { public string Name { get; } public Variable(string name) : base(DataType.FloatingPoint, dependsOnVariables: true, isIdempotent: false) { Name = name; } public override bool Equals(object obj) { if (obj is Variable variable) { return Name.Equals(variable.Name); } return false; } public override int GetHashCode() { return Name.GetHashCode(); } } } namespace Adletec.Sonic.Execution { public class ConstantInfo { public string ConstantName { get; private set; } public double Value { get; private set; } public ConstantInfo(string constantName, double value) { ConstantName = constantName; Value = value; } } public class ConstantRegistry : IConstantRegistry, IEnumerable<ConstantInfo>, IEnumerable { private readonly Dictionary<string, ConstantInfo> constants; private readonly bool guardedMode; public ConstantRegistry(bool caseSensitive, bool guardedMode) { constants = (caseSensitive ? new Dictionary<string, ConstantInfo>() : new Dictionary<string, ConstantInfo>(StringComparer.OrdinalIgnoreCase)); this.guardedMode = guardedMode; } public IEnumerator<ConstantInfo> GetEnumerator() { return constants.Values.GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } public ConstantInfo GetConstantInfo(string constantName) { if (string.IsNullOrEmpty(constantName)) { throw new ArgumentNullException("constantName"); } if (!constants.TryGetValue(constantName, out var value)) { return null; } return value; } public bool IsConstantName(string constantName) { if (string.IsNullOrEmpty(constantName)) { throw new ArgumentNullException("constantName"); } return constants.ContainsKey(constantName); } public void RegisterConstant(string constantName, double value) { if (string.IsNullOrEmpty(constantName)) { throw new ArgumentNullException("constantName"); } if (guardedMode && constants.ContainsKey(constantName)) { throw new ArgumentException("The constant \"" + constantName + "\" cannot be overwritten."); } ConstantInfo value2 = new ConstantInfo(constantName, value); constants[constantName] = value2; } } public class DynamicCompiler : IExecutor { private static class PrecompiledMethods { public static double GetVariableValueOrThrow(string variableName, FormulaContext context) { if (context.Variables.TryGetValue(variableName, out var value)) { return value; } throw new VariableNotDefinedException("The variable \"" + variableName + "\" used is not defined.", variableName); } } private readonly string funcAssemblyQualifiedName; private readonly bool caseSensitive; private readonly bool guardedMode; public DynamicCompiler() : this(caseSensitive: false, guardedMode: false) { } public DynamicCompiler(bool caseSensitive, bool guardedMode) { this.caseSensitive = caseSensitive; this.guardedMode = guardedMode; funcAssemblyQualifiedName = typeof(Func<double, double, double, double, double, double, double, double, double, double>).GetTypeInfo().Assembly.FullName; } public double Execute(Operation operation, IFunctionRegistry functionRegistry, IConstantRegistry constantRegistry) { return Execute(operation, functionRegistry, constantRegistry, new Dictionary<string, double>()); } public double Execute(Operation operation, IFunctionRegistry functionRegistry, IConstantRegistry constantRegistry, IDictionary<string, double> variables) { return BuildFormula(operation, functionRegistry, constantRegistry)(variables); } public Func<IDictionary<string, double>, double> BuildFormula(Operation operation, IFunctionRegistry functionRegistry, IConstantRegistry constantRegistry) { Func<FormulaContext, double> func = BuildFormulaInternal(operation, functionRegistry); if (caseSensitive) { if (guardedMode) { return delegate(IDictionary<string, double> variables) { VariableVerifier.VerifyVariableNames(variables, constantRegistry, functionRegistry); FormulaContext arg4 = new FormulaContext(variables, functionRegistry, constantRegistry); return func(arg4); }; } return delegate(IDictionary<string, double> variables) { FormulaContext arg3 = new FormulaContext(variables, functionRegistry, constantRegistry); return func(arg3); }; } if (guardedMode) { return delegate(IDictionary<string, double> variables) { variables = EngineUtil.ConvertToCaseInsensitiveDictionary(variables); VariableVerifier.VerifyVariableNames(variables, constantRegistry, functionRegistry); FormulaContext arg2 = new FormulaContext(variables, functionRegistry, constantRegistry); return func(arg2); }; } return delegate(IDictionary<string, double> variables) { variables = EngineUtil.ConvertToCaseInsensitiveDictionary(variables); FormulaContext arg = new FormulaContext(variables, functionRegistry, constantRegistry); return func(arg); }; } private Func<FormulaContext, double> BuildFormulaInternal(Operation operation, IFunctionRegistry functionRegistry) { ParameterExpression parameterExpression = Expression.Parameter(typeof(FormulaContext), "context"); return Expression.Lambda<Func<FormulaContext, double>>(GenerateMethodBody(operation, parameterExpression, functionRegistry), new ParameterExpression[1] { parameterExpression }).Compile(); } private Expression GenerateMethodBody(Operation operation, ParameterExpression contextParameter, IFunctionRegistry functionRegistry) { if (operation == null) { throw new ArgumentNullException("operation"); } if (operation.GetType() == typeof(IntegerConstant)) { return Expression.Constant((double)((IntegerConstant)operation).Value, typeof(double)); } if (operation.GetType() == typeof(FloatingPointConstant)) { return Expression.Constant(((FloatingPointConstant)operation).Value, typeof(double)); } if (operation.GetType() == typeof(Variable)) { Variable variable = (Variable)operation; Func<string, FormulaContext, double> del = PrecompiledMethods.GetVariableValueOrThrow; return Expression.Call(null, del.GetMethodInfo(), Expression.Constant(variable.Name), contextParameter); } if (operation.GetType() == typeof(Multiplication)) { Multiplication multiplication = (Multiplication)operation; Expression left = GenerateMethodBody(multiplication.Argument1, contextParameter, functionRegistry); Expression right = GenerateMethodBody(multiplication.Argument2, contextParameter, functionRegistry); return Expression.Multiply(left, right); } if (operation.GetType() == typeof(Addition)) { Addition addition = (Addition)operation; Expression left2 = GenerateMethodBody(addition.Argument1, contextParameter, functionRegistry); Expression right2 = GenerateMethodBody(addition.Argument2, contextParameter, functionRegistry); return Expression.Add(left2, right2); } if (operation.GetType() == typeof(Subtraction)) { Subtraction subtraction = (Subtraction)operation; Expression left3 = GenerateMethodBody(subtraction.Argument1, contextParameter, functionRegistry); Expression right3 = GenerateMethodBody(subtraction.Argument2, contextParameter, functionRegistry); return Expression.Subtract(left3, right3); } if (operation.GetType() == typeof(Division)) { Division division = (Division)operation; Expression left4 = GenerateMethodBody(division.Dividend, contextParameter, functionRegistry); Expression right4 = GenerateMethodBody(division.Divisor, contextParameter, functionRegistry); return Expression.Divide(left4, right4); } if (operation.GetType() == typeof(Modulo)) { Modulo modulo = (Modulo)operation; Expression left5 = GenerateMethodBody(modulo.Dividend, contextParameter, functionRegistry); Expression right5 = GenerateMethodBody(modulo.Divisor, contextParameter, functionRegistry); return Expression.Modulo(left5, right5); } if (operation.GetType() == typeof(Exponentiation)) { Exponentiation exponentiation = (Exponentiation)operation; Expression arg = GenerateMethodBody(exponentiation.Base, contextParameter, functionRegistry); Expression arg2 = GenerateMethodBody(exponentiation.Exponent, contextParameter, functionRegistry); return Expression.Call(null, typeof(Math).GetRuntimeMethod("Pow", new Type[2] { typeof(double), typeof(double) }), arg, arg2); } if (operation.GetType() == typeof(UnaryMinus)) { UnaryMinus unaryMinus = (UnaryMinus)operation; return Expression.Negate(GenerateMethodBody(unaryMinus.Argument, contextParameter, functionRegistry)); } if (operation.GetType() == typeof(And)) { And and = (And)operation; BinaryExpression left6 = Expression.NotEqual(GenerateMethodBody(and.Argument1, contextParameter, functionRegistry), Expression.Constant(0.0)); Expression right6 = Expression.NotEqual(GenerateMethodBody(and.Argument2, contextParameter, functionRegistry), Expression.Constant(0.0)); return Expression.Condition(Expression.And(left6, right6), Expression.Constant(1.0), Expression.Constant(0.0)); } if (operation.GetType() == typeof(Or)) { Or or = (Or)operation; BinaryExpression left7 = Expression.NotEqual(GenerateMethodBody(or.Argument1, contextParameter, functionRegistry), Expression.Constant(0.0)); Expression right7 = Expression.NotEqual(GenerateMethodBody(or.Argument2, contextParameter, functionRegistry), Expression.Constant(0.0)); return Expression.Condition(Expression.Or(left7, right7), Expression.Constant(1.0), Expression.Constant(0.0)); } if (operation.GetType() == typeof(LessThan)) { LessThan lessThan = (LessThan)operation; Expression left8 = GenerateMethodBody(lessThan.Argument1, contextParameter, functionRegistry); Expression right8 = GenerateMethodBody(lessThan.Argument2, contextParameter, functionRegistry); return Expression.Condition(Expression.LessThan(left8, right8), Expression.Constant(1.0), Expression.Constant(0.0)); } if (operation.GetType() == typeof(LessOrEqualThan)) { LessOrEqualThan lessOrEqualThan = (LessOrEqualThan)operation; Expression left9 = GenerateMethodBody(lessOrEqualThan.Argument1, contextParameter, functionRegistry); Expression right9 = GenerateMethodBody(lessOrEqualThan.Argument2, contextParameter, functionRegistry); return Expression.Condition(Expression.LessThanOrEqual(left9, right9), Expression.Constant(1.0), Expression.Constant(0.0)); } if (operation.GetType() == typeof(GreaterThan)) { GreaterThan greaterThan = (GreaterThan)operation; Expression left10 = GenerateMethodBody(greaterThan.Argument1, contextParameter, functionRegistry); Expression right10 = GenerateMethodBody(greaterThan.Argument2, contextParameter, functionRegistry); return Expression.Condition(Expression.GreaterThan(left10, right10), Expression.Constant(1.0), Expression.Constant(0.0)); } if (operation.GetType() == typeof(GreaterOrEqualThan)) { GreaterOrEqualThan greaterOrEqualThan = (GreaterOrEqualThan)operation; Expression left11 = GenerateMethodBody(greaterOrEqualThan.Argument1, contextParameter, functionRegistry); Expression right11 = GenerateMethodBody(greaterOrEqualThan.Argument2, contextParameter, functionRegistry); return Expression.Condition(Expression.GreaterThanOrEqual(left11, right11), Expression.Constant(1.0), Expression.Constant(0.0)); } if (operation.GetType() == typeof(Equal)) { Equal equal = (Equal)operation; Expression left12 = GenerateMethodBody(equal.Argument1, contextParameter, functionRegistry); Expression right12 = GenerateMethodBody(equal.Argument2, contextParameter, functionRegistry); return Expression.Condition(Expression.Equal(left12, right12), Expression.Constant(1.0), Expression.Constant(0.0)); } if (operation.GetType() == typeof(NotEqual)) { NotEqual notEqual = (NotEqual)operation; Expression left13 = GenerateMethodBody(notEqual.Argument1, contextParameter, functionRegistry); Expression right13 = GenerateMethodBody(notEqual.Argument2, contextParameter, functionRegistry); return Expression.Condition(Expression.NotEqual(left13, right13), Expression.Constant(1.0), Expression.Constant(0.0)); } if (operation.GetType() == typeof(Function)) { Function function = (Function)operation; FunctionInfo functionInfo = functionRegistry.GetFunctionInfo(function.FunctionName); Type type; Type[] parameters; Expression[] array2; if (functionInfo.IsDynamicFunc) { type = typeof(DynamicFunc<double, double>); parameters = new Type[1] { typeof(double[]) }; Expression[] array = new Expression[function.Arguments.Count]; for (int j = 0; j < function.Arguments.Count; j++) { array[j] = GenerateMethodBody(function.Arguments[j], contextParameter, functionRegistry); } array2 = new Expression[1] { Expression.NewArrayInit(typeof(double), array) }; } else { type = GetFuncType(functionInfo.NumberOfParameters); parameters = (from i in Enumerable.Range(0, functionInfo.NumberOfParameters) select typeof(double)).ToArray(); array2 = new Expression[functionInfo.NumberOfParameters]; for (int k = 0; k < functionInfo.NumberOfParameters; k++) { array2[k] = GenerateMethodBody(function.Arguments[k], contextParameter, functionRegistry); } } return Expression.Call(Expression.Convert(Expression.Property(Expression.Call(Expression.Property(contextParameter, "FunctionRegistry"), typeof(IFunctionRegistry).GetRuntimeMethod("GetFunctionInfo", new Type[1] { typeof(string) }), Expression.Constant(function.FunctionName)), "Function"), type), type.GetRuntimeMethod("Invoke", parameters), array2); } throw new ArgumentException("Unsupported operation \"" + operation.GetType().FullName + "\".", "operation"); } private Type GetFuncType(int numberOfParameters) { string text = ((numberOfParameters < 9) ? $"System.Func`{numberOfParameters + 1}" : $"System.Func`{numberOfParameters + 1}, {funcAssemblyQualifiedName}"); Type type = Type.GetType(text); if (type == null) { throw new InvalidOperationException("Couldn't get type of $" + text + "."); } Type[] array = new Type[numberOfParameters + 1]; for (int i = 0; i < array.Length; i++) { array[i] = typeof(double); } return type.MakeGenericType(array); } } public enum ExecutionMode { Interpreted, Compiled } public class FunctionInfo { public string FunctionName { get; } public int NumberOfParameters { get; } public bool IsIdempotent { get; } public bool IsDynamicFunc { get; } public Delegate Function { get; } public FunctionInfo(string functionName, int numberOfParameters, bool isIdempotent, bool isDynamicFunc, Delegate function) { FunctionName = functionName; NumberOfParameters = numberOfParameters; IsIdempotent = isIdempotent; IsDynamicFunc = isDynamicFunc; Function = function; } } public class FunctionRegistry : IFunctionRegistry, IEnumerable<FunctionInfo>, IEnumerable { private const string DynamicFuncName = "Adletec.Sonic.DynamicFunc"; private readonly Dictionary<string, FunctionInfo> functions; private readonly bool guardedMode; public FunctionRegistry(bool caseSensitive, bool guardedMode) { functions = (caseSensitive ? new Dictionary<string, FunctionInfo>() : new Dictionary<string, FunctionInfo>(StringComparer.OrdinalIgnoreCase)); this.guardedMode = guardedMode; } public IEnumerator<FunctionInfo> GetEnumerator() { return functions.Values.GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } public FunctionInfo GetFunctionInfo(string functionName) { if (string.IsNullOrEmpty(functionName)) { throw new ArgumentNullException("functionName"); } if (!functions.TryGetValue(functionName, out var value)) { return null; } return value; } public void RegisterFunction(string functionName, Delegate function) { RegisterFunction(functionName, function, isIdempotent: true); } public void RegisterFunction(string functionName, Delegate function, bool isIdempotent) { if (string.IsNullOrEmpty(functionName)) { throw new ArgumentNullException("functionName"); } if ((object)function == null) { throw new ArgumentNullException("function"); } Type type = function.GetType(); bool flag = false; int num = -1; if (type.FullName != null && type.FullName.StartsWith("System.Func")) { Type[] genericTypeArguments = type.GenericTypeArguments; for (int i = 0; i < genericTypeArguments.Length; i++) { if (genericTypeArguments[i] != typeof(double)) { throw new ArgumentException("Only doubles are supported as function arguments.", "function"); } } num = function.GetMethodInfo().GetParameters().Count((System.Reflection.ParameterInfo p) => p.ParameterType == typeof(double)); } else { if (type.FullName == null || !type.FullName.StartsWith("Adletec.Sonic.DynamicFunc")) { throw new ArgumentException("Only System.Func and Adletec.Sonic.DynamicFunc delegates are permitted.", "function"); } flag = true; } if (guardedMode && functions.ContainsKey(functionName)) { throw new ArgumentException("The function \"" + functionName + "\" cannot be overwritten."); } if (functions.ContainsKey(functionName) && functions[functionName].NumberOfParameters != num) { throw new ArgumentException("The number of parameters cannot be changed when overwriting a method."); } if (functions.ContainsKey(functionName) && functions[functionName].IsDynamicFunc != flag) { throw new ArgumentException("A Func can only be overwritten by another Func and a DynamicFunc can only be overwritten by another DynamicFunc."); } FunctionInfo value = new FunctionInfo(functionName, num, isIdempotent, flag, function); functions[functionName] = value; } public bool IsFunctionName(string functionName) { if (string.IsNullOrEmpty(functionName)) { throw new ArgumentNullException("functionName"); } return functions.ContainsKey(functionName); } } public interface IConstantRegistry : IEnumerable<ConstantInfo>, IEnumerable { ConstantInfo GetConstantInfo(string constantName); bool IsConstantName(string constantName); void RegisterConstant(string constantName, double value); } public interface IExecutor { double Execute(Operation operation, IFunctionRegistry functionRegistry, IConstantRegistry constantRegistry); double Execute(Operation operation, IFunctionRegistry functionRegistry, IConstantRegistry constantRegistry, IDictionary<string, double> variables); Func<IDictionary<string, double>, double> BuildFormula(Operation operation, IFunctionRegistry functionRegistry, IConstantRegistry constantRegistry); } public interface IFunctionRegistry : IEnumerable<FunctionInfo>, IEnumerable { FunctionInfo GetFunctionInfo(string functionName); bool IsFunctionName(string functionName); void RegisterFunction(string functionName, Delegate function); void RegisterFunction(string functionName, Delegate function, bool isIdempotent); } public class Interpreter : IExecutor { private readonly bool caseSensitive; private readonly bool guardedMode; public Interpreter() : this(caseSensitive: false, guardedMode: false) { } public Interpreter(bool caseSensitive, bool guardedMode) { this.caseSensitive = caseSensitive; this.guardedMode = guardedMode; } public Func<IDictionary<string, double>, double> BuildFormula(Operation operation, IFunctionRegistry functionRegistry, IConstantRegistry constantRegistry) { if (caseSensitive) { if (guardedMode) { return delegate(IDictionary<string, double> variables) { VariableVerifier.VerifyVariableNames(variables, constantRegistry, functionRegistry); return Execute(operation, functionRegistry, constantRegistry, variables); }; } return (IDictionary<string, double> variables) => Execute(operation, functionRegistry, constantRegistry, variables); } if (guardedMode) { return delegate(IDictionary<string, double> variables) { variables = EngineUtil.ConvertToCaseInsensitiveDictionary(variables); VariableVerifier.VerifyVariableNames(variables, constantRegistry, functionRegistry); return Execute(operation, functionRegistry, constantRegistry, variables); }; } return delegate(IDictionary<string, double> variables) { variables = EngineUtil.ConvertToCaseInsensitiveDictionary(variables); return Execute(operation, functionRegistry, constantRegistry, variables); }; } public double Execute(Operation operation, IFunctionRegistry functionRegistry, IConstantRegistry constantRegistry) { return Execute(operation, functionRegistry, constantRegistry, new Dictionary<string, double>()); } public double Execute(Operation operation, IFunctionRegistry functionRegistry, IConstantRegistry constantRegistry, IDictionary<string, double> variables) { return ExecuteInternal(operation, functionRegistry, variables); } private double ExecuteInternal(Operation operation, IFunctionRegistry functionRegistry, IDictionary<string, double> variables) { if (operation == null) { throw new ArgumentNullException("operation"); } if (operation.GetType() == typeof(IntegerConstant)) { return ((IntegerConstant)operation).Value; } if (operation.GetType() == typeof(FloatingPointConstant)) { return ((FloatingPointConstant)operation).Value; } if (operation.GetType() == typeof(Variable)) { Variable variable = (Variable)operation; if (variables.TryGetValue(variable.Name, out var value)) { return value; } throw new VariableNotDefinedException("The variable \"" + variable.Name + "\" used is not defined.", variable.Name); } if (operation.GetType() == typeof(Multiplication)) { Multiplication multiplication = (Multiplication)operation;