Skip to content

Commit

Permalink
Implementing FEEL runtime
Browse files Browse the repository at this point in the history
  • Loading branch information
etirelli committed Aug 14, 2016
1 parent 400739c commit 311930d
Show file tree
Hide file tree
Showing 10 changed files with 191 additions and 12 deletions.
Expand Up @@ -20,9 +20,7 @@
import org.kie.dmn.feel.lang.EvaluationContext;
import org.kie.dmn.feel.lang.runtime.functions.CustomFEELFunction;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.*;

public class ContextNode
extends BaseNode {
Expand Down Expand Up @@ -52,7 +50,7 @@ public void setEntries(List<ContextEntryNode> entries) {
public Object evaluate(EvaluationContext ctx) {
try {
ctx.enterFrame();
HashMap<String, Object> c = new HashMap<>();
Map<String, Object> c = new LinkedHashMap<>();
for( ContextEntryNode cen : entries ) {
String name = cen.evaluateName( ctx );
Object value = cen.evaluate( ctx );
Expand Down
Expand Up @@ -21,6 +21,7 @@
import org.kie.dmn.feel.util.EvalHelper;

import java.math.BigDecimal;
import java.math.MathContext;
import java.util.function.BiPredicate;
import java.util.function.BinaryOperator;

Expand Down Expand Up @@ -107,7 +108,7 @@ public Object evaluate(EvaluationContext ctx) {
case DIV:
return math( left, right, ctx, (l, r) -> l.divide( r ) );
case POW:
return math( left, right, ctx, (l, r) -> l.pow( r.intValue() ) );
return math( left, right, ctx, (l, r) -> l.pow( r.intValue(), MathContext.DECIMAL128 ) );
case AND:
return and( left, right, ctx );
case OR:
Expand Down
Expand Up @@ -35,6 +35,7 @@ public ASTNode getExpression() {

public Object evaluate(Map<String, Object> inputVariables) {
EvaluationContextImpl ctx = new EvaluationContextImpl();
inputVariables.entrySet().stream().forEach( e -> ctx.setValue( e.getKey(), e.getValue() ) );
return expression.evaluate( ctx );
}

Expand Down
Expand Up @@ -28,13 +28,16 @@
import org.kie.dmn.feel.lang.types.BuiltInType;
import org.kie.dmn.feel.util.EvalHelper;

import java.util.Collections;
import java.util.Map;

/**
* Language runtime entry point
*/
public class FEEL {

private static final Map<String,Object> EMPTY_INPUT = Collections.emptyMap();

public static CompilerContext newCompilerContext() {
return new CompilerContextImpl();
}
Expand All @@ -48,6 +51,10 @@ public static CompiledExpression compile(String expression, CompilerContext ctx)
return ce;
}

public static Object evaluate(String expression) {
return evaluate( expression, EMPTY_INPUT );
}

public static Object evaluate(String expression, Map<String, Object> inputVariables) {
CompilerContext ctx = newCompilerContext();
if ( inputVariables != null ) {
Expand Down
@@ -0,0 +1,90 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.kie.dmn.feel.lang.examples;

import org.junit.Assert;
import org.junit.Test;
import org.kie.dmn.feel.lang.runtime.FEEL;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Map;

public class ExamplesTest {

private static final Logger logger = LoggerFactory.getLogger( ExamplesTest.class );
public static final String DEFAULT_IDENT = " ";

@Test
public void testLoadApplicantContext() {
String expression = loadExpression( "applicant.feel" );
Map applicant = (Map) FEEL.evaluate( expression );
System.out.println( printContext( applicant ) );
}

@Test
public void testLoadExample_10_6_1() {
String expression = loadExpression( "example_10_6_1.feel" );
Map context = (Map) FEEL.evaluate( expression );
System.out.println( printContext( context ) );
}

@Test
public void testLoadExample_10_6_2() {
String expression = loadExpression( "example_10_6_1.feel" );
Map context = (Map) FEEL.evaluate( expression );
Number yearlyIncome = (Number) FEEL.evaluate( "monthly income * 12", context );
System.out.println( "Yearly income = " + yearlyIncome );
}

private String loadExpression(String fileName) {
try {
return new String( Files.readAllBytes( Paths.get( getClass().getResource( fileName ).toURI() ) ) );
} catch ( Exception e ) {
logger.error( "Error reading file " + fileName, e );
Assert.fail("Error reading file "+fileName);
}
return null;
}

private String printContext( Map context ) {
return printContext( context, "" );
}

private String printContext( Map<String, Object> context, String ident ) {
StringBuilder builder = new StringBuilder( );
builder.append( "{\n" );
for( Map.Entry e : context.entrySet() ) {
builder.append( ident )
.append( DEFAULT_IDENT )
.append( e.getKey() )
.append( ": " );
if( e.getValue() instanceof Map ) {
builder.append( printContext( (Map<String, Object>) e.getValue(), ident + DEFAULT_IDENT ) );
} else {
builder.append( e.getValue() )
.append( "\n" );
}
}
builder.append( ident+"}\n" );
return builder.toString();
}

}
Expand Up @@ -73,6 +73,7 @@ public static Collection<Object[]> data() {
{ "(10 + 20) / (-5 * 3)", EMPTY_INPUT, BigDecimal.valueOf( -2 ) },
{ "(10 + 20) / 0", EMPTY_INPUT, null },
{ "10 ** 5", EMPTY_INPUT, BigDecimal.valueOf( 100000 ) },
{ "10 ** -5", EMPTY_INPUT, new BigDecimal( "0.00001" ) },
{ "(5+2) ** 5", EMPTY_INPUT, BigDecimal.valueOf( 16807 ) },
{ "5+2 ** 5", EMPTY_INPUT, BigDecimal.valueOf( 37 ) },
{ "5+2 ** 5+3", EMPTY_INPUT, BigDecimal.valueOf( 40 ) },
Expand Down Expand Up @@ -314,11 +315,4 @@ public void testExpression() {
assertThat( "Evaluating: '"+expression+"'", FEEL.evaluate( expression, inputVariables ), is( result ) );
}
}

// @Test @Ignore( "Java BigDecimals do not support negative numbers as power. Need to figure out what to do." )
// public void testMathExprPow2() {
// assertThat( FEEL.evaluate( "10 ** -5", EMPTY_INPUT ), is( BigDecimal.valueOf( -0.00001 ) ) );
// }


}
@@ -0,0 +1,16 @@
/*
* Example context from DMN Spec page 100
*
* Defines the data of an Applicant
*/
{
Age : 51,
Marital Status : "M",
Employment Status : "EMPLOYED",
Existing Customer : false,
Monthly : {
Income: 10000.00,
Repayments: 2500.00,
Expenses: 3000.00
}
}
@@ -0,0 +1,15 @@
{
Age : Applicant.Age,
Monthly Income : Applicant.Monthly.Income,
Pre-Bureau Risk Category : Affordability.Pre-Bureau Risk Category
Installment Affordable : Affordability.Installment Affordable
Eligibility :
if Pre-Bureau Risk Category = "DECLINE" or
Installment Affordable = false or
Age < 18 or
Monthly Income < 100
then
"INELIGIBLE"
else
"ELIGIBLE"
}.Eligibility
@@ -0,0 +1,40 @@
/*
* Example context from DMN Spec page 142
*
* Defines a complex context
*/
{
applicant : {
age : 51,
marital status : "M",
employment status : "EMPLOYED",
existing customer : false,
monthly : {
income: 10000.00,
repayments: 2500.00,
expenses: 3000.00
}
},
requested product : {
product type : "STANDARD LOAN",
rate : 0.25,
term : 36,
amount : 100000.00
},
monthly income : applicant.monthly.income,
monthly outgoings : [ applicant.monthly.repayments, applicant.monthly.expenses ],
credit history : [
{
record date : date( "2008-03-12" ),
event : "home mortgage",
weight : 100
},
{
record date : date( "2011-04-01" ),
event : "foreclosure warning",
weight : 150
}
],
PMT : function( rate, term, amount )
(amount * rate / 12) / ( 1 - ( 1 + rate / 12 ) ** -term )
}
@@ -0,0 +1,17 @@
{ a : 10,
b : 10,
c : a + b,
a + b : 5,
x : a + b + c
}


if Pre-Bureau Risk Category = "DECLINE" or
Installment Affordable = false or
Age < 18 or
Monthly Income < 100
then
"INELIGIBLE"
else
"ELIGIBLE"

0 comments on commit 311930d

Please sign in to comment.