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