Skip to content

Commit

Permalink
[#655] DMN 1.5: Support for negative duration function (DMN15-86)
Browse files Browse the repository at this point in the history
  • Loading branch information
opatrascoiu committed Jan 10, 2025
1 parent 590bef8 commit c4f3f86
Show file tree
Hide file tree
Showing 17 changed files with 755 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -648,12 +648,12 @@ public Element<Type> visit(ArithmeticNegation<Type> element, DMNContext context)

// Derive type
Type type = element.getLeftOperand().getType();
element.setType(NUMBER);
if (type != NUMBER) {
if (type != NUMBER && !(type instanceof DurationType)) {
handleError(context, element, String.format("Operator '%s' cannot be applied to '%s'", element.getOperator(), type));
}

// Derive type
element.setType(type);
return element;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -721,7 +721,12 @@ public Object visit(ArithmeticNegation<Type> element, DMNContext context) {
LOGGER.debug("Visiting element '{}'", element);

Object leftOperand = element.getLeftOperand().accept(this, context);
return this.lib.numericUnaryMinus((NUMBER) leftOperand);
Type type = element.getType();
if (type == NumberType.NUMBER) {
return this.lib.numericUnaryMinus((NUMBER) leftOperand);
} else {
return this.lib.durationUnaryMinus((DURATION) leftOperand);
}
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@
import java.util.*;
import java.util.stream.Collectors;

import static com.gs.dmn.feel.analysis.semantics.type.NumberType.NUMBER;

public class FEELToTripleNativeVisitor extends AbstractFEELToJavaVisitor<Object> {
private static final int INITIAL_VALUE = -1;
private int filterCount = INITIAL_VALUE;
Expand Down Expand Up @@ -366,7 +368,7 @@ public Triple visit(FilterExpression<Type> element, DMNContext context) {
// Filter
if (filterType == BooleanType.BOOLEAN) {
return this.triples.makeCollectionLogicFilter(source, newParameterName, filter);
} else if (filterType == NumberType.NUMBER) {
} else if (filterType == NUMBER) {
// Compute element type
Type elementType;
if (sourceType instanceof ListType) {
Expand Down Expand Up @@ -514,7 +516,12 @@ public Triple visit(Exponentiation<Type> element, DMNContext context) {
public Triple visit(ArithmeticNegation<Type> element, DMNContext context) {
Expression<Type> leftOperand = element.getLeftOperand();
Triple leftOpd = (Triple) leftOperand.accept(this, context);
return this.triples.makeBuiltinFunctionInvocation("numericUnaryMinus", leftOpd);
Type type = element.getType();
if (type == NUMBER) {
return this.triples.makeBuiltinFunctionInvocation("numericUnaryMinus", leftOpd);
} else {
return this.triples.makeBuiltinFunctionInvocation("durationUnaryMinus", leftOpd);
}
}

//
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@
import com.gs.dmn.feel.analysis.syntax.ast.test.UnaryTests;
import com.gs.dmn.feel.lib.FEELLib;
import com.gs.dmn.runtime.Context;
import com.gs.dmn.runtime.ExecutionContext;
import com.gs.dmn.runtime.ExecutionContextBuilder;
import com.gs.dmn.runtime.Range;
import com.gs.dmn.runtime.interpreter.DMNInterpreter;
import com.gs.dmn.runtime.interpreter.Result;
Expand Down Expand Up @@ -2192,46 +2194,85 @@ public void testExponentiation() {
@Test
public void testArithmeticNegation() {
String number = "1";
String yearsAndMonthsDuration = "@\"P1Y1M\"";
String daysAndTimeDuration = "@\"P1DT1H\"";
String duration = "duration(\"P1DT1H\")";
List<EnvironmentEntry> entries = Collections.singletonList(
new EnvironmentEntry("input", NUMBER, this.lib.number("1")));
new EnvironmentEntry("input", NUMBER, this.lib.number("1"))
);

// number
doExpressionTest(entries, "", String.format("- %s", number),
"ArithmeticNegation(NumericLiteral(1))",
"number",
"numericUnaryMinus(number(\"1\"))",
this.lib.numericUnaryMinus(this.lib.number("1")),
this.lib.number("-1"));

doExpressionTest(entries, "", String.format("-- %s", number),
"NumericLiteral(1)",
"number",
"number(\"1\")",
this.lib.number("1"),
this.lib.number("1"));

doExpressionTest(entries, "", String.format("--- %s", number),
"ArithmeticNegation(NumericLiteral(1))",
"number",
"numericUnaryMinus(number(\"1\"))",
this.lib.numericUnaryMinus(this.lib.number("1")),
this.lib.number("-1"));

// duration
doExpressionTest(entries, "", String.format("- %s", yearsAndMonthsDuration),
"ArithmeticNegation(DateTimeLiteral(duration, \"P1Y1M\"))",
"years and months duration",
"durationUnaryMinus(duration(\"P1Y1M\"))",
this.lib.durationUnaryMinus(this.lib.duration("P1Y1M")),
this.lib.duration("-P1Y1M"));
doExpressionTest(entries, "", String.format("- %s", daysAndTimeDuration),
"ArithmeticNegation(DateTimeLiteral(duration, \"P1DT1H\"))",
"days and time duration",
"durationUnaryMinus(duration(\"P1DT1H\"))",
this.lib.durationUnaryMinus(this.lib.duration("P1DT1H")),
this.lib.duration("-PT25H"));
doExpressionTest(entries, "", String.format("- %s", duration),
"ArithmeticNegation(DateTimeLiteral(duration, \"P1DT1H\"))",
"days and time duration",
"durationUnaryMinus(duration(\"P1DT1H\"))",
this.lib.durationUnaryMinus(this.lib.duration("P1DT1H")),
this.lib.duration("-PT25H"));

// complex
doExpressionTest(entries, "", "-(1+2+3)",
"ArithmeticNegation(Addition(+,Addition(+,NumericLiteral(1),NumericLiteral(2)),NumericLiteral(3)))",
"number",
"numericUnaryMinus(numericAdd(numericAdd(number(\"1\"), number(\"2\")), number(\"3\")))",
this.lib.numericUnaryMinus(this.lib.numericAdd(this.lib.numericAdd(this.lib.number("1"), this.lib.number("2")), this.lib.number("3"))),
this.lib.number("-6"));
ExecutionContext context_ = ExecutionContextBuilder.executionContext().build();
doExpressionTest(entries, "", "-(function(a) a)(10)",
"ArithmeticNegation(FunctionInvocation(FunctionDefinition(FormalParameter(a, number, false, false), Name(a), false) -> PositionalParameters(NumericLiteral(10))))",
"number",
"numericUnaryMinus(new com.gs.dmn.runtime.LambdaExpression<" + numberType() + ">() {public " + numberType() + " apply(Object... args_) {" + numberType() + " a = (" + numberType() + ")args_[0];return a;}}.apply(number(\"10\"), context_))",
this.lib.numericUnaryMinus(new com.gs.dmn.runtime.LambdaExpression<NUMBER>() {public NUMBER apply(Object... args_) {NUMBER a = (NUMBER)args_[0];return a;}}.apply(this.lib.number("10"), context_)),
this.lib.number("-10"));

}

@Test
public void testArithmeticNegationOnIncorrectOperands() {
Assertions.assertThrows(SemanticError.class, () -> {
String yearsAndMonthsDuration = "duration(\"P1Y1M\")";
String daysAndTimeDuration = "duration(\"P1DT1H\")";
String date = "date(\"2020-01-01\")";
String time = "time(\"21:00:00\")";

List<EnvironmentEntry> entries = Collections.singletonList(
new EnvironmentEntry("input", NUMBER, this.lib.number("1")));
doExpressionTest(entries, "", String.format("- %s", yearsAndMonthsDuration),
doExpressionTest(entries, "", String.format("- %s", date),
"ArithmeticNegation(DateTimeLiteral(duration, \"P1Y1M\"))",
"years and months duration",
"numericUnaryMinus(duration(\"P1Y1M\"))",
null,
null);
doExpressionTest(entries, "", String.format("- %s", daysAndTimeDuration),
doExpressionTest(entries, "", String.format("- %s", time),
"ArithmeticNegation(DateTimeLiteral(duration, \"P1DT1H\"))",
"days and time duration",
"numericUnaryMinus(duration(\"P1DT1H\"))",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -622,6 +622,11 @@ public void test_15_cl3_0068_feel_equality() {
doSingleModelTest("1.5", "0068-feel-equality", new Pair<>("strongTyping", "false") );
}

@Test
public void test_15_cl3_0099_arithmetic_negation() {
doSingleModelTest("1.5", "0099-arithmetic-negation");
}

@Test
public void test_15_cl3_1155_list_replace_function() {
doSingleModelTest("1.5", "1155-list-replace-function");
Expand Down
11 changes: 11 additions & 0 deletions dmn-runtime/src/main/java/com/gs/dmn/feel/lib/BaseFEELLib.java
Original file line number Diff line number Diff line change
Expand Up @@ -1113,6 +1113,17 @@ public DURATION durationDivideNumber(DURATION first, NUMBER second) {
}
}

@Override
public DURATION durationUnaryMinus(DURATION first) {
try {
return durationType.durationUnaryMinus(first);
} catch (Exception e) {
String message = String.format("durationUnaryMinus(%s", first);
logError(message, e);
return null;
}
}

//
// List operators
//
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,6 @@ default boolean isDuration(Object value) {
DURATION durationMultiplyNumber(DURATION first, NUMBER second);

DURATION durationDivideNumber(DURATION first, NUMBER second);

DURATION durationUnaryMinus(DURATION first);
}
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,21 @@ public TemporalAmount durationDivideNumber(TemporalAmount first, Number second)
}
}

@Override
public TemporalAmount durationUnaryMinus(TemporalAmount first) {
if (first == null) {
return null;
}

if (isDaysAndTimeDuration(first)) {
return ((Duration) first).negated();
} else if (isYearsAndMonthsDuration(first)) {
return ((Period) first).negated();
} else {
throw new DMNRuntimeException(String.format("Cannot negate '%s'", first));
}
}

private Long value(Period duration) {
return duration == null ? null : duration.toTotalMonths();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,21 @@ public Duration durationDivideNumber(Duration first, BigDecimal second) {
}
}

@Override
public Duration durationUnaryMinus(Duration first) {
if (first == null) {
return null;
}

if (isDaysAndTimeDuration(first)) {
return first.negate();
} else if (isYearsAndMonthsDuration(first)) {
return first.negate();
} else {
throw new DMNRuntimeException(String.format("Cannot negate '%s'", first));
}
}

private BigDecimal multiplyNumbers(Long firstValue, BigDecimal second) {
return BigDecimal.valueOf(firstValue).multiply(second);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,4 +100,19 @@ public Duration durationDivideNumber(Duration first, Double second) {
throw new DMNRuntimeException(String.format("Cannot divide '%s' by '%s'", first, second));
}
}

@Override
public Duration durationUnaryMinus(Duration first) {
if (first == null) {
return null;
}

if (isDaysAndTimeDuration(first)) {
return first.negate();
} else if (isYearsAndMonthsDuration(first)) {
return first.negate();
} else {
throw new DMNRuntimeException(String.format("Cannot negate '%s'", first));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -90,4 +90,9 @@ public DURATION durationMultiplyNumber(DURATION first, NUMBER second) {
public DURATION durationDivideNumber(DURATION first, NUMBER second) {
throw new DMNRuntimeException("Not supported yet");
}

@Override
public DURATION durationUnaryMinus(DURATION first) {
throw new DMNRuntimeException("Not supported yet");
}
}
2 changes: 1 addition & 1 deletion dmn-tck-it/dmn-tck-it-translator/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2109,7 +2109,7 @@
</execution>

<execution>
<id>cl3-1155-list-replace-function-test</id>
<id>cl3-1155-list-replace-function</id>
<phase>generate-sources</phase>
<goals>
<goal>dmn-to-java</goal>
Expand Down
Loading

0 comments on commit c4f3f86

Please sign in to comment.