Reza Owliaei
Reza Owliaei

Reputation: 3363

Pars Text or script into C# code

I want to store combination of method invocations and formulas as text or script into database. for example, i want to store something like this in Database as a string and execute it somewhere in code:

if(Vessel.Weight>200) 
{
    return Cargo.Weight*Cargo.Tariff*2
} 
else 
{
    Cargo.Weight*Cargo.Tariff
} 

invocation:

var cost = executeFormula("CalculateTariff",new {Cargo= GetCargo(), Vessel=GetVessel()});

because these rules will change frequently and i don't want to deploy dll (CLR solution), and i don't want to store these rules as SP and mix business rules with DAL.

Any idea or tool?

Upvotes: 0

Views: 428

Answers (1)

user1088520
user1088520

Reputation:

If you place all values in a hashtable or a dictionary by changing the . with a _ (Vessel.Weight will become "Vessel_Weight") and simplify the syntax to one line it will be much easier to create a solution. This rule can be written for example as:

result=(Vessel_Weight>200)
    ?(Cargo_Weight*Cargo_Tariff*2)
    :(Cargo_Weight*Cargo_Tariff)

Having rules defined like the above one you can use the following (draft, not optimal ...) code as a guide for a properly coded function that will do the job. I repeat that the following code is not perfect, but bottom line it's more than enough as a proof of concept.

Dictionary<string, dynamic> compute = new Dictionary<string, dynamic>();
compute.Add("Vessel_Weight", 123);
compute.Add("Cargo_Weight", 24);
compute.Add("Cargo_Tariff", 9);

    string rule = "result=(Vessel_Weight>200)
        ?(Cargo_Weight*Cargo_Tariff*2)
        :(Cargo_Weight*Cargo_Tariff)";

    string process = rule.Replace(" ", "");
    foreach (Match level1 in Regex.Matches(process, "\\([^\\)]+\\)"))
    {
        string parenthesis = level1.Value;
        string keepit = parenthesis;
        Console.Write("{0} -> ", parenthesis);
        // replace all named variable with values from the dictionary
        foreach (Match level2 in Regex.Matches(parenthesis, "[a-zA-z0-9_]+"))
        {
            string variable = level2.Value;
            if (Regex.IsMatch(variable, "[a-zA-z_]+"))
            {
                if (!compute.ContainsKey(variable))
                    throw new Exception("Variable not found");
                parenthesis = parenthesis.Replace(variable, compute[variable].ToString());
            }
        }
        parenthesis = parenthesis.Replace("(", "").Replace(")", "");
        Console.Write("{0} -> ", parenthesis);
        // do the math
        List<double> d = new List<double>();
        foreach (Match level3 in Regex.Matches(parenthesis, "[0-9]+(\\.[0-9]+)?"))
        {
            d.Add(double.Parse(level3.Value));
            parenthesis = Regex.Replace(parenthesis, level3.Value, "");
        }
        double start = d[0];
        for (var i = 1; i < d.Count; i++)
        {
            switch (parenthesis[i - 1])
            {
                case '+':
                    start += d[i];
                    break;
                case '-':
                    start -= d[i];
                    break;
                case '*':
                    start *= d[i];
                    break;
                case '/':
                    start /= d[i];
                    break;
                case '=':
                    start = (start == d[i]) ? 0 : 1;
                    break;
                case '>':
                    start = (start > d[i]) ? 0 : 1;
                    break;
                case '<':
                    start = (start < d[i]) ? 0 : 1;
                    break;
            }
        }
        parenthesis = start.ToString();
        Console.WriteLine(parenthesis);
        rule = rule.Replace(keepit, parenthesis);
    }
    Console.WriteLine(rule);
    // peek a value in case of a condition
    string condition = "[0-9]+(\\.[0-9]+)?\\?[0-9]+(\\.[0-9]+)?:[0-9]+(\\.[0-9]+)?";
    if (Regex.IsMatch(rule, condition))
    {
        MatchCollection m = Regex.Matches(rule, "[0-9]+(\\.[0-9]+)?");
        int check = int.Parse(m[0].Value) + 1;
        rule = rule.Replace(Regex.Match(rule, condition).Value, m[check].Value);
    }
    Console.WriteLine(rule);
    // final touch
    int equal = rule.IndexOf("=");
    compute.Add(rule.Substring(0, equal - 1), double.Parse(rule.Substring(equal + 1)));

Now the result is a named item in the dictionary. This way you may process more rules in the sense of intermediate results and have a final rule based on them. The code as is written does not guarantee correct execution order for arithmetic operations, but if you keep your rules simple (and possibly split them if is needed) your will achieve your goal.

Upvotes: 1

Related Questions