From 67bdc5f6801e7934cc96c2d3fcf6ee5b1142536f Mon Sep 17 00:00:00 2001 From: camunda-bot Date: Fri, 12 Jan 2024 10:10:34 +0000 Subject: [PATCH] Deploy website - based on 6c694c4b8691ed67ebfc924b5ac2d99168110a81 --- 404.html | 4 ++-- assets/js/{a7980671.880adb68.js => a7980671.d99d97f2.js} | 2 +- .../{runtime~main.2bfea792.js => runtime~main.2b285b67.js} | 2 +- docs/1.10/feel-grammar/index.html | 4 ++-- .../developer-guide/function-provider-spi/index.html | 4 ++-- .../reference/developer-guide/value-mapper-spi/index.html | 4 ++-- docs/1.10/reference/index.html | 4 ++-- docs/1.10/samples/index.html | 4 ++-- docs/1.11/feel-grammar/index.html | 4 ++-- docs/1.11/reference/developer-guide/clock-spi/index.html | 4 ++-- .../developer-guide/function-provider-spi/index.html | 4 ++-- .../reference/developer-guide/value-mapper-spi/index.html | 4 ++-- docs/1.11/reference/index.html | 4 ++-- docs/1.11/samples/index.html | 4 ++-- docs/1.12/feel-grammar/index.html | 4 ++-- docs/1.12/reference/developer-guide/clock-spi/index.html | 4 ++-- .../developer-guide/function-provider-spi/index.html | 4 ++-- .../reference/developer-guide/value-mapper-spi/index.html | 4 ++-- docs/1.12/reference/index.html | 4 ++-- docs/1.12/samples/index.html | 4 ++-- docs/1.13/reference/developer-guide/clock-spi/index.html | 4 ++-- .../developer-guide/function-provider-spi/index.html | 4 ++-- .../reference/developer-guide/value-mapper-spi/index.html | 4 ++-- docs/1.13/reference/index.html | 4 ++-- docs/1.13/samples/context-samples/index.html | 4 ++-- docs/1.13/samples/index.html | 4 ++-- docs/1.13/samples/list-samples/index.html | 4 ++-- docs/1.13/samples/temporal-samples/index.html | 4 ++-- .../1.14/reference/developer-guide/bootstrapping/index.html | 4 ++-- docs/1.14/reference/developer-guide/clock-spi/index.html | 4 ++-- .../developer-guide/developer-guide-introduction/index.html | 4 ++-- .../developer-guide/function-provider-spi/index.html | 4 ++-- .../reference/developer-guide/value-mapper-spi/index.html | 4 ++-- docs/1.14/reference/index.html | 4 ++-- docs/1.14/samples/context-samples/index.html | 4 ++-- docs/1.14/samples/index.html | 4 ++-- docs/1.14/samples/list-samples/index.html | 4 ++-- docs/1.14/samples/temporal-samples/index.html | 4 ++-- docs/1.15/changelog/index.html | 4 ++-- docs/1.15/playground/index.html | 4 ++-- .../1.15/reference/developer-guide/bootstrapping/index.html | 4 ++-- docs/1.15/reference/developer-guide/clock-spi/index.html | 4 ++-- .../developer-guide/developer-guide-introduction/index.html | 4 ++-- .../developer-guide/function-provider-spi/index.html | 4 ++-- .../reference/developer-guide/value-mapper-spi/index.html | 4 ++-- docs/1.15/reference/index.html | 4 ++-- docs/1.15/samples/context-samples/index.html | 4 ++-- docs/1.15/samples/index.html | 4 ++-- docs/1.15/samples/list-samples/index.html | 4 ++-- docs/1.15/samples/temporal-samples/index.html | 4 ++-- docs/1.15/tutorial/index.html | 4 ++-- docs/1.15/tutorial/tutorial-1-1/index.html | 4 ++-- docs/1.15/tutorial/tutorial-1-2/index.html | 4 ++-- docs/1.15/tutorial/tutorial-1-3/index.html | 4 ++-- docs/1.15/tutorial/tutorial-2-1/index.html | 4 ++-- docs/1.15/tutorial/tutorial-3-1/index.html | 4 ++-- docs/1.15/tutorial/tutorial-3-2/index.html | 4 ++-- docs/1.15/tutorial/tutorial-4-1/index.html | 4 ++-- docs/1.16/changelog/index.html | 4 ++-- docs/1.16/learn/challenge/chapter-1/index.html | 4 ++-- docs/1.16/learn/challenge/chapter-2/index.html | 4 ++-- docs/1.16/learn/challenge/chapter-3/index.html | 4 ++-- docs/1.16/learn/challenge/chapter-4/index.html | 4 ++-- docs/1.16/learn/challenge/chapter-5/index.html | 4 ++-- docs/1.16/learn/challenge/chapter-6/index.html | 4 ++-- docs/1.16/learn/challenge/index.html | 4 ++-- docs/1.16/learn/index.html | 4 ++-- docs/1.16/learn/samples/context-samples/index.html | 4 ++-- docs/1.16/learn/samples/list-samples/index.html | 4 ++-- docs/1.16/learn/samples/temporal-samples/index.html | 4 ++-- docs/1.16/playground/index.html | 4 ++-- docs/1.16/playground/repl/index.html | 4 ++-- .../1.16/reference/developer-guide/bootstrapping/index.html | 4 ++-- docs/1.16/reference/developer-guide/clock-spi/index.html | 4 ++-- .../developer-guide/developer-guide-introduction/index.html | 4 ++-- .../developer-guide/function-provider-spi/index.html | 4 ++-- .../reference/developer-guide/value-mapper-spi/index.html | 4 ++-- docs/1.16/reference/index.html | 4 ++-- docs/changelog/index.html | 4 ++-- docs/learn/challenge/chapter-1/index.html | 4 ++-- docs/learn/challenge/chapter-2/index.html | 4 ++-- docs/learn/challenge/chapter-3/index.html | 4 ++-- docs/learn/challenge/chapter-4/index.html | 4 ++-- docs/learn/challenge/chapter-5/index.html | 4 ++-- docs/learn/challenge/chapter-6/index.html | 4 ++-- docs/learn/challenge/index.html | 4 ++-- docs/learn/index.html | 4 ++-- docs/learn/samples/context-samples/index.html | 4 ++-- docs/learn/samples/list-samples/index.html | 4 ++-- docs/learn/samples/temporal-samples/index.html | 4 ++-- docs/next/changelog/index.html | 4 ++-- docs/next/learn/challenge/chapter-1/index.html | 4 ++-- docs/next/learn/challenge/chapter-2/index.html | 4 ++-- docs/next/learn/challenge/chapter-3/index.html | 4 ++-- docs/next/learn/challenge/chapter-4/index.html | 4 ++-- docs/next/learn/challenge/chapter-5/index.html | 4 ++-- docs/next/learn/challenge/chapter-6/index.html | 4 ++-- docs/next/learn/challenge/index.html | 4 ++-- docs/next/learn/index.html | 4 ++-- docs/next/learn/samples/context-samples/index.html | 4 ++-- docs/next/learn/samples/list-samples/index.html | 4 ++-- docs/next/learn/samples/temporal-samples/index.html | 4 ++-- docs/next/playground/index.html | 4 ++-- docs/next/playground/repl/index.html | 4 ++-- .../next/reference/developer-guide/bootstrapping/index.html | 4 ++-- docs/next/reference/developer-guide/clock-spi/index.html | 4 ++-- .../developer-guide/developer-guide-introduction/index.html | 4 ++-- .../developer-guide/function-provider-spi/index.html | 6 +++--- .../reference/developer-guide/value-mapper-spi/index.html | 4 ++-- docs/next/reference/index.html | 4 ++-- docs/playground/index.html | 4 ++-- docs/playground/repl/index.html | 4 ++-- docs/reference/developer-guide/bootstrapping/index.html | 4 ++-- docs/reference/developer-guide/clock-spi/index.html | 4 ++-- .../developer-guide/developer-guide-introduction/index.html | 4 ++-- .../developer-guide/function-provider-spi/index.html | 4 ++-- docs/reference/developer-guide/value-mapper-spi/index.html | 4 ++-- docs/reference/index.html | 4 ++-- index.html | 4 ++-- search/index.html | 4 ++-- 120 files changed, 239 insertions(+), 239 deletions(-) rename assets/js/{a7980671.880adb68.js => a7980671.d99d97f2.js} (92%) rename assets/js/{runtime~main.2bfea792.js => runtime~main.2b285b67.js} (99%) diff --git a/404.html b/404.html index aab132e32..bf688907b 100644 --- a/404.html +++ b/404.html @@ -5,13 +5,13 @@ Page Not Found | FEEL-Scala - +
Skip to main content

Page Not Found

We could not find what you were looking for.

Please contact the owner of the site that linked you to the original URL and let them know their link is broken.

