using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Reflection.Emit; using System.Text; using Jace.Operations; using Jace.Util; namespace Jace.Execution { public class FormulaBuilder { private readonly CalculationEngine engine; private string formulaText; private bool caseSensitive; private DataType? resultDataType; private List parameters; private IDictionary constants; /// /// Creates a new instance of the FormulaBuilder class. /// /// /// A calculation engine instance that can be used for interpreting and executing /// the formula. /// internal FormulaBuilder(string formulaText, bool caseSensitive, CalculationEngine engine) { this.parameters = new List(); this.constants = new Dictionary(); this.formulaText = formulaText; this.engine = engine; this.caseSensitive = caseSensitive; } /// /// Add a new parameter to the formula being constructed. Parameters are /// added in the order of which they are defined. /// /// The name of the parameter. /// The date type of the parameter. /// The instance. public FormulaBuilder Parameter(string name, DataType dataType) { if (string.IsNullOrEmpty(name)) throw new ArgumentNullException("name"); if (engine.FunctionRegistry.IsFunctionName(name)) throw new ArgumentException(string.Format("The name \"{0}\" is a function name. Parameters cannot have this name.", name), "name"); if (parameters.Any(p => p.Name == name)) throw new ArgumentException(string.Format("A parameter with the name \"{0}\" was already defined.", name), "name"); parameters.Add(new ParameterInfo() {Name = name, DataType = dataType}); return this; } /// /// Add a new constant to the formula being constructed. /// /// The name of the constant. /// The value of the constant. Variables for which a constant value is defined will be replaced at pre-compilation time. /// The instance. public FormulaBuilder Constant(string name, int constantValue) { return Constant(name, (double)constantValue); } /// /// Add a new constant to the formula being constructed. The /// /// The name of the constant. /// The value of the constant. /// The instance. public FormulaBuilder Constant(string name, double constantValue) { if (string.IsNullOrEmpty(name)) throw new ArgumentNullException("name"); if (constants.Any(p => p.Key == name)) throw new ArgumentException(string.Format("A constant with the name \"{0}\" was already defined.", name), "name"); constants[name] = constantValue; return this; } /// /// Define the result data type for the formula. /// /// The result data type for the formula. /// The instance. public FormulaBuilder Result(DataType dataType) { if (resultDataType.HasValue) throw new InvalidOperationException("The result can only be defined once for a given formula."); resultDataType = dataType; return this; } /// /// Build the formula defined. This will create a func delegate matching with the parameters /// and the return type specified. /// /// The func delegate for the defined formula. public Delegate Build() { if (!resultDataType.HasValue) throw new Exception("Please define a result data type for the formula."); Func, double> formula = engine.Build(formulaText, constants); FuncAdapter adapter = new FuncAdapter(); return adapter.Wrap(parameters, variables => { if(!caseSensitive) variables = EngineUtil.ConvertVariableNamesToLowerCase(variables); engine.VerifyVariableNames(variables); // Add the reserved variables to the dictionary foreach (ConstantInfo constant in engine.ConstantRegistry) variables.Add(constant.ConstantName, constant.Value); return formula(variables); }); } } }