npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

imicros-feel-interpreter

v0.0.13

Published

FEEL interpreter

Downloads

31

Readme

imicros-feel-interpreter

Build Status

FEEL interpreter written in JavaScript.

Developed for of the imicros backend, but can be used also stand-alone.

Installation

$ npm install imicros-feel-interpreter

Usage

const { Interpreter } = require("imicros-feel-interpreter");

const interpreter = new Interpreter();

/*** parse and evaluate in a single step ***/
let result = interpreter.evaluate("a/b**-c-d",{a:1,b:2,c:4,d:3});
// 13

/*** or in two steps: parse single evaluate multiple***/
let success = interpreter.parse("a/b**-c-d");
// true

let serialized = JSON.stringify(interpreter.ast);
interpreter.ast = JSON.parse(serialized);
// serialized ast can be stored somewhere and restored for multiple usage with different data sets

let result = interpreter.evaluate({a:1,b:2,c:4,d:3});
// 13

Usage Converter to convert a DMN file (XML) to a single FEEL expression

const { DMNParser, DMNConverter } = require("imicros-feel-interpreter");
const fs = require("fs");

const xmlData = fs.readFileSync(./assets/Sample.dmn).toString();
const expression = new DMNConverter().convert({ xml: xmlData });

Features

  • Complete support of DMN 1.4. Known restrictions see below.
  • Provide build-in functions as listed below.