- + \ No newline at end of file diff --git a/assets/js/a7980671.880adb68.js b/assets/js/a7980671.d99d97f2.js similarity index 92% rename from assets/js/a7980671.880adb68.js rename to assets/js/a7980671.d99d97f2.js index 1d6faaab5..869028d3a 100644 --- a/assets/js/a7980671.880adb68.js +++ b/assets/js/a7980671.d99d97f2.js @@ -1 +1 @@ -"use strict";(self.webpackChunkfeel_scala=self.webpackChunkfeel_scala||[]).push([[3084],{3905:(e,n,t)=>{t.d(n,{Zo:()=>c,kt:()=>f});var a=t(7294);function r(e,n,t){return n in e?Object.defineProperty(e,n,{value:t,enumerable:!0,configurable:!0,writable:!0}):e[n]=t,e}function i(e,n){var t=Object.keys(e);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);n&&(a=a.filter((function(n){return Object.getOwnPropertyDescriptor(e,n).enumerable}))),t.push.apply(t,a)}return t}function o(e){for(var n=1;n=0||(r[t]=e[t]);return r}(e,n);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(a=0;a=0||Object.prototype.propertyIsEnumerable.call(e,t)&&(r[t]=e[t])}return r}var u=a.createContext({}),s=function(e){var n=a.useContext(u),t=n;return e&&(t="function"==typeof e?e(n):o(o({},n),e)),t},c=function(e){var n=s(e.components);return a.createElement(u.Provider,{value:n},e.children)},p={inlineCode:"code",wrapper:function(e){var n=e.children;return a.createElement(a.Fragment,{},n)}},d=a.forwardRef((function(e,n){var t=e.components,r=e.mdxType,i=e.originalType,u=e.parentName,c=l(e,["components","mdxType","originalType","parentName"]),d=s(t),f=r,m=d["".concat(u,".").concat(f)]||d[f]||p[f]||i;return t?a.createElement(m,o(o({ref:n},c),{},{components:t})):a.createElement(m,o({ref:n},c))}));function f(e,n){var t=arguments,r=n&&n.mdxType;if("string"==typeof e||r){var i=t.length,o=new Array(i);o[0]=d;var l={};for(var u in n)hasOwnProperty.call(n,u)&&(l[u]=n[u]);l.originalType=e,l.mdxType="string"==typeof e?e:r,o[1]=l;for(var s=2;s{t.d(n,{Z:()=>o});var a=t(7294),r=t(6010);const i="tabItem_Ymn6";function o(e){let{children:n,hidden:t,className:o}=e;return a.createElement("div",{role:"tabpanel",className:(0,r.Z)(i,o),hidden:t},n)}},5488:(e,n,t)=>{t.d(n,{Z:()=>f});var a=t(7462),r=t(7294),i=t(6010),o=t(2389),l=t(7392),u=t(7094),s=t(2466);const c="tabList__CuJ",p="tabItem_LNqP";function d(e){var n,t;const{lazy:o,block:d,defaultValue:f,values:m,groupId:v,className:b}=e,g=r.Children.map(e.children,(e=>{if((0,r.isValidElement)(e)&&"value"in e.props)return e;throw new Error("Docusaurus error: Bad child <"+("string"==typeof e.type?e.type:e.type.name)+'>: all children of the component should be , and every should have a unique "value" prop.')})),h=null!=m?m:g.map((e=>{let{props:{value:n,label:t,attributes:a}}=e;return{value:n,label:t,attributes:a}})),y=(0,l.l)(h,((e,n)=>e.value===n.value));if(y.length>0)throw new Error('Docusaurus error: Duplicate values "'+y.map((e=>e.value)).join(", ")+'" found in . Every value needs to be unique.');const k=null===f?f:null!=(n=null!=f?f:null==(t=g.find((e=>e.props.default)))?void 0:t.props.value)?n:g[0].props.value;if(null!==k&&!h.some((e=>e.value===k)))throw new Error('Docusaurus error: The has a defaultValue "'+k+'" but none of its children has the corresponding value. Available values are: '+h.map((e=>e.value)).join(", ")+". If you intend to show no default tab, use defaultValue={null} instead.");const{tabGroupChoices:N,setTabGroupChoices:x}=(0,u.U)(),[w,O]=(0,r.useState)(k),F=[],{blockElementScrollPositionUntilNextRender:P}=(0,s.o5)();if(null!=v){const e=N[v];null!=e&&e!==w&&h.some((n=>n.value===e))&&O(e)}const T=e=>{const n=e.currentTarget,t=F.indexOf(n),a=h[t].value;a!==w&&(P(n),O(a),null!=v&&x(v,String(a)))},C=e=>{var n;let t=null;switch(e.key){case"ArrowRight":{var a;const n=F.indexOf(e.currentTarget)+1;t=null!=(a=F[n])?a:F[0];break}case"ArrowLeft":{var r;const n=F.indexOf(e.currentTarget)-1;t=null!=(r=F[n])?r:F[F.length-1];break}}null==(n=t)||n.focus()};return r.createElement("div",{className:(0,i.Z)("tabs-container",c)},r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,i.Z)("tabs",{"tabs--block":d},b)},h.map((e=>{let{value:n,label:t,attributes:o}=e;return r.createElement("li",(0,a.Z)({role:"tab",tabIndex:w===n?0:-1,"aria-selected":w===n,key:n,ref:e=>F.push(e),onKeyDown:C,onFocus:T,onClick:T},o,{className:(0,i.Z)("tabs__item",p,null==o?void 0:o.className,{"tabs__item--active":w===n})}),null!=t?t:n)}))),o?(0,r.cloneElement)(g.filter((e=>e.props.value===w))[0],{className:"margin-top--md"}):r.createElement("div",{className:"margin-top--md"},g.map(((e,n)=>(0,r.cloneElement)(e,{key:n,hidden:e.props.value!==w})))))}function f(e){const n=(0,o.Z)();return r.createElement(d,(0,a.Z)({key:String(n)},e))}},1728:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>c,contentTitle:()=>u,default:()=>f,frontMatter:()=>l,metadata:()=>s,toc:()=>p});var a=t(7462),r=(t(7294),t(3905)),i=t(5488),o=t(5162);const l={id:"function-provider-spi",title:"Function Provider SPI"},u=void 0,s={unversionedId:"reference/developer-guide/function-provider-spi",id:"reference/developer-guide/function-provider-spi",title:"Function Provider SPI",description:"Functions can be invoked in expressions and unary tests. The engine includes some predefined built-in functions.",source:"@site/docs/reference/developer-guide/function-provider-spi.md",sourceDirName:"reference/developer-guide",slug:"/reference/developer-guide/function-provider-spi",permalink:"/feel-scala/docs/next/reference/developer-guide/function-provider-spi",draft:!1,editUrl:"https://github.com/camunda/feel-scala/edit/main/docs/docs/reference/developer-guide/function-provider-spi.md",tags:[],version:"current",frontMatter:{id:"function-provider-spi",title:"Function Provider SPI"},sidebar:"Reference",previous:{title:"Bootstrapping",permalink:"/feel-scala/docs/next/reference/developer-guide/bootstrapping"},next:{title:"Value Mapper SPI",permalink:"/feel-scala/docs/next/reference/developer-guide/value-mapper-spi"}},c={},p=[{value:"Register the Function",id:"register-the-function",level:2}],d={toc:p};function f(e){let{components:n,...t}=e;return(0,r.kt)("wrapper",(0,a.Z)({},d,t,{components:n,mdxType:"MDXLayout"}),(0,r.kt)("p",null,"Functions can be invoked in expressions and unary tests. The engine includes some predefined built-in functions."),(0,r.kt)("p",null,"Own functions can be defined in two ways:"),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},"declaring them in an expression (e.g. a context)"),(0,r.kt)("li",{parentName:"ul"},"via the function provider SPI")),(0,r.kt)("p",null,"Using the SPI, the function can be implemented in Scala/Java and is not limited by FEEL. So, it's possible to use language features or libraries. "),(0,r.kt)(i.Z,{defaultValue:"scala",values:[{label:"Scala",value:"scala"},{label:"Java",value:"java"}],mdxType:"Tabs"},(0,r.kt)(o.Z,{value:"scala",mdxType:"TabItem"},(0,r.kt)("p",null,"Create a sub-class of ",(0,r.kt)("inlineCode",{parentName:"p"},"org.camunda.feel.context.CustomFunctionProvider")," and implement the method ",(0,r.kt)("inlineCode",{parentName:"p"},"getFunction()")," which returns the function for the given name. If a function can have different parameters (i.e. different parameter count) then override ",(0,r.kt)("inlineCode",{parentName:"p"},"getFunctions()")," instead."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-scala"},'class CustomScalaFunctionProvider extends CustomFunctionProvider {\n\n def getFunction(name: String): Option[ValFunction] = functions.get(name)\n\n def functionNames: Iterable[String] = functions.keys\n\n val functions: Map[String, ValFunction] = Map(\n "incr" -> ValFunction(\n params = List("x"),\n invoke = { case List(ValNumber(x)) => ValNumber(x + 1) }\n )\n )\n\n}\n')),(0,r.kt)("p",null,"The function must be of type ",(0,r.kt)("inlineCode",{parentName:"p"},"ValFunction"),". It contains"),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("inlineCode",{parentName:"li"},"params")," - list of the named parameters of the function"),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("inlineCode",{parentName:"li"},"invoke")," - business logic as function which takes the arguments and returns the result. The order of the arguments is defined by the parameter list. "),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("inlineCode",{parentName:"li"},"hasVarArgs")," - if ",(0,r.kt)("inlineCode",{parentName:"li"},"true")," the function can have variable arguments for the last parameter. The last argument is of type list. "))),(0,r.kt)(o.Z,{value:"java",mdxType:"TabItem"},"Using Java, create a sub-class of `org.camunda.feel.context.JavaFunctionProvider` instead. It is equal to the Scala one but uses more Java-like classes.",(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-java"},'public class CustomJavaFunctionProvider extends JavaFunctionProvider\n{\n private static final Map functions = new HashMap<>();\n\n static {\n \n final JavaFunction function = new JavaFunction(Arrays.asList("x"), args -> {\n final ValNumber arg = (ValNumber) args.get(0);\n\n int x = arg.value().intValue();\n\n return new ValNumber(BigDecimal.valueOf(x - 1));\n });\n\n functions.put("decr", function);\n }\n\n @Override\n public Optional resolveFunction(String functionName)\n {\n return Optional.ofNullable(functions.get(functionName));\n }\n \n @Override\n public Collection getFunctionNames() {\n return functions.keySet();\n }\n\n}\n')))),(0,r.kt)("h2",{id:"register-the-function"},"Register the Function"),(0,r.kt)("p",null,"Depending how the FEEL engine is used, the function provider can be passed directly on creation, or is loaded via Java ServiceLoader mechanism. "),(0,r.kt)("p",null,"In the second case, create a new file ",(0,r.kt)("inlineCode",{parentName:"p"},"org.camunda.feel.context.CustomFunctionProvider")," in the folder ",(0,r.kt)("inlineCode",{parentName:"p"},"META-INF/services/"),". It must contain all function providers by their full qualified name."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre"},"org.camunda.feel.example.context.CustomScalaFunctionProvider\norg.camunda.feel.example.context.CustomJavaFunctionProvider\n")))}f.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunkfeel_scala=self.webpackChunkfeel_scala||[]).push([[3084],{3905:(e,n,t)=>{t.d(n,{Zo:()=>c,kt:()=>f});var a=t(7294);function r(e,n,t){return n in e?Object.defineProperty(e,n,{value:t,enumerable:!0,configurable:!0,writable:!0}):e[n]=t,e}function i(e,n){var t=Object.keys(e);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);n&&(a=a.filter((function(n){return Object.getOwnPropertyDescriptor(e,n).enumerable}))),t.push.apply(t,a)}return t}function o(e){for(var n=1;n=0||(r[t]=e[t]);return r}(e,n);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(a=0;a=0||Object.prototype.propertyIsEnumerable.call(e,t)&&(r[t]=e[t])}return r}var u=a.createContext({}),s=function(e){var n=a.useContext(u),t=n;return e&&(t="function"==typeof e?e(n):o(o({},n),e)),t},c=function(e){var n=s(e.components);return a.createElement(u.Provider,{value:n},e.children)},p={inlineCode:"code",wrapper:function(e){var n=e.children;return a.createElement(a.Fragment,{},n)}},d=a.forwardRef((function(e,n){var t=e.components,r=e.mdxType,i=e.originalType,u=e.parentName,c=l(e,["components","mdxType","originalType","parentName"]),d=s(t),f=r,m=d["".concat(u,".").concat(f)]||d[f]||p[f]||i;return t?a.createElement(m,o(o({ref:n},c),{},{components:t})):a.createElement(m,o({ref:n},c))}));function f(e,n){var t=arguments,r=n&&n.mdxType;if("string"==typeof e||r){var i=t.length,o=new Array(i);o[0]=d;var l={};for(var u in n)hasOwnProperty.call(n,u)&&(l[u]=n[u]);l.originalType=e,l.mdxType="string"==typeof e?e:r,o[1]=l;for(var s=2;s{t.d(n,{Z:()=>o});var a=t(7294),r=t(6010);const i="tabItem_Ymn6";function o(e){let{children:n,hidden:t,className:o}=e;return a.createElement("div",{role:"tabpanel",className:(0,r.Z)(i,o),hidden:t},n)}},5488:(e,n,t)=>{t.d(n,{Z:()=>f});var a=t(7462),r=t(7294),i=t(6010),o=t(2389),l=t(7392),u=t(7094),s=t(2466);const c="tabList__CuJ",p="tabItem_LNqP";function d(e){var n,t;const{lazy:o,block:d,defaultValue:f,values:m,groupId:v,className:b}=e,g=r.Children.map(e.children,(e=>{if((0,r.isValidElement)(e)&&"value"in e.props)return e;throw new Error("Docusaurus error: Bad child <"+("string"==typeof e.type?e.type:e.type.name)+'>: all children of the component should be , and every should have a unique "value" prop.')})),h=null!=m?m:g.map((e=>{let{props:{value:n,label:t,attributes:a}}=e;return{value:n,label:t,attributes:a}})),y=(0,l.l)(h,((e,n)=>e.value===n.value));if(y.length>0)throw new Error('Docusaurus error: Duplicate values "'+y.map((e=>e.value)).join(", ")+'" found in . Every value needs to be unique.');const k=null===f?f:null!=(n=null!=f?f:null==(t=g.find((e=>e.props.default)))?void 0:t.props.value)?n:g[0].props.value;if(null!==k&&!h.some((e=>e.value===k)))throw new Error('Docusaurus error: The has a defaultValue "'+k+'" but none of its children has the corresponding value. Available values are: '+h.map((e=>e.value)).join(", ")+". If you intend to show no default tab, use defaultValue={null} instead.");const{tabGroupChoices:N,setTabGroupChoices:x}=(0,u.U)(),[w,O]=(0,r.useState)(k),F=[],{blockElementScrollPositionUntilNextRender:P}=(0,s.o5)();if(null!=v){const e=N[v];null!=e&&e!==w&&h.some((n=>n.value===e))&&O(e)}const C=e=>{const n=e.currentTarget,t=F.indexOf(n),a=h[t].value;a!==w&&(P(n),O(a),null!=v&&x(v,String(a)))},T=e=>{var n;let t=null;switch(e.key){case"ArrowRight":{var a;const n=F.indexOf(e.currentTarget)+1;t=null!=(a=F[n])?a:F[0];break}case"ArrowLeft":{var r;const n=F.indexOf(e.currentTarget)-1;t=null!=(r=F[n])?r:F[F.length-1];break}}null==(n=t)||n.focus()};return r.createElement("div",{className:(0,i.Z)("tabs-container",c)},r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,i.Z)("tabs",{"tabs--block":d},b)},h.map((e=>{let{value:n,label:t,attributes:o}=e;return r.createElement("li",(0,a.Z)({role:"tab",tabIndex:w===n?0:-1,"aria-selected":w===n,key:n,ref:e=>F.push(e),onKeyDown:T,onFocus:C,onClick:C},o,{className:(0,i.Z)("tabs__item",p,null==o?void 0:o.className,{"tabs__item--active":w===n})}),null!=t?t:n)}))),o?(0,r.cloneElement)(g.filter((e=>e.props.value===w))[0],{className:"margin-top--md"}):r.createElement("div",{className:"margin-top--md"},g.map(((e,n)=>(0,r.cloneElement)(e,{key:n,hidden:e.props.value!==w})))))}function f(e){const n=(0,o.Z)();return r.createElement(d,(0,a.Z)({key:String(n)},e))}},1728:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>c,contentTitle:()=>u,default:()=>f,frontMatter:()=>l,metadata:()=>s,toc:()=>p});var a=t(7462),r=(t(7294),t(3905)),i=t(5488),o=t(5162);const l={id:"function-provider-spi",title:"Function Provider SPI"},u=void 0,s={unversionedId:"reference/developer-guide/function-provider-spi",id:"reference/developer-guide/function-provider-spi",title:"Function Provider SPI",description:"Functions can be invoked in expressions and unary tests. The engine includes some predefined built-in functions.",source:"@site/docs/reference/developer-guide/function-provider-spi.md",sourceDirName:"reference/developer-guide",slug:"/reference/developer-guide/function-provider-spi",permalink:"/feel-scala/docs/next/reference/developer-guide/function-provider-spi",draft:!1,editUrl:"https://github.com/camunda/feel-scala/edit/main/docs/docs/reference/developer-guide/function-provider-spi.md",tags:[],version:"current",frontMatter:{id:"function-provider-spi",title:"Function Provider SPI"},sidebar:"Reference",previous:{title:"Bootstrapping",permalink:"/feel-scala/docs/next/reference/developer-guide/bootstrapping"},next:{title:"Value Mapper SPI",permalink:"/feel-scala/docs/next/reference/developer-guide/value-mapper-spi"}},c={},p=[{value:"Register the Function",id:"register-the-function",level:2}],d={toc:p};function f(e){let{components:n,...t}=e;return(0,r.kt)("wrapper",(0,a.Z)({},d,t,{components:n,mdxType:"MDXLayout"}),(0,r.kt)("p",null,"Functions can be invoked in expressions and unary tests. The engine includes some predefined built-in functions."),(0,r.kt)("p",null,"Own functions can be defined in two ways:"),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},"declaring them in an expression (e.g. a context)"),(0,r.kt)("li",{parentName:"ul"},"via the function provider SPI")),(0,r.kt)("p",null,"Using the SPI, the function can be implemented in Scala/Java and is not limited by FEEL. So, it's possible to use language features or libraries. "),(0,r.kt)(i.Z,{defaultValue:"scala",values:[{label:"Scala",value:"scala"},{label:"Java",value:"java"}],mdxType:"Tabs"},(0,r.kt)(o.Z,{value:"scala",mdxType:"TabItem"},(0,r.kt)("p",null,"Create a sub-class of ",(0,r.kt)("inlineCode",{parentName:"p"},"org.camunda.feel.context.CustomFunctionProvider")," and implement the method ",(0,r.kt)("inlineCode",{parentName:"p"},"getFunction()")," which returns the function for the given name. If a function can have different parameters (i.e. different parameter count) then override ",(0,r.kt)("inlineCode",{parentName:"p"},"getFunctions()")," instead."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-scala"},'class CustomScalaFunctionProvider extends CustomFunctionProvider {\n\n def getFunction(name: String): Option[ValFunction] = functions.get(name)\n\n def functionNames: Iterable[String] = functions.keys\n\n val functions: Map[String, ValFunction] = Map(\n "incr" -> ValFunction(\n params = List("x"),\n invoke = { case List(ValNumber(x)) => ValNumber(x + 1) }\n )\n )\n\n}\n')),(0,r.kt)("p",null,"The function must be of type ",(0,r.kt)("inlineCode",{parentName:"p"},"ValFunction"),". It contains"),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("inlineCode",{parentName:"li"},"params")," - list of the named parameters of the function"),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("inlineCode",{parentName:"li"},"invoke")," - business logic as function which takes the arguments and returns the result. The order of the arguments is defined by the parameter list. "),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("inlineCode",{parentName:"li"},"hasVarArgs")," - if ",(0,r.kt)("inlineCode",{parentName:"li"},"true")," the function can have variable arguments for the last parameter. The last argument is of type list. "))),(0,r.kt)(o.Z,{value:"java",mdxType:"TabItem"},"Using Java, create a sub-class of `org.camunda.feel.context.JavaFunctionProvider` instead. It is equal to the Scala one but uses more Java-like classes.",(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-java"},'public class CustomJavaFunctionProvider extends JavaFunctionProvider\n{\n private static final Map functions = new HashMap<>();\n\n static {\n \n final JavaFunction function = new JavaFunction(Arrays.asList("x"), args -> {\n final ValNumber arg = (ValNumber) args.get(0);\n\n int x = arg.value().intValue();\n\n return new ValNumber(BigDecimal.valueOf(x - 1));\n });\n\n functions.put("decr", function);\n }\n\n @Override\n public Optional resolveFunction(String functionName)\n {\n return Optional.ofNullable(functions.get(functionName));\n }\n \n @Override\n public Collection getFunctionNames() {\n return functions.keySet();\n }\n\n}\n')))),(0,r.kt)("h2",{id:"register-the-function"},"Register the Function"),(0,r.kt)("p",null,"Depending how the FEEL engine is used, the function provider can be passed directly on creation, or is loaded via Java ServiceLoader mechanism (by using the ",(0,r.kt)("inlineCode",{parentName:"p"},"SpiServiceLoader.loadFunctionProvider()")," as function provider). "),(0,r.kt)("p",null,"In the second case, create a new file ",(0,r.kt)("inlineCode",{parentName:"p"},"org.camunda.feel.context.CustomFunctionProvider")," in the folder ",(0,r.kt)("inlineCode",{parentName:"p"},"META-INF/services/"),". It must contain all function providers by their full qualified name."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre"},"org.camunda.feel.example.context.CustomScalaFunctionProvider\norg.camunda.feel.example.context.CustomJavaFunctionProvider\n")))}f.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/runtime~main.2bfea792.js b/assets/js/runtime~main.2b285b67.js similarity index 99% rename from assets/js/runtime~main.2bfea792.js rename to assets/js/runtime~main.2b285b67.js index 9a9101df4..590264c65 100644 --- a/assets/js/runtime~main.2bfea792.js +++ b/assets/js/runtime~main.2b285b67.js @@ -1 +1 @@ -(()=>{"use strict";var e,a,f,d,c,b={},t={};function r(e){var a=t[e];if(void 0!==a)return a.exports;var f=t[e]={exports:{}};return b[e].call(f.exports,f,f.exports,r),f.exports}r.m=b,e=[],r.O=(a,f,d,c)=>{if(!f){var b=1/0;for(i=0;i=c)&&Object.keys(r.O).every((e=>r.O[e](f[o])))?f.splice(o--,1):(t=!1,c0&&e[i-1][2]>c;i--)e[i]=e[i-1];e[i]=[f,d,c]},r.n=e=>{var a=e&&e.__esModule?()=>e.default:()=>e;return r.d(a,{a:a}),a},f=Object.getPrototypeOf?e=>Object.getPrototypeOf(e):e=>e.__proto__,r.t=function(e,d){if(1&d&&(e=this(e)),8&d)return e;if("object"==typeof e&&e){if(4&d&&e.__esModule)return e;if(16&d&&"function"==typeof e.then)return e}var c=Object.create(null);r.r(c);var b={};a=a||[null,f({}),f([]),f(f)];for(var t=2&d&&e;"object"==typeof t&&!~a.indexOf(t);t=f(t))Object.getOwnPropertyNames(t).forEach((a=>b[a]=()=>e[a]));return b.default=()=>e,r.d(c,b),c},r.d=(e,a)=>{for(var f in a)r.o(a,f)&&!r.o(e,f)&&Object.defineProperty(e,f,{enumerable:!0,get:a[f]})},r.f={},r.e=e=>Promise.all(Object.keys(r.f).reduce(((a,f)=>(r.f[f](e,a),a)),[])),r.u=e=>"assets/js/"+({53:"935f2afb",228:"3a640b15",461:"89cba808",470:"b148d8b7",498:"eaecb81f",665:"7289fa3b",732:"46beb044",756:"b2f52c97",783:"e31c5264",884:"0d26a3ad",941:"41e4718f",1011:"e42f1f7c",1066:"0fefe46b",1341:"47b4dca6",1373:"60473957",1501:"7d0f8844",1650:"23f72bf1",1738:"f84843fb",1756:"d04f8c34",1774:"7b25391d",2033:"ca5ba8f5",2053:"48e75d8a",2076:"1f26b7fd",2119:"34b54feb",2268:"a828da21",2402:"74380f41",2488:"a192ddcb",2666:"2440b6a3",2695:"439164bf",2760:"c4e4930b",2807:"d4e3d498",2867:"5c8ae3cb",2941:"ca7bc8bb",2953:"222f19ca",2997:"f85becba",3048:"fef5baaf",3084:"a7980671",3192:"3066c8e8",3213:"186ede56",3240:"c3236a3b",3281:"62d2c2a3",3341:"3c5cd300",3402:"57bd307b",3434:"7a24af0c",3556:"79188c1d",3616:"071c0e14",3618:"827f7b88",3838:"5800c980",4001:"2ae20730",4083:"7d5a7ee2",4170:"dea1c85e",4195:"c4f5d8e4",4206:"1b5ebb31",4283:"fb5a1aaa",4355:"5e7e5ae1",4423:"a82100bf",4437:"c942110d",4471:"0ed6245a",4482:"964de64a",4589:"26e5274d",4723:"11248379",4856:"e7141bf6",4860:"4d32f1a6",5e3:"6509bb71",5130:"09d7cd32",5173:"76d7044e",5231:"72ea3ac6",5394:"10477a8c",5439:"87edf171",5490:"ba312e09",5492:"c3f1de65",5497:"d0c688d3",5520:"f7382644",5615:"ab863f18",6071:"47901ffd",6163:"f7bb47de",6193:"428320b6",6273:"d25afec6",6314:"55d0250f",6371:"468696a0",6389:"76d6756c",6527:"cc41a32f",6599:"48659a8d",6690:"d797eebd",6703:"bfd78cd5",6861:"ae19943c",6972:"121d3fa0",7010:"649e27ea",7047:"ce024d07",7078:"a0fe9f0d",7094:"a8e58396",7157:"f8caef39",7379:"377506af",7380:"4532ca06",7520:"9a28a973",7539:"f5a28f53",7603:"b442c408",7875:"e5d0536a",7897:"18be9ddd",7918:"17896441",7920:"1a4e3797",7958:"98a44ec0",8145:"aea17236",8165:"5b62678e",8167:"aeedb3fa",8334:"a26a68e8",8406:"5767ff35",8475:"e09e4f75",8671:"8cd73165",8788:"8de501d1",8886:"fe838d54",9036:"f187ac9d",9077:"f1c9ebbe",9127:"7b99bfe5",9137:"6077e204",9199:"aa91d553",9332:"4648408f",9472:"cb479609",9514:"1be78505",9549:"bbde66de",9594:"030343dc",9601:"05b9fece",9618:"6c0f2e43",9663:"f0ce7fd8",9724:"a1edee72",9731:"a60d7910",9739:"5e75f739",9740:"1d25729c",9910:"ba02248f",9980:"58de979f",9994:"0e7cded1"}[e]||e)+"."+{53:"7af3078d",228:"09002a26",461:"dc3a72df",470:"b4ef324e",498:"092c3407",665:"4d2b97dd",732:"41acf53f",756:"2c311e6d",783:"3c9d32fb",884:"2429405c",941:"bb5d72e6",1011:"5bf9c650",1066:"7eaf3bd3",1341:"375d0b9f",1373:"7f8143c2",1501:"8f9e8b50",1650:"1bf897b6",1738:"feca317f",1756:"f2ca8c75",1774:"75c9dea7",2033:"5ece6bdb",2053:"cee780c8",2076:"8ae5ae98",2119:"0b5372d8",2268:"6e7bc95a",2402:"56526b74",2488:"9c20f580",2666:"814abcf9",2695:"d106e88f",2760:"9bc8ba26",2807:"63efa767",2867:"e776caa9",2941:"af3fd84a",2953:"8cf51578",2997:"8eccbd5f",3048:"e6437538",3084:"880adb68",3152:"f29081b5",3192:"aec9fa8a",3213:"381f8b60",3240:"7f01c6cf",3281:"ec25da20",3341:"2dc0c7c5",3402:"f28a8c90",3434:"1be139e0",3556:"222c9b26",3616:"10b71e5e",3618:"1d0777a7",3838:"c78de1cb",4001:"7dd9b7a0",4083:"13d3f886",4170:"208dbb48",4195:"8ad72f3f",4206:"b96d61e0",4283:"faeeb1d5",4355:"d8f51553",4423:"6a5d9c96",4437:"7f6f1a75",4471:"a46a5c7b",4482:"25846805",4589:"859597e8",4723:"843bf598",4856:"ce14fda3",4860:"869f5f2d",4972:"b2f98d0d",5e3:"46e91532",5130:"6441299c",5173:"7ff36908",5231:"a341f92b",5394:"a0b76bd6",5439:"31624c17",5490:"e4bd1cfd",5492:"99840c05",5497:"ad697a5c",5520:"ef0f1302",5615:"bb26cf91",6071:"8f37ef3a",6163:"dc673a99",6193:"22d1da6a",6273:"8b15cb15",6314:"26f516ee",6371:"58e6199f",6389:"85073674",6527:"c7c7bdf9",6599:"01a25f62",6690:"fe1ec287",6703:"423457fc",6780:"96cd4695",6861:"7d51a5a1",6945:"deaffdb9",6972:"8b3c25a1",7010:"a01f9caa",7047:"bbcf07c1",7078:"cae9aa87",7094:"d0b8a538",7157:"11424a99",7379:"288a2ff7",7380:"8102cbbc",7520:"4d20fb25",7539:"78f015ed",7603:"471f9ee0",7612:"2bae4442",7875:"e0ffb0dd",7897:"b313a5d8",7918:"78deec07",7920:"05ad13c8",7958:"5c6f0deb",8145:"b4ef5fea",8165:"c60b0840",8167:"aae29df3",8334:"a1686e74",8406:"3c60dee3",8475:"ea239097",8671:"643cc8a1",8788:"68c6ff3d",8886:"1418f86f",8894:"1c188825",9036:"ea37c039",9077:"2ac8f49e",9127:"87bd4074",9137:"2c03aa3e",9199:"ee8cb5ab",9332:"f31e0840",9472:"9ee43b69",9514:"aa696f0f",9549:"a0f4fb59",9594:"a6ed0eb8",9601:"d1b68054",9618:"a32284fc",9663:"3527b33f",9724:"4a4dc7cf",9731:"c9cfc978",9739:"0cb664ce",9740:"28166e9b",9910:"919ecba4",9980:"020c9139",9994:"14c545d1"}[e]+".js",r.miniCssF=e=>{},r.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),r.o=(e,a)=>Object.prototype.hasOwnProperty.call(e,a),d={},c="feel-scala:",r.l=(e,a,f,b)=>{if(d[e])d[e].push(a);else{var t,o;if(void 0!==f)for(var n=document.getElementsByTagName("script"),i=0;i{t.onerror=t.onload=null,clearTimeout(s);var c=d[e];if(delete d[e],t.parentNode&&t.parentNode.removeChild(t),c&&c.forEach((e=>e(f))),a)return a(f)},s=setTimeout(u.bind(null,void 0,{type:"timeout",target:t}),12e4);t.onerror=u.bind(null,t.onerror),t.onload=u.bind(null,t.onload),o&&document.head.appendChild(t)}},r.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r.p="/feel-scala/",r.gca=function(e){return e={11248379:"4723",17896441:"7918",60473957:"1373","935f2afb":"53","3a640b15":"228","89cba808":"461",b148d8b7:"470",eaecb81f:"498","7289fa3b":"665","46beb044":"732",b2f52c97:"756",e31c5264:"783","0d26a3ad":"884","41e4718f":"941",e42f1f7c:"1011","0fefe46b":"1066","47b4dca6":"1341","7d0f8844":"1501","23f72bf1":"1650",f84843fb:"1738",d04f8c34:"1756","7b25391d":"1774",ca5ba8f5:"2033","48e75d8a":"2053","1f26b7fd":"2076","34b54feb":"2119",a828da21:"2268","74380f41":"2402",a192ddcb:"2488","2440b6a3":"2666","439164bf":"2695",c4e4930b:"2760",d4e3d498:"2807","5c8ae3cb":"2867",ca7bc8bb:"2941","222f19ca":"2953",f85becba:"2997",fef5baaf:"3048",a7980671:"3084","3066c8e8":"3192","186ede56":"3213",c3236a3b:"3240","62d2c2a3":"3281","3c5cd300":"3341","57bd307b":"3402","7a24af0c":"3434","79188c1d":"3556","071c0e14":"3616","827f7b88":"3618","5800c980":"3838","2ae20730":"4001","7d5a7ee2":"4083",dea1c85e:"4170",c4f5d8e4:"4195","1b5ebb31":"4206",fb5a1aaa:"4283","5e7e5ae1":"4355",a82100bf:"4423",c942110d:"4437","0ed6245a":"4471","964de64a":"4482","26e5274d":"4589",e7141bf6:"4856","4d32f1a6":"4860","6509bb71":"5000","09d7cd32":"5130","76d7044e":"5173","72ea3ac6":"5231","10477a8c":"5394","87edf171":"5439",ba312e09:"5490",c3f1de65:"5492",d0c688d3:"5497",f7382644:"5520",ab863f18:"5615","47901ffd":"6071",f7bb47de:"6163","428320b6":"6193",d25afec6:"6273","55d0250f":"6314","468696a0":"6371","76d6756c":"6389",cc41a32f:"6527","48659a8d":"6599",d797eebd:"6690",bfd78cd5:"6703",ae19943c:"6861","121d3fa0":"6972","649e27ea":"7010",ce024d07:"7047",a0fe9f0d:"7078",a8e58396:"7094",f8caef39:"7157","377506af":"7379","4532ca06":"7380","9a28a973":"7520",f5a28f53:"7539",b442c408:"7603",e5d0536a:"7875","18be9ddd":"7897","1a4e3797":"7920","98a44ec0":"7958",aea17236:"8145","5b62678e":"8165",aeedb3fa:"8167",a26a68e8:"8334","5767ff35":"8406",e09e4f75:"8475","8cd73165":"8671","8de501d1":"8788",fe838d54:"8886",f187ac9d:"9036",f1c9ebbe:"9077","7b99bfe5":"9127","6077e204":"9137",aa91d553:"9199","4648408f":"9332",cb479609:"9472","1be78505":"9514",bbde66de:"9549","030343dc":"9594","05b9fece":"9601","6c0f2e43":"9618",f0ce7fd8:"9663",a1edee72:"9724",a60d7910:"9731","5e75f739":"9739","1d25729c":"9740",ba02248f:"9910","58de979f":"9980","0e7cded1":"9994"}[e]||e,r.p+r.u(e)},(()=>{var e={1303:0,532:0};r.f.j=(a,f)=>{var d=r.o(e,a)?e[a]:void 0;if(0!==d)if(d)f.push(d[2]);else if(/^(1303|532)$/.test(a))e[a]=0;else{var c=new Promise(((f,c)=>d=e[a]=[f,c]));f.push(d[2]=c);var b=r.p+r.u(a),t=new Error;r.l(b,(f=>{if(r.o(e,a)&&(0!==(d=e[a])&&(e[a]=void 0),d)){var c=f&&("load"===f.type?"missing":f.type),b=f&&f.target&&f.target.src;t.message="Loading chunk "+a+" failed.\n("+c+": "+b+")",t.name="ChunkLoadError",t.type=c,t.request=b,d[1](t)}}),"chunk-"+a,a)}},r.O.j=a=>0===e[a];var a=(a,f)=>{var d,c,b=f[0],t=f[1],o=f[2],n=0;if(b.some((a=>0!==e[a]))){for(d in t)r.o(t,d)&&(r.m[d]=t[d]);if(o)var i=o(r)}for(a&&a(f);n{"use strict";var e,a,f,d,c,b={},t={};function r(e){var a=t[e];if(void 0!==a)return a.exports;var f=t[e]={exports:{}};return b[e].call(f.exports,f,f.exports,r),f.exports}r.m=b,e=[],r.O=(a,f,d,c)=>{if(!f){var b=1/0;for(i=0;i=c)&&Object.keys(r.O).every((e=>r.O[e](f[o])))?f.splice(o--,1):(t=!1,c0&&e[i-1][2]>c;i--)e[i]=e[i-1];e[i]=[f,d,c]},r.n=e=>{var a=e&&e.__esModule?()=>e.default:()=>e;return r.d(a,{a:a}),a},f=Object.getPrototypeOf?e=>Object.getPrototypeOf(e):e=>e.__proto__,r.t=function(e,d){if(1&d&&(e=this(e)),8&d)return e;if("object"==typeof e&&e){if(4&d&&e.__esModule)return e;if(16&d&&"function"==typeof e.then)return e}var c=Object.create(null);r.r(c);var b={};a=a||[null,f({}),f([]),f(f)];for(var t=2&d&&e;"object"==typeof t&&!~a.indexOf(t);t=f(t))Object.getOwnPropertyNames(t).forEach((a=>b[a]=()=>e[a]));return b.default=()=>e,r.d(c,b),c},r.d=(e,a)=>{for(var f in a)r.o(a,f)&&!r.o(e,f)&&Object.defineProperty(e,f,{enumerable:!0,get:a[f]})},r.f={},r.e=e=>Promise.all(Object.keys(r.f).reduce(((a,f)=>(r.f[f](e,a),a)),[])),r.u=e=>"assets/js/"+({53:"935f2afb",228:"3a640b15",461:"89cba808",470:"b148d8b7",498:"eaecb81f",665:"7289fa3b",732:"46beb044",756:"b2f52c97",783:"e31c5264",884:"0d26a3ad",941:"41e4718f",1011:"e42f1f7c",1066:"0fefe46b",1341:"47b4dca6",1373:"60473957",1501:"7d0f8844",1650:"23f72bf1",1738:"f84843fb",1756:"d04f8c34",1774:"7b25391d",2033:"ca5ba8f5",2053:"48e75d8a",2076:"1f26b7fd",2119:"34b54feb",2268:"a828da21",2402:"74380f41",2488:"a192ddcb",2666:"2440b6a3",2695:"439164bf",2760:"c4e4930b",2807:"d4e3d498",2867:"5c8ae3cb",2941:"ca7bc8bb",2953:"222f19ca",2997:"f85becba",3048:"fef5baaf",3084:"a7980671",3192:"3066c8e8",3213:"186ede56",3240:"c3236a3b",3281:"62d2c2a3",3341:"3c5cd300",3402:"57bd307b",3434:"7a24af0c",3556:"79188c1d",3616:"071c0e14",3618:"827f7b88",3838:"5800c980",4001:"2ae20730",4083:"7d5a7ee2",4170:"dea1c85e",4195:"c4f5d8e4",4206:"1b5ebb31",4283:"fb5a1aaa",4355:"5e7e5ae1",4423:"a82100bf",4437:"c942110d",4471:"0ed6245a",4482:"964de64a",4589:"26e5274d",4723:"11248379",4856:"e7141bf6",4860:"4d32f1a6",5e3:"6509bb71",5130:"09d7cd32",5173:"76d7044e",5231:"72ea3ac6",5394:"10477a8c",5439:"87edf171",5490:"ba312e09",5492:"c3f1de65",5497:"d0c688d3",5520:"f7382644",5615:"ab863f18",6071:"47901ffd",6163:"f7bb47de",6193:"428320b6",6273:"d25afec6",6314:"55d0250f",6371:"468696a0",6389:"76d6756c",6527:"cc41a32f",6599:"48659a8d",6690:"d797eebd",6703:"bfd78cd5",6861:"ae19943c",6972:"121d3fa0",7010:"649e27ea",7047:"ce024d07",7078:"a0fe9f0d",7094:"a8e58396",7157:"f8caef39",7379:"377506af",7380:"4532ca06",7520:"9a28a973",7539:"f5a28f53",7603:"b442c408",7875:"e5d0536a",7897:"18be9ddd",7918:"17896441",7920:"1a4e3797",7958:"98a44ec0",8145:"aea17236",8165:"5b62678e",8167:"aeedb3fa",8334:"a26a68e8",8406:"5767ff35",8475:"e09e4f75",8671:"8cd73165",8788:"8de501d1",8886:"fe838d54",9036:"f187ac9d",9077:"f1c9ebbe",9127:"7b99bfe5",9137:"6077e204",9199:"aa91d553",9332:"4648408f",9472:"cb479609",9514:"1be78505",9549:"bbde66de",9594:"030343dc",9601:"05b9fece",9618:"6c0f2e43",9663:"f0ce7fd8",9724:"a1edee72",9731:"a60d7910",9739:"5e75f739",9740:"1d25729c",9910:"ba02248f",9980:"58de979f",9994:"0e7cded1"}[e]||e)+"."+{53:"7af3078d",228:"09002a26",461:"dc3a72df",470:"b4ef324e",498:"092c3407",665:"4d2b97dd",732:"41acf53f",756:"2c311e6d",783:"3c9d32fb",884:"2429405c",941:"bb5d72e6",1011:"5bf9c650",1066:"7eaf3bd3",1341:"375d0b9f",1373:"7f8143c2",1501:"8f9e8b50",1650:"1bf897b6",1738:"feca317f",1756:"f2ca8c75",1774:"75c9dea7",2033:"5ece6bdb",2053:"cee780c8",2076:"8ae5ae98",2119:"0b5372d8",2268:"6e7bc95a",2402:"56526b74",2488:"9c20f580",2666:"814abcf9",2695:"d106e88f",2760:"9bc8ba26",2807:"63efa767",2867:"e776caa9",2941:"af3fd84a",2953:"8cf51578",2997:"8eccbd5f",3048:"e6437538",3084:"d99d97f2",3152:"f29081b5",3192:"aec9fa8a",3213:"381f8b60",3240:"7f01c6cf",3281:"ec25da20",3341:"2dc0c7c5",3402:"f28a8c90",3434:"1be139e0",3556:"222c9b26",3616:"10b71e5e",3618:"1d0777a7",3838:"c78de1cb",4001:"7dd9b7a0",4083:"13d3f886",4170:"208dbb48",4195:"8ad72f3f",4206:"b96d61e0",4283:"faeeb1d5",4355:"d8f51553",4423:"6a5d9c96",4437:"7f6f1a75",4471:"a46a5c7b",4482:"25846805",4589:"859597e8",4723:"843bf598",4856:"ce14fda3",4860:"869f5f2d",4972:"b2f98d0d",5e3:"46e91532",5130:"6441299c",5173:"7ff36908",5231:"a341f92b",5394:"a0b76bd6",5439:"31624c17",5490:"e4bd1cfd",5492:"99840c05",5497:"ad697a5c",5520:"ef0f1302",5615:"bb26cf91",6071:"8f37ef3a",6163:"dc673a99",6193:"22d1da6a",6273:"8b15cb15",6314:"26f516ee",6371:"58e6199f",6389:"85073674",6527:"c7c7bdf9",6599:"01a25f62",6690:"fe1ec287",6703:"423457fc",6780:"96cd4695",6861:"7d51a5a1",6945:"deaffdb9",6972:"8b3c25a1",7010:"a01f9caa",7047:"bbcf07c1",7078:"cae9aa87",7094:"d0b8a538",7157:"11424a99",7379:"288a2ff7",7380:"8102cbbc",7520:"4d20fb25",7539:"78f015ed",7603:"471f9ee0",7612:"2bae4442",7875:"e0ffb0dd",7897:"b313a5d8",7918:"78deec07",7920:"05ad13c8",7958:"5c6f0deb",8145:"b4ef5fea",8165:"c60b0840",8167:"aae29df3",8334:"a1686e74",8406:"3c60dee3",8475:"ea239097",8671:"643cc8a1",8788:"68c6ff3d",8886:"1418f86f",8894:"1c188825",9036:"ea37c039",9077:"2ac8f49e",9127:"87bd4074",9137:"2c03aa3e",9199:"ee8cb5ab",9332:"f31e0840",9472:"9ee43b69",9514:"aa696f0f",9549:"a0f4fb59",9594:"a6ed0eb8",9601:"d1b68054",9618:"a32284fc",9663:"3527b33f",9724:"4a4dc7cf",9731:"c9cfc978",9739:"0cb664ce",9740:"28166e9b",9910:"919ecba4",9980:"020c9139",9994:"14c545d1"}[e]+".js",r.miniCssF=e=>{},r.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),r.o=(e,a)=>Object.prototype.hasOwnProperty.call(e,a),d={},c="feel-scala:",r.l=(e,a,f,b)=>{if(d[e])d[e].push(a);else{var t,o;if(void 0!==f)for(var n=document.getElementsByTagName("script"),i=0;i{t.onerror=t.onload=null,clearTimeout(s);var c=d[e];if(delete d[e],t.parentNode&&t.parentNode.removeChild(t),c&&c.forEach((e=>e(f))),a)return a(f)},s=setTimeout(u.bind(null,void 0,{type:"timeout",target:t}),12e4);t.onerror=u.bind(null,t.onerror),t.onload=u.bind(null,t.onload),o&&document.head.appendChild(t)}},r.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r.p="/feel-scala/",r.gca=function(e){return e={11248379:"4723",17896441:"7918",60473957:"1373","935f2afb":"53","3a640b15":"228","89cba808":"461",b148d8b7:"470",eaecb81f:"498","7289fa3b":"665","46beb044":"732",b2f52c97:"756",e31c5264:"783","0d26a3ad":"884","41e4718f":"941",e42f1f7c:"1011","0fefe46b":"1066","47b4dca6":"1341","7d0f8844":"1501","23f72bf1":"1650",f84843fb:"1738",d04f8c34:"1756","7b25391d":"1774",ca5ba8f5:"2033","48e75d8a":"2053","1f26b7fd":"2076","34b54feb":"2119",a828da21:"2268","74380f41":"2402",a192ddcb:"2488","2440b6a3":"2666","439164bf":"2695",c4e4930b:"2760",d4e3d498:"2807","5c8ae3cb":"2867",ca7bc8bb:"2941","222f19ca":"2953",f85becba:"2997",fef5baaf:"3048",a7980671:"3084","3066c8e8":"3192","186ede56":"3213",c3236a3b:"3240","62d2c2a3":"3281","3c5cd300":"3341","57bd307b":"3402","7a24af0c":"3434","79188c1d":"3556","071c0e14":"3616","827f7b88":"3618","5800c980":"3838","2ae20730":"4001","7d5a7ee2":"4083",dea1c85e:"4170",c4f5d8e4:"4195","1b5ebb31":"4206",fb5a1aaa:"4283","5e7e5ae1":"4355",a82100bf:"4423",c942110d:"4437","0ed6245a":"4471","964de64a":"4482","26e5274d":"4589",e7141bf6:"4856","4d32f1a6":"4860","6509bb71":"5000","09d7cd32":"5130","76d7044e":"5173","72ea3ac6":"5231","10477a8c":"5394","87edf171":"5439",ba312e09:"5490",c3f1de65:"5492",d0c688d3:"5497",f7382644:"5520",ab863f18:"5615","47901ffd":"6071",f7bb47de:"6163","428320b6":"6193",d25afec6:"6273","55d0250f":"6314","468696a0":"6371","76d6756c":"6389",cc41a32f:"6527","48659a8d":"6599",d797eebd:"6690",bfd78cd5:"6703",ae19943c:"6861","121d3fa0":"6972","649e27ea":"7010",ce024d07:"7047",a0fe9f0d:"7078",a8e58396:"7094",f8caef39:"7157","377506af":"7379","4532ca06":"7380","9a28a973":"7520",f5a28f53:"7539",b442c408:"7603",e5d0536a:"7875","18be9ddd":"7897","1a4e3797":"7920","98a44ec0":"7958",aea17236:"8145","5b62678e":"8165",aeedb3fa:"8167",a26a68e8:"8334","5767ff35":"8406",e09e4f75:"8475","8cd73165":"8671","8de501d1":"8788",fe838d54:"8886",f187ac9d:"9036",f1c9ebbe:"9077","7b99bfe5":"9127","6077e204":"9137",aa91d553:"9199","4648408f":"9332",cb479609:"9472","1be78505":"9514",bbde66de:"9549","030343dc":"9594","05b9fece":"9601","6c0f2e43":"9618",f0ce7fd8:"9663",a1edee72:"9724",a60d7910:"9731","5e75f739":"9739","1d25729c":"9740",ba02248f:"9910","58de979f":"9980","0e7cded1":"9994"}[e]||e,r.p+r.u(e)},(()=>{var e={1303:0,532:0};r.f.j=(a,f)=>{var d=r.o(e,a)?e[a]:void 0;if(0!==d)if(d)f.push(d[2]);else if(/^(1303|532)$/.test(a))e[a]=0;else{var c=new Promise(((f,c)=>d=e[a]=[f,c]));f.push(d[2]=c);var b=r.p+r.u(a),t=new Error;r.l(b,(f=>{if(r.o(e,a)&&(0!==(d=e[a])&&(e[a]=void 0),d)){var c=f&&("load"===f.type?"missing":f.type),b=f&&f.target&&f.target.src;t.message="Loading chunk "+a+" failed.\n("+c+": "+b+")",t.name="ChunkLoadError",t.type=c,t.request=b,d[1](t)}}),"chunk-"+a,a)}},r.O.j=a=>0===e[a];var a=(a,f)=>{var d,c,b=f[0],t=f[1],o=f[2],n=0;if(b.some((a=>0!==e[a]))){for(d in t)r.o(t,d)&&(r.m[d]=t[d]);if(o)var i=o(r)}for(a&&a(f);n FEEL Grammar | FEEL-Scala - +
Version: 1.10

FEEL Grammar

EBNF

This is the original grammar from the spec.

1. expression = 
a. textual expression |
b. boxed expression ;

2. textual expression =
a. function definition | for expression | if expression | quantified expression |
b. disjunction |
c. conjunction |
d. comparison |
e. arithmetic expression |
f. instance of |
g. path expression |
h. filter expression | function invocation |
i. literal | simple positive unary test | name | "(" , textual expression , ")" ;

3. textual expressions = textual expression , { "," , textual expression } ;

4. arithmetic expression =
a. addition | subtraction |
b. multiplication | division |
c. exponentiation |
d. arithmetic negation ;

5. simple expression = arithmetic expression | simple value ;

6. simple expressions = simple expression , { "," , simple expression } ;

7. simple positive unary test =
a. [ "<" | "<=" | ">" | ">=" ] , endpoint |
b. interval ;

8. interval = ( open interval start | closed interval start ) , endpoint , ".." , endpoint , ( open interval end | closed interval
end ) ;

9. open interval start = "(" | "]" ;

10. closed interval start = "[" ;

11. open interval end = ")" | "[" ;

12. closed interval end = "]" ;

13. simple positive unary tests = simple positive unary test , { "," , simple positive unary test } ;

14. simple unary tests =
a. simple positive unary tests |
b. "not", "(", simple positive unary tests, ")" |
c. "-";

15. positive unary test = simple positive unary test | "null" ;

16. positive unary tests = positive unary test , { "," , positive unary test } ;

17. unary tests =
a. positive unary tests |
b. "not", " (", positive unary tests, ")" |
c. "-"

18. endpoint = simple value ;

19. simple value = qualified name | simple literal ;

20. qualified name = name , { "." , name } ;

21. addition = expression , "+" , expression ;

22. subtraction = expression , "-" , expression ;

23. multiplication = expression , "*" , expression ;

24. division = expression , "/" , expression ;

25. exponentiation = expression, "**", expression ;

26. arithmetic negation = "-" , expression ;

27. name = name start , { name part | additional name symbols } ;

28. name start = name start char, { name part char } ;

29. name part = name part char , { name part char } ;

30. name start char = "?" | [A-Z] | "_" | [a-z] | [\uC0-\uD6] | [\uD8-\uF6] | [\uF8-\u2FF] | [\u370-\u37D] | [\u37F-\u1FFF] |
[\u200C-\u200D] | [\u2070-\u218F] | [\u2C00-\u2FEF] | [\u3001-\uD7FF] | [\uF900-\uFDCF] | [\uFDF0-\uFFFD] |
[\u10000-\uEFFFF] ;

31. name part char = name start char | digit | \uB7 | [\u0300-\u036F] | [\u203F-\u2040] ;

32. additional name symbols = "." | "/" | "-" | "’" | "+" | "*" ;

33. literal = simple literal | "null" ;

34. simple literal = numeric literal | string literal | Boolean literal | date time literal ;

35. string literal = '"' , { character – ('"' | vertical space) }, '"' ;

36. Boolean literal = "true" | "false" ;

37. numeric literal = [ "-" ] , ( digits , [ ".", digits ] | "." , digits ) ;

38. digit = [0-9] ;

39. digits = digit , {digit} ;

40. function invocation = expression , parameters ;

41. parameters = "(" , ( named parameters | positional parameters ) , ")" ;

42. named parameters = parameter name , ":" , expression ,
{ "," , parameter name , ":" , expression } ;

43. parameter name = name ;

44. positional parameters = [ expression , { "," , expression } ] ;

45. path expression = expression , "." , name ;

46. for expression = "for" , name , "in" , expression { "," , name , "in" , expression } , "return" , expression ;

47. if expression = "if" , expression , "then" , expression , "else" expression ;

48. quantified expression = ("some" | "every") , name , "in" , expression , { name , "in" , expression } , "satisfies" ,
expression ;

49. disjunction = expression , "or" , expression ;

50. conjunction = expression , "and" , expression ;

51. comparison =
a. expression , ( "=" | "!=" | "<" | "<=" | ">" | ">=" ) , expression |
b. expression , "between" , expression , "and" , expression |
c. expression , "in" , positive unary test ;
d. expression , "in" , " (", positive unary tests, ")" ;

52. filter expression = expression , "[" , expression , "]" ;

53. instance of = expression , "instance" , "of" , type ;

54. type = qualified name ;

55. boxed expression = list | function definition | context ;

56. list = "[" [ expression , { "," , expression } ] , "]" ;

57. function definition = "function" , "(" , [ formal parameter { "," , formal parameter } ] , ")" ,
[ "external" ] , expression ;

58. formal parameter = parameter name ;

59. context = "{" , [context entry , { "," , context entry } ] , "}" ;

60. context entry = key , ":" , expression ;

61. key = name | string literal ;

62. date time literal = ( "date" | "time" | "date and time" | "duration" ) , "(" , string literal , ")" ;

PEG

Rewritten grammar which is used by the parser.

// 1 a)
expression = textualExpression
// 1 b)
expression10 = boxedExpression

// 3
textualExpressions = textualExpression ( "," textualExpression )*

// 2 a)
textualExpression = functionDefinition / forExpression / ifExpression / quantifiedExpression / expression2
// 2 b)
expression2 = disjunction
// 2 c)
expression3 = conjunction
// 2 d)
expression4 = comparison / expression5
// 2 e)
expression5 = arithmeticExpression
// 2 f)
expression6 = instanceOf / expression7
// 2 g)
expression7 = pathExpression
// 2 h)
expression8 = filterExpression / functionInvocation / expression9
// 2 i)
expression9 = literal / name / simplePositiveUnaryTest / ( "(" textualExpression ")" ) / expression10

// 6
simpleExpressions = simpleExpression ( "," simple expression )*

// 5
simpleExpression = arithmeticExpression / simpleValue

// 4 a) -> 21+22
arithmeticExpression = arithmeticExpression2 ( "+" arithmeticExpression2 / "-" arithmeticExpression2 )*
// 4 b) -> 23+24
arithmeticExpression2 = arithmeticExpression3 ( "*" arithmeticExpression3 / "/" arithmeticExpression3 )*
// 4 c) -> 25
arithmeticExpression3 = arithmeticExpression4 ( "**" arithmeticExpression4 )*
// 4 d) -> 26
arithmeticExpression4 = ("-")? expression6

// 17
unaryTests = "-" / ( "not" "(" positiveUnaryTests ")" ) / positiveUnaryTests

// 16
positiveUnaryTests = positiveUnaryTest ( "," positiveUnaryTest )*

// 15
positiveUnaryTest = "null" / simplePositiveUnaryTest

// 14
simpleUnaryTests = "-" / ( "not" "(" simplePositiveUnaryTests ")" ) / simplePositiveUnaryTests

// 13
simplePositiveUnaryTests = simplePositiveUnaryTest ( "," simplePositiveUnaryTest )*

// 7
simplePositiveUnaryTest = ( ( "<" / "<=" / ">" / ">=" )? endpoint ) / interval

// 18
endpoint = simpleValue

// 19
simpleValue = simpleLiteral / qualifiedName

// 33
literal = "null" / simpleLiteral

// 34
simpleLiteral = booleanLiteral / dateTimeLiteral / stringLiteral / numericLiteral

// 36
booleanLiteral = "true" / "false"

// 62
dateTimeLiteral = ( "date" / "time" / "date and time" / "duration" ) "(" stringLiteral ")"

// 35
stringLiteral = '"' ( !('"' / verticalSpace) character )* '"'

// 37
numericLiteral = ( "-" )? ( ( digits ( "." digits )? ) / ( "." digits ) )

// 39
digits = digit ( digit )*

// 38
digit = [0-9]

// 20
qualifiedName = name ( "." name )*

// 27
name = nameStart ( namePart / additionalNameSymbols )*

// 28
nameStart = nameStartChar ( namePartChar )*

// 29
namePart = ( namePartChar )+

// 30
nameStartChar = "?" / [A-Z] / "_" / [a-z] / [\uC0-\uD6] / [\uD8-\uF6] / [\uF8-\u2FF] / [\u370-\u37D] / [\u37F-\u1FFF] /
[\u200C-\u200D] / [\u2070-\u218F] / [\u2C00-\u2FEF] / [\u3001-\uD7FF] / [\uF900-\uFDCF] / [\uFDF0-\uFFFD] /
[\u10000-\uEFFFF]

// 31
namePartChar = nameStartChar / digit / [\uB7] / [\u0300-\u036F] / [\u203F-\u2040]

// 32
additionalNameSymbols = "." / "/" / "-" / "’" / "+" / "*"

// 8
interval = ( openIntervalStart / closedIntervalStart ) endpoint ".." endpoint ( openIntervalEnd / closedIntervalEnd )

// 9
openIntervalStart = "(" / "]"

// 10
closedIntervalStart = "["

// 11
openIntervalEnd = ")" / "["

// 12
closedIntervalEnd = "]"

// 46
forExpression = "for" name "in" expression ( "," name "in" expression )* "return" expression

// 47
ifExpression = "if" expression "then" expression "else" expression

// 48
quantifiedExpression = ("some" / "every") (name "in" expression)+ "satisfies" expression

// 49
disjunction = expression3 ( "or" expression3 )*

// 50
conjunction = expression4 ( "and" expression )*

// 51
comparison = ( expression5 ( "=" / "!=" / "<" / "<=" / ">" / ">=" ) expression5 ) /
( expression5 "between" expression "and" expression ) /
( expression5 "in" "(" positiveUnaryTests ")" ) /
( expression5 "in" positiveUnaryTest )

// 53
instanceOf = expression7 "instance" "of" type

// 54
type = qualifiedName

// 45
pathExpression = expression8 ( "." name )* ( "[" expression "]" )

// 52
filterExpression = expression9 "[" expression "]"

// 40
functionInvocation = expression9 parameters

// 41
parameters = "(" namedParameters / positionalParameters ")"

// 42
namedParameters = parameterName ":" expression ( "," parameterName ":" expression )*

// 43
parameterName = name

// 44
positionalParameters = ( expression ( "," expression )* )?

// 55
boxedExpression = list / functionDefinition / context

// 56
list = "[" ( expression ( "," expression )* )? "]"

// 57
functionDefinition = "function" "(" ( formalParameter ( "," formalParameter )* )? ")" ( "external" )? expression

// 58
formalParameter = parameterName

// 59
context = "{" ( contextEntry ( "," contextEntry )* )? "}"

// 60
contextEntry = key ":" expression

// 61
key = name / stringLiteral
- + \ No newline at end of file diff --git a/docs/1.10/reference/developer-guide/function-provider-spi/index.html b/docs/1.10/reference/developer-guide/function-provider-spi/index.html index ed90cc0fe..bd8930f3b 100644 --- a/docs/1.10/reference/developer-guide/function-provider-spi/index.html +++ b/docs/1.10/reference/developer-guide/function-provider-spi/index.html @@ -5,13 +5,13 @@ Function Provider SPI | FEEL-Scala - +
Version: 1.10

Function Provider SPI

Functions can be invoked in expressions and unary tests. The engine includes some predefined built-in functions.

Own functions can be defined in two ways:

  • declaring them in an expression (e.g. a context)
  • via the function provider SPI

Using the SPI, the function can be implemented in Scala/Java and is not limited by FEEL. So, it's possible to use language features or libraries.

Create a sub-class of org.camunda.feel.spi.CustomFunctionProvider and implement the method getFunction() which returns the function for the given name. If a function can have different parameters (i.e. different parameter count) then override getFunctions() instead.

class CustomScalaFunctionProvider extends CustomFunctionProvider {

def getFunction(name: String): Option[ValFunction] = functions.get(name)

def functionNames: Iterable[String] = functions.keys

val functions: Map[String, ValFunction] = Map(
"incr" -> ValFunction(
params = List("x"),
invoke = { case List(ValNumber(x)) => ValNumber(x + 1) }
)
)

}

The function must be of type ValFunction. It contains

  • params - list of the named parameters of the function
  • invoke - business logic as function which takes the arguments and returns the result. The order of the arguments is defined by the parameter list.
  • hasVarArgs - if true the function can have variable arguments for the last parameter. The last argument is of type list.

Register the Function

Depending how the FEEL engine is used, the function provider can be passed directly on creation, or is loaded via Java ServiceLoader mechanism.

In the second case, create a new file org.camunda.feel.spi.CustomFunctionProvider in the folder META-INF/services/. It must contain all function providers by their full qualified name.

org.camunda.feel.example.spi.CustomScalaFunctionProvider
org.camunda.feel.example.spi.CustomJavaFunctionProvider
- + \ No newline at end of file diff --git a/docs/1.10/reference/developer-guide/value-mapper-spi/index.html b/docs/1.10/reference/developer-guide/value-mapper-spi/index.html index 53037362a..cbd49f07a 100644 --- a/docs/1.10/reference/developer-guide/value-mapper-spi/index.html +++ b/docs/1.10/reference/developer-guide/value-mapper-spi/index.html @@ -5,13 +5,13 @@ Value Mapper SPI | FEEL-Scala - +
Version: 1.10

Value Mapper SPI

The value mapper is used while evaluating expressions and unary tests to

  • transform a variable into a FEEL data type (e.g. when it is referenced in an expression x + 1)
  • transform the result of the expression or unary tests from a FEEL data type into a common data type (e.g. to String or BigDecimal/Long)

Using the SPI, the transformation can be customized to support more/custom data types, or changing the data type of the result.

Create a sub-class of org.camunda.feel.spi.CustomValueMapper. Implement the method toVal() and unpackVal() to transform the object. Set the priority of the value mapper to define the precedence compared to the other mappers.

class MyValueMapper extends CustomValueMapper {

override def toVal(x: Any, innerValueMapper: Any => Val): Option[Val] = x match {
case c: Custom => Some(ValString(c.getName))
case _ => None
}

override def unpackVal(value: Val, innerValueMapper: Val => Any): Option[Any] = value match {
case ValNumber(number) => Some(number.doubleValue) // map BigDecimal to Double
case _ => None
}

override val priority: Int = 1

}

Register the Value Mapper

Depending how the FEEL engine is used, the value mapper can be passed directly on creation, or is loaded via Java ServiceLoader mechanism.

In the second case, create a new file org.camunda.feel.spi.CustomValueMapper in the folder META-INF/services/. It must contain the full qualified name of the value mapper.

org.camunda.feel.example.spi.MyValueMapper
- + \ No newline at end of file diff --git a/docs/1.10/reference/index.html b/docs/1.10/reference/index.html index 61a952134..a0faa11d9 100644 --- a/docs/1.10/reference/index.html +++ b/docs/1.10/reference/index.html @@ -5,7 +5,7 @@ Get Started | FEEL-Scala - + @@ -15,7 +15,7 @@ the following pages of the language guide:

If you want to integrate the FEEL engine in your application then have a look at the Developer Guide.

If you want to try out your FEEL expressions in development then check out the Playground (online or local).

- + \ No newline at end of file diff --git a/docs/1.10/samples/index.html b/docs/1.10/samples/index.html index 465074ca6..902e6e5bf 100644 --- a/docs/1.10/samples/index.html +++ b/docs/1.10/samples/index.html @@ -5,13 +5,13 @@ Samples | FEEL-Scala - +
Version: 1.10

Samples

Some example FEEL expressions which are used with the engine. Feel free to add your examples ;)

