Search code examples
javascriptjavapythondslrule-engine

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.


Solution

  • 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.