A parser and interpreter for FEEL (Friendly Enough Expression Language) written in Scala (see What is FEEL?).
The FEEL engine started as a slack time project, grown into a community-driven project, and is now officially maintained by Camunda. 🚀
It is integrated in the following projects:
- Camunda 7 as part of the DMN engine
- Camunda 8 (Zeebe) as the expression language
- DMN-Scala as part of the DMN engine
Features: ✨
- Full support for unary-tests and expressions
- Including built-in functions
- Extensible by custom functions and object mappers
Please have a look at the Language Guide. It describes how to write FEEL expressions (e.g. data types, language constructs, built-in functions, etc.).
Want to try it out? Use the FEEL Playground to evaluate FEEL expressions directly in the browser.
Add the FEEL engine as a dependency to your project:
<dependency>
<groupId>org.camunda.feel</groupId>
<artifactId>feel-engine</artifactId>
<version>${VERSION}</version>
</dependency>The FEEL engine provides an API to evaluate expressions and unary-tests.
Java:
final FeelEngineApi engine = FeelEngineBuilder.forJava().build();
final Map<String, Object> variables = Map.of("x", 21);
final EvaluationResult evaluationResult = engine.evaluateExpression("x + 21", variables);
if (evaluationResult.isSuccess()) {
final Object value = evaluationResult.result();
System.out.println("result is " + value); // result is 42
} else {
throw new RuntimeException(evaluationResult.failure().message());
}Scala:
val engine: FeelEngineApi = FeelEngineBuilder().build()
val evaluationResult: EvaluationResult = engine.evaluateExpression("x + 21", Map("x" -> 21))
if (evaluationResult.isSuccess) {
println(s"result: ${evaluationResult.result}")
} else {
println(s"failure: ${evaluationResult.failure.message}")
}The FEEL engine implements Java's script engine API JSR 223.
The FEEL expression evaluation is registered under the following names:
feelfeel-scalahttp://www.omg.org/spec/FEEL/20140401(FEEL namespace)
The FEEL unary-tests evaluation is registered under the following names:
feel-unary-testsfeel-scala-unary-tests
The FEEL engine can be extended and customized by implementing one of the following SPIs (Service Provider Interface). The implementations are loaded via Java's ServiceLoader mechanism.
Implement org.camunda.feel.context.JavaFunctionProvider (Java) or org.camunda.feel.context.CustomFunctionProvider (Scala) to provide custom functions.
public class CustomJavaFunctionProvider extends JavaFunctionProvider {
private static final Map<String, JavaFunction> functions = new HashMap<>();
static {
final JavaFunction function = new JavaFunction(Arrays.asList("x"), args -> {
final ValNumber arg = (ValNumber) args.get(0);
int x = arg.value().intValue();
return new ValNumber(BigDecimal.valueOf(x - 1));
});
functions.put("decr", function);
}
@Override
public Optional<JavaFunction> resolveFunction(String functionName) {
return Optional.ofNullable(functions.get(functionName));
}
@Override
public Collection<String> getFunctionNames() {
return functions.keySet();
}
}Register the provider by creating the file META-INF/services/org.camunda.feel.context.CustomFunctionProvider with the fully qualified class name.
Implement org.camunda.feel.valuemapper.JavaCustomValueMapper (Java) or org.camunda.feel.valuemapper.CustomValueMapper (Scala) to transform custom types.
public class CustomValueMapper extends JavaCustomValueMapper {
@Override
public Optional<Val> toValue(Object x, Function<Object, Val> innerValueMapper) {
if (x instanceof MyType) {
return Optional.of(new ValString(((MyType) x).getName()));
}
return Optional.empty();
}
@Override
public Optional<Object> unpackValue(Val value, Function<Val, Object> innerValueMapper) {
return Optional.empty();
}
@Override
public int priority() {
return 1;
}
}Register the mapper by creating the file META-INF/services/org.camunda.feel.valuemapper.CustomValueMapper with the fully qualified class name.
Implement org.camunda.feel.FeelEngineClock to replace the system clock used by the engine (e.g. for testing).
import java.time.Instant;
import java.time.ZonedDateTime;
public class MyClock extends FeelEngineClock {
@Override
public ZonedDateTime getCurrentTime() {
return ZonedDateTime.now(); // return the current time from your clock
}
}Register the clock by creating the file META-INF/services/org.camunda.feel.FeelEngineClock with the fully qualified class name.
Contributions are welcome 🎉 Please have a look at the Contribution Guide.