Date/Time Calculation

Example: check if a date is at least 6 months before another.

date1 < date2 + duration("P6M")

Filter a List and Return the first Element

Example: return the first packaging element which unit is "Palette".

(data.attribute.packaging[unit = "Palette"])[1]

Validate Data using a Context

Example: validate journal entries and return all violations.

{
check1: {
error: "Document Type invalid for current year posting",
violations: collection[documentType = "S2" and glDate > startFiscalYear]
},
check2: {
error: "Document Type invalid for current year posting",
violations: collection[ledgerType = "GP" and foreignAmount != null]
},
result: ([check1, check2])[count(violations) > 0]
}

Structure Calculation using a Context

Example: calculate the minimum age of a given list of birthdays.

{
age: function(birthday) (today - birthday).years,
ages: for birthday in birthdays return age(birthday),
minAge: min(ages)
}.minAge
- + \ No newline at end of file diff --git a/docs/1.11/feel-grammar/index.html b/docs/1.11/feel-grammar/index.html index 0141a94cd..3594e0afa 100644 --- a/docs/1.11/feel-grammar/index.html +++ b/docs/1.11/feel-grammar/index.html @@ -5,13 +5,13 @@ FEEL Grammar | FEEL-Scala - +
Version: 1.11

FEEL Grammar

EBNF

This is the original grammar from the spec.

1. expression = 
a. textual expression |
b. boxed expression ;

2. textual expression =
a. function definition | for expression | if expression | quantified expression |
b. disjunction |
c. conjunction |
d. comparison |
e. arithmetic expression |
f. instance of |
g. path expression |
h. filter expression | function invocation |
i. literal | simple positive unary test | name | "(" , textual expression , ")" ;

3. textual expressions = textual expression , { "," , textual expression } ;

4. arithmetic expression =
a. addition | subtraction |
b. multiplication | division |
c. exponentiation |
d. arithmetic negation ;

5. simple expression = arithmetic expression | simple value ;

6. simple expressions = simple expression , { "," , simple expression } ;

7. simple positive unary test =
a. [ "<" | "<=" | ">" | ">=" ] , endpoint |
b. interval ;

8. interval = ( open interval start | closed interval start ) , endpoint , ".." , endpoint , ( open interval end | closed interval
end ) ;

9. open interval start = "(" | "]" ;

10. closed interval start = "[" ;

11. open interval end = ")" | "[" ;

12. closed interval end = "]" ;

13. simple positive unary tests = simple positive unary test , { "," , simple positive unary test } ;

14. simple unary tests =
a. simple positive unary tests |
b. "not", "(", simple positive unary tests, ")" |
c. "-";

15. positive unary test = simple positive unary test | "null" ;

16. positive unary tests = positive unary test , { "," , positive unary test } ;

17. unary tests =
a. positive unary tests |
b. "not", " (", positive unary tests, ")" |
c. "-"

18. endpoint = simple value ;

19. simple value = qualified name | simple literal ;

20. qualified name = name , { "." , name } ;

21. addition = expression , "+" , expression ;

22. subtraction = expression , "-" , expression ;

23. multiplication = expression , "*" , expression ;

24. division = expression , "/" , expression ;

25. exponentiation = expression, "**", expression ;

26. arithmetic negation = "-" , expression ;

27. name = name start , { name part | additional name symbols } ;

28. name start = name start char, { name part char } ;

29. name part = name part char , { name part char } ;

30. name start char = "?" | [A-Z] | "_" | [a-z] | [\uC0-\uD6] | [\uD8-\uF6] | [\uF8-\u2FF] | [\u370-\u37D] | [\u37F-\u1FFF] |
[\u200C-\u200D] | [\u2070-\u218F] | [\u2C00-\u2FEF] | [\u3001-\uD7FF] | [\uF900-\uFDCF] | [\uFDF0-\uFFFD] |
[\u10000-\uEFFFF] ;

31. name part char = name start char | digit | \uB7 | [\u0300-\u036F] | [\u203F-\u2040] ;

32. additional name symbols = "." | "/" | "-" | "’" | "+" | "*" ;

33. literal = simple literal | "null" ;

34. simple literal = numeric literal | string literal | Boolean literal | date time literal ;

35. string literal = '"' , { character – ('"' | vertical space) }, '"' ;

36. Boolean literal = "true" | "false" ;

37. numeric literal = [ "-" ] , ( digits , [ ".", digits ] | "." , digits ) ;

38. digit = [0-9] ;

39. digits = digit , {digit} ;

40. function invocation = expression , parameters ;

41. parameters = "(" , ( named parameters | positional parameters ) , ")" ;

42. named parameters = parameter name , ":" , expression ,
{ "," , parameter name , ":" , expression } ;

43. parameter name = name ;

44. positional parameters = [ expression , { "," , expression } ] ;

45. path expression = expression , "." , name ;

46. for expression = "for" , name , "in" , expression { "," , name , "in" , expression } , "return" , expression ;

47. if expression = "if" , expression , "then" , expression , "else" expression ;

48. quantified expression = ("some" | "every") , name , "in" , expression , { name , "in" , expression } , "satisfies" ,
expression ;

49. disjunction = expression , "or" , expression ;

50. conjunction = expression , "and" , expression ;

51. comparison =
a. expression , ( "=" | "!=" | "<" | "<=" | ">" | ">=" ) , expression |
b. expression , "between" , expression , "and" , expression |
c. expression , "in" , positive unary test ;
d. expression , "in" , " (", positive unary tests, ")" ;

52. filter expression = expression , "[" , expression , "]" ;

53. instance of = expression , "instance" , "of" , type ;

54. type = qualified name ;

55. boxed expression = list | function definition | context ;

56. list = "[" [ expression , { "," , expression } ] , "]" ;

57. function definition = "function" , "(" , [ formal parameter { "," , formal parameter } ] , ")" ,
[ "external" ] , expression ;

58. formal parameter = parameter name ;

59. context = "{" , [context entry , { "," , context entry } ] , "}" ;

60. context entry = key , ":" , expression ;

61. key = name | string literal ;

62. date time literal = ( "date" | "time" | "date and time" | "duration" ) , "(" , string literal , ")" ;

PEG

Rewritten grammar which is used by the parser.

// 1 a)
expression = textualExpression
// 1 b)
expression10 = boxedExpression

// 3
textualExpressions = textualExpression ( "," textualExpression )*

// 2 a)
textualExpression = functionDefinition / forExpression / ifExpression / quantifiedExpression / expression2
// 2 b)
expression2 = disjunction
// 2 c)
expression3 = conjunction
// 2 d)
expression4 = comparison / expression5
// 2 e)
expression5 = arithmeticExpression
// 2 f)
expression6 = instanceOf / expression7
// 2 g)
expression7 = pathExpression
// 2 h)
expression8 = filterExpression / functionInvocation / expression9
// 2 i)
expression9 = literal / name / simplePositiveUnaryTest / ( "(" textualExpression ")" ) / expression10

// 6
simpleExpressions = simpleExpression ( "," simple expression )*

// 5
simpleExpression = arithmeticExpression / simpleValue

// 4 a) -> 21+22
arithmeticExpression = arithmeticExpression2 ( "+" arithmeticExpression2 / "-" arithmeticExpression2 )*
// 4 b) -> 23+24
arithmeticExpression2 = arithmeticExpression3 ( "*" arithmeticExpression3 / "/" arithmeticExpression3 )*
// 4 c) -> 25
arithmeticExpression3 = arithmeticExpression4 ( "**" arithmeticExpression4 )*
// 4 d) -> 26
arithmeticExpression4 = ("-")? expression6

// 17
unaryTests = "-" / ( "not" "(" positiveUnaryTests ")" ) / positiveUnaryTests

// 16
positiveUnaryTests = positiveUnaryTest ( "," positiveUnaryTest )*

// 15
positiveUnaryTest = "null" / simplePositiveUnaryTest

// 14
simpleUnaryTests = "-" / ( "not" "(" simplePositiveUnaryTests ")" ) / simplePositiveUnaryTests

// 13
simplePositiveUnaryTests = simplePositiveUnaryTest ( "," simplePositiveUnaryTest )*

// 7
simplePositiveUnaryTest = ( ( "<" / "<=" / ">" / ">=" )? endpoint ) / interval

// 18
endpoint = simpleValue

// 19
simpleValue = simpleLiteral / qualifiedName

// 33
literal = "null" / simpleLiteral

// 34
simpleLiteral = booleanLiteral / dateTimeLiteral / stringLiteral / numericLiteral

// 36
booleanLiteral = "true" / "false"

// 62
dateTimeLiteral = ( "date" / "time" / "date and time" / "duration" ) "(" stringLiteral ")"

// 35
stringLiteral = '"' ( !('"' / verticalSpace) character )* '"'

// 37
numericLiteral = ( "-" )? ( ( digits ( "." digits )? ) / ( "." digits ) )

// 39
digits = digit ( digit )*

// 38
digit = [0-9]

// 20
qualifiedName = name ( "." name )*

// 27
name = nameStart ( namePart / additionalNameSymbols )*

// 28
nameStart = nameStartChar ( namePartChar )*

// 29
namePart = ( namePartChar )+

// 30
nameStartChar = "?" / [A-Z] / "_" / [a-z] / [\uC0-\uD6] / [\uD8-\uF6] / [\uF8-\u2FF] / [\u370-\u37D] / [\u37F-\u1FFF] /
[\u200C-\u200D] / [\u2070-\u218F] / [\u2C00-\u2FEF] / [\u3001-\uD7FF] / [\uF900-\uFDCF] / [\uFDF0-\uFFFD] /
[\u10000-\uEFFFF]

// 31
namePartChar = nameStartChar / digit / [\uB7] / [\u0300-\u036F] / [\u203F-\u2040]

// 32
additionalNameSymbols = "." / "/" / "-" / "’" / "+" / "*"

// 8
interval = ( openIntervalStart / closedIntervalStart ) endpoint ".." endpoint ( openIntervalEnd / closedIntervalEnd )

// 9
openIntervalStart = "(" / "]"

// 10
closedIntervalStart = "["

// 11
openIntervalEnd = ")" / "["

// 12
closedIntervalEnd = "]"

// 46
forExpression = "for" name "in" expression ( "," name "in" expression )* "return" expression

// 47
ifExpression = "if" expression "then" expression "else" expression

// 48
quantifiedExpression = ("some" / "every") (name "in" expression)+ "satisfies" expression

// 49
disjunction = expression3 ( "or" expression3 )*

// 50
conjunction = expression4 ( "and" expression )*

