Please disclose if your mod was created primarily using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of Adletec Sonic v1.6.0
BepInEx/core/Adletec.Sonic/netstandard2.0/Adletec.Sonic.dll
Decompiled 5 months 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;