Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Creating a DSL expressions parser / rules engine

I'm building an app which has a feature for embedding expressions/rules in a config yaml file. So for example user can reference a variable defined in yaml file like ${variables.name == 'John'} or ${is_equal(variables.name, 'John')}. I can probably get by with simple expressions but I want to support complex rules/expressions such ${variables.name == 'John'} and (${variables.age > 18} OR ${variables.adult == true})

I'm looking for a parsing/dsl/rules-engine library that can support these type of expressions and normalize it. I'm open using ruby, javascript, java, or python if anyone knows of a library for that languages.

One option I thought of was to just support javascript as conditons/rules and basically pass it through eval with the right context setup with access to variables and other reference-able vars.

like image 202
ed1t Avatar asked Jun 29 '20 16:06

ed1t


Video Answer


1 Answers

I don't know if you use Golang or not, but if you use it, I recommend this https://github.com/antonmedv/expr.

I have used it for parsing bot strategy that (stock options bot). This is from my test unit:

func TestPattern(t *testing.T) {
    a := "pattern('asdas asd 12dasd') && lastdigit(23asd) < sma(50) && sma(14) > sma(12) && ( macd(5,20) > macd_signal(12,26,9) || macd(5,20) <= macd_histogram(12,26,9) )"

    r, _ := regexp.Compile(`(\w+)(\s+)?[(]['\d.,\s\w]+[)]`)
    indicator := r.FindAllString(a, -1)
    t.Logf("%v\n", indicator)
    t.Logf("%v\n", len(indicator))

    for _, i := range indicator {
        t.Logf("%v\n", i)
        if strings.HasPrefix(i, "pattern") {
            r, _ = regexp.Compile(`pattern(\s+)?\('(.+)'\)`)
            check1 := r.ReplaceAllString(i, "$2")
            t.Logf("%v\n", check1)
            r, _ = regexp.Compile(`[^du]`)
            check2 := r.FindAllString(check1, -1)
            t.Logf("%v\n", len(check2))
        } else if strings.HasPrefix(i, "lastdigit") {
            r, _ = regexp.Compile(`lastdigit(\s+)?\((.+)\)`)
            args := r.ReplaceAllString(i, "$2")
            r, _ = regexp.Compile(`[^\d]`)
            parameter := r.FindAllString(args, -1)
            t.Logf("%v\n", parameter)
        } else {

        }
    }
}

Combine it with regex and you have good (if not great, string translator).

And for Java, I personally use https://github.com/ridencww/expression-evaluator but not for production. It has similar feature with above link.

It supports many condition and you don't have to worry about Parentheses and Brackets.

Assignment  =
Operators   + - * / DIV MOD % ^ 
Logical     < <= == != >= > AND OR NOT
Ternary     ? :  
Shift       << >>
Property    ${<id>}
DataSource  @<id>
Constants   NULL PI
Functions   CLEARGLOBAL, CLEARGLOBALS, DIM, GETGLOBAL, SETGLOBAL
            NOW PRECISION

Hope it helps.

like image 72
Fahim Bagar Avatar answered Sep 30 '22 11:09

Fahim Bagar