// 51
comparison = ( expression5 ( "=" / "!=" / "<" / "<=" / ">" / ">=" ) expression5 ) /
( expression5 "between" expression "and" expression ) /
( expression5 "in" "(" positiveUnaryTests ")" ) /
( expression5 "in" positiveUnaryTest )

// 53
instanceOf = expression7 "instance" "of" type

// 54
type = qualifiedName

// 45
pathExpression = expression8 ( "." name )* ( "[" expression "]" )

// 52
filterExpression = expression9 "[" expression "]"

// 40
functionInvocation = expression9 parameters

// 41
parameters = "(" namedParameters / positionalParameters ")"

// 42
namedParameters = parameterName ":" expression ( "," parameterName ":" expression )*

// 43
parameterName = name

// 44
positionalParameters = ( expression ( "," expression )* )?

// 55
boxedExpression = list / functionDefinition / context

// 56
list = "[" ( expression ( "," expression )* )? "]"

// 57
functionDefinition = "function" "(" ( formalParameter ( "," formalParameter )* )? ")" ( "external" )? expression

// 58
formalParameter = parameterName

// 59
context = "{" ( contextEntry ( "," contextEntry )* )? "}"

// 60
contextEntry = key ":" expression

// 61
key = name / stringLiteral
- + \ No newline at end of file diff --git a/docs/1.11/reference/developer-guide/clock-spi/index.html b/docs/1.11/reference/developer-guide/clock-spi/index.html index ba4c4498a..e0086ff6b 100644 --- a/docs/1.11/reference/developer-guide/clock-spi/index.html +++ b/docs/1.11/reference/developer-guide/clock-spi/index.html @@ -5,13 +5,13 @@ Clock SPI | FEEL-Scala - +
Version: 1.11

Clock SPI

The clock is used when accessing the current time while evaluating expressions and unary tests (e.g. the builtin function now()). By default, it uses the system clock.

Using the SPI, the clock can be replaced by a custom one. For example, if the application uses its own and not the system clock.

Implement a FEEL Engine Clock

Create a sub-class of org.camunda.feel.FeelEngineClock. Implement the method getCurrentTime() to return the current time.

class MyClock extends FeelEngineClock {

override def getCurrentTime(): ZonedDateTime = {
val currentMillis = ...
Instant.ofEpochMilli(currentMillis).atZone(ZoneId.systemDefault())
}

}

Register the FEEL Engine Clock

Depending on how the FEEL engine is used, the clock can be passed directly on creation, or is loaded via Java ServiceLoader mechanism.

In the second case, create a new file org.camunda.feel.FeelEngineClock in the folder META-INF/services/. It must contain the full qualified name of the class.

org.camunda.feel.example.MyClock
- + \ No newline at end of file diff --git a/docs/1.11/reference/developer-guide/function-provider-spi/index.html b/docs/1.11/reference/developer-guide/function-provider-spi/index.html index 7351ecbb8..e39cb6313 100644 --- a/docs/1.11/reference/developer-guide/function-provider-spi/index.html +++ b/docs/1.11/reference/developer-guide/function-provider-spi/index.html @@ -5,13 +5,13 @@ Function Provider SPI | FEEL-Scala - +
Version: 1.11

Function Provider SPI

Functions can be invoked in expressions and unary tests. The engine includes some predefined built-in functions.

Own functions can be defined in two ways:

  • declaring them in an expression (e.g. a context)
  • via the function provider SPI

Using the SPI, the function can be implemented in Scala/Java and is not limited by FEEL. So, it's possible to use language features or libraries.

Create a sub-class of org.camunda.feel.context.CustomFunctionProvider and implement the method getFunction() which returns the function for the given name. If a function can have different parameters (i.e. different parameter count) then override getFunctions() instead.

class CustomScalaFunctionProvider extends CustomFunctionProvider {

def getFunction(name: String): Option[ValFunction] = functions.get(name)

def functionNames: Iterable[String] = functions.keys

val functions: Map[String, ValFunction] = Map(
"incr" -> ValFunction(
params = List("x"),
invoke = { case List(ValNumber(x)) => ValNumber(x + 1) }
)
)

}

The function must be of type ValFunction. It contains

  • params - list of the named parameters of the function
  • invoke - business logic as function which takes the arguments and returns the result. The order of the arguments is defined by the parameter list.
  • hasVarArgs - if true the function can have variable arguments for the last parameter. The last argument is of type list.

Register the Function

Depending how the FEEL engine is used, the function provider can be passed directly on creation, or is loaded via Java ServiceLoader mechanism.

In the second case, create a new file org.camunda.feel.context.CustomFunctionProvider in the folder META-INF/services/. It must contain all function providers by their full qualified name.

org.camunda.feel.example.context.CustomScalaFunctionProvider
org.camunda.feel.example.context.CustomJavaFunctionProvider
- + \ No newline at end of file diff --git a/docs/1.11/reference/developer-guide/value-mapper-spi/index.html b/docs/1.11/reference/developer-guide/value-mapper-spi/index.html index aedd49881..e831fd5b4 100644 --- a/docs/1.11/reference/developer-guide/value-mapper-spi/index.html +++ b/docs/1.11/reference/developer-guide/value-mapper-spi/index.html @@ -5,13 +5,13 @@ Value Mapper SPI | FEEL-Scala - +
Version: 1.11

Value Mapper SPI

The value mapper is used while evaluating expressions and unary tests to

  • transform a variable into a FEEL data type (e.g. when it is referenced in an expression x + 1)
  • transform the result of the expression or unary tests from a FEEL data type into a common data type (e.g. to String or BigDecimal/Long)

Using the SPI, the transformation can be customized to support more/custom data types, or changing the data type of the result.

Create a sub-class of org.camunda.feel.valuemapper.CustomValueMapper. Implement the method toVal() and unpackVal() to transform the object. Set the priority of the value mapper to define the precedence compared to the other mappers.

class MyValueMapper extends CustomValueMapper {

override def toVal(x: Any, innerValueMapper: Any => Val): Option[Val] = x match {
case c: Custom => Some(ValString(c.getName))
case _ => None
}

override def unpackVal(value: Val, innerValueMapper: Val => Any): Option[Any] = value match {
case ValNumber(number) => Some(number.doubleValue) // map BigDecimal to Double
case _ => None
}

override val priority: Int = 1

}

Register the Value Mapper

Depending how the FEEL engine is used, the value mapper can be passed directly on creation, or is loaded via Java ServiceLoader mechanism.

In the second case, create a new file org.camunda.feel.valuemapper.CustomValueMapper in the folder META-INF/services/. It must contain the full qualified name of the value mapper.

org.camunda.feel.example.valuemapper.MyValueMapper
- + \ No newline at end of file diff --git a/docs/1.11/reference/index.html b/docs/1.11/reference/index.html index 57f73e8ac..1c555efb7 100644 --- a/docs/1.11/reference/index.html +++ b/docs/1.11/reference/index.html @@ -5,7 +5,7 @@ Get Started | FEEL-Scala - + @@ -15,7 +15,7 @@ the following pages of the language guide:

If you want to integrate the FEEL engine in your application then have a look at the Developer Guide.

If you want to try out your FEEL expressions in development then check out the Playground (online or local).

- + \ No newline at end of file diff --git a/docs/1.11/samples/index.html b/docs/1.11/samples/index.html index 0fb454d45..2504200e6 100644 --- a/docs/1.11/samples/index.html +++ b/docs/1.11/samples/index.html @@ -5,13 +5,13 @@ Samples | FEEL-Scala - +
Version: 1.11

Samples

Some example FEEL expressions which are used with the engine. Feel free to add your examples ;)

Date/Time Calculation

Example: check if a date is at least 6 months before another.

date1 < date2 + duration("P6M")

Filter a List and Return the first Element

Example: return the first packaging element which unit is "Palette".

(data.attribute.packaging[unit = "Palette"])[1]

Validate Data using a Context

Example: validate journal entries and return all violations.

{
check1: {
error: "Document Type invalid for current year posting",
violations: collection[documentType = "S2" and glDate > startFiscalYear]
},
check2: {
error: "Document Type invalid for current year posting",
violations: collection[ledgerType = "GP" and foreignAmount != null]
},
result: ([check1, check2])[count(violations) > 0]
}

Structure Calculation using a Context

Example: calculate the minimum age of a given list of birthdays.

{
age: function(birthday) (today - birthday).years,
ages: for birthday in birthdays return age(birthday),
minAge: min(ages)
}.minAge
- + \ No newline at end of file diff --git a/docs/1.12/feel-grammar/index.html b/docs/1.12/feel-grammar/index.html index 6a7414340..a7e00fa7c 100644 --- a/docs/1.12/feel-grammar/index.html +++ b/docs/1.12/feel-grammar/index.html @@ -5,13 +5,13 @@ FEEL Grammar | FEEL-Scala - +
Version: 1.12

FEEL Grammar

EBNF

This is the original grammar from the spec.

1. expression = 
a. textual expression |
b. boxed expression ;

2. textual expression =
a. function definition | for expression | if expression | quantified expression |
b. disjunction |
c. conjunction |
d. comparison |
e. arithmetic expression |
f. instance of |
g. path expression |
h. filter expression | function invocation |
i. literal | simple positive unary test | name | "(" , textual expression , ")" ;

3. textual expressions = textual expression , { "," , textual expression } ;

4. arithmetic expression =
a. addition | subtraction |
b. multiplication | division |
c. exponentiation |
d. arithmetic negation ;

5. simple expression = arithmetic expression | simple value ;

6. simple expressions = simple expression , { "," , simple expression } ;

7. simple positive unary test =
a. [ "<" | "<=" | ">" | ">=" ] , endpoint |
b. interval ;

8. interval = ( open interval start | closed interval start ) , endpoint , ".." , endpoint , ( open interval end | closed interval
end ) ;

9. open interval start = "(" | "]" ;

10. closed interval start = "[" ;

11. open interval end = ")" | "[" ;

12. closed interval end = "]" ;

13. simple positive unary tests = simple positive unary test , { "," , simple positive unary test } ;

14. simple unary tests =
a. simple positive unary tests |
b. "not", "(", simple positive unary tests, ")" |
c. "-";

15. positive unary test = simple positive unary test | "null" ;

16. positive unary tests = positive unary test , { "," , positive unary test } ;

17. unary tests =
a. positive unary tests |
b. "not", " (", positive unary tests, ")" |
c. "-"

18. endpoint = simple value ;

19. simple value = qualified name | simple literal ;

20. qualified name = name , { "." , name } ;

21. addition = expression , "+" , expression ;

22. subtraction = expression , "-" , expression ;

23. multiplication = expression , "*" , expression ;

24. division = expression , "/" , expression ;

25. exponentiation = expression, "**", expression ;

26. arithmetic negation = "-" , expression ;

27. name = name start , { name part | additional name symbols } ;

28. name start = name start char, { name part char } ;

29. name part = name part char , { name part char } ;

30. name start char = "?" | [A-Z] | "_" | [a-z] | [\uC0-\uD6] | [\uD8-\uF6] | [\uF8-\u2FF] | [\u370-\u37D] | [\u37F-\u1FFF] |
[\u200C-\u200D] | [\u2070-\u218F] | [\u2C00-\u2FEF] | [\u3001-\uD7FF] | [\uF900-\uFDCF] | [\uFDF0-\uFFFD] |
[\u10000-\uEFFFF] ;

31. name part char = name start char | digit | \uB7 | [\u0300-\u036F] | [\u203F-\u2040] ;

32. additional name symbols = "." | "/" | "-" | "’" | "+" | "*" ;

33. literal = simple literal | "null" ;

34. simple literal = numeric literal | string literal | Boolean literal | date time literal ;

35. string literal = '"' , { character – ('"' | vertical space) }, '"' ;

36. Boolean literal = "true" | "false" ;

37. numeric literal = [ "-" ] , ( digits , [ ".", digits ] | "." , digits ) ;

38. digit = [0-9] ;

39. digits = digit , {digit} ;

40. function invocation = expression , parameters ;

41. parameters = "(" , ( named parameters | positional parameters ) , ")" ;

42. named parameters = parameter name , ":" , expression ,
{ "," , parameter name , ":" , expression } ;

43. parameter name = name ;

44. positional parameters = [ expression , { "," , expression } ] ;

45. path expression = expression , "." , name ;

46. for expression = "for" , name , "in" , expression { "," , name , "in" , expression } , "return" , expression ;

47. if expression = "if" , expression , "then" , expression , "else" expression ;

48. quantified expression = ("some" | "every") , name , "in" , expression , { name , "in" , expression } , "satisfies" ,
expression ;

49. disjunction = expression , "or" , expression ;

50. conjunction = expression , "and" , expression ;

51. comparison =
a. expression , ( "=" | "!=" | "<" | "<=" | ">" | ">=" ) , expression |
b. expression , "between" , expression , "and" , expression |
c. expression , "in" , positive unary test ;
d. expression , "in" , " (", positive unary tests, ")" ;

52. filter expression = expression , "[" , expression , "]" ;

53. instance of = expression , "instance" , "of" , type ;

54. type = qualified name ;

55. boxed expression = list | function definition | context ;

56. list = "[" [ expression , { "," , expression } ] , "]" ;

57. function definition = "function" , "(" , [ formal parameter { "," , formal parameter } ] , ")" ,
[ "external" ] , expression ;

58. formal parameter = parameter name ;

59. context = "{" , [context entry , { "," , context entry } ] , "}" ;

60. context entry = key , ":" , expression ;

61. key = name | string literal ;

62. date time literal = ( "date" | "time" | "date and time" | "duration" ) , "(" , string literal , ")" ;

PEG

Rewritten grammar which is used by the parser.

// 1 a)
expression = textualExpression
// 1 b)
expression10 = boxedExpression

// 3
textualExpressions = textualExpression ( "," textualExpression )*

// 2 a)
textualExpression = functionDefinition / forExpression / ifExpression / quantifiedExpression / expression2
// 2 b)
expression2 = disjunction
// 2 c)
expression3 = conjunction
// 2 d)
expression4 = comparison / expression5
// 2 e)
expression5 = arithmeticExpression
// 2 f)
expression6 = instanceOf / expression7
// 2 g)
expression7 = pathExpression
// 2 h)
expression8 = filterExpression / functionInvocation / expression9
// 2 i)
expression9 = literal / name / simplePositiveUnaryTest / ( "(" textualExpression ")" ) / expression10

// 6
simpleExpressions = simpleExpression ( "," simple expression )*

// 5
simpleExpression = arithmeticExpression / simpleValue

// 4 a) -> 21+22
arithmeticExpression = arithmeticExpression2 ( "+" arithmeticExpression2 / "-" arithmeticExpression2 )*
// 4 b) -> 23+24
arithmeticExpression2 = arithmeticExpression3 ( "*" arithmeticExpression3 / "/" arithmeticExpression3 )*
// 4 c) -> 25
arithmeticExpression3 = arithmeticExpression4 ( "**" arithmeticExpression4 )*
// 4 d) -> 26
arithmeticExpression4 = ("-")? expression6

// 17
unaryTests = "-" / ( "not" "(" positiveUnaryTests ")" ) / positiveUnaryTests

// 16
positiveUnaryTests = positiveUnaryTest ( "," positiveUnaryTest )*

// 15
positiveUnaryTest = "null" / simplePositiveUnaryTest

// 14
simpleUnaryTests = "-" / ( "not" "(" simplePositiveUnaryTests ")" ) / simplePositiveUnaryTests

// 13
simplePositiveUnaryTests = simplePositiveUnaryTest ( "," simplePositiveUnaryTest )*

// 7
simplePositiveUnaryTest = ( ( "<" / "<=" / ">" / ">=" )? endpoint ) / interval

// 18
endpoint = simpleValue

// 19
simpleValue = simpleLiteral / qualifiedName

// 33
literal = "null" / simpleLiteral

// 34
simpleLiteral = booleanLiteral / dateTimeLiteral / stringLiteral / numericLiteral

// 36
booleanLiteral = "true" / "false"

// 62
dateTimeLiteral = ( "date" / "time" / "date and time" / "duration" ) "(" stringLiteral ")"

// 35
stringLiteral = '"' ( !('"' / verticalSpace) character )* '"'

// 37
numericLiteral = ( "-" )? ( ( digits ( "." digits )? ) / ( "." digits ) )

// 39
digits = digit ( digit )*

// 38
digit = [0-9]

// 20
qualifiedName = name ( "." name )*

// 27
name = nameStart ( namePart / additionalNameSymbols )*

// 28
nameStart = nameStartChar ( namePartChar )*

// 29
namePart = ( namePartChar )+

// 30
nameStartChar = "?" / [A-Z] / "_" / [a-z] / [\uC0-\uD6] / [\uD8-\uF6] / [\uF8-\u2FF] / [\u370-\u37D] / [\u37F-\u1FFF] /
[\u200C-\u200D] / [\u2070-\u218F] / [\u2C00-\u2FEF] / [\u3001-\uD7FF] / [\uF900-\uFDCF] / [\uFDF0-\uFFFD] /
[\u10000-\uEFFFF]

// 31
namePartChar = nameStartChar / digit / [\uB7] / [\u0300-\u036F] / [\u203F-\u2040]

// 32
additionalNameSymbols = "." / "/" / "-" / "’" / "+" / "*"

// 8
interval = ( openIntervalStart / closedIntervalStart ) endpoint ".." endpoint ( openIntervalEnd / closedIntervalEnd )

// 9
openIntervalStart = "(" / "]"

// 10
closedIntervalStart = "["

// 11
openIntervalEnd = ")" / "["

// 12
closedIntervalEnd = "]"

// 46
forExpression = "for" name "in" expression ( "," name "in" expression )* "return" expression

// 47
ifExpression = "if" expression "then" expression "else" expression

// 48
quantifiedExpression = ("some" / "every") (name "in" expression)+ "satisfies" expression

// 49
disjunction = expression3 ( "or" expression3 )*

// 50
conjunction = expression4 ( "and" expression )*

// 51
comparison = ( expression5 ( "=" / "!=" / "<" / "<=" / ">" / ">=" ) expression5 ) /
( expression5 "between" expression "and" expression ) /
( expression5 "in" "(" positiveUnaryTests ")" ) /
( expression5 "in" positiveUnaryTest )

// 53
instanceOf = expression7 "instance" "of" type

// 54
type = qualifiedName

// 45
pathExpression = expression8 ( "." name )* ( "[" expression "]" )

// 52
filterExpression = expression9 "[" expression "]"

// 40
functionInvocation = expression9 parameters

// 41
parameters = "(" namedParameters / positionalParameters ")"

// 42
namedParameters = parameterName ":" expression ( "," parameterName ":" expression )*

// 43
parameterName = name

// 44
positionalParameters = ( expression ( "," expression )* )?

// 55
boxedExpression = list / functionDefinition / context

// 56
list = "[" ( expression ( "," expression )* )? "]"

// 57
functionDefinition = "function" "(" ( formalParameter ( "," formalParameter )* )? ")" ( "external" )? expression

// 58
formalParameter = parameterName

// 59
context = "{" ( contextEntry ( "," contextEntry )* )? "}"

// 60
contextEntry = key ":" expression

// 61
key = name / stringLiteral
- + \ No newline at end of file diff --git a/docs/1.12/reference/developer-guide/clock-spi/index.html b/docs/1.12/reference/developer-guide/clock-spi/index.html index dec415442..2d081cd34 100644 --- a/docs/1.12/reference/developer-guide/clock-spi/index.html +++ b/docs/1.12/reference/developer-guide/clock-spi/index.html @@ -5,13 +5,13 @@ Clock SPI | FEEL-Scala - +
Version: 1.12

Clock SPI

The clock is used when accessing the current time while evaluating expressions and unary tests (e.g. the builtin function now()). By default, it uses the system clock.

Using the SPI, the clock can be replaced by a custom one. For example, if the application uses its own and not the system clock.

Implement a FEEL Engine Clock

Create a sub-class of org.camunda.feel.FeelEngineClock. Implement the method getCurrentTime() to return the current time.

class MyClock extends FeelEngineClock {

override def getCurrentTime(): ZonedDateTime = {
val currentMillis = ...
Instant.ofEpochMilli(currentMillis).atZone(ZoneId.systemDefault())
}

}

Register the FEEL Engine Clock

Depending on how the FEEL engine is used, the clock can be passed directly on creation, or is loaded via Java ServiceLoader mechanism.

In the second case, create a new file org.camunda.feel.FeelEngineClock in the folder META-INF/services/. It must contain the full qualified name of the class.

org.camunda.feel.example.MyClock
- + \ No newline at end of file diff --git a/docs/1.12/reference/developer-guide/function-provider-spi/index.html b/docs/1.12/reference/developer-guide/function-provider-spi/index.html index 3b37e9c46..3f00d791b 100644 --- a/docs/1.12/reference/developer-guide/function-provider-spi/index.html +++ b/docs/1.12/reference/developer-guide/function-provider-spi/index.html @@ -5,13 +5,13 @@ Function Provider SPI | FEEL-Scala - +
Version: 1.12

Function Provider SPI

Functions can be invoked in expressions and unary tests. The engine includes some predefined built-in functions.

Own functions can be defined in two ways:

  • declaring them in an expression (e.g. a context)
  • via the function provider SPI

Using the SPI, the function can be implemented in Scala/Java and is not limited by FEEL. So, it's possible to use language features or libraries.

Create a sub-class of org.camunda.feel.context.CustomFunctionProvider and implement the method getFunction() which returns the function for the given name. If a function can have different parameters (i.e. different parameter count) then override getFunctions() instead.

class CustomScalaFunctionProvider extends CustomFunctionProvider {

def getFunction(name: String): Option[ValFunction] = functions.get(name)

def functionNames: Iterable[String] = functions.keys

val functions: Map[String, ValFunction] = Map(
"incr" -> ValFunction(
params = List("x"),
invoke = { case List(ValNumber(x)) => ValNumber(x + 1) }
)
)

}

The function must be of type ValFunction. It contains

  • params - list of the named parameters of the function
  • invoke - business logic as function which takes the arguments and returns the result. The order of the arguments is defined by the parameter list.
  • hasVarArgs - if true the function can have variable arguments for the last parameter. The last argument is of type list.

Register the Function

Depending how the FEEL engine is used, the function provider can be passed directly on creation, or is loaded via Java ServiceLoader mechanism.

In the second case, create a new file org.camunda.feel.context.CustomFunctionProvider in the folder META-INF/services/. It must contain all function providers by their full qualified name.

org.camunda.feel.example.context.CustomScalaFunctionProvider
org.camunda.feel.example.context.CustomJavaFunctionProvider
- + \ No newline at end of file diff --git a/docs/1.12/reference/developer-guide/value-mapper-spi/index.html b/docs/1.12/reference/developer-guide/value-mapper-spi/index.html index 5a17fa046..1186d9a04 100644 --- a/docs/1.12/reference/developer-guide/value-mapper-spi/index.html +++ b/docs/1.12/reference/developer-guide/value-mapper-spi/index.html @@ -5,13 +5,13 @@ Value Mapper SPI | FEEL-Scala - +
Version: 1.12

Value Mapper SPI

The value mapper is used while evaluating expressions and unary tests to

  • transform a variable into a FEEL data type (e.g. when it is referenced in an expression x + 1)
  • transform the result of the expression or unary tests from a FEEL data type into a common data type (e.g. to String or BigDecimal/Long)

Using the SPI, the transformation can be customized to support more/custom data types, or changing the data type of the result.

Create a sub-class of org.camunda.feel.valuemapper.CustomValueMapper. Implement the method toVal() and unpackVal() to transform the object. Set the priority of the value mapper to define the precedence compared to the other mappers.

class MyValueMapper extends CustomValueMapper {

override def toVal(x: Any, innerValueMapper: Any => Val): Option[Val] = x match {
case c: Custom => Some(ValString(c.getName))
case _ => None
}

override def unpackVal(value: Val, innerValueMapper: Val => Any): Option[Any] = value match {
case ValNumber(number) => Some(number.doubleValue) // map BigDecimal to Double
case _ => None
}

override val priority: Int = 1

}

Register the Value Mapper

Depending how the FEEL engine is used, the value mapper can be passed directly on creation, or is loaded via Java ServiceLoader mechanism.

In the second case, create a new file org.camunda.feel.valuemapper.CustomValueMapper in the folder META-INF/services/. It must contain the full qualified name of the value mapper.

org.camunda.feel.example.valuemapper.MyValueMapper
- + \ No newline at end of file diff --git a/docs/1.12/reference/index.html b/docs/1.12/reference/index.html index 104d03045..8939a1cce 100644 --- a/docs/1.12/reference/index.html +++ b/docs/1.12/reference/index.html @@ -5,7 +5,7 @@ Get Started | FEEL-Scala - + @@ -15,7 +15,7 @@ the following pages of the language guide:

If you want to integrate the FEEL engine in your application then have a look at the Developer Guide.

If you want to try out your FEEL expressions in development then check out the Playground (online or local).

- + \ No newline at end of file diff --git a/docs/1.12/samples/index.html b/docs/1.12/samples/index.html index c01ed8bec..522973835 100644 --- a/docs/1.12/samples/index.html +++ b/docs/1.12/samples/index.html @@ -5,13 +5,13 @@ Samples | FEEL-Scala - +
Version: 1.12

Samples

Some example FEEL expressions which are used with the engine. Feel free to add your examples ;)

Date/Time Calculation

Example: check if a date is at least 6 months before another.

date1 < date2 + duration("P6M")

Filter a List and Return the first Element

Example: return the first packaging element which unit is "Palette".

(data.attribute.packaging[unit = "Palette"])[1]

Validate Data using a Context

Example: validate journal entries and return all violations.

{
check1: {
error: "Document Type invalid for current year posting",
violations: collection[documentType = "S2" and glDate > startFiscalYear]
},
check2: {
error: "Document Type invalid for current year posting",
violations: collection[ledgerType = "GP" and foreignAmount != null]
},
result: ([check1, check2])[count(violations) > 0]
}

Structure Calculation using a Context

Example: calculate the minimum age of a given list of birthdays.

{
age: function(birthday) (today - birthday).years,
ages: for birthday in birthdays return age(birthday),
minAge: min(ages)
}.minAge
- + \ No newline at end of file diff --git a/docs/1.13/reference/developer-guide/clock-spi/index.html b/docs/1.13/reference/developer-guide/clock-spi/index.html index f7f6b748c..3338553ee 100644 --- a/docs/1.13/reference/developer-guide/clock-spi/index.html +++ b/docs/1.13/reference/developer-guide/clock-spi/index.html @@ -5,13 +5,13 @@ Clock SPI | FEEL-Scala - +
Version: 1.13

Clock SPI

The clock is used when accessing the current time while evaluating expressions and unary tests (e.g. the builtin function now()). By default, it uses the system clock.

Using the SPI, the clock can be replaced by a custom one. For example, if the application uses its own and not the system clock.

Implement a FEEL Engine Clock

Create a sub-class of org.camunda.feel.FeelEngineClock. Implement the method getCurrentTime() to return the current time.

class MyClock extends FeelEngineClock {

override def getCurrentTime(): ZonedDateTime = {
val currentMillis = ...
Instant.ofEpochMilli(currentMillis).atZone(ZoneId.systemDefault())
}

}

Register the FEEL Engine Clock

Depending on how the FEEL engine is used, the clock can be passed directly on creation, or is loaded via Java ServiceLoader mechanism.

In the second case, create a new file org.camunda.feel.FeelEngineClock in the folder META-INF/services/. It must contain the full qualified name of the class.

org.camunda.feel.example.MyClock
- + \ No newline at end of file diff --git a/docs/1.13/reference/developer-guide/function-provider-spi/index.html b/docs/1.13/reference/developer-guide/function-provider-spi/index.html index 63f83502e..4b8a8129e 100644 --- a/docs/1.13/reference/developer-guide/function-provider-spi/index.html +++ b/docs/1.13/reference/developer-guide/function-provider-spi/index.html @@ -5,13 +5,13 @@ Function Provider SPI | FEEL-Scala - +
Version: 1.13

Function Provider SPI

Functions can be invoked in expressions and unary tests. The engine includes some predefined built-in functions.

Own functions can be defined in two ways:

  • declaring them in an expression (e.g. a context)
  • via the function provider SPI