Restrictions

  • Additional name symbols ./-+* according rule 30. of the sepcification as well as keywords for,return,if,true,false,in,and,or,between,some,every,then,else,not,string,number,boolean,null,date,time,duration in names are not supported. (The package uses nearley as parser and I didn't found a way to implement the ambiguity). White spaces are allowed and normalized (doubled spaces will be replaced by just one space). Therefore expresssions like {"new example": 5}.new    example as well as { "new     example": 5}.new example will work. Beside white spaces the special characters _?' which are not used as operators are allowed.
  • No external functions are supported.

Performance considerations

In case of intensive usage with large number of data sets consider the pre-parsing possibility.

A simple expression if even(i) then (i*a) else (i*b) with parsing and evaluation in one step evaluates 2.500 data sets per second and with a single parsing you can evaluate up to 200.000 data sets per second on an average hardware with single thread processing.

Example expressions

  • date and time("2022-04-05T23:59:59") < date("2022-04-06") w/o context --> true
  • if a>b then c+4 else d with context {a:3,b:2,c:5.1,d:4} --> 9.1
  • {"Mother's finest":5, "result": 5 + Mother's finest}.result --> 10
  • "best of " + lower case("IMicros") w/o context --> "best of imicros"
  • [{a:3,b:1},{a:4,b:2}][item.a > 3] w/o context --> [{a:4,b:2}]
  • [1,2,3,4,5,6,7,8,9][a*(item+1)=6] with context {a:2} --> [2]
  • a+b > c+d with context {a:5,b:4,c:3,d:5} --> true
  • flight list[item.status = "cancelled"].flight number with context {"flight list": [{ "flight number": 123, status: "boarding"},{ "flight number": 234, status: "cancelled"}]} --> [234]
  • {calc:function (a:number,b:number) a-b, y:calc(b:c,a:d)+3}.y with context {c:4,d:5} --> 4
  • deep.a.b + deep.c with context {deep:{a:{b:3},c:2}} --> 5
  • {a:3}.aw/o context --> 3
  • extract("references are 1234, 1256, 1378", "12[0-9]*") w/o context --> ["1234","1256"]
  • (a+b)>(8.9) and (c+d)>(8.1) with context {a:5,b:4,c:4,d:5}--> true
  • @"2022-04-10T13:15:20" + @"P1M" w/o context --> "2022-05-10T13:15:20"
  • day of year(@"2022-04-16") w/o context --> 106
  • @"P7M2Y" + @"P5D" w/o context --> "P5D7M2Y"
  • { "PMT": function (p:number,r:number,n:number) (p*r/12)/(1-(1+r/12)**-n), "MonthlyPayment": PMT(Loan.amount, Loan.rate, Loan.term) + fee }.MonthlyPayment with context {Loan: { amount: 600000, rate: 0.0375, term:360 }, fee: 100} --> 2878.6935494327668
  • decision table( outputs: ["Applicant Risk Rating"], inputs: ["Applicant Age","Medical History"], rule list: [ [>60,"good","Medium"], [>60,"bad","High"], [[25..60],-,"Medium"], [<25,"good","Low"], [<25,"bad","Medium"] ], hit policy: "Unique" ) with context {"Applicant Age": 65, "Medical History": "bad"} --> { "Applicant Risk Rating": "High" }

Supported expressions

(not the complete list - refer to the test cases for a complete list of tested expressions)

Arithmetic

Muliplication: *, Division: /, Addition: +, Subtraction: -, Exponentation: **

  • (x - 2)**2 + 3/a - c*2

Negation: -

  • -5

Boolean

And: and, Or: or

Equal to: =, not equal to: !=, less than: <, less than or equal to: <=, greater than: >, greater than or equal to: >=

  • 5 = 5 and 6 != 5 and 3 <= 4 and date("2022-05-08") > date("2022-05-07") --> true

Existence check: is defined(var)

  • is defined({x:null}.x) --> true
  • is defined({}.x) --> false

Negation: not(expression)

  • {a:5,b:3,result: not(a<b)}.result --> true

Type check: expression instance of type

  • a instance of b with context {a:3,b:5} --> true
  • a instance of string with context {a:"test"} --> true
  • a instance of number with context {a:3} --> true
  • a instance of boolean with context {a:true} --> true

String

Concatenate: + (only possible with both terms type string)

  • "foo" + "bar" --> "foobar"

Context and path

Context is a defintion in JSON notation with { key: value }.
The key must evaluate to a string, the value can be any expression (including function definitions and complete decision table calls).
With the .name notation an attribute of the context is accessed.

  • {a:3}.a --> 3
  • deep.a.b + deep.c with context {deep:{a:{b:3},c:2}} --> 5
  • {calc:function (a:number,b:number) a-b, y:calc(b:c,a:d)+3}.y with context {c:4,d:5} --> 4
  • {calc:function (a:number,b:number) a+b, y:calc(4,5)+3} --> {y:12}

Filter (Lists)

Get element by index (index count is starting with 1)

  • [1,2,3,4][2] --> 2

Negative indices are counted from the end

  • [1,2,3,4][-1] --> 3
  • [1,2,3,4][-0] --> 4

Reduce list based on logic expression - variable item is the current element

  • [1,2,3,4][item > 2] --> [3,4]
  • [1,2,3,4,5,6,7,8,9][a*(item+1)=6] with context {a:2} --> [2]
  • [1,2,3,4][even(item)] --> [2,4]
  • flight list[item.status = "cancelled"].flight number with context {"flight list": [{ "flight number": 123, status: "boarding"},{ "flight number": 234, status: "cancelled"}]} --> [234]

Temporal

Date or date and time expressions as well as durations can be written with the @String notation

  • @"2022-05-10T13:15:20" - @"P1M" --> "2022-04-10T13:15:20"
  • @"13:45:20" - @"PT30M" --> "13:15:20"
  • date("2022-05-14") - date("2020-09-10") --> "P4D8M1Y"
  • date("2020-09-10")-date("2022-05-14") --> "-P4D8M1Y"

Comparison with <,<=,>,>=,=
Additon/Subtraction with date|date and time +/- duration

  • date("2022-04-05") < date("2022-04-06") --> true
  • date and time("2022-04-15T08:00:00") = date and time("2022-04-15T00:00:00") + @"P8H" --> true
  • @"P5D" > @"P2D" --> true
  • @"P5D" > @"P4DT23H" --> true

Comparison with in interval

  • date("2022-04-05") in [date("2022-04-04")..date("2022-04-06")] --> true
  • (date("2022-04-01")+duration("P3D")) in [date("2022-04-04")..date("2022-04-06")] --> true

Comparison with between date|date and time and date|date and time

  • date("2022-04-05") between date("2022-04-04") and date("2022-04-06") --> true

Access of attributes of the temporal type

  • @"2022-04-10".month --> 4
  • date("2022-04-10").day --> 10
  • date and time("2022-04-10T13:15:20").year --> 2022
  • date and time("2022-04-10T13:15:20").hour --> 13
  • date and time("2022-04-10T13:15:20").minute --> 15
  • date and time("2022-04-10T13:15:20").second --> 20
  • @"P12D5M".months --> 5
  • today().year --> current year
  • now().minute --> current minute
  • day of week(@"2022-04-16") --> "Saturday"
  • day of year(@"2022-04-16") --> 106
  • week of year(@"2022-04-16") --> 15
  • abs(@"-P7M2Y") --> "P7M2Y"

If

if condition then expression else expression

  • if 1 > 2 then 3 else 4

For

for name in iteration context return expression

  • for a in [1,2,3] return a*2 --> [2,4,6]

Comments

single line comments starting with // until the end of the line single line or multiline comments framed with /* and */.

/*  start 
    comment */ 
decision table(
    outputs: ["Applicant Risk Rating"],  
    inputs: ["Applicant Age","Medical History"],
    /* multi line
        between */
    rule list: [
        [>60,"good","Medium"],
        [>60,"bad","High"],
        [[25..60],-,"Medium"],
        /**** 
         * important comment 
         ****/
        [<25,"good","Low"],
        [<25,"bad","Medium"] // single line comment
    ],
    hit policy: "Unique"
) /* end comment */

Supported build-in functions

Conversion

  • date(from|year,month,day)
  • time(from|hour,minute,second,offset?) with offset type duration (e.g. @"PT1H")
  • missing: date and time(from - with named parameter|date,time)
  • years and months duration(from,to) with from,to type date
  • number(from) with from type string
  • string(from)
  • context(entries) with entries type object with attributes key and value (e.g. {key: "a",value: 1})

Temporal

  • today()
  • now()
  • day of week(date)
  • day of year(date)
  • week of year(date)
  • month of year(date)
  • abs(duration)

Arithmetic

  • decimal(n,scale)
  • floor(n)
  • ceiling(n)
  • round up(n,scale?)
  • round down(n,scale?)
  • round half up(n,scale?)
  • round half down(n,scale?)
  • abs(number)
  • modulo(dividend,divisor)
  • sqrt(number)
  • log(number)
  • exp(number)
  • odd(number)
  • even(number)

Logical

  • is defined(value)
  • not(negand)

Ranges

  • before(a,b) with a,b either point or interval
  • after(a,b) with a,b either point or interval
  • meets(a,b) with a,b intervals
  • met by(a,b) with a,b intervals
  • overlaps(a,b) with a,b intervals
  • overlaps before(a,b) with a,b intervals
  • overlaps after(a,b) with a,b intervals
  • finishes(a,b) with a eiter point or interval and b interval
  • finished by(a,b) with a interval and b either point or interval
  • includes(a,b) with a interval and b either point or interval
  • during(a,b) with a eiter point or interval and b interval
  • starts(a,b) with a eiter point or interval and b interval
  • started by(a,b) with a interval and b either point or interval
  • coinsides(a,b) with a,b either both points or both intervals

Lists

  • list contains(list,element)
  • count(list) / count(...item)
  • min(list) / min(...item)
  • max(list) / max(...item)
  • sum(list) / sum(...item)
  • product(list) / product(...item)
  • mean(list) / mean(...item)
  • median(list) / median(...item)
  • stddev(list) / stddev(...item)
  • mode(list) / mode(...item)
  • all(list) / all(...item)
  • and(list)
  • any(list) / any(...item)
  • or(list)
  • sublist(list, startposition, length?)
  • append(list,...item)
  • union(...list)
  • concatenate(...list)
  • insert before(list,position,newItem)
  • remove(list,position)
  • reverse(list)
  • index of(list,match)
  • distinct values(list)
  • flatten(list)
  • sort(list,precedes)
  • string join(list,delimiter?,prefix?,suffix?)

Strings

  • substring(string,start,length)
  • string length(string)
  • upper case(string)
  • lower case(string)
  • substring before(string,match)
  • substring after(string,match)
  • contains(string,match)
  • starts with(string,match)
  • ends with(string,match)
  • matches(input,pattern)
  • replace(input,pattern,replacement,flags)
  • split(string,delimiter)
  • extract(string,pattern)

Context

  • get value(context,key)
  • get entries(context)
  • put(context,key,value)
  • put all(entries)

Decisions

  • boxed expression(context,expression)
  • decision table(output, input, rule list, hit policy) (supported hit policies: "U"|"Unique","A"|"Any","F"|"First","R"|"Rule order","C"|"Collect","C+"|"C<"|"C>"|"C#")

Complete (complex) decisions

Also complex decisions like the example under assets/Sample.dmn can be written as a complex FEEL expression and evaluated - here for example as a context returning the last evaluated context entry.

const Interpreter = require("../lib/interpreter.js");

const interpreter = new Interpreter();

let exp = `
{   "Lender Acceptable DTI": function () 0.36,
    "Lender Acceptable PITI": function () 0.28,
    "DTI": function (d,i) d/i,
    "PITI": function (pmt,tax,insurance,income) (pmt+tax+insurance)/income,
    "Credit Score.FICO": Credit Score.FICO,
    "Credit Score Rating": decision table(
            inputs: ["Credit Score.FICO"],
            outputs: ["Credit Score Rating"],
            rule list: [
                [>=750,"Excellent"],
                [[700..750),"Good"],
                [[650..700),"Fair"],
                [[600..650),"Poor"],
                [< 600,"Bad"]
            ],
            hit policy: "U"
        ).Credit Score Rating,
    "Client DTI": DTI(d: Applicant Data.Monthly.Repayments + Applicant Data.Monthly.Expenses, i: Applicant Data.Monthly.Income),
    "Client PITI": PITI(
        pmt: (Requested Product.Amount*((Requested Product.Rate/100)/12))/(1-(1/(1+(Requested Product.Rate/100)/12)**-Requested Product.Term)),
        tax: Applicant Data.Monthly.Tax,
        insurance: Applicant Data.Monthly.Insurance,
        income: Applicant Data.Monthly.Income
    ),
    "Back End Ratio": if Client DTI <= Lender Acceptable DTI()
        then "Sufficient"
        else "Insufficient",
    "Front End Ratio": if Client PITI <= Lender Acceptable PITI()
                    then "Sufficient"
                    else "Insufficient",
    "Loan PreQualification": decision table(
                    outputs: ["Qualification","Reason"],
                    inputs: ["Credit Score Rating","Back End Ratio","Front End Ratio"],
                    rule list: [
                        [["Poor","Bad"],-,-,"Not Qualified","Credit Score too low."],
                        [-,"Insufficient","Sufficient","Not Qualified","Debt to income ratio is too high."],
                        [-,"Sufficient","Insufficient","Not Qualified","Mortgage payment to income ratio is too high."],
                        [-,"Insufficient","Insufficient","Not Qualified","Debt to income ratio is too high AND mortgage payment to income ratio is too high."],
                        [["Fair","Good","Excellent"],"Sufficient","Sufficient","Qualified","The borrower has been successfully prequalified for the requested loan."]
                    ],
                    hit policy: "F"
                )
}.Loan PreQualification
`
let success = interpreter.parse(exp);
if (!success) console.log(interpreter.error);

result = interpreter.evaluate(exp,{
    "Credit Score": { FICO: 700 }, 
    "Applicant Data": { Monthly: { Repayments: 1000, Tax: 1000, Insurance: 100, Expenses: 500, Income: 5000 } },
    "Requested Product": { Amount: 600000, Rate: 0.0375, Term: 360 }
});

console.log(result);
// {
//   Qualification: 'Qualified',
//   Reason: 'The borrower has been successfully prequalified for the requested loan.'
// }