Skip to content

Commit

Permalink
[#655] DMN 1.5: Loop over date ranges (DMN15-121)
Browse files Browse the repository at this point in the history
  • Loading branch information
opatrascoiu committed Nov 1, 2024
1 parent b24a625 commit 449b28b
Show file tree
Hide file tree
Showing 21 changed files with 2,289 additions and 271 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
import com.gs.dmn.el.analysis.semantics.type.Type;
import com.gs.dmn.error.ErrorHandler;
import com.gs.dmn.feel.analysis.semantics.SemanticError;
import com.gs.dmn.feel.analysis.semantics.type.DateType;
import com.gs.dmn.feel.analysis.semantics.type.NumberType;
import com.gs.dmn.feel.analysis.syntax.ast.expression.Expression;
import com.gs.dmn.feel.analysis.syntax.ast.visitor.AbstractVisitor;
import com.gs.dmn.feel.synthesis.type.NativeTypeFactory;
Expand Down Expand Up @@ -59,6 +61,10 @@ public BasicDMNToNativeTransformer<T, C> getDmnTransformer() {
return dmnTransformer;
}

protected boolean isValidForIterationDomainType(Type elementType) {
return Type.conformsTo(elementType, NumberType.NUMBER) || Type.conformsTo(elementType, DateType.DATE);
}

protected void handleError(String message) {
throw new SemanticError(message);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -322,9 +322,11 @@ public Element<Type> visit(ContextEntryKey<Type> element, DMNContext context) {

@Override
public Element<Type> visit(ForExpression<Type> element, DMNContext context) {
// Visit children
// Visit iterators
List<Iterator<Type>> iterators = element.getIterators();
DMNContext qParamsContext = visitIterators(element, context, iterators);

// Visit body
Type partialType = ListType.ANY_LIST;
qParamsContext.addDeclaration(this.environmentFactory.makeVariableDeclaration(PARTIAL_PARAMETER_NAME, partialType));
Expression<Type> body = element.getBody();
Expand Down Expand Up @@ -413,9 +415,11 @@ public Element<Type> visit(IfExpression<Type> element, DMNContext context) {

@Override
public Element<Type> visit(QuantifiedExpression<Type> element, DMNContext context) {
// Visit children
// Visit iterators
List<Iterator<Type>> iterators = element.getIterators();
DMNContext qParamsContext = visitIterators(element, context, iterators);

// Visit body
Expression<Type> body = element.getBody();
body.accept(this, qParamsContext);

Expand Down Expand Up @@ -1102,14 +1106,25 @@ private DMNContext visitIterators(final Expression<Type> element, DMNContext con
iterators.forEach(it -> {
it.accept(visitor, qContext);
String itName = it.getName();
Type domainType = it.getDomain().getType();
IteratorDomain<Type> domain = it.getDomain();
Type domainType = domain.getType();
Type itType = null;
if (domainType instanceof ListType) {
itType = ((ListType) domainType).getElementType();
} else if (domainType instanceof RangeType) {
itType = ((RangeType) domainType).getRangeType();
if (element instanceof QuantifiedExpression) {
if (domain instanceof ExpressionIteratorDomain && domainType instanceof ListType) {
itType = ((ListType) domainType).getElementType();
} else {
handleError(context, element, String.format("Type '%s' is not supported for iteration domains", domainType));
}
} else if (element instanceof ForExpression) {
if (domain instanceof ExpressionIteratorDomain && domainType instanceof ListType) {
itType = ((ListType) domainType).getElementType();
} else if (domain instanceof RangeIteratorDomain && domainType instanceof RangeType && isValidForIterationDomainType(((RangeType) domainType).getRangeType())) {
itType = ((RangeType) domainType).getRangeType();
} else {
handleError(context, element, String.format("Type '%s' is not supported for iteration domains", domainType));
}
} else {
handleError(context, element, String.format("Cannot resolve iterator type for '%s'", domainType));
handleError(context, element, String.format("Type '%s' is not supported for iteration domains", domainType));
}
qContext.addDeclaration(this.environmentFactory.makeVariableDeclaration(itName, itType));
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -354,7 +354,7 @@ public Object visit(FunctionDefinition<Type> element, DMNContext context) {
public Object visit(FormalParameter<Type> element, DMNContext context) {
LOGGER.debug("Visiting element '{}'", element);

throw new UnsupportedOperationException("FEEL '" + element.getClass().getSimpleName() + "' is not supported yet");
throw new DMNRuntimeException("FEEL '" + element.getClass().getSimpleName() + "' is not supported yet");
}

@Override
Expand Down Expand Up @@ -403,7 +403,7 @@ public Object visit(ForExpression<Type> element, DMNContext context) {
IteratorDomain<Type> expressionDomain = iterator.getDomain();
Object domain = expressionDomain.accept(this, context);

// Loop over domain and evaluate body
// Loop over domains and evaluate body
DMNContext iteratorContext = this.dmnTransformer.makeIteratorContext(context);
List result = new ArrayList<>();
iteratorContext.bind(PARTIAL_PARAMETER_NAME, result);
Expand All @@ -412,15 +412,17 @@ public Object visit(ForExpression<Type> element, DMNContext context) {
iteratorContext.bind(iterator.getName(), value);
result.add(element.getBody().accept(this, iteratorContext));
}
} else {
NUMBER start = toNumber(((Pair) domain).getLeft());
NUMBER end = toNumber(((Pair) domain).getRight());
java.util.Iterator<NUMBER> numberIterator = this.lib.rangeToStream(start, end).iterator();
while (numberIterator.hasNext()) {
NUMBER number = numberIterator.next();
iteratorContext.bind(iterator.getName(), number);
} else if (expressionDomain instanceof RangeIteratorDomain) {
Object start = ((Pair) domain).getLeft();
Object end = ((Pair) domain).getRight();
java.util.Iterator<Object> domainIterator = this.lib.rangeToStream(start, end).iterator();
while (domainIterator.hasNext()) {
Object value = domainIterator.next();
iteratorContext.bind(iterator.getName(), value);
result.add(element.getBody().accept(this, iteratorContext));
}
} else {
handleError(context, element, String.format("Not supported iteration domain type '%s'", expressionDomain.getType()));
}
for (int i = 1; i <= iteratorNo - 1; i++) {
result = this.lib.flattenFirstLevel(result);
Expand All @@ -439,7 +441,7 @@ private NUMBER toNumber(Object number) {
public Object visit(Iterator<Type> element, DMNContext context) {
LOGGER.debug("Visiting element '{}'", element);

throw new UnsupportedOperationException("FEEL '" + element.getClass().getSimpleName() + "' is not supported yet");
throw new DMNRuntimeException("FEEL '" + element.getClass().getSimpleName() + "' is not supported yet");
}

@Override
Expand All @@ -448,27 +450,10 @@ public Object visit(ExpressionIteratorDomain<Type> element, DMNContext context)

Expression<Type> expressionDomain = element.getExpression();
List domain;
if (expressionDomain instanceof Name) {
String name = ((Name<Type>) expressionDomain).getName();
domain = (List) context.lookupBinding(name);
} else if (expressionDomain instanceof EndpointsRange) {
EndpointsRange<Type> test = (EndpointsRange<Type>) expressionDomain;
if (test.getType() instanceof RangeType && Type.conformsTo(((RangeType) test.getType()).getRangeType(), NumberType.NUMBER)) {
Object start = test.getStart().accept(this, context);
Object end = test.getEnd().accept(this, context);
domain = this.lib.rangeToList(test.isOpenStart(), (NUMBER) start, test.isOpenEnd(), (NUMBER) end);
} else {
throw new UnsupportedOperationException("FEEL '" + element.getClass().getSimpleName() + "' is not supported yet");
}
} else if (expressionDomain instanceof ListTest) {
ListTest<Type> test = (ListTest<Type>) expressionDomain;
domain = (List) test.getListLiteral().accept(this, context);
} else if (expressionDomain instanceof ListLiteral) {
domain = (List) expressionDomain.accept(this, context);
} else if (expressionDomain instanceof FunctionInvocation) {
if (element.getType() instanceof ListType) {
domain = (List) expressionDomain.accept(this, context);
} else {
throw new UnsupportedOperationException(String.format("FEEL '%s' is not supported yet with domain '%s'",
throw new DMNRuntimeException(String.format("FEEL '%s' is not supported yet with domain '%s'",
element.getClass().getSimpleName(), expressionDomain.getClass().getSimpleName()));
}
return domain;
Expand Down Expand Up @@ -499,18 +484,20 @@ public Object visit(IfExpression<Type> element, DMNContext context) {
public Object visit(QuantifiedExpression<Type> element, DMNContext context) {
LOGGER.debug("Visiting element '{}'", element);

// Transform into nested for expressions
ForExpression<Type> equivalentForExpression = element.toForExpression();
// Evaluate
List result = (List) equivalentForExpression.accept(this, context);
// Transform into equivalent nested for expressions
ForExpression<Type> forExpression = element.toForExpression();

// Evaluate for expression
List result = (List) forExpression.accept(this, context);

// Apply predicate
String predicate = element.getPredicate();
if ("some".equals(predicate)) {
return this.lib.or(result);
} else if ("every".equals(predicate)) {
return this.lib.and(result);
} else {
throw new UnsupportedOperationException("Predicate '" + predicate + "' is not supported yet");
throw new DMNRuntimeException("Predicate '" + predicate + "' is not supported yet");
}
}

Expand Down Expand Up @@ -540,7 +527,7 @@ public Object visit(FilterExpression<Type> element, DMNContext context) {
Object filterValue = element.getFilter().accept(this, context);
return this.lib.elementAt((List) source, (NUMBER) filterValue);
} else {
throw new UnsupportedOperationException("FEEL '" + element.getClass().getSimpleName() + "' is not supported yet");
throw new DMNRuntimeException("FEEL '" + element.getClass().getSimpleName() + "' is not supported yet");
}
}

Expand All @@ -566,7 +553,7 @@ public Object visit(InstanceOfExpression<Type> element, DMNContext context) {
public Object visit(ExpressionList<Type> element, DMNContext context) {
LOGGER.debug("Visiting element '{}'", element);

throw new UnsupportedOperationException("FEEL '" + element.getClass().getSimpleName() + "' is not supported yet");
throw new DMNRuntimeException("FEEL '" + element.getClass().getSimpleName() + "' is not supported yet");
}

@Override
Expand Down Expand Up @@ -1175,7 +1162,7 @@ public Object visit(QualifiedName<Type> element, DMNContext context) {
return context.lookupBinding(name);
}
} else {
throw new UnsupportedOperationException("FEEL '" + element.getClass().getSimpleName() + "' is not supported yet");
throw new DMNRuntimeException("FEEL '" + element.getClass().getSimpleName() + "' is not supported yet");
}
}

Expand All @@ -1194,34 +1181,34 @@ public Object visit(Name<Type> element, DMNContext context) {
public Object visit(NamedTypeExpression<Type> element, DMNContext context) {
LOGGER.debug("Visiting element '{}'", element);

throw new UnsupportedOperationException("FEEL '" + element.getClass().getSimpleName() + "' is not supported yet");
throw new DMNRuntimeException("FEEL '" + element.getClass().getSimpleName() + "' is not supported yet");
}

@Override
public Object visit(ContextTypeExpression<Type> element, DMNContext context) {
LOGGER.debug("Visiting element '{}'", element);

throw new UnsupportedOperationException("FEEL '" + element.getClass().getSimpleName() + "' is not supported yet");
throw new DMNRuntimeException("FEEL '" + element.getClass().getSimpleName() + "' is not supported yet");
}

@Override
public Object visit(RangeTypeExpression<Type> element, DMNContext context) {
LOGGER.debug("Visiting element '{}'", element);

throw new UnsupportedOperationException("FEEL '" + element.getClass().getSimpleName() + "' is not supported yet");
throw new DMNRuntimeException("FEEL '" + element.getClass().getSimpleName() + "' is not supported yet");
}

@Override
public Object visit(FunctionTypeExpression<Type> element, DMNContext context) {
LOGGER.debug("Visiting element '{}'", element);

throw new UnsupportedOperationException("FEEL '" + element.getClass().getSimpleName() + "' is not supported yet");
throw new DMNRuntimeException("FEEL '" + element.getClass().getSimpleName() + "' is not supported yet");
}

@Override
public Object visit(ListTypeExpression<Type> element, DMNContext context) {
LOGGER.debug("Visiting element '{}'", element);

throw new UnsupportedOperationException("FEEL '" + element.getClass().getSimpleName() + "' is not supported yet");
throw new DMNRuntimeException("FEEL '" + element.getClass().getSimpleName() + "' is not supported yet");
}
}
Loading

0 comments on commit 449b28b

Please sign in to comment.