Using the SPI, the function can be implemented in Scala/Java and is not limited by FEEL. So, it's possible to use language features or libraries.

Create a sub-class of org.camunda.feel.context.CustomFunctionProvider and implement the method getFunction() which returns the function for the given name. If a function can have different parameters (i.e. different parameter count) then override getFunctions() instead.

class CustomScalaFunctionProvider extends CustomFunctionProvider {

def getFunction(name: String): Option[ValFunction] = functions.get(name)

def functionNames: Iterable[String] = functions.keys

val functions: Map[String, ValFunction] = Map(
"incr" -> ValFunction(
params = List("x"),
invoke = { case List(ValNumber(x)) => ValNumber(x + 1) }
)
)

}

The function must be of type ValFunction. It contains

  • params - list of the named parameters of the function
  • invoke - business logic as function which takes the arguments and returns the result. The order of the arguments is defined by the parameter list.
  • hasVarArgs - if true the function can have variable arguments for the last parameter. The last argument is of type list.

Register the Function

Depending how the FEEL engine is used, the function provider can be passed directly on creation, or is loaded via Java ServiceLoader mechanism.

In the second case, create a new file org.camunda.feel.context.CustomFunctionProvider in the folder META-INF/services/. It must contain all function providers by their full qualified name.

org.camunda.feel.example.context.CustomScalaFunctionProvider
org.camunda.feel.example.context.CustomJavaFunctionProvider
- + \ No newline at end of file diff --git a/docs/1.13/reference/developer-guide/value-mapper-spi/index.html b/docs/1.13/reference/developer-guide/value-mapper-spi/index.html index 790b26235..8190926d3 100644 --- a/docs/1.13/reference/developer-guide/value-mapper-spi/index.html +++ b/docs/1.13/reference/developer-guide/value-mapper-spi/index.html @@ -5,13 +5,13 @@ Value Mapper SPI | FEEL-Scala - +
Version: 1.13

Value Mapper SPI

The value mapper is used while evaluating expressions and unary tests to

  • transform a variable into a FEEL data type (e.g. when it is referenced in an expression x + 1)
  • transform the result of the expression or unary tests from a FEEL data type into a common data type (e.g. to String or BigDecimal/Long)

Using the SPI, the transformation can be customized to support more/custom data types, or changing the data type of the result.

Create a sub-class of org.camunda.feel.valuemapper.CustomValueMapper. Implement the method toVal() and unpackVal() to transform the object. Set the priority of the value mapper to define the precedence compared to the other mappers.

class MyValueMapper extends CustomValueMapper {

override def toVal(x: Any, innerValueMapper: Any => Val): Option[Val] = x match {
case c: Custom => Some(ValString(c.getName))
case _ => None
}

override def unpackVal(value: Val, innerValueMapper: Val => Any): Option[Any] = value match {
case ValNumber(number) => Some(number.doubleValue) // map BigDecimal to Double
case _ => None
}

override val priority: Int = 1

}

Register the Value Mapper

Depending how the FEEL engine is used, the value mapper can be passed directly on creation, or is loaded via Java ServiceLoader mechanism.

In the second case, create a new file org.camunda.feel.valuemapper.CustomValueMapper in the folder META-INF/services/. It must contain the full qualified name of the value mapper.

org.camunda.feel.example.valuemapper.MyValueMapper
- + \ No newline at end of file diff --git a/docs/1.13/reference/index.html b/docs/1.13/reference/index.html index 5921f80fb..454b146b4 100644 --- a/docs/1.13/reference/index.html +++ b/docs/1.13/reference/index.html @@ -5,7 +5,7 @@ Get Started | FEEL-Scala - + @@ -15,7 +15,7 @@ the following pages of the language guide:

If you want to integrate the FEEL engine in your application then have a look at the Developer Guide.

If you want to try out your FEEL expressions in development then check out the Playground (online or local).

- + \ No newline at end of file diff --git a/docs/1.13/samples/context-samples/index.html b/docs/1.13/samples/context-samples/index.html index 47d050d34..7cf602cb3 100644 --- a/docs/1.13/samples/context-samples/index.html +++ b/docs/1.13/samples/context-samples/index.html @@ -5,13 +5,13 @@ Context Samples | FEEL-Scala - +
Version: 1.13

Context Samples

Validate Data

Validate journal entries and return all violations.

{
check1: {
error: "Document Type invalid for current year posting",
violations: collection[documentType = "S2" and glDate > startFiscalYear]
},
check2: {
error: "Document Type invalid for current year posting",
violations: collection[ledgerType = "GP" and foreignAmount != null]
},
result: ([check1, check2])[count(violations) > 0]
}

Structure a Calculation

Calculate the minimum age of a given list of birthdays.

{
age: function(birthday) (today() - birthday).years,
ages: for birthday in birthdays return age(birthday),
minAge: min(ages)
}.minAge
- + \ No newline at end of file diff --git a/docs/1.13/samples/index.html b/docs/1.13/samples/index.html index 65f0464a1..e7b5ebae3 100644 --- a/docs/1.13/samples/index.html +++ b/docs/1.13/samples/index.html @@ -5,13 +5,13 @@ Samples | FEEL-Scala - +
Version: 1.13

Samples

The following pages contains example FEEL expressions to solve common problems.

Feel free to contribute your examples!

- + \ No newline at end of file diff --git a/docs/1.13/samples/list-samples/index.html b/docs/1.13/samples/list-samples/index.html index 8b97f0d8c..ae7a7d484 100644 --- a/docs/1.13/samples/list-samples/index.html +++ b/docs/1.13/samples/list-samples/index.html @@ -5,13 +5,13 @@ List Samples | FEEL-Scala - +
Version: 1.13

List Samples

Filter a List and Return the First Element

Return the first packaging element which unit is "Palette".

(data.attribute.packaging[unit = "Palette"])[1]
- + \ No newline at end of file diff --git a/docs/1.13/samples/temporal-samples/index.html b/docs/1.13/samples/temporal-samples/index.html index 3c447ba46..ebce524ad 100644 --- a/docs/1.13/samples/temporal-samples/index.html +++ b/docs/1.13/samples/temporal-samples/index.html @@ -5,13 +5,13 @@ Temporal Samples | FEEL-Scala - +
Version: 1.13

Temporal Samples

Compare a Date with Offset

Check if a date is at least 6 months before another.

date1 < date2 + duration("P6M")

Calculate the Age

Return the current age of a person based on a given birthday.

years and months duration(date(birthday), today()).years

Check for Weekend

Check if the current day is on weekend or not.

day of week(today()) in ("Saturday","Sunday")

Calculate the Duration between Dates

Return the duration between now and the next Tuesday at 08:00.

(for x in 1..7 
return date and time(today(),time("08:00:00Z"))
+ duration("P"+string(x)+"D")
)[day of week(item) = "Tuesday"][1] - now()

Calculate the Next Weekday

Return the next day that is not a weekend at 00:00.

(for x in 1..3 
return date and time(today(),time("00:00:00Z"))
+ duration("P"+string(x)+"D")
)[not(day of week(item) in ("Saturday","Sunday"))][1]
- + \ No newline at end of file diff --git a/docs/1.14/reference/developer-guide/bootstrapping/index.html b/docs/1.14/reference/developer-guide/bootstrapping/index.html index ef4626949..7d2618a57 100644 --- a/docs/1.14/reference/developer-guide/bootstrapping/index.html +++ b/docs/1.14/reference/developer-guide/bootstrapping/index.html @@ -5,7 +5,7 @@ Bootstrapping | FEEL-Scala - + @@ -15,7 +15,7 @@ code or accessing sensitive data. It is recommended to use the FunctionProvider API instead.

Use as script engine

Calling the FEEL engine via Java's script engine API (JSR 223).

object MyProgram {

val scriptEngineManager = new ScriptEngineManager

def feel(script: String, context: ScriptContext) {

val scriptEngine: FeelScriptEngine = scriptEngineManager.getEngineByName("feel")

val result: Object = scriptEngine.eval(script, context)
// ...
}

}

The engine is registered under the following names:

  • feel
  • http://www.omg.org/spec/FEEL/20140401 (FEEL namespace)
  • feel-scala

To evaluate a unary-tests expression, use one of the following names:

  • feel-unary-tests
  • feel-scala-unary-tests
- + \ No newline at end of file diff --git a/docs/1.14/reference/developer-guide/clock-spi/index.html b/docs/1.14/reference/developer-guide/clock-spi/index.html index 6fd867d2c..1cb22950c 100644 --- a/docs/1.14/reference/developer-guide/clock-spi/index.html +++ b/docs/1.14/reference/developer-guide/clock-spi/index.html @@ -5,13 +5,13 @@ Clock SPI | FEEL-Scala - +
Version: 1.14

Clock SPI

The clock is used when accessing the current time while evaluating expressions and unary tests (e.g. the builtin function now()). By default, it uses the system clock.

Using the SPI, the clock can be replaced by a custom one. For example, if the application uses its own and not the system clock.

Implement a FEEL Engine Clock

Create a sub-class of org.camunda.feel.FeelEngineClock. Implement the method getCurrentTime() to return the current time.

class MyClock extends FeelEngineClock {

override def getCurrentTime(): ZonedDateTime = {
val currentMillis = ...
Instant.ofEpochMilli(currentMillis).atZone(ZoneId.systemDefault())
}

}

Register the FEEL Engine Clock

Depending on how the FEEL engine is used, the clock can be passed directly on creation, or is loaded via Java ServiceLoader mechanism.

In the second case, create a new file org.camunda.feel.FeelEngineClock in the folder META-INF/services/. It must contain the full qualified name of the class.

org.camunda.feel.example.MyClock
- + \ No newline at end of file diff --git a/docs/1.14/reference/developer-guide/developer-guide-introduction/index.html b/docs/1.14/reference/developer-guide/developer-guide-introduction/index.html index c7c6bf0b6..aaf4981e9 100644 --- a/docs/1.14/reference/developer-guide/developer-guide-introduction/index.html +++ b/docs/1.14/reference/developer-guide/developer-guide-introduction/index.html @@ -5,7 +5,7 @@ Introduction | FEEL-Scala - + @@ -13,7 +13,7 @@
- + \ No newline at end of file diff --git a/docs/1.14/reference/developer-guide/function-provider-spi/index.html b/docs/1.14/reference/developer-guide/function-provider-spi/index.html index 360ab9a80..e327a9eda 100644 --- a/docs/1.14/reference/developer-guide/function-provider-spi/index.html +++ b/docs/1.14/reference/developer-guide/function-provider-spi/index.html @@ -5,13 +5,13 @@ Function Provider SPI | FEEL-Scala - +
Version: 1.14

Function Provider SPI

Functions can be invoked in expressions and unary tests. The engine includes some predefined built-in functions.

Own functions can be defined in two ways:

  • declaring them in an expression (e.g. a context)
  • via the function provider SPI

Using the SPI, the function can be implemented in Scala/Java and is not limited by FEEL. So, it's possible to use language features or libraries.

Create a sub-class of org.camunda.feel.context.CustomFunctionProvider and implement the method getFunction() which returns the function for the given name. If a function can have different parameters (i.e. different parameter count) then override getFunctions() instead.

class CustomScalaFunctionProvider extends CustomFunctionProvider {

def getFunction(name: String): Option[ValFunction] = functions.get(name)

def functionNames: Iterable[String] = functions.keys

val functions: Map[String, ValFunction] = Map(
"incr" -> ValFunction(
params = List("x"),
invoke = { case List(ValNumber(x)) => ValNumber(x + 1) }
)
)

}

The function must be of type ValFunction. It contains

  • params - list of the named parameters of the function
  • invoke - business logic as function which takes the arguments and returns the result. The order of the arguments is defined by the parameter list.
  • hasVarArgs - if true the function can have variable arguments for the last parameter. The last argument is of type list.

Register the Function

Depending how the FEEL engine is used, the function provider can be passed directly on creation, or is loaded via Java ServiceLoader mechanism.

In the second case, create a new file org.camunda.feel.context.CustomFunctionProvider in the folder META-INF/services/. It must contain all function providers by their full qualified name.

org.camunda.feel.example.context.CustomScalaFunctionProvider
org.camunda.feel.example.context.CustomJavaFunctionProvider
- + \ No newline at end of file diff --git a/docs/1.14/reference/developer-guide/value-mapper-spi/index.html b/docs/1.14/reference/developer-guide/value-mapper-spi/index.html index b3bf81a3f..297b6492b 100644 --- a/docs/1.14/reference/developer-guide/value-mapper-spi/index.html +++ b/docs/1.14/reference/developer-guide/value-mapper-spi/index.html @@ -5,13 +5,13 @@ Value Mapper SPI | FEEL-Scala - +
Version: 1.14

Value Mapper SPI

The value mapper is used while evaluating expressions and unary tests to

  • transform a variable into a FEEL data type (e.g. when it is referenced in an expression x + 1)
  • transform the result of the expression or unary tests from a FEEL data type into a common data type (e.g. to String or BigDecimal/Long)

Using the SPI, the transformation can be customized to support more/custom data types, or changing the data type of the result.

Implement a Value Mapper

Create a sub-class of org.camunda.feel.valuemapper.CustomValueMapper. Implement the method toVal() and unpackVal() to transform the object. Set the priority of the value mapper to define the precedence compared to the other mappers.

class MyValueMapper extends CustomValueMapper {

override def toVal(x: Any, innerValueMapper: Any => Val): Option[Val] = x match {
case c: Custom => Some(ValString(c.getName))
case _ => None
}

override def unpackVal(value: Val, innerValueMapper: Val => Any): Option[Any] = value match {
case ValNumber(number) => Some(number.doubleValue) // map BigDecimal to Double
case _ => None
}

override val priority: Int = 1

}

Register a Value Mapper

Depending on how the FEEL engine is used, the value mapper can be passed directly on creation, or is loaded via Java ServiceLoader mechanism.

In the second case, create a new file org.camunda.feel.valuemapper.CustomValueMapper in the folder META-INF/services/. It must contain the full qualified name of the value mapper.

org.camunda.feel.example.valuemapper.MyValueMapper
tip

The FEEL engine contains a built-in value mapper org.camunda.feel.impl.JavaValueMapper to transform the result of an expression into a Java type, for example, to a java.util.List or a java.util.Map. This is useful if the FEEL engine is called from Java code.

- + \ No newline at end of file diff --git a/docs/1.14/reference/index.html b/docs/1.14/reference/index.html index 79c3242ad..f8916e623 100644 --- a/docs/1.14/reference/index.html +++ b/docs/1.14/reference/index.html @@ -5,7 +5,7 @@ Get Started | FEEL-Scala - + @@ -15,7 +15,7 @@ the following pages of the language guide:

If you want to integrate the FEEL engine in your application then have a look at the Developer Guide.

If you want to try out your FEEL expressions in development then check out the Playground (online or local).

- + \ No newline at end of file diff --git a/docs/1.14/samples/context-samples/index.html b/docs/1.14/samples/context-samples/index.html index 93434b8f0..873fb235c 100644 --- a/docs/1.14/samples/context-samples/index.html +++ b/docs/1.14/samples/context-samples/index.html @@ -5,13 +5,13 @@ Context Samples | FEEL-Scala - +
Version: 1.14

Context Samples

Validate Data

Validate journal entries and return all violations.

{
check1: {
error: "Document Type invalid for current year posting",
violations: collection[documentType = "S2" and glDate > startFiscalYear]
},
check2: {
error: "Document Type invalid for current year posting",
violations: collection[ledgerType = "GP" and foreignAmount != null]
},
result: ([check1, check2])[count(violations) > 0]
}

Structure a Calculation

Calculate the minimum age of a given list of birthdays.

{
age: function(birthday) (today() - birthday).years,
ages: for birthday in birthdays return age(birthday),
minAge: min(ages)
}.minAge
- + \ No newline at end of file diff --git a/docs/1.14/samples/index.html b/docs/1.14/samples/index.html index 89041b28d..0e935597c 100644 --- a/docs/1.14/samples/index.html +++ b/docs/1.14/samples/index.html @@ -5,13 +5,13 @@ Samples | FEEL-Scala - +
Version: 1.14

Samples

The following pages contains example FEEL expressions to solve common problems.

Feel free to contribute your examples!

- + \ No newline at end of file diff --git a/docs/1.14/samples/list-samples/index.html b/docs/1.14/samples/list-samples/index.html index 5dc42cc09..550ac519f 100644 --- a/docs/1.14/samples/list-samples/index.html +++ b/docs/1.14/samples/list-samples/index.html @@ -5,14 +5,14 @@ List Samples | FEEL-Scala - +
Version: 1.14

List Samples

Filter a List and Return the First Element

Return the first packaging element which unit is "Palette".

(data.attribute.packaging[unit = "Palette"])[1]

Group a List

Group the given list of invoices by their person.

Each invoice has a person. The persons are extracted from the invoices and are used as a filter for the list.

for p in distinct values(invoices.person) return invoices[person = p]

Input:

{"invoices":[
{"id":1, "person":"A", "amount": 10},
{"id":2, "person":"A", "amount": 20},
{"id":3, "person":"A", "amount": 30},
{"id":4, "person":"A", "amount": 40},
{"id":5, "person":"B", "amount": 15},
{"id":6, "person":"B", "amount": 25}
]}

Output:

[
[
{"id":1, "person":"A", "amount": 10},
{"id":2, "person":"A", "amount": 20},
{"id":3, "person":"A", "amount": 30},
{"id":4, "person":"A", "amount": 40}
],
[
{"id":5, "person":"B", "amount": 15},
{"id":6, "person":"B", "amount": 25}
]
]

Merge two Lists

Merge two given lists. Each list contains context values with the same structure. Each context has an entry "id" that identifies the value.

The result is a list that contains all context values grouped by the identifier.

 {
ids: union(x.files.id,y.files.id),
getById: function (files,fileId)
if (count(files[id=fileId]) > 0)
then files[id=fileId][1]
else {},
merge: for id in ids return put all(getById(x.files, id), getById(y.files, id))
}.merge

Input:

{
"x": {"files": [
{"id":1, "content":"a"},
{"id":2, "content":"b"}
]},
"y": {"files": [
{"id":1, "content":"a2"},
{"id":3, "content":"c"}
]}
}

Output:

[
{"id":1, "content":"a2"},
{"id":2, "content":"b"},
{"id":3, "content":"c"}
]
- + \ No newline at end of file diff --git a/docs/1.14/samples/temporal-samples/index.html b/docs/1.14/samples/temporal-samples/index.html index 34508cfe6..f292a1ac6 100644 --- a/docs/1.14/samples/temporal-samples/index.html +++ b/docs/1.14/samples/temporal-samples/index.html @@ -5,13 +5,13 @@ Temporal Samples | FEEL-Scala - +
Version: 1.14

Temporal Samples

Compare a Date with Offset

Check if a date is at least 6 months before another.

date1 < date2 + duration("P6M")

Calculate the Age

Return the current age of a person based on a given birthday.

years and months duration(date(birthday), today()).years

Check for Weekend

Check if the current day is on weekend or not.

day of week(today()) in ("Saturday","Sunday")

Calculate the Duration between Dates

Return the duration between now and the next Tuesday at 08:00.

(for x in 1..7 
return date and time(today(),time("08:00:00Z"))
+ duration("P"+string(x)+"D")
)[day of week(item) = "Tuesday"][1] - now()

Calculate the Duration between Times

Return the duration between now and the next time it is 09:00 in Europe/Berlin timezone.

{
time: time("09:00:00@Europe/Berlin"),
date: if (time(now()) < time) then today() else today() + duration("P1D"),
duration: date and time(date, time) - now()
}.duration

Output:

duration("PT18H30M38S")

Calculate the Next Weekday

Return the next day that is not a weekend at 00:00.

(for x in 1..3 
return date and time(today(),time("00:00:00Z"))
+ duration("P"+string(x)+"D")
)[not(day of week(item) in ("Saturday","Sunday"))][1]

Change the format of Dates

Transform a given list of date-time values into a custom format.

for d in dates return { 
date: date(date and time(d)),
day: string(date.day),
month: substring(month of year(date), 1, 3),
year: string(date.year),
formatted: day + "-" + month + "-" + year
}.formatted

Input:

["2021-04-21T07:25:06.000Z","2021-04-22T07:25:06.000Z"]

Output:

["21-Apr-2021","22-Apr-2021"]

Create a Unix Timestamp

Return the current point in time as a Unix timestamp.

(now() - date and time("1970-01-01T00:00Z")) / duration("PT1S") * 1000

Output:

