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 13, 2016
1 parent 6c5a1c2 commit 2c56b5d
Show file tree
Hide file tree
Showing 16 changed files with 296 additions and 31 deletions.
Expand Up @@ -18,6 +18,7 @@


import org.antlr.v4.runtime.ParserRuleContext; import org.antlr.v4.runtime.ParserRuleContext;
import org.kie.dmn.feel.lang.EvaluationContext; import org.kie.dmn.feel.lang.EvaluationContext;
import org.kie.dmn.feel.lang.runtime.functions.CustomFEELFunction;


import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
Expand Down Expand Up @@ -55,6 +56,10 @@ public Object evaluate(EvaluationContext ctx) {
for( ContextEntryNode cen : entries ) { for( ContextEntryNode cen : entries ) {
String name = cen.evaluateName( ctx ); String name = cen.evaluateName( ctx );
Object value = cen.evaluate( ctx ); Object value = cen.evaluate( ctx );
if( value instanceof CustomFEELFunction ) {
// helpful for debugging
((CustomFEELFunction) value).setName( name );
}


ctx.setValue( name, value ); ctx.setValue( name, value );
c.put( name, value ); c.put( name, value );
Expand Down
Expand Up @@ -17,9 +17,12 @@
package org.kie.dmn.feel.lang.ast; package org.kie.dmn.feel.lang.ast;


import org.antlr.v4.runtime.ParserRuleContext; import org.antlr.v4.runtime.ParserRuleContext;
import org.kie.dmn.feel.lang.EvaluationContext;
import org.kie.dmn.feel.lang.runtime.functions.CustomFEELFunction;


import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.stream.Collectors;


public class FunctionDefNode public class FunctionDefNode
extends BaseNode { extends BaseNode {
Expand Down Expand Up @@ -63,4 +66,14 @@ public BaseNode getBody() {
public void setBody(BaseNode body) { public void setBody(BaseNode body) {
this.body = body; this.body = body;
} }

@Override
public Object evaluate(EvaluationContext ctx) {
if( external ) {
throw new UnsupportedOperationException( " not implemented yet " );
} else {
List<String> params = formalParameters.stream().map( p -> p.evaluate( ctx ) ).collect( Collectors.toList() );
return new CustomFEELFunction( "<anonymous>", params, body );
}
}
} }
Expand Up @@ -19,6 +19,7 @@
import org.antlr.v4.runtime.ParserRuleContext; import org.antlr.v4.runtime.ParserRuleContext;
import org.kie.dmn.feel.lang.EvaluationContext; import org.kie.dmn.feel.lang.EvaluationContext;
import org.kie.dmn.feel.lang.runtime.FEELFunction; import org.kie.dmn.feel.lang.runtime.FEELFunction;
import org.kie.dmn.feel.lang.runtime.NamedParameter;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;


Expand Down Expand Up @@ -69,7 +70,7 @@ public Object evaluate(EvaluationContext ctx) {
} }
if ( function != null ) { if ( function != null ) {
Object[] p = params.getElements().stream().map( e -> e.evaluate( ctx ) ).toArray( Object[]::new ); Object[] p = params.getElements().stream().map( e -> e.evaluate( ctx ) ).toArray( Object[]::new );
Object result = function.applyReflectively( p ); Object result = function.applyReflectively( ctx, p );
return result; return result;
} else { } else {
logger.error( "Function not found: '" + name.getText() + "'" ); logger.error( "Function not found: '" + name.getText() + "'" );
Expand Down
Expand Up @@ -61,12 +61,7 @@ public void setName(String name) {
} }


@Override @Override
public Object evaluate(EvaluationContext ctx) { public String evaluate(EvaluationContext ctx) {
if( parts != null ) { return getText();
return parts.stream().collect( Collectors.joining( " " ) );
} else if( name != null ) {
return name;
}
return null;
} }
} }
Expand Up @@ -17,6 +17,8 @@
package org.kie.dmn.feel.lang.ast; package org.kie.dmn.feel.lang.ast;


import org.antlr.v4.runtime.ParserRuleContext; import org.antlr.v4.runtime.ParserRuleContext;
import org.kie.dmn.feel.lang.EvaluationContext;
import org.kie.dmn.feel.lang.runtime.NamedParameter;


public class NamedParameterNode public class NamedParameterNode
extends BaseNode { extends BaseNode {
Expand Down Expand Up @@ -45,4 +47,11 @@ public BaseNode getExpression() {
public void setExpression(BaseNode expression) { public void setExpression(BaseNode expression) {
this.expression = expression; this.expression = expression;
} }

@Override
public Object evaluate(EvaluationContext ctx) {
String n = name.evaluate( ctx );
Object val = expression.evaluate( ctx );
return new NamedParameter( n, val );
}
} }
Expand Up @@ -16,14 +16,19 @@


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


import org.kie.dmn.feel.lang.EvaluationContext;
import org.kie.dmn.feel.lang.Symbol; import org.kie.dmn.feel.lang.Symbol;


import java.util.List;

public interface FEELFunction { public interface FEELFunction {


String getName(); String getName();


List<List<String>> getParameterNames();

Symbol getSymbol(); Symbol getSymbol();


Object applyReflectively(Object[] params); Object applyReflectively(EvaluationContext ctx, Object[] params);


} }
@@ -0,0 +1,40 @@
/*
* 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.runtime;

public class NamedParameter {
private final String name;
private final Object value;

public NamedParameter(String name, Object value) {
this.name = name;
this.value = value;
}

public String getName() {
return name;
}

public Object getValue() {
return value;
}

@Override
public String toString() {
return name + " : " + value;
}
}
Expand Up @@ -16,14 +16,20 @@


package org.kie.dmn.feel.lang.runtime.functions; package org.kie.dmn.feel.lang.runtime.functions;


import org.kie.dmn.feel.lang.EvaluationContext;
import org.kie.dmn.feel.lang.Symbol; import org.kie.dmn.feel.lang.Symbol;
import org.kie.dmn.feel.lang.runtime.FEELFunction; import org.kie.dmn.feel.lang.runtime.FEELFunction;
import org.kie.dmn.feel.lang.runtime.NamedParameter;
import org.kie.dmn.feel.lang.types.FunctionSymbol; import org.kie.dmn.feel.lang.types.FunctionSymbol;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;


import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.Arrays; import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;


public abstract class BaseFEELFunction implements FEELFunction { public abstract class BaseFEELFunction implements FEELFunction {
Expand All @@ -43,46 +49,85 @@ public String getName() {
return name; return name;
} }


public void setName( String name ) {
this.name = name;
((FunctionSymbol)this.symbol).setId( name );
}

@Override @Override
public Symbol getSymbol() { public Symbol getSymbol() {
return symbol; return symbol;
} }


// TODO: can this method be improved somehow?? // TODO: can this method be improved somehow??
@Override @Override
public Object applyReflectively(Object[] params) { public Object applyReflectively(EvaluationContext ctx, Object[] params) {
// use reflection to call the appropriate apply method // use reflection to call the appropriate apply method
try { try {
Class[] classes = Stream.of( params ).map( p -> p != null ? p.getClass() : null ).toArray( Class[]::new ); if( params.length > 0 && params[0] instanceof NamedParameter ) {
Method apply = null; // using named parameters, so need to adjust
for( Method m : getClass().getDeclaredMethods() ) { List<List<String>> names = getParameterNames();
Class<?>[] parameterTypes = m.getParameterTypes(); names.sort( (o1, o2) -> o1.size() <= o2.size() ? -1 : +1 );
if( !m.getName().equals( "apply" ) || parameterTypes.length != params.length ) {
continue; List<String> available = Stream.of( params ).map( p -> ((NamedParameter)p).getName() ).collect( Collectors.toList() );
}
boolean found = true; boolean found = false;
for( int i = 0; i < parameterTypes.length; i++ ) { for( List<String> candidate : names ) {
if ( classes[i] != null && ! parameterTypes[i].isAssignableFrom( classes[i] ) ) { if( candidate.containsAll( available ) ) {
found = false; Object[] newParams = new Object[candidate.size()];
for( Object o : params ) {
NamedParameter np = (NamedParameter) o;
newParams[ candidate.indexOf( np.getName() ) ] = np.getValue();
}
params = newParams;
found = true;
break; break;
} }
} }
if( found ) { if( !found ) {
apply = m; logger.error( "Unable to find function "+getName()+"( "+available.stream().collect( Collectors.joining(", ") )+ " )" );
break; return null;
} }
} }
if( apply != null ) { if ( ! isCustomFunction() ) {
Object result = apply.invoke( this, params ); Class[] classes = Stream.of( params ).map( p -> p != null ? p.getClass() : null ).toArray( Class[]::new );
return result; Method apply = null;
for( Method m : getClass().getDeclaredMethods() ) {
Class<?>[] parameterTypes = m.getParameterTypes();
if( !m.getName().equals( "apply" ) || parameterTypes.length != params.length ) {
continue;
}
boolean found = true;
for( int i = 0; i < parameterTypes.length; i++ ) {
if ( classes[i] != null && ! parameterTypes[i].isAssignableFrom( classes[i] ) ) {
found = false;
break;
}
}
if( found ) {
apply = m;
break;
}
}
if( apply != null ) {
Object result = apply.invoke( this, params );
return result;
} else {
String ps = Arrays.toString( classes );
logger.error( "Unable to find function '" + getName() + "( " + ps.substring( 1, ps.length()-1 ) +" )'" );
}
} else { } else {
String ps = Arrays.toString( classes ); Object result = ((CustomFEELFunction)this).apply( ctx, params );
logger.error( "Unable to find function '" + getName() + "( " + ps.substring( 1, ps.length()-1 ) +" )'" ); return result;
} }
} catch ( Exception e ) { } catch ( Exception e ) {
logger.error( "Error trying to call function "+getName()+".", e ); logger.error( "Error trying to call function "+getName()+".", e );
} }
return null; return null;
} }


protected boolean isCustomFunction() {
return false;
}

} }
@@ -0,0 +1,81 @@
/*
* 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.runtime.functions;

import org.kie.dmn.feel.lang.EvaluationContext;
import org.kie.dmn.feel.lang.ast.BaseNode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class CustomFEELFunction
extends BaseFEELFunction {

private static final Logger logger = LoggerFactory.getLogger( CustomFEELFunction.class );

private final List<String> parameters;
private final BaseNode body;

public CustomFEELFunction(String name, List<String> parameters, BaseNode body) {
super( name );
this.parameters = parameters;
this.body = body;
}

@Override
public List<List<String>> getParameterNames() {
return Arrays.asList( parameters );
}

public Object apply(EvaluationContext ctx, Object[] params ) {
if( params.length != parameters.size() ) {
logger.error( "Illegal invocation of function. Expecting " + getSignature() + " but got " + getName() + "( " + Arrays.asList(params)+" )" );
return null;
}
try {
ctx.enterFrame();
for ( int i = 0; i < parameters.size(); i++ ) {
ctx.setValue( parameters.get( i ), params[i] );
}
Object result = this.body.evaluate( ctx );
return result;
} catch( Exception e ) {
logger.error( "Error invoking function " + getSignature() + ".", e );
} finally {
ctx.exitFrame();
}
return null;
}

private String getSignature() {
return getName()+"( "+parameters.stream().collect( Collectors.joining(", ") ) +" )";
}

@Override
protected boolean isCustomFunction() {
return true;
}

@Override
public String toString() {
return "function "+getSignature();
}
}

0 comments on commit 2c56b5d

Please sign in to comment.