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 Jan 17, 2025
1 parent c4f3f86 commit af68f84
Show file tree
Hide file tree
Showing 19 changed files with 2,170 additions and 173 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
import com.gs.dmn.el.analysis.semantics.type.FunctionType;
import com.gs.dmn.el.analysis.semantics.type.Type;
import com.gs.dmn.el.analysis.syntax.ast.expression.Expression;
import com.gs.dmn.runtime.DMNRuntimeException;
import com.gs.dmn.feel.analysis.semantics.SemanticError;

import java.util.ArrayList;
import java.util.LinkedHashMap;
Expand Down Expand Up @@ -65,11 +65,11 @@ public void addDeclaration(Declaration declaration) {
existingDeclarations.add(declaration);
this.variablesTable.put(name, existingDeclarations);
} else {
throw new DMNRuntimeException(String.format("%s '%s' already exists", declaration.getClass().getSimpleName(), name));
throw new SemanticError(String.format("%s '%s' already exists", declaration.getClass().getSimpleName(), name));
}
}
} else {
throw new DMNRuntimeException(String.format("Could not add declaration with missing name %s", declaration));
throw new SemanticError(String.format("Could not add declaration with missing name %s", declaration));
}
}

Expand All @@ -82,7 +82,7 @@ public Declaration lookupLocalVariableDeclaration(String name) {
} else if (declarations.stream().allMatch(d -> d.getType() instanceof FunctionType)) {
return null;
} else {
throw new DMNRuntimeException(String.format("Multiple variables for 'name' in the same context %s", declarations));
throw new SemanticError(String.format("Multiple variables for 'name' in the same context %s", declarations));
}
}

Expand Down
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,24 +403,26 @@ 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<Object> result = new ArrayList<>();
iteratorContext.bind(PARTIAL_PARAMETER_NAME, result);
if (expressionDomain instanceof ExpressionIteratorDomain) {
for (Object value : (List<?>) domain) {
for (Object value : (List) domain) {
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,36 +441,19 @@ 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
public Object visit(ExpressionIteratorDomain<Type> element, DMNContext context) {
LOGGER.debug("Visiting element '{}'", element);

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) {
domain = (List<?>) expressionDomain.accept(this, context);
List domain;
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 @@ -562,7 +549,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 @@ -1170,7 +1157,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 @@ -1189,34 +1176,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 af68f84

Please sign in to comment.