1618200039000
- + \ No newline at end of file diff --git a/docs/1.15/changelog/index.html b/docs/1.15/changelog/index.html index 27e05c3cf..6baaa9a10 100644 --- a/docs/1.15/changelog/index.html +++ b/docs/1.15/changelog/index.html @@ -5,7 +5,7 @@ Changelog | FEEL-Scala - + @@ -34,7 +34,7 @@ that returns name of the weekday
  • New function day of year() that returns the number of the day within the year
  • See the full changelog here.

    1.11

    Camunda Cloud: 0.23.0Camunda Platform: 7.13.0

    Expressions:

    • Access the element of a list using a numeric variable
    • Disable external functions by default for security reasons

    See the full changelog here.

    - + \ No newline at end of file diff --git a/docs/1.15/playground/index.html b/docs/1.15/playground/index.html index e14dc97b6..d1cb459b4 100644 --- a/docs/1.15/playground/index.html +++ b/docs/1.15/playground/index.html @@ -5,7 +5,7 @@ Playground for FEEL expressions | FEEL-Scala - + @@ -16,7 +16,7 @@ "x": 5 }

    Result

    <click 'Evaluate' to see the result of the expression>

    Warnings

    <none>
    - + \ No newline at end of file diff --git a/docs/1.15/reference/developer-guide/bootstrapping/index.html b/docs/1.15/reference/developer-guide/bootstrapping/index.html index ba153d6bc..8bd41e01b 100644 --- a/docs/1.15/reference/developer-guide/bootstrapping/index.html +++ b/docs/1.15/reference/developer-guide/bootstrapping/index.html @@ -5,7 +5,7 @@ Bootstrapping | FEEL-Scala - + @@ -15,7 +15,7 @@ code or accessing sensitive data. It is recommended to use the FunctionProvider API instead.

    Use as script engine

    Calling the FEEL engine via Java's script engine API (JSR 223).

    object MyProgram {

    val scriptEngineManager = new ScriptEngineManager

    def feel(script: String, context: ScriptContext) {

    val scriptEngine: FeelScriptEngine = scriptEngineManager.getEngineByName("feel")

    val result: Object = scriptEngine.eval(script, context)
    // ...
    }

    }

    The engine is registered under the following names:

    • feel
    • http://www.omg.org/spec/FEEL/20140401 (FEEL namespace)
    • feel-scala

    To evaluate a unary-tests expression, use one of the following names:

    • feel-unary-tests
    • feel-scala-unary-tests
    - + \ No newline at end of file diff --git a/docs/1.15/reference/developer-guide/clock-spi/index.html b/docs/1.15/reference/developer-guide/clock-spi/index.html index 1bb7cc078..331fe27de 100644 --- a/docs/1.15/reference/developer-guide/clock-spi/index.html +++ b/docs/1.15/reference/developer-guide/clock-spi/index.html @@ -5,13 +5,13 @@ Clock SPI | FEEL-Scala - +
    Version: 1.15

    Clock SPI

    The clock is used when accessing the current time while evaluating expressions and unary tests (e.g. the builtin function now()). By default, it uses the system clock.

    Using the SPI, the clock can be replaced by a custom one. For example, if the application uses its own and not the system clock.

    Implement a FEEL Engine Clock

    Create a sub-class of org.camunda.feel.FeelEngineClock. Implement the method getCurrentTime() to return the current time.

    class MyClock extends FeelEngineClock {

    override def getCurrentTime(): ZonedDateTime = {
    val currentMillis = ...
    Instant.ofEpochMilli(currentMillis).atZone(ZoneId.systemDefault())
    }

    }

    Register the FEEL Engine Clock

    Depending on how the FEEL engine is used, the clock can be passed directly on creation, or is loaded via Java ServiceLoader mechanism.

    In the second case, create a new file org.camunda.feel.FeelEngineClock in the folder META-INF/services/. It must contain the full qualified name of the class.

    org.camunda.feel.example.MyClock
    - + \ No newline at end of file diff --git a/docs/1.15/reference/developer-guide/developer-guide-introduction/index.html b/docs/1.15/reference/developer-guide/developer-guide-introduction/index.html index 40fe5f6e9..b1485bfba 100644 --- a/docs/1.15/reference/developer-guide/developer-guide-introduction/index.html +++ b/docs/1.15/reference/developer-guide/developer-guide-introduction/index.html @@ -5,7 +5,7 @@ Introduction | FEEL-Scala - + @@ -13,7 +13,7 @@
    - + \ No newline at end of file diff --git a/docs/1.15/reference/developer-guide/function-provider-spi/index.html b/docs/1.15/reference/developer-guide/function-provider-spi/index.html index 389a4da12..ca269e524 100644 --- a/docs/1.15/reference/developer-guide/function-provider-spi/index.html +++ b/docs/1.15/reference/developer-guide/function-provider-spi/index.html @@ -5,13 +5,13 @@ Function Provider SPI | FEEL-Scala - +
    Version: 1.15

    Function Provider SPI

    Functions can be invoked in expressions and unary tests. The engine includes some predefined built-in functions.

    Own functions can be defined in two ways:

    • declaring them in an expression (e.g. a context)
    • via the function provider SPI

    Using the SPI, the function can be implemented in Scala/Java and is not limited by FEEL. So, it's possible to use language features or libraries.

    Create a sub-class of org.camunda.feel.context.CustomFunctionProvider and implement the method getFunction() which returns the function for the given name. If a function can have different parameters (i.e. different parameter count) then override getFunctions() instead.

    class CustomScalaFunctionProvider extends CustomFunctionProvider {

    def getFunction(name: String): Option[ValFunction] = functions.get(name)

    def functionNames: Iterable[String] = functions.keys

    val functions: Map[String, ValFunction] = Map(
    "incr" -> ValFunction(
    params = List("x"),
    invoke = { case List(ValNumber(x)) => ValNumber(x + 1) }
    )
    )

    }

    The function must be of type ValFunction. It contains

    • params - list of the named parameters of the function
    • invoke - business logic as function which takes the arguments and returns the result. The order of the arguments is defined by the parameter list.
    • hasVarArgs - if true the function can have variable arguments for the last parameter. The last argument is of type list.

    Register the Function

    Depending how the FEEL engine is used, the function provider can be passed directly on creation, or is loaded via Java ServiceLoader mechanism.

    In the second case, create a new file org.camunda.feel.context.CustomFunctionProvider in the folder META-INF/services/. It must contain all function providers by their full qualified name.

    org.camunda.feel.example.context.CustomScalaFunctionProvider
    org.camunda.feel.example.context.CustomJavaFunctionProvider
    - + \ No newline at end of file diff --git a/docs/1.15/reference/developer-guide/value-mapper-spi/index.html b/docs/1.15/reference/developer-guide/value-mapper-spi/index.html index ef8398acf..397920b3a 100644 --- a/docs/1.15/reference/developer-guide/value-mapper-spi/index.html +++ b/docs/1.15/reference/developer-guide/value-mapper-spi/index.html @@ -5,13 +5,13 @@ Value Mapper SPI | FEEL-Scala - +
    Version: 1.15

    Value Mapper SPI

    The value mapper is used while evaluating expressions and unary tests to

    • transform a variable into a FEEL data type (e.g. when it is referenced in an expression x + 1)
    • transform the result of the expression or unary tests from a FEEL data type into a common data type (e.g. to String or BigDecimal/Long)

    Using the SPI, the transformation can be customized to support more/custom data types, or changing the data type of the result.

    Implement a Value Mapper

    Create a sub-class of org.camunda.feel.valuemapper.CustomValueMapper. Implement the method toVal() and unpackVal() to transform the object. Set the priority of the value mapper to define the precedence compared to the other mappers.

    class MyValueMapper extends CustomValueMapper {

    override def toVal(x: Any, innerValueMapper: Any => Val): Option[Val] = x match {
    case c: Custom => Some(ValString(c.getName))
    case _ => None
    }

    override def unpackVal(value: Val, innerValueMapper: Val => Any): Option[Any] = value match {
    case ValNumber(number) => Some(number.doubleValue) // map BigDecimal to Double
    case _ => None
    }

    override val priority: Int = 1

    }

    Register a Value Mapper

    Depending on how the FEEL engine is used, the value mapper can be passed directly on creation, or is loaded via Java ServiceLoader mechanism.

    In the second case, create a new file org.camunda.feel.valuemapper.CustomValueMapper in the folder META-INF/services/. It must contain the full qualified name of the value mapper.

    org.camunda.feel.example.valuemapper.MyValueMapper
    tip

    The FEEL engine contains a built-in value mapper org.camunda.feel.impl.JavaValueMapper to transform the result of an expression into a Java type, for example, to a java.util.List or a java.util.Map. This is useful if the FEEL engine is called from Java code.

    - + \ No newline at end of file diff --git a/docs/1.15/reference/index.html b/docs/1.15/reference/index.html index f5b9324ec..057c4ff9a 100644 --- a/docs/1.15/reference/index.html +++ b/docs/1.15/reference/index.html @@ -5,7 +5,7 @@ Get Started | FEEL-Scala - + @@ -15,7 +15,7 @@ the following pages of the language guide:

    If you want to integrate the FEEL engine in your application then have a look at the Developer Guide.

    If you want to try out your FEEL expressions in development then check out the Playground (online or local).

    - + \ No newline at end of file diff --git a/docs/1.15/samples/context-samples/index.html b/docs/1.15/samples/context-samples/index.html index ce1b7a2a6..370c0ca57 100644 --- a/docs/1.15/samples/context-samples/index.html +++ b/docs/1.15/samples/context-samples/index.html @@ -5,13 +5,13 @@ Context Samples | FEEL-Scala - +
    Version: 1.15

    Context Samples

    Validate Data

    Validate journal entries and return all violations.

    {
    check1: {
    error: "Document Type invalid for current year posting",
    violations: collection[documentType = "S2" and glDate > startFiscalYear]
    },
    check2: {
    error: "Document Type invalid for current year posting",
    violations: collection[ledgerType = "GP" and foreignAmount != null]
    },
    result: ([check1, check2])[count(violations) > 0]
    }

    Structure a Calculation

    Calculate the minimum age of a given list of birthdays.

    {
    age: function(birthday) (today() - birthday).years,
    ages: for birthday in birthdays return age(birthday),
    minAge: min(ages)
    }.minAge
    - + \ No newline at end of file diff --git a/docs/1.15/samples/index.html b/docs/1.15/samples/index.html index 33a09e9a5..6e451f22c 100644 --- a/docs/1.15/samples/index.html +++ b/docs/1.15/samples/index.html @@ -5,13 +5,13 @@ Samples | FEEL-Scala - +
    Version: 1.15

    Samples

    The following pages contains example FEEL expressions to solve common problems.

    Feel free to contribute your examples!

    - + \ No newline at end of file diff --git a/docs/1.15/samples/list-samples/index.html b/docs/1.15/samples/list-samples/index.html index 76bb0aba1..32f81fff5 100644 --- a/docs/1.15/samples/list-samples/index.html +++ b/docs/1.15/samples/list-samples/index.html @@ -5,14 +5,14 @@ List Samples | FEEL-Scala - +
    Version: 1.15

    List Samples

    Filter a List and Return the First Element

    Return the first packaging element which unit is "Palette".

    (data.attribute.packaging[unit = "Palette"])[1]

    Group a List

    Group the given list of invoices by their person.

    Each invoice has a person. The persons are extracted from the invoices and are used as a filter for the list.

    for p in distinct values(invoices.person) return invoices[person = p]

    Input:

    {"invoices":[
    {"id":1, "person":"A", "amount": 10},
    {"id":2, "person":"A", "amount": 20},
    {"id":3, "person":"A", "amount": 30},
    {"id":4, "person":"A", "amount": 40},
    {"id":5, "person":"B", "amount": 15},
    {"id":6, "person":"B", "amount": 25}
    ]}

    Output:

    [
    [
    {"id":1, "person":"A", "amount": 10},
    {"id":2, "person":"A", "amount": 20},
    {"id":3, "person":"A", "amount": 30},
    {"id":4, "person":"A", "amount": 40}
    ],
    [
    {"id":5, "person":"B", "amount": 15},
    {"id":6, "person":"B", "amount": 25}
    ]
    ]

    Merge two Lists

    Merge two given lists. Each list contains context values with the same structure. Each context has an entry "id" that identifies the value.

    The result is a list that contains all context values grouped by the identifier.

     {
    ids: union(x.files.id,y.files.id),
    getById: function (files,fileId)
    if (count(files[id=fileId]) > 0)
    then files[id=fileId][1]
    else {},
    merge: for id in ids return put all(getById(x.files, id), getById(y.files, id))
    }.merge

    Input:

    {
    "x": {"files": [
    {"id":1, "content":"a"},
    {"id":2, "content":"b"}
    ]},
    "y": {"files": [
    {"id":1, "content":"a2"},
    {"id":3, "content":"c"}
    ]}
    }

    Output:

    [
    {"id":1, "content":"a2"},
    {"id":2, "content":"b"},
    {"id":3, "content":"c"}
    ]
    - + \ No newline at end of file diff --git a/docs/1.15/samples/temporal-samples/index.html b/docs/1.15/samples/temporal-samples/index.html index fda339bc8..b6278ceb4 100644 --- a/docs/1.15/samples/temporal-samples/index.html +++ b/docs/1.15/samples/temporal-samples/index.html @@ -5,13 +5,13 @@ Temporal Samples | FEEL-Scala - +
    Version: 1.15

    Temporal Samples

    Compare a Date with Offset

    Check if a date is at least 6 months before another.

    date1 < date2 + duration("P6M")

    Calculate the Age

    Return the current age of a person based on a given birthday.

    years and months duration(date(birthday), today()).years

    Check for Weekend

    Check if the current day is on weekend or not.

    day of week(today()) in ("Saturday","Sunday")

    Calculate the Duration between Dates

    Return the duration between now and the next Tuesday at 08:00.

    (for x in 1..7 
    return date and time(today(),time("08:00:00Z"))
    + duration("P"+string(x)+"D")
    )[day of week(item) = "Tuesday"][1] - now()

    Calculate the Duration between Times

    Return the duration between now and the next time it is 09:00 in Europe/Berlin timezone.

    {
    time: time("09:00:00@Europe/Berlin"),
    date: if (time(now()) < time) then today() else today() + duration("P1D"),
    duration: date and time(date, time) - now()
    }.duration

    Output:

    duration("PT18H30M38S")

    Calculate the Next Weekday

    Return the next day that is not a weekend at 00:00.

    (for x in 1..3 
    return date and time(today(),time("00:00:00Z"))
    + duration("P"+string(x)+"D")
    )[not(day of week(item) in ("Saturday","Sunday"))][1]

    Change the format of Dates

    Transform a given list of date-time values into a custom format.

    for d in dates return { 
    date: date(date and time(d)),
    day: string(date.day),
    month: substring(month of year(date), 1, 3),
    year: string(date.year),
    formatted: day + "-" + month + "-" + year
    }.formatted

    Input:

    ["2021-04-21T07:25:06.000Z","2021-04-22T07:25:06.000Z"]

    Output:

    ["21-Apr-2021","22-Apr-2021"]

    Create a Unix Timestamp

    Return the current point in time as a Unix timestamp.

    (now() - date and time("1970-01-01T00:00Z")) / duration("PT1S") * 1000

    Output:

    1618200039000
    - + \ No newline at end of file diff --git a/docs/1.15/tutorial/index.html b/docs/1.15/tutorial/index.html index 83a961cb2..bbe9ebd7f 100644 --- a/docs/1.15/tutorial/index.html +++ b/docs/1.15/tutorial/index.html @@ -5,7 +5,7 @@ The quest begins | FEEL-Scala - + @@ -17,7 +17,7 @@ FEEL expression and greet Zee.

    Expression

    "Hello Zee"
     


    Result

    <click 'Evaluate' to see the result of the expression>

    Warnings

    <none>
    note

    FEEL is an expression language. Compared to script languages or other complex programming languages, an expression language evaluates only a single expression.

    Zee is happy to have you on board. So, let the journey begin. 🚀

    - + \ No newline at end of file diff --git a/docs/1.15/tutorial/tutorial-1-1/index.html b/docs/1.15/tutorial/tutorial-1-1/index.html index 96e986490..8a51d82d8 100644 --- a/docs/1.15/tutorial/tutorial-1-1/index.html +++ b/docs/1.15/tutorial/tutorial-1-1/index.html @@ -5,7 +5,7 @@ 1.1 Numeric expressions | FEEL-Scala - + @@ -25,7 +25,7 @@ "restInterval": 5 }

    Result

    <click 'Evaluate' to see the result of the expression>

    Warnings

    <none>
    Solution
    It would take Zee 24 hours to complete the trip.

    Expression
    round up(restInHrs * (distance / speed) / restInterval + distance / speed, 0)
    - + \ No newline at end of file diff --git a/docs/1.15/tutorial/tutorial-1-2/index.html b/docs/1.15/tutorial/tutorial-1-2/index.html index 58dc26404..0cb70fcb0 100644 --- a/docs/1.15/tutorial/tutorial-1-2/index.html +++ b/docs/1.15/tutorial/tutorial-1-2/index.html @@ -5,7 +5,7 @@ 1.2 More numeric expressions | FEEL-Scala - + @@ -22,7 +22,7 @@ "exponent": 5 }

    Result

    <click 'Evaluate' to see the result of the expression>

    Warnings

    <none>
    Solution
    By the 5th time, there were 32 heads and no apparent way out, thankfully the heads started fighting with each other and Zee was able to escape.

    Expression
    base ** exponent
    - + \ No newline at end of file diff --git a/docs/1.15/tutorial/tutorial-1-3/index.html b/docs/1.15/tutorial/tutorial-1-3/index.html index 99303bb45..37f54d947 100644 --- a/docs/1.15/tutorial/tutorial-1-3/index.html +++ b/docs/1.15/tutorial/tutorial-1-3/index.html @@ -5,7 +5,7 @@ 1.3 Numeric functions | FEEL-Scala - + @@ -23,7 +23,7 @@ "modulus": 24 }

    Result

    <click 'Evaluate' to see the result of the expression>

    Warnings

    <none>
    Solution
    Zee would take 6 days to arrive and would be there at 8:00 AM.

    Expression
    modulo((hours + startingHour), modulus)
    - + \ No newline at end of file diff --git a/docs/1.15/tutorial/tutorial-2-1/index.html b/docs/1.15/tutorial/tutorial-2-1/index.html index f8fb6ae58..fc2f01f8c 100644 --- a/docs/1.15/tutorial/tutorial-2-1/index.html +++ b/docs/1.15/tutorial/tutorial-2-1/index.html @@ -5,7 +5,7 @@ 2.1 String expressions | FEEL-Scala - + @@ -20,7 +20,7 @@ "lastName": "?" }

    Result

    <click 'Evaluate' to see the result of the expression>

    Warnings

    <none>
    Zee
    Camunda Services GmbH
    Zossener Str. 55
    10961 Berlin
    Germany
    < Fill in the name here >
    Camunda Inc.
    INDUSTRY Denver
    3001 Brighton Blvd, Suite 450
    Denver, CO 80216
    USA
    Solution
    Zee labels the envelopes and send them out.

    Expression
    firstName + " " + lastName
    - + \ No newline at end of file diff --git a/docs/1.15/tutorial/tutorial-3-1/index.html b/docs/1.15/tutorial/tutorial-3-1/index.html index b281a4ea5..d08f23e12 100644 --- a/docs/1.15/tutorial/tutorial-3-1/index.html +++ b/docs/1.15/tutorial/tutorial-3-1/index.html @@ -5,7 +5,7 @@ 3.1 Temporal expressions | FEEL-Scala - + @@ -20,7 +20,7 @@ "targetDate": "2022-10-05" }

    Result

    <click 'Evaluate' to see the result of the expression>

    Warnings

    <none>
    Solution
    Zee has 11 days to arrive in Berlin.

    Expression
    (date(targetDate) - date(startingDate) - duration("PT200H")) / duration("P1D")
    - + \ No newline at end of file diff --git a/docs/1.15/tutorial/tutorial-3-2/index.html b/docs/1.15/tutorial/tutorial-3-2/index.html index 1b56b92f7..c5c2509c4 100644 --- a/docs/1.15/tutorial/tutorial-3-2/index.html +++ b/docs/1.15/tutorial/tutorial-3-2/index.html @@ -5,7 +5,7 @@ 3.2 Temporal functions | FEEL-Scala - + @@ -21,7 +21,7 @@ "remainingTime": "P11D" }

    Result

    <click 'Evaluate' to see the result of the expression>

    Warnings

    <none>
    Solution
    It is Saturday. Zee can stay the weekend in Cologne.

    Expression
    day of week(date(targetDate) - duration(remainingTime))
    - + \ No newline at end of file diff --git a/docs/1.15/tutorial/tutorial-4-1/index.html b/docs/1.15/tutorial/tutorial-4-1/index.html index 2b1e38351..da25cd6c6 100644 --- a/docs/1.15/tutorial/tutorial-4-1/index.html +++ b/docs/1.15/tutorial/tutorial-4-1/index.html @@ -5,13 +5,13 @@ 4.1 Final Stop: Lists expressions | FEEL-Scala - +
    Version: 1.15

    4.1 Final Stop: Lists expressions

    danger

    Under Construction 🚧

    Here we will have some witty way of explaining usage of list expressions in FEEL

    Final Stop BERLIN!!!! in time for CamundaCon

    - + \ No newline at end of file diff --git a/docs/1.16/changelog/index.html b/docs/1.16/changelog/index.html index 5f9fec0d1..698a551ef 100644 --- a/docs/1.16/changelog/index.html +++ b/docs/1.16/changelog/index.html @@ -5,7 +5,7 @@ Changelog | FEEL-Scala - + @@ -34,7 +34,7 @@ that returns name of the weekday
  • New function day of year() that returns the number of the day within the year
  • See the full changelog here.

    1.11

    Zeebe: 0.23.0Camunda Platform: 7.13.0

    Expressions:

    • Access the element of a list using a numeric variable
    • Disable external functions by default for security reasons

    See the full changelog here.

    - + \ No newline at end of file diff --git a/docs/1.16/learn/challenge/chapter-1/index.html b/docs/1.16/learn/challenge/chapter-1/index.html index 68ce7cf94..f14aa7342 100644 --- a/docs/1.16/learn/challenge/chapter-1/index.html +++ b/docs/1.16/learn/challenge/chapter-1/index.html @@ -5,7 +5,7 @@ Chapter 1 | FEEL-Scala - + @@ -25,7 +25,7 @@ "restInterval": 5 }

    Result

    <click 'Evaluate' to see the result of the expression>

    Warnings

    <none>
    Solution
    It would take Zee 24 hours to complete the trip.

    Expression
    round up(restInHrs * (distance / speed) / restInterval + distance / speed, 0)
    - + \ No newline at end of file diff --git a/docs/1.16/learn/challenge/chapter-2/index.html b/docs/1.16/learn/challenge/chapter-2/index.html index 1094ed732..4f3e6fe93 100644 --- a/docs/1.16/learn/challenge/chapter-2/index.html +++ b/docs/1.16/learn/challenge/chapter-2/index.html @@ -5,7 +5,7 @@ Chapter 2 | FEEL-Scala - + @@ -22,7 +22,7 @@ "exponent": 5 }

    Result

    <click 'Evaluate' to see the result of the expression>

    Warnings

    <none>
    Solution
    By the 5th time, there were 32 heads and no apparent way out, thankfully the heads started fighting with each other and Zee was able to escape.

    Expression
    base ** exponent
    - + \ No newline at end of file diff --git a/docs/1.16/learn/challenge/chapter-3/index.html b/docs/1.16/learn/challenge/chapter-3/index.html index 4ca36bf2e..6712379b3 100644 --- a/docs/1.16/learn/challenge/chapter-3/index.html +++ b/docs/1.16/learn/challenge/chapter-3/index.html @@ -5,7 +5,7 @@ Chapter 3 | FEEL-Scala - + @@ -23,7 +23,7 @@ "modulus": 24 }

    Result

    <click 'Evaluate' to see the result of the expression>

    Warnings

    <none>
    Solution
    Zee would take 6 days to arrive and would be there at 8:00 AM.

    Expression
    modulo((hours + startingHour), modulus)
    - + \ No newline at end of file diff --git a/docs/1.16/learn/challenge/chapter-4/index.html b/docs/1.16/learn/challenge/chapter-4/index.html index 85f801470..57086d401 100644 --- a/docs/1.16/learn/challenge/chapter-4/index.html +++ b/docs/1.16/learn/challenge/chapter-4/index.html @@ -5,7 +5,7 @@ Chapter 4 | FEEL-Scala - + @@ -20,7 +20,7 @@ "lastName": "?" }

    Result

    <click 'Evaluate' to see the result of the expression>

    Warnings

    <none>
    Zee
    Camunda Services GmbH
    Zossener Str. 55
    10961 Berlin
    Germany
    < Fill in the name here >
    Camunda Inc.
    INDUSTRY Denver
    3001 Brighton Blvd, Suite 450
    Denver, CO 80216
    USA
    Solution
    Zee labels the envelopes and send them out.

    Expression
    firstName + " " + lastName
    - + \ No newline at end of file diff --git a/docs/1.16/learn/challenge/chapter-5/index.html b/docs/1.16/learn/challenge/chapter-5/index.html index 6b1aa7eee..8c97bc6c6 100644 --- a/docs/1.16/learn/challenge/chapter-5/index.html +++ b/docs/1.16/learn/challenge/chapter-5/index.html @@ -5,7 +5,7 @@ Chapter 5 | FEEL-Scala - + @@ -20,7 +20,7 @@ "targetDate": "2022-10-05" }

    Result

    <click 'Evaluate' to see the result of the expression>

    Warnings

    <none>
    Solution
    Zee has 11 days to arrive in Berlin.

    Expression
    (date(targetDate) - date(startingDate) - duration("PT200H")) / duration("P1D")
    - + \ No newline at end of file diff --git a/docs/1.16/learn/challenge/chapter-6/index.html b/docs/1.16/learn/challenge/chapter-6/index.html index 20aa3c829..8a5a8628f 100644 --- a/docs/1.16/learn/challenge/chapter-6/index.html +++ b/docs/1.16/learn/challenge/chapter-6/index.html @@ -5,7 +5,7 @@ Chapter 6 | FEEL-Scala - + @@ -47,7 +47,7 @@ ] }

    Result

    <click 'Evaluate' to see the result of the expression>

    Warnings

    <none>
    Solution
    Zee follows the blue route and reaches CamundaCon just-in-time. He is happy to be there and meet the community.

    Expression
    routes["Berlin" in stops][1].route
    - + \ No newline at end of file diff --git a/docs/1.16/learn/challenge/index.html b/docs/1.16/learn/challenge/index.html index ed247fa2f..f046f6bdd 100644 --- a/docs/1.16/learn/challenge/index.html +++ b/docs/1.16/learn/challenge/index.html @@ -5,7 +5,7 @@ The quest begins | FEEL-Scala - + @@ -16,7 +16,7 @@ 🇩🇪 in time for CamundaCon (2022). With the use of FEEL we'll be able to help in the journey.

    Before we start, let's say "Hi" to Zee:

    Zee

    Use the interactive editor below to evaluate the FEEL expression and greet Zee.

    Expression

    "Hello Zee"
     


    Result

    <click 'Evaluate' to see the result of the expression>

    Warnings

    <none>

    Zee is happy to have you on board. So, let the journey begin. 🚀

    info

    This challenge is an experiment. It was created as part of our Camunda Summer Hack Days 2022.

    - + \ No newline at end of file diff --git a/docs/1.16/learn/index.html b/docs/1.16/learn/index.html index 67e5e7602..b86da547b 100644 --- a/docs/1.16/learn/index.html +++ b/docs/1.16/learn/index.html @@ -5,7 +5,7 @@ Overview | FEEL-Scala - + @@ -14,7 +14,7 @@ examples:

    If you want to test your knowledge, take a look at the challenge.

    If you want to know more about FEEL in general and how to write FEEL expressions, see the lanugage guide.

    info

    We welcome contributions. Share your examples, improve the challenge, or suggest new ideas how to learn FEEL. 💡

    - + \ No newline at end of file diff --git a/docs/1.16/learn/samples/context-samples/index.html b/docs/1.16/learn/samples/context-samples/index.html index 7e5f9c1f9..2185cdad2 100644 --- a/docs/1.16/learn/samples/context-samples/index.html +++ b/docs/1.16/learn/samples/context-samples/index.html @@ -5,13 +5,13 @@ Context expressions | FEEL-Scala - +
    Version: 1.16

    Context expressions

    Validate Data

    Validate journal entries and return all violations.

    {
    check1: {
    error: "Document Type invalid for current year posting",
    violations: collection[documentType = "S2" and glDate > startFiscalYear]
    },
    check2: {
    error: "Document Type invalid for current year posting",
    violations: collection[ledgerType = "GP" and foreignAmount != null]
    },
    result: ([check1, check2])[count(violations) > 0]
    }

    Structure a Calculation

    Calculate the minimum age of a given list of birthdays.

    {
    age: function(birthday) (today() - birthday).years,
    ages: for birthday in birthdays return age(birthday),
    minAge: min(ages)
    }.minAge
    - + \ No newline at end of file diff --git a/docs/1.16/learn/samples/list-samples/index.html b/docs/1.16/learn/samples/list-samples/index.html index 3e9427546..5faaaef07 100644 --- a/docs/1.16/learn/samples/list-samples/index.html +++ b/docs/1.16/learn/samples/list-samples/index.html @@ -5,14 +5,14 @@ List expressions | FEEL-Scala - +
    Version: 1.16

    List expressions

    Filter a List and Return the First Element

    Return the first packaging element which unit is "Palette".

    (data.attribute.packaging[unit = "Palette"])[1]

    Group a List

    Group the given list of invoices by their person.

    Each invoice has a person. The persons are extracted from the invoices and are used as a filter for the list.

    for p in distinct values(invoices.person) return invoices[person = p]

    Input:

    {"invoices":[
    {"id":1, "person":"A", "amount": 10},
    {"id":2, "person":"A", "amount": 20},
    {"id":3, "person":"A", "amount": 30},
    {"id":4, "person":"A", "amount": 40},
    {"id":5, "person":"B", "amount": 15},
    {"id":6, "person":"B", "amount": 25}
    ]}

    Output:

    [
    [
    {"id":1, "person":"A", "amount": 10},
    {"id":2, "person":"A", "amount": 20},
    {"id":3, "person":"A", "amount": 30},
    {"id":4, "person":"A", "amount": 40}
    ],
    [
    {"id":5, "person":"B", "amount": 15},
    {"id":6, "person":"B", "amount": 25}
    ]
    ]

    Merge two Lists

    Merge two given lists. Each list contains context values with the same structure. Each context has an entry "id" that identifies the value.

    The result is a list that contains all context values grouped by the identifier.

     {
    ids: union(x.files.id,y.files.id),
    getById: function (files,fileId)
    if (count(files[id=fileId]) > 0)
    then files[id=fileId][1]
    else {},
    merge: for id in ids return put all(getById(x.files, id), getById(y.files, id))
    }.merge

    Input:

    {
    "x": {"files": [
    {"id":1, "content":"a"},
    {"id":2, "content":"b"}
    ]},
    "y": {"files": [
    {"id":1, "content":"a2"},
    {"id":3, "content":"c"}
    ]}
    }

    Output:

    [
    {"id":1, "content":"a2"},
    {"id":2, "content":"b"},
    {"id":3, "content":"c"}
    ]
    - + \ No newline at end of file diff --git a/docs/1.16/learn/samples/temporal-samples/index.html b/docs/1.16/learn/samples/temporal-samples/index.html index aad38726f..321d5dbf2 100644 --- a/docs/1.16/learn/samples/temporal-samples/index.html +++ b/docs/1.16/learn/samples/temporal-samples/index.html @@ -5,13 +5,13 @@ Temporal expressions | FEEL-Scala - +
    Version: 1.16

    Temporal expressions

    Compare a Date with Offset

    Check if a date is at least 6 months before another.

    date1 < date2 + duration("P6M")

    Calculate the Age

    Return the current age of a person based on a given birthday.

    years and months duration(date(birthday), today()).years

    Check for Weekend

    Check if the current day is on weekend or not.

    day of week(today()) in ("Saturday","Sunday")

    Calculate the Duration between Dates

    Return the duration between now and the next Tuesday at 08:00.

    (for x in 1..7 
    return date and time(today(),time("08:00:00Z"))
    + duration("P"+string(x)+"D")
    )[day of week(item) = "Tuesday"][1] - now()

    Calculate the Duration between Times

    Return the duration between now and the next time it is 09:00 in Europe/Berlin timezone.

    {
    time: time("09:00:00@Europe/Berlin"),
    date: if (time(now()) < time) then today() else today() + duration("P1D"),
    duration: date and time(date, time) - now()
    }.duration

    Output:

    duration("PT18H30M38S")

    Calculate the Next Weekday

    Return the next day that is not a weekend at 00:00.

    (for x in 1..3 
    return date and time(today(),time("00:00:00Z"))
    + duration("P"+string(x)+"D")
    )[not(day of week(item) in ("Saturday","Sunday"))][1]

    Change the format of Dates

    Transform a given list of date-time values into a custom format.

    for d in dates return { 
    date: date(date and time(d)),
    day: string(date.day),
    month: substring(month of year(date), 1, 3),
    year: string(date.year),
    formatted: day + "-" + month + "-" + year
    }.formatted

    Input:

    ["2021-04-21T07:25:06.000Z","2021-04-22T07:25:06.000Z"]

    Output:

    ["21-Apr-2021","22-Apr-2021"]

    Create a Unix Timestamp

    Return the current point in time as a Unix timestamp.

    (now() - date and time("1970-01-01T00:00Z")) / duration("PT1S") * 1000

    Output:

    1618200039000
    - + \ No newline at end of file diff --git a/docs/1.16/playground/index.html b/docs/1.16/playground/index.html index 4aaea1b43..a2f3405b3 100644 --- a/docs/1.16/playground/index.html +++ b/docs/1.16/playground/index.html @@ -5,7 +5,7 @@ FEEL Playground (online) | FEEL-Scala - + @@ -22,7 +22,7 @@ }

    Result

    <click 'Evaluate' to see the result of the expression>

    Warnings

    <none>

    About

    The playground uses the FEEL engine in the version ?.

    If the playground is broken, please report the failure here.

    - + \ No newline at end of file diff --git a/docs/1.16/playground/repl/index.html b/docs/1.16/playground/repl/index.html index 66a853006..77867113c 100644 --- a/docs/1.16/playground/repl/index.html +++ b/docs/1.16/playground/repl/index.html @@ -5,7 +5,7 @@ FEEL REPL (local) | FEEL-Scala - + @@ -14,7 +14,7 @@ (Read-Eval-Print-Loop) of the FEEL engine. It is a simple script based on Ammonite (aka Scala Scripting) that downloads the dependency to the FEEL engine and initialize it for you.

    the-feel-repl

    Install

    • Download Ammonite: http://ammonite.io/#Ammonite-REPL
      • On Linux:
      sudo sh -c '(echo "#!/usr/bin/env sh" && curl -L https://github.com/com-lihaoyi/Ammonite/releases/download/2.4.0/2.13-2.4.0) > /usr/local/bin/amm && chmod +x /usr/local/bin/amm' && amm
      • On Mac:
      brew install ammonite-repl
    • Download the script feel-repl.sc or clone the Git repository

    Usage

    Run the following script to start the REPL:

    amm --predef feel-repl.sc

    In the REPL, use one of the following functions to evaluate a FEEL expression:

    feel("1 + 3")
    // evaluate an expression without any context

    val context = Map("x" -> 3)
    feel("1 + x", context)
    // evaluate an expression with a map-based context

    feel("1 + x", "{ \"x\": 3}")
    // evaluate an expression with a JSON context

    feel(""" date("2020-04-06") + duration("P3D") """)
    // evaluate an expression ignoring any quotes in the expression

    //----------------------------------------------------------------------

    unaryTests("> 3", 5)
    // evaluate a unary-tests with a given input value

    unaryTests("> 3", 5)
    // evaluate a unary-tests with a given input value

    val context = Map("x" -> 3)
    unaryTests("> x", 5, context)
    // evaluate a unary-tests with a given input value and map-based context

    unaryTests("> x", "5", "{ \"x\": 3}")
    // evaluate a unary-tests with a given JSON input value and JSON context
    - + \ No newline at end of file diff --git a/docs/1.16/reference/developer-guide/bootstrapping/index.html b/docs/1.16/reference/developer-guide/bootstrapping/index.html index 09ef784f9..03ddcb208 100644 --- a/docs/1.16/reference/developer-guide/bootstrapping/index.html +++ b/docs/1.16/reference/developer-guide/bootstrapping/index.html @@ -5,7 +5,7 @@ Bootstrapping | FEEL-Scala - + @@ -15,7 +15,7 @@ code or accessing sensitive data. It is recommended to use the FunctionProvider API instead.

    Use as script engine

    Calling the FEEL engine via Java's script engine API (JSR 223).

    object MyProgram {

    val scriptEngineManager = new ScriptEngineManager

    def feel(script: String, context: ScriptContext) {

    val scriptEngine: FeelScriptEngine = scriptEngineManager.getEngineByName("feel")

    val result: Object = scriptEngine.eval(script, context)
    // ...
    }

    }

    The engine is registered under the following names:

    • feel
    • http://www.omg.org/spec/FEEL/20140401 (FEEL namespace)
    • feel-scala

    To evaluate a unary-tests expression, use one of the following names:

    • feel-unary-tests
    • feel-scala-unary-tests
    - + \ No newline at end of file diff --git a/docs/1.16/reference/developer-guide/clock-spi/index.html b/docs/1.16/reference/developer-guide/clock-spi/index.html index a375a49c6..93fadc7d7 100644 --- a/docs/1.16/reference/developer-guide/clock-spi/index.html +++ b/docs/1.16/reference/developer-guide/clock-spi/index.html @@ -5,13 +5,13 @@ Clock SPI | FEEL-Scala - +
    Version: 1.16

    Clock SPI

    The clock is used when accessing the current time while evaluating expressions and unary tests (e.g. the builtin function now()). By default, it uses the system clock.

    Using the SPI, the clock can be replaced by a custom one. For example, if the application uses its own and not the system clock.

    Implement a FEEL Engine Clock

    Create a sub-class of org.camunda.feel.FeelEngineClock. Implement the method getCurrentTime() to return the current time.

    class MyClock extends FeelEngineClock {

    override def getCurrentTime(): ZonedDateTime = {
    val currentMillis = ...
    Instant.ofEpochMilli(currentMillis).atZone(ZoneId.systemDefault())
    }

    }

    Register the FEEL Engine Clock

    Depending on how the FEEL engine is used, the clock can be passed directly on creation, or is loaded via Java ServiceLoader mechanism.

    In the second case, create a new file org.camunda.feel.FeelEngineClock in the folder META-INF/services/. It must contain the full qualified name of the class.

    org.camunda.feel.example.MyClock
    - + \ No newline at end of file diff --git a/docs/1.16/reference/developer-guide/developer-guide-introduction/index.html b/docs/1.16/reference/developer-guide/developer-guide-introduction/index.html index 0eff5b4b2..feffbe373 100644 --- a/docs/1.16/reference/developer-guide/developer-guide-introduction/index.html +++ b/docs/1.16/reference/developer-guide/developer-guide-introduction/index.html @@ -5,7 +5,7 @@ Introduction | FEEL-Scala - + @@ -13,7 +13,7 @@
    - + \ No newline at end of file diff --git a/docs/1.16/reference/developer-guide/function-provider-spi/index.html b/docs/1.16/reference/developer-guide/function-provider-spi/index.html index b38e53bbc..d2337b646 100644 --- a/docs/1.16/reference/developer-guide/function-provider-spi/index.html +++ b/docs/1.16/reference/developer-guide/function-provider-spi/index.html @@ -5,13 +5,13 @@ Function Provider SPI | FEEL-Scala - +
    Version: 1.16

    Function Provider SPI

    Functions can be invoked in expressions and unary tests. The engine includes some predefined built-in functions.

    Own functions can be defined in two ways:

    • declaring them in an expression (e.g. a context)
    • via the function provider SPI

    Using the SPI, the function can be implemented in Scala/Java and is not limited by FEEL. So, it's possible to use language features or libraries.

    Create a sub-class of org.camunda.feel.context.CustomFunctionProvider and implement the method getFunction() which returns the function for the given name. If a function can have different parameters (i.e. different parameter count) then override getFunctions() instead.

    class CustomScalaFunctionProvider extends CustomFunctionProvider {

    def getFunction(name: String): Option[ValFunction] = functions.get(name)

    def functionNames: Iterable[String] = functions.keys

    val functions: Map[String, ValFunction] = Map(
    "incr" -> ValFunction(
    params = List("x"),
    invoke = { case List(ValNumber(x)) => ValNumber(x + 1) }
    )
    )

    }

    The function must be of type ValFunction. It contains

    • params - list of the named parameters of the function
    • invoke - business logic as function which takes the arguments and returns the result. The order of the arguments is defined by the parameter list.
    • hasVarArgs - if true the function can have variable arguments for the last parameter. The last argument is of type list.

    Register the Function

    Depending how the FEEL engine is used, the function provider can be passed directly on creation, or is loaded via Java ServiceLoader mechanism.

    In the second case, create a new file org.camunda.feel.context.CustomFunctionProvider in the folder META-INF/services/. It must contain all function providers by their full qualified name.

    org.camunda.feel.example.context.CustomScalaFunctionProvider
    org.camunda.feel.example.context.CustomJavaFunctionProvider
    - + \ No newline at end of file diff --git a/docs/1.16/reference/developer-guide/value-mapper-spi/index.html b/docs/1.16/reference/developer-guide/value-mapper-spi/index.html index 84f6e10df..4c7135ecf 100644 --- a/docs/1.16/reference/developer-guide/value-mapper-spi/index.html +++ b/docs/1.16/reference/developer-guide/value-mapper-spi/index.html @@ -5,13 +5,13 @@ Value Mapper SPI | FEEL-Scala - +
    Version: 1.16

    Value Mapper SPI

    The value mapper is used while evaluating expressions and unary tests to

    • transform a variable into a FEEL data type (e.g. when it is referenced in an expression x + 1)
    • transform the result of the expression or unary tests from a FEEL data type into a common data type (e.g. to String or BigDecimal/Long)

    Using the SPI, the transformation can be customized to support more/custom data types, or changing the data type of the result.

    Implement a Value Mapper

    Create a sub-class of org.camunda.feel.valuemapper.CustomValueMapper. Implement the method toVal() and unpackVal() to transform the object. Set the priority of the value mapper to define the precedence compared to the other mappers.

    class MyValueMapper extends CustomValueMapper {

    override def toVal(x: Any, innerValueMapper: Any => Val): Option[Val] = x match {
    case c: Custom => Some(ValString(c.getName))
    case _ => None
    }

    override def unpackVal(value: Val, innerValueMapper: Val => Any): Option[Any] = value match {
    case ValNumber(number) => Some(number.doubleValue) // map BigDecimal to Double
    case _ => None
    }

    override val priority: Int = 1

    }

    Register a Value Mapper

    Depending on how the FEEL engine is used, the value mapper can be passed directly on creation, or is loaded via Java ServiceLoader mechanism.

    In the second case, create a new file org.camunda.feel.valuemapper.CustomValueMapper in the folder META-INF/services/. It must contain the full qualified name of the value mapper.

    org.camunda.feel.example.valuemapper.MyValueMapper
    tip

    The FEEL engine contains a built-in value mapper org.camunda.feel.impl.JavaValueMapper to transform the result of an expression into a Java type, for example, to a java.util.List or a java.util.Map. This is useful if the FEEL engine is called from Java code.

    - + \ No newline at end of file diff --git a/docs/1.16/reference/index.html b/docs/1.16/reference/index.html index 016b5dbc8..a421e2c63 100644 --- a/docs/1.16/reference/index.html +++ b/docs/1.16/reference/index.html @@ -5,7 +5,7 @@ Get Started | FEEL-Scala - + @@ -15,7 +15,7 @@ the following pages of the language guide:

    If you want to integrate the FEEL engine in your application then have a look at the Developer Guide.

    If you want to try out your FEEL expressions in development then check out the Playground (online or local).

    - + \ No newline at end of file diff --git a/docs/changelog/index.html b/docs/changelog/index.html index 678a84f82..7fcaffe6a 100644 --- a/docs/changelog/index.html +++ b/docs/changelog/index.html @@ -5,7 +5,7 @@ Changelog | FEEL-Scala - + @@ -41,7 +41,7 @@ that returns name of the weekday
  • New function day of year() that returns the number of the day within the year
  • See the full changelog here.

    1.11

    Zeebe: 0.23.0Camunda Platform: 7.13.0

    Expressions:

    • Access the element of a list using a numeric variable
    • Disable external functions by default for security reasons

    See the full changelog here.

    - + \ No newline at end of file diff --git a/docs/learn/challenge/chapter-1/index.html b/docs/learn/challenge/chapter-1/index.html index a2c5c2d1a..bc685c5ec 100644 --- a/docs/learn/challenge/chapter-1/index.html +++ b/docs/learn/challenge/chapter-1/index.html @@ -5,7 +5,7 @@ Chapter 1 | FEEL-Scala - + @@ -25,7 +25,7 @@ "restInterval": 5 }

    Result

    <click 'Evaluate' to see the result of the expression>

    Warnings

    <none>
    Solution
    It would take Zee 24 hours to complete the trip.

    Expression
    round up(restInHrs * (distance / speed) / restInterval + distance / speed, 0)
    - + \ No newline at end of file diff --git a/docs/learn/challenge/chapter-2/index.html b/docs/learn/challenge/chapter-2/index.html index 25f8c3b46..abc763360 100644 --- a/docs/learn/challenge/chapter-2/index.html +++ b/docs/learn/challenge/chapter-2/index.html @@ -5,7 +5,7 @@ Chapter 2 | FEEL-Scala - + @@ -22,7 +22,7 @@ "exponent": 5 }

    Result

    <click 'Evaluate' to see the result of the expression>

    Warnings

    <none>
    Solution
    By the 5th time, there were 32 heads and no apparent way out, thankfully the heads started fighting with each other and Zee was able to escape.

    Expression
    base ** exponent
    - + \ No newline at end of file diff --git a/docs/learn/challenge/chapter-3/index.html b/docs/learn/challenge/chapter-3/index.html index c68709175..a50f28551 100644 --- a/docs/learn/challenge/chapter-3/index.html +++ b/docs/learn/challenge/chapter-3/index.html @@ -5,7 +5,7 @@ Chapter 3 | FEEL-Scala - + @@ -23,7 +23,7 @@ "modulus": 24 }

    Result

    <click 'Evaluate' to see the result of the expression>

    Warnings

    <none>
    Solution
    Zee would take 6 days to arrive and would be there at 8:00 AM.

    Expression
    modulo((hours + startingHour), modulus)
    - + \ No newline at end of file diff --git a/docs/learn/challenge/chapter-4/index.html b/docs/learn/challenge/chapter-4/index.html index e20d4e223..282d376cb 100644 --- a/docs/learn/challenge/chapter-4/index.html +++ b/docs/learn/challenge/chapter-4/index.html @@ -5,7 +5,7 @@ Chapter 4 | FEEL-Scala - + @@ -20,7 +20,7 @@ "lastName": "?" }

    Result

    <click 'Evaluate' to see the result of the expression>

    Warnings

    <none>
    Zee
    Camunda Services GmbH
    Zossener Str. 55
    10961 Berlin
    Germany
    < Fill in the name here >
    Camunda Inc.
    INDUSTRY Denver
    3001 Brighton Blvd, Suite 450
    Denver, CO 80216
    USA
    Solution
    Zee labels the envelopes and send them out.

    Expression
    firstName + " " + lastName
    - + \ No newline at end of file diff --git a/docs/learn/challenge/chapter-5/index.html b/docs/learn/challenge/chapter-5/index.html index 61d450a1d..34a40d2db 100644 --- a/docs/learn/challenge/chapter-5/index.html +++ b/docs/learn/challenge/chapter-5/index.html @@ -5,7 +5,7 @@ Chapter 5 | FEEL-Scala - + @@ -20,7 +20,7 @@ "targetDate": "2022-10-05" }

    Result

    <click 'Evaluate' to see the result of the expression>

    Warnings

    <none>
    Solution
    Zee has 11 days to arrive in Berlin.

    Expression
    (date(targetDate) - date(startingDate) - duration("PT200H")) / duration("P1D")
    - + \ No newline at end of file diff --git a/docs/learn/challenge/chapter-6/index.html b/docs/learn/challenge/chapter-6/index.html index 5a7aaf430..d6aef0d7e 100644 --- a/docs/learn/challenge/chapter-6/index.html +++ b/docs/learn/challenge/chapter-6/index.html @@ -5,7 +5,7 @@ Chapter 6 | FEEL-Scala - + @@ -47,7 +47,7 @@ ] }

    Result

    <click 'Evaluate' to see the result of the expression>

    Warnings

    <none>
    Solution
    Zee follows the blue route and reaches CamundaCon just-in-time. He is happy to be there and meet the community.

    Expression
    routes["Berlin" in stops][1].route
    - + \ No newline at end of file diff --git a/docs/learn/challenge/index.html b/docs/learn/challenge/index.html index be52ee3ed..69df7a904 100644 --- a/docs/learn/challenge/index.html +++ b/docs/learn/challenge/index.html @@ -5,7 +5,7 @@ The quest begins | FEEL-Scala - + @@ -16,7 +16,7 @@ 🇩🇪 in time for CamundaCon (2022). With the use of FEEL we'll be able to help in the journey.

    Before we start, let's say "Hi" to Zee:

    Zee

    Use the interactive editor below to evaluate the FEEL expression and greet Zee.

    Expression

    "Hello Zee"
     


    Result

    <click 'Evaluate' to see the result of the expression>

    Warnings

    <none>

    Zee is happy to have you on board. So, let the journey begin. 🚀

    info

    This challenge is an experiment. It was created as part of our Camunda Summer Hack Days 2022.

    - + \ No newline at end of file diff --git a/docs/learn/index.html b/docs/learn/index.html index 5880e354d..2d06a7294 100644 --- a/docs/learn/index.html +++ b/docs/learn/index.html @@ -5,7 +5,7 @@ Overview | FEEL-Scala - + @@ -14,7 +14,7 @@ examples:

    If you want to test your knowledge, take a look at the challenge.

    If you want to know more about FEEL in general and how to write FEEL expressions, see the lanugage guide.

    info

    We welcome contributions. Share your examples, improve the challenge, or suggest new ideas how to learn FEEL. 💡

    - + \ No newline at end of file diff --git a/docs/learn/samples/context-samples/index.html b/docs/learn/samples/context-samples/index.html index 1de3d43e4..373c7fc6a 100644 --- a/docs/learn/samples/context-samples/index.html +++ b/docs/learn/samples/context-samples/index.html @@ -5,13 +5,13 @@ Context expressions | FEEL-Scala - +
    Version: 1.17

    Context expressions

    Validate Data

    Validate journal entries and return all violations.

    {
    check1: {
    error: "Document Type invalid for current year posting",
    violations: collection[documentType = "S2" and glDate > startFiscalYear]
    },
    check2: {
    error: "Document Type invalid for current year posting",
    violations: collection[ledgerType = "GP" and foreignAmount != null]
    },
    result: ([check1, check2])[count(violations) > 0]
    }

    Structure a Calculation

    Calculate the minimum age of a given list of birthdays.

    {
    age: function(birthday) (today() - birthday).years,
    ages: for birthday in birthdays return age(birthday),
    minAge: min(ages)
    }.minAge
    - + \ No newline at end of file diff --git a/docs/learn/samples/list-samples/index.html b/docs/learn/samples/list-samples/index.html index de0184e8e..1887ddb8e 100644 --- a/docs/learn/samples/list-samples/index.html +++ b/docs/learn/samples/list-samples/index.html @@ -5,14 +5,14 @@ List expressions | FEEL-Scala - +
    Version: 1.17

    List expressions

    Filter a List and Return the First Element

    Return the first packaging element which unit is "Palette".

    (data.attribute.packaging[unit = "Palette"])[1]

    Group a List

    Group the given list of invoices by their person.

    Each invoice has a person. The persons are extracted from the invoices and are used as a filter for the list.

    for p in distinct values(invoices.person) return invoices[person = p]

    Input:

    {"invoices":[
    {"id":1, "person":"A", "amount": 10},
    {"id":2, "person":"A", "amount": 20},
    {"id":3, "person":"A", "amount": 30},
    {"id":4, "person":"A", "amount": 40},
    {"id":5, "person":"B", "amount": 15},
    {"id":6, "person":"B", "amount": 25}
    ]}

    Output:

    [
    [
    {"id":1, "person":"A", "amount": 10},
    {"id":2, "person":"A", "amount": 20},
    {"id":3, "person":"A", "amount": 30},
    {"id":4, "person":"A", "amount": 40}
    ],
    [
    {"id":5, "person":"B", "amount": 15},
    {"id":6, "person":"B", "amount": 25}
    ]
    ]

    Merge two Lists

    Merge two given lists. Each list contains context values with the same structure. Each context has an entry "id" that identifies the value.

    The result is a list that contains all context values grouped by the identifier.

     {
    ids: union(x.files.id,y.files.id),
    getById: function (files,fileId)
    if (count(files[id=fileId]) > 0)
    then files[id=fileId][1]
    else {},
    merge: for id in ids return put all(getById(x.files, id), getById(y.files, id))
    }.merge

    Input:

    {
    "x": {"files": [
    {"id":1, "content":"a"},
    {"id":2, "content":"b"}
    ]},
    "y": {"files": [
    {"id":1, "content":"a2"},
    {"id":3, "content":"c"}
    ]}
    }

    Output:

    [
    {"id":1, "content":"a2"},
    {"id":2, "content":"b"},
    {"id":3, "content":"c"}
    ]
    - + \ No newline at end of file diff --git a/docs/learn/samples/temporal-samples/index.html b/docs/learn/samples/temporal-samples/index.html index fc3f50273..4f770549d 100644 --- a/docs/learn/samples/temporal-samples/index.html +++ b/docs/learn/samples/temporal-samples/index.html @@ -5,13 +5,13 @@ Temporal expressions | FEEL-Scala - +
    Version: 1.17

    Temporal expressions

    Compare a Date with Offset

    Check if a date is at least 6 months before another.

    date1 < date2 + duration("P6M")

    Calculate the Age

    Return the current age of a person based on a given birthday.

    years and months duration(date(birthday), today()).years

    Check for Weekend

    Check if the current day is on weekend or not.

    day of week(today()) in ("Saturday","Sunday")

    Calculate the Duration between Dates

    Return the duration between now and the next Tuesday at 08:00.

    (for x in 1..7 
    return date and time(today(),time("08:00:00Z"))
    + duration("P"+string(x)+"D")
    )[day of week(item) = "Tuesday"][1] - now()

    Calculate the Duration between Times

    Return the duration between now and the next time it is 09:00 in Europe/Berlin timezone.

    {
    time: time("09:00:00@Europe/Berlin"),
    date: if (time(now()) < time) then today() else today() + duration("P1D"),
    duration: date and time(date, time) - now()
    }.duration

    Output:

    duration("PT18H30M38S")

    Calculate the Next Weekday

    Return the next day that is not a weekend at 00:00.

    (for x in 1..3 
    return date and time(today(),time("00:00:00Z"))
    + duration("P"+string(x)+"D")
    )[not(day of week(item) in ("Saturday","Sunday"))][1]

    Change the format of Dates

    Transform a given list of date-time values into a custom format.

    for d in dates return { 
    date: date(date and time(d)),
    day: string(date.day),
    month: substring(month of year(date), 1, 3),
    year: string(date.year),
    formatted: day + "-" + month + "-" + year
    }.formatted

    Input:

    ["2021-04-21T07:25:06.000Z","2021-04-22T07:25:06.000Z"]

    Output:

    ["21-Apr-2021","22-Apr-2021"]

    Create a Unix Timestamp

    Return the current point in time as a Unix timestamp.

    (now() - date and time("1970-01-01T00:00Z")) / duration("PT1S") * 1000

    Output:

    1618200039000
    - + \ No newline at end of file diff --git a/docs/next/changelog/index.html b/docs/next/changelog/index.html index 58672c61f..8c8bafffd 100644 --- a/docs/next/changelog/index.html +++ b/docs/next/changelog/index.html @@ -5,7 +5,7 @@ Changelog | FEEL-Scala - + @@ -41,7 +41,7 @@ that returns name of the weekday
  • New function day of year() that returns the number of the day within the year
  • See the full changelog here.

    1.11

    Zeebe: 0.23.0Camunda Platform: 7.13.0

    Expressions:

    • Access the element of a list using a numeric variable
    • Disable external functions by default for security reasons

    See the full changelog here.

    - + \ No newline at end of file diff --git a/docs/next/learn/challenge/chapter-1/index.html b/docs/next/learn/challenge/chapter-1/index.html index 47217417e..b104058c0 100644 --- a/docs/next/learn/challenge/chapter-1/index.html +++ b/docs/next/learn/challenge/chapter-1/index.html @@ -5,7 +5,7 @@ Chapter 1 | FEEL-Scala - + @@ -25,7 +25,7 @@ "restInterval": 5 }

    Result

    <click 'Evaluate' to see the result of the expression>

    Warnings

    <none>
    Solution
    It would take Zee 24 hours to complete the trip.

    Expression
    round up(restInHrs * (distance / speed) / restInterval + distance / speed, 0)
    - + \ No newline at end of file diff --git a/docs/next/learn/challenge/chapter-2/index.html b/docs/next/learn/challenge/chapter-2/index.html index fd78b093b..c45f68bb4 100644 --- a/docs/next/learn/challenge/chapter-2/index.html +++ b/docs/next/learn/challenge/chapter-2/index.html @@ -5,7 +5,7 @@ Chapter 2 | FEEL-Scala - + @@ -22,7 +22,7 @@ "exponent": 5 }

    Result

    <click 'Evaluate' to see the result of the expression>

    Warnings

    <none>
    Solution
    By the 5th time, there were 32 heads and no apparent way out, thankfully the heads started fighting with each other and Zee was able to escape.

    Expression
    base ** exponent
    - + \ No newline at end of file diff --git a/docs/next/learn/challenge/chapter-3/index.html b/docs/next/learn/challenge/chapter-3/index.html index 902e92fdf..f620915d5 100644 --- a/docs/next/learn/challenge/chapter-3/index.html +++ b/docs/next/learn/challenge/chapter-3/index.html @@ -5,7 +5,7 @@ Chapter 3 | FEEL-Scala - + @@ -23,7 +23,7 @@ "modulus": 24 }

    Result

    <click 'Evaluate' to see the result of the expression>

    Warnings

    <none>
    Solution
    Zee would take 6 days to arrive and would be there at 8:00 AM.

    Expression
    modulo((hours + startingHour), modulus)
    - + \ No newline at end of file diff --git a/docs/next/learn/challenge/chapter-4/index.html b/docs/next/learn/challenge/chapter-4/index.html index 6ac26a2eb..6abe9d4e0 100644 --- a/docs/next/learn/challenge/chapter-4/index.html +++ b/docs/next/learn/challenge/chapter-4/index.html @@ -5,7 +5,7 @@ Chapter 4 | FEEL-Scala - + @@ -20,7 +20,7 @@ "lastName": "?" }

    Result

    <click 'Evaluate' to see the result of the expression>

    Warnings

    <none>
    Zee
    Camunda Services GmbH
    Zossener Str. 55
    10961 Berlin
    Germany
    < Fill in the name here >
    Camunda Inc.
    INDUSTRY Denver
    3001 Brighton Blvd, Suite 450
    Denver, CO 80216
    USA
    Solution
    Zee labels the envelopes and send them out.

    Expression
    firstName + " " + lastName
    - + \ No newline at end of file diff --git a/docs/next/learn/challenge/chapter-5/index.html b/docs/next/learn/challenge/chapter-5/index.html index 88c2001a8..30804f470 100644 --- a/docs/next/learn/challenge/chapter-5/index.html +++ b/docs/next/learn/challenge/chapter-5/index.html @@ -5,7 +5,7 @@ Chapter 5 | FEEL-Scala - + @@ -20,7 +20,7 @@ "targetDate": "2022-10-05" }

    Result

    <click 'Evaluate' to see the result of the expression>

    Warnings

    <none>
    Solution
    Zee has 11 days to arrive in Berlin.

    Expression
    (date(targetDate) - date(startingDate) - duration("PT200H")) / duration("P1D")
    - + \ No newline at end of file diff --git a/docs/next/learn/challenge/chapter-6/index.html b/docs/next/learn/challenge/chapter-6/index.html index a236ba1c3..55bba2cf6 100644 --- a/docs/next/learn/challenge/chapter-6/index.html +++ b/docs/next/learn/challenge/chapter-6/index.html @@ -5,7 +5,7 @@ Chapter 6 | FEEL-Scala - + @@ -47,7 +47,7 @@ ] }

    Result

    <click 'Evaluate' to see the result of the expression>

    Warnings

    <none>
    Solution
    Zee follows the blue route and reaches CamundaCon just-in-time. He is happy to be there and meet the community.

    Expression
    routes["Berlin" in stops][1].route
    - + \ No newline at end of file diff --git a/docs/next/learn/challenge/index.html b/docs/next/learn/challenge/index.html index 362c6a412..6591c9b22 100644 --- a/docs/next/learn/challenge/index.html +++ b/docs/next/learn/challenge/index.html @@ -5,7 +5,7 @@ The quest begins | FEEL-Scala - + @@ -16,7 +16,7 @@ 🇩🇪 in time for CamundaCon (2022). With the use of FEEL we'll be able to help in the journey.

    Before we start, let's say "Hi" to Zee:

    Zee

    Use the interactive editor below to evaluate the FEEL expression and greet Zee.

    Expression

    "Hello Zee"
     


    Result

    <click 'Evaluate' to see the result of the expression>

    Warnings

    <none>

    Zee is happy to have you on board. So, let the journey begin. 🚀

    info

    This challenge is an experiment. It was created as part of our Camunda Summer Hack Days 2022.

    - + \ No newline at end of file diff --git a/docs/next/learn/index.html b/docs/next/learn/index.html index 2d9822dbd..77f9e9121 100644 --- a/docs/next/learn/index.html +++ b/docs/next/learn/index.html @@ -5,7 +5,7 @@ Overview | FEEL-Scala - + @@ -14,7 +14,7 @@ examples:

    If you want to test your knowledge, take a look at the challenge.

    If you want to know more about FEEL in general and how to write FEEL expressions, see the lanugage guide.

    info

    We welcome contributions. Share your examples, improve the challenge, or suggest new ideas how to learn FEEL. 💡

    - + \ No newline at end of file diff --git a/docs/next/learn/samples/context-samples/index.html b/docs/next/learn/samples/context-samples/index.html index 8b918f10d..546b11b5f 100644 --- a/docs/next/learn/samples/context-samples/index.html +++ b/docs/next/learn/samples/context-samples/index.html @@ -5,13 +5,13 @@ Context expressions | FEEL-Scala - +
    Version: 1.18 (unreleased)

    Context expressions

    Validate Data

    Validate journal entries and return all violations.

    {
    check1: {
    error: "Document Type invalid for current year posting",
    violations: collection[documentType = "S2" and glDate > startFiscalYear]
    },
    check2: {
    error: "Document Type invalid for current year posting",
    violations: collection[ledgerType = "GP" and foreignAmount != null]
    },
    result: ([check1, check2])[count(violations) > 0]
    }

    Structure a Calculation

    Calculate the minimum age of a given list of birthdays.

    {
    age: function(birthday) (today() - birthday).years,
    ages: for birthday in birthdays return age(birthday),
    minAge: min(ages)
    }.minAge
    - + \ No newline at end of file diff --git a/docs/next/learn/samples/list-samples/index.html b/docs/next/learn/samples/list-samples/index.html index 28f084603..2e33c99dd 100644 --- a/docs/next/learn/samples/list-samples/index.html +++ b/docs/next/learn/samples/list-samples/index.html @@ -5,14 +5,14 @@ List expressions | FEEL-Scala - +
    Version: 1.18 (unreleased)

    List expressions

    Filter a List and Return the First Element

    Return the first packaging element which unit is "Palette".

    (data.attribute.packaging[unit = "Palette"])[1]

    Group a List

    Group the given list of invoices by their person.

    Each invoice has a person. The persons are extracted from the invoices and are used as a filter for the list.

    for p in distinct values(invoices.person) return invoices[person = p]

    Input:

    {"invoices":[
    {"id":1, "person":"A", "amount": 10},
    {"id":2, "person":"A", "amount": 20},
    {"id":3, "person":"A", "amount": 30},
    {"id":4, "person":"A", "amount": 40},
    {"id":5, "person":"B", "amount": 15},
    {"id":6, "person":"B", "amount": 25}
    ]}

    Output:

    [
    [
    {"id":1, "person":"A", "amount": 10},
    {"id":2, "person":"A", "amount": 20},
    {"id":3, "person":"A", "amount": 30},
    {"id":4, "person":"A", "amount": 40}
    ],
    [
    {"id":5, "person":"B", "amount": 15},
    {"id":6, "person":"B", "amount": 25}
    ]
    ]

    Merge two Lists

    Merge two given lists. Each list contains context values with the same structure. Each context has an entry "id" that identifies the value.

    The result is a list that contains all context values grouped by the identifier.

     {
    ids: union(x.files.id,y.files.id),
    getById: function (files,fileId)
    if (count(files[id=fileId]) > 0)
    then files[id=fileId][1]
    else {},
    merge: for id in ids return put all(getById(x.files, id), getById(y.files, id))
    }.merge

    Input:

    {
    "x": {"files": [
    {"id":1, "content":"a"},
    {"id":2, "content":"b"}
    ]},
    "y": {"files": [
    {"id":1, "content":"a2"},
    {"id":3, "content":"c"}
    ]}
    }

    Output:

    [
    {"id":1, "content":"a2"},
    {"id":2, "content":"b"},
    {"id":3, "content":"c"}
    ]
    - + \ No newline at end of file diff --git a/docs/next/learn/samples/temporal-samples/index.html b/docs/next/learn/samples/temporal-samples/index.html index 69e34512c..5d42f1e52 100644 --- a/docs/next/learn/samples/temporal-samples/index.html +++ b/docs/next/learn/samples/temporal-samples/index.html @@ -5,13 +5,13 @@ Temporal expressions | FEEL-Scala - +
    Version: 1.18 (unreleased)

    Temporal expressions

    Compare a Date with Offset

    Check if a date is at least 6 months before another.

    date1 < date2 + duration("P6M")

    Calculate the Age

    Return the current age of a person based on a given birthday.

    years and months duration(date(birthday), today()).years

    Check for Weekend

    Check if the current day is on weekend or not.

    day of week(today()) in ("Saturday","Sunday")

    Calculate the Duration between Dates

    Return the duration between now and the next Tuesday at 08:00.

    (for x in 1..7 
    return date and time(today(),time("08:00:00Z"))
    + duration("P"+string(x)+"D")
    )[day of week(item) = "Tuesday"][1] - now()

    Calculate the Duration between Times

    Return the duration between now and the next time it is 09:00 in Europe/Berlin timezone.

    {
    time: time("09:00:00@Europe/Berlin"),
    date: if (time(now()) < time) then today() else today() + duration("P1D"),
    duration: date and time(date, time) - now()
    }.duration

    Output:

    duration("PT18H30M38S")

    Calculate the Next Weekday

    Return the next day that is not a weekend at 00:00.

    (for x in 1..3 
    return date and time(today(),time("00:00:00Z"))
    + duration("P"+string(x)+"D")
    )[not(day of week(item) in ("Saturday","Sunday"))][1]

    Change the format of Dates

    Transform a given list of date-time values into a custom format.

    for d in dates return { 
    date: date(date and time(d)),
    day: string(date.day),
    month: substring(month of year(date), 1, 3),
    year: string(date.year),
    formatted: day + "-" + month + "-" + year
    }.formatted

    Input:

    ["2021-04-21T07:25:06.000Z","2021-04-22T07:25:06.000Z"]

    Output:

    ["21-Apr-2021","22-Apr-2021"]

    Create a Unix Timestamp

    Return the current point in time as a Unix timestamp.

    (now() - date and time("1970-01-01T00:00Z")) / duration("PT1S") * 1000

    Output:

    1618200039000
    - + \ No newline at end of file diff --git a/docs/next/playground/index.html b/docs/next/playground/index.html index bc89f5267..3e281f2be 100644 --- a/docs/next/playground/index.html +++ b/docs/next/playground/index.html @@ -5,7 +5,7 @@ FEEL Playground (online) | FEEL-Scala - + @@ -22,7 +22,7 @@ }

    Result

    <click 'Evaluate' to see the result of the expression>

    Warnings

    <none>

    About

    The playground uses the FEEL engine in the version ?.

    If the playground is broken, please report the failure here.

    - + \ No newline at end of file diff --git a/docs/next/playground/repl/index.html b/docs/next/playground/repl/index.html index 25842d252..66312ebae 100644 --- a/docs/next/playground/repl/index.html +++ b/docs/next/playground/repl/index.html @@ -5,7 +5,7 @@ FEEL REPL (local) | FEEL-Scala - + @@ -14,7 +14,7 @@ (Read-Eval-Print-Loop) of the FEEL engine. It is a simple script based on Ammonite (aka Scala Scripting) that downloads the dependency to the FEEL engine and initialize it for you.

    the-feel-repl

    Install

    • Download Ammonite: http://ammonite.io/#Ammonite-REPL
      • On Linux:
      sudo sh -c '(echo "#!/usr/bin/env sh" && curl -L https://github.com/com-lihaoyi/Ammonite/releases/download/2.4.0/2.13-2.4.0) > /usr/local/bin/amm && chmod +x /usr/local/bin/amm' && amm
      • On Mac:
      brew install ammonite-repl
    • Download the script feel-repl.sc or clone the Git repository

    Usage

    Run the following script to start the REPL:

    amm --predef feel-repl.sc

    In the REPL, use one of the following functions to evaluate a FEEL expression:

    feel("1 + 3")
    // evaluate an expression without any context

    val context = Map("x" -> 3)
    feel("1 + x", context)
    // evaluate an expression with a map-based context

    feel("1 + x", "{ \"x\": 3}")
    // evaluate an expression with a JSON context

    feel(""" date("2020-04-06") + duration("P3D") """)
    // evaluate an expression ignoring any quotes in the expression

    //----------------------------------------------------------------------

    unaryTests("> 3", 5)
    // evaluate a unary-tests with a given input value

    unaryTests("> 3", 5)
    // evaluate a unary-tests with a given input value

    val context = Map("x" -> 3)
    unaryTests("> x", 5, context)
    // evaluate a unary-tests with a given input value and map-based context

    unaryTests("> x", "5", "{ \"x\": 3}")
    // evaluate a unary-tests with a given JSON input value and JSON context
    - + \ No newline at end of file diff --git a/docs/next/reference/developer-guide/bootstrapping/index.html b/docs/next/reference/developer-guide/bootstrapping/index.html index 4219c4bf4..cc6930fbc 100644 --- a/docs/next/reference/developer-guide/bootstrapping/index.html +++ b/docs/next/reference/developer-guide/bootstrapping/index.html @@ -5,7 +5,7 @@ Bootstrapping | FEEL-Scala - + @@ -15,7 +15,7 @@ code or accessing sensitive data. It is recommended to use the FunctionProvider API instead.

    Use as script engine

    Calling the FEEL engine via Java's script engine API (JSR 223).

    object MyProgram {

    val scriptEngineManager = new ScriptEngineManager

    def feel(script: String, context: ScriptContext) {

    val scriptEngine: FeelScriptEngine = scriptEngineManager.getEngineByName("feel")

    val result: Object = scriptEngine.eval(script, context)
    // ...
    }

    }

    The engine is registered under the following names:

    • feel
    • http://www.omg.org/spec/FEEL/20140401 (FEEL namespace)
    • feel-scala

    To evaluate a unary-tests expression, use one of the following names:

    • feel-unary-tests
    • feel-scala-unary-tests
    - + \ No newline at end of file diff --git a/docs/next/reference/developer-guide/clock-spi/index.html b/docs/next/reference/developer-guide/clock-spi/index.html index 07db676a7..f6938820b 100644 --- a/docs/next/reference/developer-guide/clock-spi/index.html +++ b/docs/next/reference/developer-guide/clock-spi/index.html @@ -5,13 +5,13 @@ Clock SPI | FEEL-Scala - +
    Version: 1.18 (unreleased)

    Clock SPI

    The clock is used when accessing the current time while evaluating expressions and unary tests (e.g. the builtin function now()). By default, it uses the system clock.

    Using the SPI, the clock can be replaced by a custom one. For example, if the application uses its own and not the system clock.

    Implement a FEEL Engine Clock

    Create a sub-class of org.camunda.feel.FeelEngineClock. Implement the method getCurrentTime() to return the current time.

    class MyClock extends FeelEngineClock {

    override def getCurrentTime(): ZonedDateTime = {
    val currentMillis = ...
    Instant.ofEpochMilli(currentMillis).atZone(ZoneId.systemDefault())
    }

    }

    Register the FEEL Engine Clock

    Depending on how the FEEL engine is used, the clock can be passed directly on creation, or is loaded via Java ServiceLoader mechanism.

    In the second case, create a new file org.camunda.feel.FeelEngineClock in the folder META-INF/services/. It must contain the full qualified name of the class.

    org.camunda.feel.example.MyClock
    - + \ No newline at end of file diff --git a/docs/next/reference/developer-guide/developer-guide-introduction/index.html b/docs/next/reference/developer-guide/developer-guide-introduction/index.html index 8c46794a2..e65168dd5 100644 --- a/docs/next/reference/developer-guide/developer-guide-introduction/index.html +++ b/docs/next/reference/developer-guide/developer-guide-introduction/index.html @@ -5,7 +5,7 @@ Introduction | FEEL-Scala - + @@ -13,7 +13,7 @@
    - + \ No newline at end of file diff --git a/docs/next/reference/developer-guide/function-provider-spi/index.html b/docs/next/reference/developer-guide/function-provider-spi/index.html index 1155c6778..d7900ad44 100644 --- a/docs/next/reference/developer-guide/function-provider-spi/index.html +++ b/docs/next/reference/developer-guide/function-provider-spi/index.html @@ -5,13 +5,13 @@ Function Provider SPI | FEEL-Scala - +
    -
    Version: 1.18 (unreleased)

    Function Provider SPI

    Functions can be invoked in expressions and unary tests. The engine includes some predefined built-in functions.

    Own functions can be defined in two ways:

    • declaring them in an expression (e.g. a context)
    • via the function provider SPI

    Using the SPI, the function can be implemented in Scala/Java and is not limited by FEEL. So, it's possible to use language features or libraries.

    Create a sub-class of org.camunda.feel.context.CustomFunctionProvider and implement the method getFunction() which returns the function for the given name. If a function can have different parameters (i.e. different parameter count) then override getFunctions() instead.

    class CustomScalaFunctionProvider extends CustomFunctionProvider {

    def getFunction(name: String): Option[ValFunction] = functions.get(name)

    def functionNames: Iterable[String] = functions.keys

    val functions: Map[String, ValFunction] = Map(
    "incr" -> ValFunction(
    params = List("x"),
    invoke = { case List(ValNumber(x)) => ValNumber(x + 1) }
    )
    )

    }

    The function must be of type ValFunction. It contains

    • params - list of the named parameters of the function
    • invoke - business logic as function which takes the arguments and returns the result. The order of the arguments is defined by the parameter list.
    • hasVarArgs - if true the function can have variable arguments for the last parameter. The last argument is of type list.

    Register the Function

    Depending how the FEEL engine is used, the function provider can be passed directly on creation, or is loaded via Java ServiceLoader mechanism.

    In the second case, create a new file org.camunda.feel.context.CustomFunctionProvider in the folder META-INF/services/. It must contain all function providers by their full qualified name.

    org.camunda.feel.example.context.CustomScalaFunctionProvider
    org.camunda.feel.example.context.CustomJavaFunctionProvider
    - +
    Version: 1.18 (unreleased)

    Function Provider SPI

    Functions can be invoked in expressions and unary tests. The engine includes some predefined built-in functions.

    Own functions can be defined in two ways:

    • declaring them in an expression (e.g. a context)
    • via the function provider SPI

    Using the SPI, the function can be implemented in Scala/Java and is not limited by FEEL. So, it's possible to use language features or libraries.

    Create a sub-class of org.camunda.feel.context.CustomFunctionProvider and implement the method getFunction() which returns the function for the given name. If a function can have different parameters (i.e. different parameter count) then override getFunctions() instead.

    class CustomScalaFunctionProvider extends CustomFunctionProvider {

    def getFunction(name: String): Option[ValFunction] = functions.get(name)

    def functionNames: Iterable[String] = functions.keys

    val functions: Map[String, ValFunction] = Map(
    "incr" -> ValFunction(
    params = List("x"),
    invoke = { case List(ValNumber(x)) => ValNumber(x + 1) }
    )
    )

    }

    The function must be of type ValFunction. It contains

    • params - list of the named parameters of the function
    • invoke - business logic as function which takes the arguments and returns the result. The order of the arguments is defined by the parameter list.
    • hasVarArgs - if true the function can have variable arguments for the last parameter. The last argument is of type list.

    Register the Function

    Depending how the FEEL engine is used, the function provider can be passed directly on creation, or is loaded via Java ServiceLoader mechanism (by using the SpiServiceLoader.loadFunctionProvider() as function provider).

    In the second case, create a new file org.camunda.feel.context.CustomFunctionProvider in the folder META-INF/services/. It must contain all function providers by their full qualified name.

    org.camunda.feel.example.context.CustomScalaFunctionProvider
    org.camunda.feel.example.context.CustomJavaFunctionProvider
    + \ No newline at end of file diff --git a/docs/next/reference/developer-guide/value-mapper-spi/index.html b/docs/next/reference/developer-guide/value-mapper-spi/index.html index d6f3f2543..6e5a1dab7 100644 --- a/docs/next/reference/developer-guide/value-mapper-spi/index.html +++ b/docs/next/reference/developer-guide/value-mapper-spi/index.html @@ -5,13 +5,13 @@ Value Mapper SPI | FEEL-Scala - +
    Version: 1.18 (unreleased)

    Value Mapper SPI

    The value mapper is used while evaluating expressions and unary tests to

    • transform a variable into a FEEL data type (e.g. when it is referenced in an expression x + 1)
    • transform the result of the expression or unary tests from a FEEL data type into a common data type (e.g. to String or BigDecimal/Long)

    Using the SPI, the transformation can be customized to support more/custom data types, or changing the data type of the result.

    Implement a Value Mapper

    Create a sub-class of org.camunda.feel.valuemapper.CustomValueMapper. Implement the method toVal() and unpackVal() to transform the object. Set the priority of the value mapper to define the precedence compared to the other mappers.

    class MyValueMapper extends CustomValueMapper {

    override def toVal(x: Any, innerValueMapper: Any => Val): Option[Val] = x match {
    case c: Custom => Some(ValString(c.getName))
    case _ => None
    }

    override def unpackVal(value: Val, innerValueMapper: Val => Any): Option[Any] = value match {
    case ValNumber(number) => Some(number.doubleValue) // map BigDecimal to Double
    case _ => None
    }

    override val priority: Int = 1

    }

    Register a Value Mapper

    Depending on how the FEEL engine is used, the value mapper can be passed directly on creation, or is loaded via Java ServiceLoader mechanism.

    In the second case, create a new file org.camunda.feel.valuemapper.CustomValueMapper in the folder META-INF/services/. It must contain the full qualified name of the value mapper.

    org.camunda.feel.example.valuemapper.MyValueMapper
    tip

    The FEEL engine contains a built-in value mapper org.camunda.feel.impl.JavaValueMapper to transform the result of an expression into a Java type, for example, to a java.util.List or a java.util.Map. This is useful if the FEEL engine is called from Java code.

    - + \ No newline at end of file diff --git a/docs/next/reference/index.html b/docs/next/reference/index.html index 468e82600..77f31601a 100644 --- a/docs/next/reference/index.html +++ b/docs/next/reference/index.html @@ -5,7 +5,7 @@ Get Started | FEEL-Scala - + @@ -15,7 +15,7 @@ the following pages of the language guide:

    If you want to integrate the FEEL engine in your application then have a look at the Developer Guide.

    If you want to try out your FEEL expressions in development then check out the Playground (online or local).

    - + \ No newline at end of file diff --git a/docs/playground/index.html b/docs/playground/index.html index 4e63cb88b..043e42871 100644 --- a/docs/playground/index.html +++ b/docs/playground/index.html @@ -5,7 +5,7 @@ FEEL Playground (online) | FEEL-Scala - + @@ -22,7 +22,7 @@ }

    Result

    <click 'Evaluate' to see the result of the expression>

    Warnings

    <none>

    About

    The playground uses the FEEL engine in the version ?.

    If the playground is broken, please report the failure here.

    - + \ No newline at end of file diff --git a/docs/playground/repl/index.html b/docs/playground/repl/index.html index 0b2eb526f..e9f691632 100644 --- a/docs/playground/repl/index.html +++ b/docs/playground/repl/index.html @@ -5,7 +5,7 @@ FEEL REPL (local) | FEEL-Scala - + @@ -14,7 +14,7 @@ (Read-Eval-Print-Loop) of the FEEL engine. It is a simple script based on Ammonite (aka Scala Scripting) that downloads the dependency to the FEEL engine and initialize it for you.

    the-feel-repl

    Install

    • Download Ammonite: http://ammonite.io/#Ammonite-REPL
      • On Linux:
      sudo sh -c '(echo "#!/usr/bin/env sh" && curl -L https://github.com/com-lihaoyi/Ammonite/releases/download/2.4.0/2.13-2.4.0) > /usr/local/bin/amm && chmod +x /usr/local/bin/amm' && amm
      • On Mac:
      brew install ammonite-repl
    • Download the script feel-repl.sc or clone the Git repository

    Usage

    Run the following script to start the REPL:

    amm --predef feel-repl.sc

    In the REPL, use one of the following functions to evaluate a FEEL expression:

    feel("1 + 3")
    // evaluate an expression without any context

    val context = Map("x" -> 3)
    feel("1 + x", context)
    // evaluate an expression with a map-based context

    feel("1 + x", "{ \"x\": 3}")
    // evaluate an expression with a JSON context

    feel(""" date("2020-04-06") + duration("P3D") """)
    // evaluate an expression ignoring any quotes in the expression

    //----------------------------------------------------------------------

    unaryTests("> 3", 5)
    // evaluate a unary-tests with a given input value

    unaryTests("> 3", 5)
    // evaluate a unary-tests with a given input value

    val context = Map("x" -> 3)
    unaryTests("> x", 5, context)
    // evaluate a unary-tests with a given input value and map-based context

    unaryTests("> x", "5", "{ \"x\": 3}")
    // evaluate a unary-tests with a given JSON input value and JSON context
    - + \ No newline at end of file diff --git a/docs/reference/developer-guide/bootstrapping/index.html b/docs/reference/developer-guide/bootstrapping/index.html index 373fa236e..ac46afad5 100644 --- a/docs/reference/developer-guide/bootstrapping/index.html +++ b/docs/reference/developer-guide/bootstrapping/index.html @@ -5,7 +5,7 @@ Bootstrapping | FEEL-Scala - + @@ -15,7 +15,7 @@ code or accessing sensitive data. It is recommended to use the FunctionProvider API instead.

    Use as script engine

    Calling the FEEL engine via Java's script engine API (JSR 223).

    object MyProgram {

    val scriptEngineManager = new ScriptEngineManager

    def feel(script: String, context: ScriptContext) {

    val scriptEngine: FeelScriptEngine = scriptEngineManager.getEngineByName("feel")

    val result: Object = scriptEngine.eval(script, context)
    // ...
    }

    }

    The engine is registered under the following names:

    • feel
    • http://www.omg.org/spec/FEEL/20140401 (FEEL namespace)
    • feel-scala

    To evaluate a unary-tests expression, use one of the following names:

    • feel-unary-tests
    • feel-scala-unary-tests
    - + \ No newline at end of file diff --git a/docs/reference/developer-guide/clock-spi/index.html b/docs/reference/developer-guide/clock-spi/index.html index c5fe4944d..1e19e353b 100644 --- a/docs/reference/developer-guide/clock-spi/index.html +++ b/docs/reference/developer-guide/clock-spi/index.html @@ -5,13 +5,13 @@ Clock SPI | FEEL-Scala - +
    Version: 1.17

    Clock SPI

    The clock is used when accessing the current time while evaluating expressions and unary tests (e.g. the builtin function now()). By default, it uses the system clock.

    Using the SPI, the clock can be replaced by a custom one. For example, if the application uses its own and not the system clock.

    Implement a FEEL Engine Clock

    Create a sub-class of org.camunda.feel.FeelEngineClock. Implement the method getCurrentTime() to return the current time.

    class MyClock extends FeelEngineClock {

    override def getCurrentTime(): ZonedDateTime = {
    val currentMillis = ...
    Instant.ofEpochMilli(currentMillis).atZone(ZoneId.systemDefault())
    }

    }

    Register the FEEL Engine Clock

    Depending on how the FEEL engine is used, the clock can be passed directly on creation, or is loaded via Java ServiceLoader mechanism.

    In the second case, create a new file org.camunda.feel.FeelEngineClock in the folder META-INF/services/. It must contain the full qualified name of the class.

    org.camunda.feel.example.MyClock
    - + \ No newline at end of file diff --git a/docs/reference/developer-guide/developer-guide-introduction/index.html b/docs/reference/developer-guide/developer-guide-introduction/index.html index 1007f7984..536fb0a2c 100644 --- a/docs/reference/developer-guide/developer-guide-introduction/index.html +++ b/docs/reference/developer-guide/developer-guide-introduction/index.html @@ -5,7 +5,7 @@ Introduction | FEEL-Scala - + @@ -13,7 +13,7 @@
    - + \ No newline at end of file diff --git a/docs/reference/developer-guide/function-provider-spi/index.html b/docs/reference/developer-guide/function-provider-spi/index.html index 7b522c543..2a9e62bcb 100644 --- a/docs/reference/developer-guide/function-provider-spi/index.html +++ b/docs/reference/developer-guide/function-provider-spi/index.html @@ -5,13 +5,13 @@ Function Provider SPI | FEEL-Scala - +
    Version: 1.17

    Function Provider SPI

    Functions can be invoked in expressions and unary tests. The engine includes some predefined built-in functions.

    Own functions can be defined in two ways:

    • declaring them in an expression (e.g. a context)
    • via the function provider SPI

    Using the SPI, the function can be implemented in Scala/Java and is not limited by FEEL. So, it's possible to use language features or libraries.

    Create a sub-class of org.camunda.feel.context.CustomFunctionProvider and implement the method getFunction() which returns the function for the given name. If a function can have different parameters (i.e. different parameter count) then override getFunctions() instead.

    class CustomScalaFunctionProvider extends CustomFunctionProvider {

    def getFunction(name: String): Option[ValFunction] = functions.get(name)

    def functionNames: Iterable[String] = functions.keys

    val functions: Map[String, ValFunction] = Map(
    "incr" -> ValFunction(
    params = List("x"),
    invoke = { case List(ValNumber(x)) => ValNumber(x + 1) }
    )
    )

    }

    The function must be of type ValFunction. It contains

    • params - list of the named parameters of the function
    • invoke - business logic as function which takes the arguments and returns the result. The order of the arguments is defined by the parameter list.
    • hasVarArgs - if true the function can have variable arguments for the last parameter. The last argument is of type list.

    Register the Function

    Depending how the FEEL engine is used, the function provider can be passed directly on creation, or is loaded via Java ServiceLoader mechanism.

    In the second case, create a new file org.camunda.feel.context.CustomFunctionProvider in the folder META-INF/services/. It must contain all function providers by their full qualified name.

    org.camunda.feel.example.context.CustomScalaFunctionProvider
    org.camunda.feel.example.context.CustomJavaFunctionProvider
    - + \ No newline at end of file diff --git a/docs/reference/developer-guide/value-mapper-spi/index.html b/docs/reference/developer-guide/value-mapper-spi/index.html index 09252375c..770a5db26 100644 --- a/docs/reference/developer-guide/value-mapper-spi/index.html +++ b/docs/reference/developer-guide/value-mapper-spi/index.html @@ -5,13 +5,13 @@ Value Mapper SPI | FEEL-Scala - +
    Version: 1.17

    Value Mapper SPI

    The value mapper is used while evaluating expressions and unary tests to

    • transform a variable into a FEEL data type (e.g. when it is referenced in an expression x + 1)
    • transform the result of the expression or unary tests from a FEEL data type into a common data type (e.g. to String or BigDecimal/Long)

    Using the SPI, the transformation can be customized to support more/custom data types, or changing the data type of the result.

    Implement a Value Mapper

    Create a sub-class of org.camunda.feel.valuemapper.CustomValueMapper. Implement the method toVal() and unpackVal() to transform the object. Set the priority of the value mapper to define the precedence compared to the other mappers.

    class MyValueMapper extends CustomValueMapper {

    override def toVal(x: Any, innerValueMapper: Any => Val): Option[Val] = x match {
    case c: Custom => Some(ValString(c.getName))
    case _ => None
    }

    override def unpackVal(value: Val, innerValueMapper: Val => Any): Option[Any] = value match {
    case ValNumber(number) => Some(number.doubleValue) // map BigDecimal to Double
    case _ => None
    }

    override val priority: Int = 1

    }

    Register a Value Mapper

    Depending on how the FEEL engine is used, the value mapper can be passed directly on creation, or is loaded via Java ServiceLoader mechanism.

    In the second case, create a new file org.camunda.feel.valuemapper.CustomValueMapper in the folder META-INF/services/. It must contain the full qualified name of the value mapper.

    org.camunda.feel.example.valuemapper.MyValueMapper
    tip

    The FEEL engine contains a built-in value mapper org.camunda.feel.impl.JavaValueMapper to transform the result of an expression into a Java type, for example, to a java.util.List or a java.util.Map. This is useful if the FEEL engine is called from Java code.

    - + \ No newline at end of file diff --git a/docs/reference/index.html b/docs/reference/index.html index c677a4446..da650f1ba 100644 --- a/docs/reference/index.html +++ b/docs/reference/index.html @@ -5,7 +5,7 @@ Get Started | FEEL-Scala - + @@ -15,7 +15,7 @@ the following pages of the language guide:

    If you want to integrate the FEEL engine in your application then have a look at the Developer Guide.

    If you want to try out your FEEL expressions in development then check out the Playground (online or local).

    - + \ No newline at end of file diff --git a/index.html b/index.html index d0811f2c9..5c2848cfd 100644 --- a/index.html +++ b/index.html @@ -5,13 +5,13 @@ FEEL-Scala Docs | FEEL-Scala - +

    FEEL-Scala

    A FEEL engine written in Scala, by Camunda.

    Powerful

    Powerful

    Support FEEL expressions, unary-tests, and built-in functions from the latest DMN standard.

    Extensible

    Extensible

    Hook into the engine and add custom functions, or support custom types.

    Flexible

    Flexible

    Embed it in an DMN engine, or use it as an script/expression language.

    - + \ No newline at end of file diff --git a/search/index.html b/search/index.html index b54309142..b71cc583f 100644 --- a/search/index.html +++ b/search/index.html @@ -5,13 +5,13 @@ Search the documentation | FEEL-Scala - + - + \ No newline at end of file