hch
2025-08-18 5c5a5cc66227439be7a7b61da5d1ff68cf187ac3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
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<ParameterInfo> parameters;
        private IDictionary<string, double> constants;
 
        /// <summary>
        /// Creates a new instance of the FormulaBuilder class.
        /// </summary>
        /// <param name="formulaText">
        /// A calculation engine instance that can be used for interpreting and executing 
        /// the formula.
        /// </param>
        internal FormulaBuilder(string formulaText, bool caseSensitive, CalculationEngine engine)
        {
            this.parameters = new List<ParameterInfo>();
            this.constants = new Dictionary<string, double>();
            this.formulaText = formulaText;
            this.engine = engine;
            this.caseSensitive = caseSensitive;
        }
 
        /// <summary>
        /// Add a new parameter to the formula being constructed. Parameters are
        /// added in the order of which they are defined.
        /// </summary>
        /// <param name="name">The name of the parameter.</param>
        /// <param name="dataType">The date type of the parameter.</param>
        /// <returns>The <see cref="FormulaBuilder"/> instance.</returns>
        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;
        }
 
        /// <summary>
        /// Add a new constant to the formula being constructed.
        /// </summary>
        /// <param name="name">The name of the constant.</param>
        /// <param name="constantValue">The value of the constant. Variables for which a constant value is defined will be replaced at pre-compilation time.</param>
        /// <returns>The <see cref="FormulaBuilder"/> instance.</returns>
        public FormulaBuilder Constant(string name, int constantValue)
        {
            return Constant(name, (double)constantValue);
        }
 
        /// <summary>
        /// Add a new constant to the formula being constructed. The
        /// </summary>
        /// <param name="name">The name of the constant.</param>
        /// <param name="constantValue">The value of the constant.</param>
        /// <returns>The <see cref="FormulaBuilder"/> instance.</returns>
        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;
        }
 
        /// <summary>
        /// Define the result data type for the formula.
        /// </summary>
        /// <param name="dataType">The result data type for the formula.</param>
        /// <returns>The <see cref="FormulaBuilder"/> instance.</returns>
        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;
        }
 
        /// <summary>
        /// Build the formula defined. This will create a func delegate matching with the parameters
        /// and the return type specified.
        /// </summary>
        /// <returns>The func delegate for the defined formula.</returns>
        public Delegate Build()
        {
            if (!resultDataType.HasValue)
                throw new Exception("Please define a result data type for the formula.");
 
            Func<IDictionary<string, double>, 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);
            });
        }
    }
}