Spot Feel

A light-weight DMN FEEL expression evaluator and business rule engine in Ruby.

This gem implements a subset of FEEL (Friendly Enough Expression Language) as defined in the DMN 1.3 specification with some additional extensions.

FEEL expressions are parsed into an abstract syntax tree (AST) and then evaluated in a context. The context is a hash of variables and functions to be resolved inside the expression.

Expressions are safe, side-effect free, and deterministic. They are ideal for capturing business logic for storage in a database or embedded in DMN, BPMN, or Form documents for execution in a workflow engine like Spot Flow.

This project was inspired by these excellent libraries:

Usage

To evaluate an expression:

SpotFeel.evaluate('"👋 Hello " + name', variables: { name: "World" })
# => "👋 Hello World"

A slightly more complex example:

variables = {
  person: {
    name: "Eric",
    age: 59,
  }
}
SpotFeel.evaluate('if person.age >= 18 then "adult" else "minor"', variables:)
# => "adult"

Calling a built-in function:

SpotFeel.evaluate('sum([1, 2, 3])')
# => 6

Calling a user-defined function:

SpotFeel.config.functions = {
  "reverse": ->(s) { s.reverse }
}
SpotFeel.evaluate('reverse("Hello World!")', functions:)
# => "!dlroW olleH"

To evaluate a unary tests:

SpotFeel.test(3, '<= 10, > 50'))
# => true
SpotFeel.test("Eric", '"Bob", "Holly", "Eric"')
# => true

Decision Table

To evaluate a DMN decision table:

variables = {
  violation: {
    type: "speed",
    actual_speed: 100,
    speed_limit: 65,
  }
}
result = SpotFeel.decide('fine_decision', definitions_xml: fixture_source("fine.dmn"), variables:)
# => { "amount" => 1000, "points" => 7 })

To get a list of variables or functions used in an expression:

LiteralExpression.new(text: 'person.first_name + " " + person.last_name').variable_names
# => ["person.age, person.last_name"]
LiteralExpression.new(text: 'sum([1, 2, 3])').function_names
# => ["sum"]
UnaryTests.new(text: '> speed - speed_limit').variable_names
# => ["speed, speed_limit"]

Supported Features

Data Types

  • [x] Boolean (true, false)
  • [x] Number (integer, decimal)
  • [x] String (single and double quoted)
  • [x] Date, Time, Duration (ISO 8601)
  • [x] List (array)
  • [x] Context (hash)

Expressions

  • [x] Literal
  • [x] Path
  • [x] Arithmetic
  • [x] Comparison
  • [x] Function Invocation
  • [x] Positional Parameters
  • [x] If Expression
  • [ ] For Expression
  • [ ] Quantified Expression
  • [ ] Filter Expression
  • [ ] Disjunction
  • [ ] Conjuction
  • [ ] Instance Of
  • [ ] Function Definition

Unary Tests

  • [x] Comparison
  • [x] Interval/Range (inclusive and exclusive)
  • [x] Disjunction
  • [x] Negation
  • [ ] Expression

Built-in Functions

  • [x] Conversion: string, number
  • [x] Boolean: is defined, get or else
  • [x] String: substring, substring before, substring after, string length, upper case, lower case, contains, starts with, ends with, matches, replace, split, strip, extract
  • [x] Numeric: decimal, floor, ceiling, round, abs, modulo, sqrt, log, exp, odd, even, random number
  • [x] List: list contains, count, min, max, sum, product, mean, median, stddev, mode, all, any, sublist, append, concatenate, insert before, remove, reverse, index of, union, distinct values, duplicate values, flatten, sort, string join
  • [x] Context: get entries, get value, get keys
  • [x] Temporal: now, today, day of week, day of year, month of year, week of year

DMN

  • [x] Parse DMN XML documents
  • [x] Evaluate DMN Decision Tables
  • [x] Evaluate dependent DMN Decision Tables
  • [x] Evaluate Expression Decisions

Installation

Execute:

$ bundle add "spot_feel"

Or install it directly:

$ gem install spot_feel

Setup

$ git clone ...
$ bin/setup
$ bin/guard

Development

Treetop Doumentation is a good place to start learning about Treetop.

License

The gem is available as open source under the terms of the MIT License.

Developed by Connected Bits