This is an automated email from the ASF dual-hosted git repository.

olabusayo pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/daffodil.git


The following commit(s) were added to refs/heads/main by this push:
     new fe6646f31 Add support for dfdl:checkRangeInclusive/checkRangeExclusive 
functions
fe6646f31 is described below

commit fe6646f317ed10d4efce31ae5214c5ccaadacc72
Author: olabusayoT <[email protected]>
AuthorDate: Thu Mar 28 13:26:12 2024 -0400

    Add support for dfdl:checkRangeInclusive/checkRangeExclusive functions
    
    - add implementation for checkRangeInclusive/Exclusive in Expressions and 
DFDLFunctions
    - create case class ComparisonOp instead of the 6-tuple for the forType Map 
value
    - add ComparisonOps.forType lazy val map and use in ComparisonExpression, 
RepTypeMixin and DFDLCheckRangeExpr
    - add tests for checkRangeInclusive/Exclusive for numeric types and 
non-numeric types
    - add unittest to test out usage exceptions for hexBinary comparisons
    
    DAFFODIL-1515
---
 .../apache/daffodil/core/dpath/Expression.scala    | 184 +++++------
 .../daffodil/core/grammar/RepTypeMixin.scala       |  42 +--
 .../daffodil/runtime1/dpath/ComparisonOps.scala    | 190 +++++++++++
 .../daffodil/runtime1/dpath/DFDLFunctions.scala    |  23 ++
 .../section23/dfdl_expressions/expressions.tdml    | 353 +++++++++++++++++++++
 .../dfdl_expressions/TestDFDLExpressions2.scala    |  82 +++++
 6 files changed, 728 insertions(+), 146 deletions(-)

diff --git 
a/daffodil-core/src/main/scala/org/apache/daffodil/core/dpath/Expression.scala 
b/daffodil-core/src/main/scala/org/apache/daffodil/core/dpath/Expression.scala
index bbd90c4f3..4bfbf177e 100644
--- 
a/daffodil-core/src/main/scala/org/apache/daffodil/core/dpath/Expression.scala
+++ 
b/daffodil-core/src/main/scala/org/apache/daffodil/core/dpath/Expression.scala
@@ -258,117 +258,23 @@ case class ComparisonExpression(op: String, adds: 
List[Expression])
   with BooleanExpression {
 
   lazy val compareOp: CompareOpBase = {
-    import NodeInfo.PrimType._
-    import NodeInfo.ArrayIndex
-    (op, convergedArgType) match {
-      case ("<", _) => subsetError("Unsupported operation '%s'. Use 'lt' 
instead.", op)
-      case (">", _) => subsetError("Unsupported operation '%s'. Use 'gt' 
instead.", op)
-      case ("<=", _) => subsetError("Unsupported operation '%s'. Use 'le' 
instead.", op)
-      case (">=", _) => subsetError("Unsupported operation '%s'. Use 'ge' 
instead.", op)
-      case ("=", _) => subsetError("Unsupported operation '%s'. Use 'eq' 
instead.", op)
-      case ("!=", _) => subsetError("Unsupported operation '%s'. Use 'ne' 
instead.", op)
-
-      case ("eq", HexBinary) => EQ_CompareByteArray
-      case ("ne", HexBinary) => NE_CompareByteArray
-      case ("eq", _) => EQ_Compare
-      case ("ne", _) => NE_Compare
-
-      case ("lt", Boolean) => LT_Boolean
-      case ("gt", Boolean) => GT_Boolean
-      case ("le", Boolean) => LE_Boolean
-      case ("ge", Boolean) => GE_Boolean
-
-      case ("lt", Date) => LT_Date
-      case ("gt", Date) => GT_Date
-      case ("le", Date) => LE_Date
-      case ("ge", Date) => GE_Date
-
-      case ("lt", Time) => LT_Time
-      case ("gt", Time) => GT_Time
-      case ("le", Time) => LE_Time
-      case ("ge", Time) => GE_Time
-
-      case ("lt", DateTime) => LT_DateTime
-      case ("gt", DateTime) => GT_DateTime
-      case ("le", DateTime) => LE_DateTime
-      case ("ge", DateTime) => GE_DateTime
-
-      case ("lt", String) => LT_String
-      case ("gt", String) => GT_String
-      case ("le", String) => LE_String
-      case ("ge", String) => GE_String
-
-      case ("lt", Decimal) => LT_Decimal
-      case ("gt", Decimal) => GT_Decimal
-      case ("le", Decimal) => LE_Decimal
-      case ("ge", Decimal) => GE_Decimal
-
-      case ("lt", Integer) => LT_Integer
-      case ("gt", Integer) => GT_Integer
-      case ("le", Integer) => LE_Integer
-      case ("ge", Integer) => GE_Integer
-
-      case ("lt", NonNegativeInteger) => LT_NonNegativeInteger
-      case ("gt", NonNegativeInteger) => GT_NonNegativeInteger
-      case ("le", NonNegativeInteger) => LE_NonNegativeInteger
-      case ("ge", NonNegativeInteger) => GE_NonNegativeInteger
-
-      case ("lt", UnsignedLong) => LT_UnsignedLong
-      case ("gt", UnsignedLong) => GT_UnsignedLong
-      case ("le", UnsignedLong) => LE_UnsignedLong
-      case ("ge", UnsignedLong) => GE_UnsignedLong
-
-      case ("lt", Long) => LT_Long
-      case ("gt", Long) => GT_Long
-      case ("le", Long) => LE_Long
-      case ("ge", Long) => GE_Long
-
-      case ("lt", UnsignedInt) => LT_UnsignedInt
-      case ("gt", UnsignedInt) => GT_UnsignedInt
-      case ("le", UnsignedInt) => LE_UnsignedInt
-      case ("ge", UnsignedInt) => GE_UnsignedInt
-
-      case ("lt", ArrayIndex) => LT_UnsignedInt
-      case ("gt", ArrayIndex) => GT_UnsignedInt
-      case ("le", ArrayIndex) => LE_UnsignedInt
-      case ("ge", ArrayIndex) => GE_UnsignedInt
-
-      case ("lt", Int) => LT_Int
-      case ("gt", Int) => GT_Int
-      case ("le", Int) => LE_Int
-      case ("ge", Int) => GE_Int
-
-      case ("lt", UnsignedShort) => LT_UnsignedShort
-      case ("gt", UnsignedShort) => GT_UnsignedShort
-      case ("le", UnsignedShort) => LE_UnsignedShort
-      case ("ge", UnsignedShort) => GE_UnsignedShort
-
-      case ("lt", Short) => LT_Short
-      case ("gt", Short) => GT_Short
-      case ("le", Short) => LE_Short
-      case ("ge", Short) => GE_Short
-
-      case ("lt", UnsignedByte) => LT_UnsignedByte
-      case ("gt", UnsignedByte) => GT_UnsignedByte
-      case ("le", UnsignedByte) => LE_UnsignedByte
-      case ("ge", UnsignedByte) => GE_UnsignedByte
-
-      case ("lt", Byte) => LT_Byte
-      case ("gt", Byte) => GT_Byte
-      case ("le", Byte) => LE_Byte
-      case ("ge", Byte) => GE_Byte
-
-      case ("lt", Float) => LT_Float
-      case ("gt", Float) => GT_Float
-      case ("le", Float) => LE_Float
-      case ("ge", Float) => GE_Float
-
-      case ("lt", Double) => LT_Double
-      case ("gt", Double) => GT_Double
-      case ("le", Double) => LE_Double
-      case ("ge", Double) => GE_Double
-
-      case _ => subsetError("Unsupported operation '%s' on type %s.", op, 
convergedArgType)
+    val comparisonOps = ComparisonOps.forType(convergedArgType)
+    import NodeInfo.PrimType.HexBinary
+    op match {
+      case "<" => subsetError("Unsupported operation '%s'. Use 'lt' instead.", 
op)
+      case ">" => subsetError("Unsupported operation '%s'. Use 'gt' instead.", 
op)
+      case "<=" => subsetError("Unsupported operation '%s'. Use 'le' 
instead.", op)
+      case ">=" => subsetError("Unsupported operation '%s'. Use 'ge' 
instead.", op)
+      case "=" => subsetError("Unsupported operation '%s'. Use 'eq' instead.", 
op)
+      case "!=" => subsetError("Unsupported operation '%s'. Use 'ne' 
instead.", op)
+      case "eq" => comparisonOps.eq
+      case "ne" => comparisonOps.ne
+      case "lt" if convergedArgType != HexBinary => comparisonOps.lt
+      case "gt" if convergedArgType != HexBinary => comparisonOps.gt
+      case "le" if convergedArgType != HexBinary => comparisonOps.le
+      case "ge" if convergedArgType != HexBinary => comparisonOps.ge
+      case _ =>
+        subsetError(s"Unsupported operation '$op' on type $convergedArgType.")
     }
   }
 
@@ -2016,6 +1922,12 @@ case class FunctionCallExpression(functionQNameString: 
String, expressions: List
       case (RefQName(_, "checkConstraints", DFDL), args) => {
         DFDLCheckConstraintsExpr(functionQNameString, functionQName, args)
       }
+      case (RefQName(_, "checkRangeInclusive", DFDL), args) => {
+        DFDLCheckRangeExpr(functionQNameString, functionQName, args, 
isExclusive = false)
+      }
+      case (RefQName(_, "checkRangeExclusive", DFDL), args) => {
+        DFDLCheckRangeExpr(functionQNameString, functionQName, args, 
isExclusive = true)
+      }
       case (RefQName(_, "decodeDFDLEntities", DFDL), args) => {
         FNOneArgExpr(
           functionQNameString,
@@ -3041,6 +2953,56 @@ case class DFDLCheckConstraintsExpr(
 
 }
 
+case class DFDLCheckRangeExpr(
+  nameAsParsed: String,
+  fnQName: RefQName,
+  args: List[Expression],
+  isExclusive: Boolean,
+) extends FunctionCallBase(nameAsParsed, fnQName, args) {
+
+  lazy val List(arg1, arg2, arg3) = { checkArgCount(3); args }
+
+  override lazy val children = args
+
+  override lazy val compiledDPath = {
+    val argDPath = arg1.compiledDPath
+    val rangeFrom = arg2.compiledDPath
+    val rangeTo = arg3.compiledDPath
+    val c = conversions
+    val comparisonOps = ComparisonOps.forType(convergedArgType)
+    val compare = if (isExclusive) comparisonOps.lt else comparisonOps.le
+    val res = new CompiledDPath(DFDLCheckRange(argDPath, rangeFrom, rangeTo, 
compare) +: c)
+    res
+  }
+
+  // we set the calculation to a lazy val so it's evaluated only once for each
+  // call to targetTypeForSubexpression since we don't care about the specific
+  // value of subexpr in targetTypeForSubexpression, and we need all 3 sub
+  // expressions to come to the right target target for the args
+  lazy val convergedArgType: NodeInfo.Kind = {
+    import NodeInfo._
+    val targetType = (arg1.inherentType, arg2.inherentType, arg3.inherentType) 
match {
+      case (testType: Numeric.Kind, minType: Numeric.Kind, maxType: 
Numeric.Kind) => {
+        val rangeType =
+          NodeInfoUtils.generalizeArgTypesForComparisonOp(nameAsParsed, 
minType, maxType)
+        val targetType =
+          NodeInfoUtils.generalizeArgTypesForComparisonOp(nameAsParsed, 
testType, rangeType)
+        targetType
+      }
+      case (test, min, max) =>
+        SDE(s"Cannot call $nameAsParsed with non-numeric types: $test, $min, 
$max")
+    }
+    targetType
+  }
+
+  override def targetTypeForSubexpression(subexpr: Expression): NodeInfo.Kind 
= {
+    convergedArgType
+  }
+
+  override lazy val inherentType: NodeInfo.Kind = NodeInfo.Boolean
+
+}
+
 /**
  * Really this just delegates anything bottom-up to the contained expression
  * and anything top-down to the parent
diff --git 
a/daffodil-core/src/main/scala/org/apache/daffodil/core/grammar/RepTypeMixin.scala
 
b/daffodil-core/src/main/scala/org/apache/daffodil/core/grammar/RepTypeMixin.scala
index d16b07b33..d55df71dd 100644
--- 
a/daffodil-core/src/main/scala/org/apache/daffodil/core/grammar/RepTypeMixin.scala
+++ 
b/daffodil-core/src/main/scala/org/apache/daffodil/core/grammar/RepTypeMixin.scala
@@ -25,26 +25,7 @@ import org.apache.daffodil.core.dsom.ElementBase
 import org.apache.daffodil.core.dsom.GlobalSimpleTypeDef
 import org.apache.daffodil.core.dsom.RepTypeQuasiElementDecl
 import org.apache.daffodil.lib.exceptions.Assert
-import org.apache.daffodil.runtime1.dpath.LE_Byte
-import org.apache.daffodil.runtime1.dpath.LE_Int
-import org.apache.daffodil.runtime1.dpath.LE_Integer
-import org.apache.daffodil.runtime1.dpath.LE_Long
-import org.apache.daffodil.runtime1.dpath.LE_NonNegativeInteger
-import org.apache.daffodil.runtime1.dpath.LE_Short
-import org.apache.daffodil.runtime1.dpath.LE_UnsignedByte
-import org.apache.daffodil.runtime1.dpath.LE_UnsignedInt
-import org.apache.daffodil.runtime1.dpath.LE_UnsignedLong
-import org.apache.daffodil.runtime1.dpath.LE_UnsignedShort
-import org.apache.daffodil.runtime1.dpath.LT_Byte
-import org.apache.daffodil.runtime1.dpath.LT_Int
-import org.apache.daffodil.runtime1.dpath.LT_Integer
-import org.apache.daffodil.runtime1.dpath.LT_Long
-import org.apache.daffodil.runtime1.dpath.LT_NonNegativeInteger
-import org.apache.daffodil.runtime1.dpath.LT_Short
-import org.apache.daffodil.runtime1.dpath.LT_UnsignedByte
-import org.apache.daffodil.runtime1.dpath.LT_UnsignedInt
-import org.apache.daffodil.runtime1.dpath.LT_UnsignedLong
-import org.apache.daffodil.runtime1.dpath.LT_UnsignedShort
+import org.apache.daffodil.runtime1.dpath.ComparisonOps
 import org.apache.daffodil.runtime1.dpath.NodeInfo
 import org.apache.daffodil.runtime1.dpath.NumberCompareOp
 import org.apache.daffodil.runtime1.infoset.DataValue.DataValueNumber
@@ -100,21 +81,12 @@ trait RepTypeMixin { self: ElementBase =>
     repTypeCompareLT: NumberCompareOp,
     repTypeCompareLE: NumberCompareOp,
   ) = LV('repTypeComparers) {
-    repTypeElementDecl.primType match {
-      case NodeInfo.Integer => (LT_Integer, LE_Integer)
-      case NodeInfo.Long => (LT_Long, LE_Long)
-      case NodeInfo.Int => (LT_Int, LE_Int)
-      case NodeInfo.Short => (LT_Short, LE_Short)
-      case NodeInfo.Byte => (LT_Byte, LE_Byte)
-      case NodeInfo.NonNegativeInteger => (LT_NonNegativeInteger, 
LE_NonNegativeInteger)
-      case NodeInfo.UnsignedLong => (LT_UnsignedLong, LE_UnsignedLong)
-      case NodeInfo.UnsignedInt => (LT_UnsignedInt, LE_UnsignedInt)
-      case NodeInfo.UnsignedShort => (LT_UnsignedShort, LE_UnsignedShort)
-      case NodeInfo.UnsignedByte => (LT_UnsignedByte, LE_UnsignedByte)
-      // $COVERAGE-OFF$
-      case _ => Assert.invariantFailed("repType should only be an integer 
type")
-      // $COVERAGE-ON$
-    }
+    Assert.invariant(
+      repTypeElementDecl.primType.isSubtypeOf(NodeInfo.Integer),
+      "repType should only be an integer type",
+    )
+    val comparisonOps = ComparisonOps.forType(repTypeElementDecl.primType)
+    (comparisonOps.lt, comparisonOps.le)
   }.value
 
   lazy val (
diff --git 
a/daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/dpath/ComparisonOps.scala
 
b/daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/dpath/ComparisonOps.scala
index 84e9a323f..08e30a391 100644
--- 
a/daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/dpath/ComparisonOps.scala
+++ 
b/daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/dpath/ComparisonOps.scala
@@ -17,10 +17,176 @@
 
 package org.apache.daffodil.runtime1.dpath
 
+import org.apache.daffodil.lib.exceptions.Assert
 import org.apache.daffodil.lib.util.Numbers._
+import org.apache.daffodil.runtime1.dpath.NodeInfo.PrimType
 import org.apache.daffodil.runtime1.infoset.DataValue.DataValueBool
 import org.apache.daffodil.runtime1.infoset.DataValue.DataValuePrimitive
 
+/**
+ * Case class used for ordering the return of the appropriate comparison 
operations for each primitive kind
+ * @param eq represents the equality comparison
+ * @param ne represents the unequality comparison
+ * @param lt represents the less than comparison
+ * @param le represents the less than or equal to comparison
+ * @param gt represents the greater than comparison
+ * @param ge represents the greater than or equal to comparison
+ */
+case class ComparisonOp(
+  eq: CompareOpBase,
+  ne: CompareOpBase,
+  lt: CompareOpBase,
+  le: CompareOpBase,
+  gt: CompareOpBase,
+  ge: CompareOpBase,
+)
+
+/**
+ * ComparisonOps.forType represents a map with key of NodeInfo.Kind and the 
value of the above ComparisonOp case class, which
+ * is a 6 param object containing the appropriate comparison object for the 
NodeInfo.Kind
+ *
+ * To use, you can call the forType map using an argument targetType, which 
queries the Map using the key targetType
+ * then use the returned object to select the ComparisonOpBase of interest
+ *
+ * @example {{{
+ *  val compOps = ComparisonOps.forType(targetType)
+ *  val res = compOps.lt.operate(x, y)
+ *  res
+ * }}}
+ *
+ */
+object ComparisonOps {
+  lazy val forType: Map[NodeInfo.Kind, ComparisonOp] = {
+    Map(
+      PrimType.Boolean -> ComparisonOp(
+        EQ_Compare,
+        NE_Compare,
+        LT_Boolean,
+        LE_Boolean,
+        GT_Boolean,
+        GE_Boolean,
+      ),
+      PrimType.Integer -> ComparisonOp(
+        EQ_Compare,
+        NE_Compare,
+        LT_Integer,
+        LE_Integer,
+        GT_Integer,
+        GE_Integer,
+      ),
+      PrimType.Date -> ComparisonOp(EQ_Compare, NE_Compare, LT_Date, LE_Date, 
GT_Date, GE_Date),
+      PrimType.Time -> ComparisonOp(EQ_Compare, NE_Compare, LT_Time, LE_Time, 
GT_Time, GE_Time),
+      PrimType.DateTime -> ComparisonOp(
+        EQ_Compare,
+        NE_Compare,
+        LT_DateTime,
+        LE_DateTime,
+        GT_DateTime,
+        GE_DateTime,
+      ),
+      PrimType.String -> ComparisonOp(
+        EQ_Compare,
+        NE_Compare,
+        LT_String,
+        LE_String,
+        GT_String,
+        GE_String,
+      ),
+      PrimType.Decimal -> ComparisonOp(
+        EQ_Compare,
+        NE_Compare,
+        LT_Decimal,
+        LE_Decimal,
+        GT_Decimal,
+        GE_Decimal,
+      ),
+      PrimType.NonNegativeInteger -> ComparisonOp(
+        EQ_Compare,
+        NE_Compare,
+        LT_NonNegativeInteger,
+        LE_NonNegativeInteger,
+        GT_NonNegativeInteger,
+        GE_NonNegativeInteger,
+      ),
+      PrimType.UnsignedLong -> ComparisonOp(
+        EQ_Compare,
+        NE_Compare,
+        LT_UnsignedLong,
+        LE_UnsignedLong,
+        GT_UnsignedLong,
+        GE_UnsignedLong,
+      ),
+      PrimType.Long -> ComparisonOp(EQ_Compare, NE_Compare, LT_Long, LE_Long, 
GT_Long, GE_Long),
+      PrimType.UnsignedInt -> ComparisonOp(
+        EQ_Compare,
+        NE_Compare,
+        LT_UnsignedInt,
+        LE_UnsignedInt,
+        GT_UnsignedInt,
+        GE_UnsignedInt,
+      ),
+      NodeInfo.ArrayIndex -> ComparisonOp(
+        EQ_Compare,
+        NE_Compare,
+        LT_UnsignedInt,
+        LE_UnsignedInt,
+        GT_UnsignedInt,
+        GE_UnsignedInt,
+      ),
+      PrimType.Int -> ComparisonOp(EQ_Compare, NE_Compare, LT_Int, LE_Int, 
GT_Int, GE_Int),
+      PrimType.UnsignedShort -> ComparisonOp(
+        EQ_Compare,
+        NE_Compare,
+        LT_UnsignedShort,
+        LE_UnsignedShort,
+        GT_UnsignedShort,
+        GE_UnsignedShort,
+      ),
+      PrimType.Short -> ComparisonOp(
+        EQ_Compare,
+        NE_Compare,
+        LT_Short,
+        LE_Short,
+        GT_Short,
+        GE_Short,
+      ),
+      PrimType.UnsignedByte -> ComparisonOp(
+        EQ_Compare,
+        NE_Compare,
+        LT_UnsignedByte,
+        LE_UnsignedByte,
+        GT_UnsignedByte,
+        GE_UnsignedByte,
+      ),
+      PrimType.Byte -> ComparisonOp(EQ_Compare, NE_Compare, LT_Byte, LE_Byte, 
GT_Byte, GE_Byte),
+      PrimType.Float -> ComparisonOp(
+        EQ_Compare,
+        NE_Compare,
+        LT_Float,
+        LE_Float,
+        GT_Float,
+        GE_Float,
+      ),
+      PrimType.Double -> ComparisonOp(
+        EQ_Compare,
+        NE_Compare,
+        LT_Double,
+        LE_Double,
+        GT_Double,
+        GE_Double,
+      ),
+      PrimType.HexBinary -> ComparisonOp(
+        EQ_CompareByteArray,
+        NE_CompareByteArray,
+        LT_ByteArray,
+        LE_ByteArray,
+        GT_ByteArray,
+        GE_ByteArray,
+      ),
+    )
+  }
+}
+
 case object EQ_Compare extends CompareOpBase {
   def operate(v1: DataValuePrimitive, v2: DataValuePrimitive): DataValueBool = 
{
     val res = v1 == v2
@@ -35,6 +201,30 @@ case object EQ_CompareByteArray extends CompareOpBase {
   }
 }
 
+case object LT_ByteArray extends CompareOpBase {
+  def operate(v1: DataValuePrimitive, v2: DataValuePrimitive): DataValueBool = 
{
+    Assert.usageError("Unsupported operation LT on Byte Array")
+  }
+}
+
+case object LE_ByteArray extends CompareOpBase {
+  def operate(v1: DataValuePrimitive, v2: DataValuePrimitive): DataValueBool = 
{
+    Assert.usageError("Unsupported operation LE on Byte Array")
+  }
+}
+
+case object GT_ByteArray extends CompareOpBase {
+  def operate(v1: DataValuePrimitive, v2: DataValuePrimitive): DataValueBool = 
{
+    Assert.usageError("Unsupported operation GT on Byte Array")
+  }
+}
+
+case object GE_ByteArray extends CompareOpBase {
+  def operate(v1: DataValuePrimitive, v2: DataValuePrimitive): DataValueBool = 
{
+    Assert.usageError("Unsupported operation GE on Byte Array")
+  }
+}
+
 case object NE_Compare extends CompareOpBase {
   def operate(v1: DataValuePrimitive, v2: DataValuePrimitive): DataValueBool = 
{
     val res = v1 != v2
diff --git 
a/daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/dpath/DFDLFunctions.scala
 
b/daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/dpath/DFDLFunctions.scala
index 94784e741..145a8533c 100644
--- 
a/daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/dpath/DFDLFunctions.scala
+++ 
b/daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/dpath/DFDLFunctions.scala
@@ -39,6 +39,29 @@ case class DFDLCheckConstraints(recipe: CompiledDPath) 
extends RecipeOpWithSubRe
   }
 }
 
+case class DFDLCheckRange(
+  dataRecipe: CompiledDPath,
+  minRecipe: CompiledDPath,
+  maxRecipe: CompiledDPath,
+  compare: CompareOpBase,
+) extends RecipeOpWithSubRecipes(dataRecipe, minRecipe, maxRecipe) {
+  override def run(dstate: DState): Unit = {
+    val saved = dstate.currentNode
+    dataRecipe.run(dstate)
+    val dataVal = dstate.currentValue.getNonNullable
+    dstate.setCurrentNode(saved)
+    minRecipe.run(dstate)
+    val minVal = dstate.currentValue.getNonNullable
+    dstate.setCurrentNode(saved)
+    maxRecipe.run(dstate)
+    val maxVal = dstate.currentValue.getNonNullable
+
+    val res =
+      compare.operate(minVal, dataVal).getBoolean && compare.operate(dataVal, 
maxVal).getBoolean
+    dstate.setCurrentValue(res)
+  }
+}
+
 case class DFDLDecodeDFDLEntities(recipe: CompiledDPath, argType: 
NodeInfo.Kind)
   extends FNOneArg(recipe, argType) {
   override def computeValue(str: DataValuePrimitive, dstate: DState): 
DataValueString = {
diff --git 
a/daffodil-test/src/test/resources/org/apache/daffodil/section23/dfdl_expressions/expressions.tdml
 
b/daffodil-test/src/test/resources/org/apache/daffodil/section23/dfdl_expressions/expressions.tdml
index 140479e89..a20b94a17 100644
--- 
a/daffodil-test/src/test/resources/org/apache/daffodil/section23/dfdl_expressions/expressions.tdml
+++ 
b/daffodil-test/src/test/resources/org/apache/daffodil/section23/dfdl_expressions/expressions.tdml
@@ -7684,4 +7684,357 @@ blastoff
     
<tdml:infoset><tdml:dfdlInfoset><add01>4</add01></tdml:dfdlInfoset></tdml:infoset>
   </tdml:parserTestCase>
 
+  <tdml:defineSchema name="DFDLCheckRange">
+    <xs:include 
schemaLocation="/org/apache/daffodil/xsd/DFDLGeneralFormat.dfdl.xsd"/>
+    <dfdl:format ref="ex:GeneralFormat" />
+    <xs:element name="literalMinMax_int">
+      <xs:complexType>
+        <xs:sequence>
+          <xs:element name="data" dfdl:lengthKind="explicit" dfdl:length="1" 
type="xs:integer"/>
+          <xs:element name="isInRangeInc" type="xs:boolean"
+                      dfdl:inputValueCalc="{ 
dfdl:checkRangeInclusive(../ex:data, 0, 8) }"/>
+          <xs:element name="isInRangeExc" type="xs:boolean"
+                      dfdl:inputValueCalc="{ 
dfdl:checkRangeExclusive(../ex:data, 0, 8) }"/>
+        </xs:sequence>
+      </xs:complexType>
+    </xs:element>
+
+    <xs:element name="expMinMax_double">
+      <xs:complexType>
+        <xs:sequence>
+          <xs:sequence dfdl:separatorPosition="infix" dfdl:separator="|">
+            <xs:element name="data" dfdl:lengthKind="delimited" 
type="xs:double"/>
+            <xs:element name="min" dfdl:lengthKind="delimited" 
type="xs:double"/>
+            <xs:element name="max" dfdl:lengthKind="delimited" 
type="xs:double"/>
+          </xs:sequence>
+          <xs:element name="isInRangeInc" type="xs:boolean"
+                      dfdl:inputValueCalc="{ 
dfdl:checkRangeInclusive(../ex:data, ../ex:min, ../ex:max) }"/>
+          <xs:element name="isInRangeExc" type="xs:boolean"
+                      dfdl:inputValueCalc="{ 
dfdl:checkRangeExclusive(../ex:data, ../ex:min, ../ex:max) }"/>
+        </xs:sequence>
+      </xs:complexType>
+    </xs:element>
+
+    <xs:element name="expMinMax_float">
+      <xs:complexType>
+        <xs:sequence>
+          <xs:sequence dfdl:separatorPosition="infix" dfdl:separator="|">
+            <xs:element name="data" dfdl:lengthKind="delimited" 
type="xs:float"/>
+            <xs:element name="min" dfdl:lengthKind="delimited" 
type="xs:float"/>
+            <xs:element name="max" dfdl:lengthKind="delimited" 
type="xs:float"/>
+          </xs:sequence>
+          <xs:element name="isInRangeInc" type="xs:boolean"
+                      dfdl:inputValueCalc="{ 
dfdl:checkRangeInclusive(../ex:data, ../ex:min, ../ex:max) }"/>
+          <xs:element name="isInRangeExc" type="xs:boolean"
+                      dfdl:inputValueCalc="{ 
dfdl:checkRangeExclusive(../ex:data, ../ex:min, ../ex:max) }"/>
+        </xs:sequence>
+      </xs:complexType>
+    </xs:element>
+
+    <xs:element name="expMinMax_decimal">
+      <xs:complexType>
+        <xs:sequence>
+          <xs:sequence dfdl:separatorPosition="infix" dfdl:separator="|">
+            <xs:element name="data" dfdl:lengthKind="delimited" 
type="xs:decimal"/>
+            <xs:element name="min" dfdl:lengthKind="delimited" 
type="xs:decimal"/>
+            <xs:element name="max" dfdl:lengthKind="delimited" 
type="xs:decimal"/>
+          </xs:sequence>
+          <xs:element name="isInRangeInc" type="xs:boolean"
+                      dfdl:inputValueCalc="{ 
dfdl:checkRangeInclusive(../ex:data, ../ex:min, ../ex:max) }"/>
+          <xs:element name="isInRangeExc" type="xs:boolean"
+                      dfdl:inputValueCalc="{ 
dfdl:checkRangeExclusive(../ex:data, ../ex:min, ../ex:max) }"/>
+        </xs:sequence>
+      </xs:complexType>
+    </xs:element>
+
+    <xs:element name="expMinMax_mixedIntAndFloat">
+      <xs:complexType>
+        <xs:sequence>
+          <xs:sequence dfdl:separatorPosition="infix" dfdl:separator="|">
+            <xs:element name="data" dfdl:lengthKind="delimited" 
type="xs:integer"/>
+            <xs:element name="min" dfdl:lengthKind="delimited" 
type="xs:float"/>
+            <xs:element name="max" dfdl:lengthKind="delimited" 
type="xs:float"/>
+          </xs:sequence>
+          <xs:element name="isInRangeInc" type="xs:boolean"
+                      dfdl:inputValueCalc="{ 
dfdl:checkRangeInclusive(../ex:data, ../ex:min, ../ex:max) }"/>
+          <xs:element name="isInRangeExc" type="xs:boolean"
+                      dfdl:inputValueCalc="{ 
dfdl:checkRangeExclusive(../ex:data, ../ex:min, ../ex:max) }"/>
+        </xs:sequence>
+      </xs:complexType>
+    </xs:element>
+    
+    <xs:element name="expMinMax_string_inc">
+      <xs:complexType>
+        <xs:sequence>
+          <xs:sequence dfdl:separatorPosition="infix" dfdl:separator="|">
+            <xs:element name="data" dfdl:lengthKind="delimited" 
type="xs:string"/>
+            <xs:element name="min" dfdl:lengthKind="delimited" 
type="xs:string"/>
+            <xs:element name="max" dfdl:lengthKind="delimited" 
type="xs:string"/>
+          </xs:sequence>
+          <xs:element name="isInRangeInc" type="xs:boolean"
+                      dfdl:inputValueCalc="{ 
dfdl:checkRangeInclusive(../ex:data, ../ex:min, ../ex:max) }"/>
+        </xs:sequence>
+      </xs:complexType>
+    </xs:element>
+
+    <xs:element name="expMinMax_string_exc">
+      <xs:complexType>
+        <xs:sequence>
+          <xs:sequence dfdl:separatorPosition="infix" dfdl:separator="|">
+            <xs:element name="data" dfdl:lengthKind="delimited" 
type="xs:string"/>
+            <xs:element name="min" dfdl:lengthKind="delimited" 
type="xs:string"/>
+            <xs:element name="max" dfdl:lengthKind="delimited" 
type="xs:string"/>
+          </xs:sequence>
+          <xs:element name="isInRangeExc" type="xs:boolean"
+                      dfdl:inputValueCalc="{ 
dfdl:checkRangeExclusive(../ex:data, ../ex:min, ../ex:max) }"/>
+        </xs:sequence>
+      </xs:complexType>
+    </xs:element>
+
+
+  </tdml:defineSchema>
+
+  <tdml:parserTestCase name="DFDLCheckRange_01" validation="on"
+                       root="literalMinMax_int" model="DFDLCheckRange">
+    <tdml:document>8</tdml:document>
+    <tdml:infoset>
+      <tdml:dfdlInfoset>
+        <literalMinMax_int>
+          <data>8</data>
+          <isInRangeInc>true</isInRangeInc>
+          <isInRangeExc>false</isInRangeExc>
+        </literalMinMax_int>
+      </tdml:dfdlInfoset>
+    </tdml:infoset>
+  </tdml:parserTestCase>
+
+  <tdml:parserTestCase name="DFDLCheckRange_02" validation="on"
+                       root="expMinMax_double" model="DFDLCheckRange">
+    <tdml:document>2.3|2.3|2.5</tdml:document>
+    <tdml:infoset>
+      <tdml:dfdlInfoset>
+        <expMinMax_double>
+          <data>2.3</data>
+          <min>2.3</min>
+          <max>2.5</max>
+          <isInRangeInc>true</isInRangeInc>
+          <isInRangeExc>false</isInRangeExc>
+        </expMinMax_double>
+      </tdml:dfdlInfoset>
+    </tdml:infoset>
+  </tdml:parserTestCase>
+
+  <tdml:parserTestCase name="DFDLCheckRange_03" validation="on"
+                       root="expMinMax_float" model="DFDLCheckRange">
+    <tdml:document>2.5|2.3|2.5</tdml:document>
+    <tdml:infoset>
+      <tdml:dfdlInfoset>
+        <expMinMax_float>
+          <data>2.5</data>
+          <min>2.3</min>
+          <max>2.5</max>
+          <isInRangeInc>true</isInRangeInc>
+          <isInRangeExc>false</isInRangeExc>
+        </expMinMax_float>
+      </tdml:dfdlInfoset>
+    </tdml:infoset>
+  </tdml:parserTestCase>
+
+  <tdml:parserTestCase name="DFDLCheckRange_04" validation="on"
+                       root="expMinMax_decimal" model="DFDLCheckRange">
+    <tdml:document>2.5|2.3|2.5</tdml:document>
+    <tdml:infoset>
+      <tdml:dfdlInfoset>
+        <expMinMax_decimal>
+          <data>2.5</data>
+          <min>2.3</min>
+          <max>2.5</max>
+          <isInRangeInc>true</isInRangeInc>
+          <isInRangeExc>false</isInRangeExc>
+        </expMinMax_decimal>
+      </tdml:dfdlInfoset>
+    </tdml:infoset>
+  </tdml:parserTestCase>
+
+  <tdml:parserTestCase name="DFDLCheckRange_05" validation="on"
+                       root="expMinMax_mixedIntAndFloat" 
model="DFDLCheckRange">
+    <tdml:document>1|1.9|2.5</tdml:document>
+    <tdml:infoset>
+      <tdml:dfdlInfoset>
+        <expMinMax_mixedIntAndFloat>
+          <data>1</data>
+          <min>1.9</min>
+          <max>2.5</max>
+          <isInRangeInc>false</isInRangeInc>
+          <isInRangeExc>false</isInRangeExc>
+        </expMinMax_mixedIntAndFloat>
+      </tdml:dfdlInfoset>
+    </tdml:infoset>
+  </tdml:parserTestCase>
+
+  <tdml:parserTestCase name="DFDLCheckRange_06" validation="on"
+                       root="literalMinMax_int" model="DFDLCheckRange">
+    <tdml:document>9</tdml:document>
+    <tdml:infoset>
+      <tdml:dfdlInfoset>
+        <literalMinMax_int>
+          <data>9</data>
+          <isInRangeInc>false</isInRangeInc>
+          <isInRangeExc>false</isInRangeExc>
+        </literalMinMax_int>
+      </tdml:dfdlInfoset>
+    </tdml:infoset>
+  </tdml:parserTestCase>
+
+  <tdml:parserTestCase name="DFDLCheckRange_07" validation="on"
+                       root="expMinMax_string_inc" model="DFDLCheckRange">
+    <tdml:document>2|1.9|2.5</tdml:document>
+    
+    <tdml:errors>
+      <tdml:error>Schema Definition Error</tdml:error>
+      <tdml:error>Cannot call dfdl:checkRangeInclusive</tdml:error>
+      <tdml:error>with non-numeric types</tdml:error>
+      <tdml:error>string, string, string</tdml:error>
+    </tdml:errors>
+  </tdml:parserTestCase>
+
+  <tdml:parserTestCase name="DFDLCheckRange_08" validation="on"
+                       root="expMinMax_string_exc" model="DFDLCheckRange">
+    <tdml:document>2|1.9|2.5</tdml:document>
+
+    <tdml:errors>
+      <tdml:error>Schema Definition Error</tdml:error>
+      <tdml:error>Cannot call dfdl:checkRangeExclusive</tdml:error>
+      <tdml:error>with non-numeric types</tdml:error>
+      <tdml:error>string, string, string</tdml:error>
+    </tdml:errors>
+  </tdml:parserTestCase>
+
+
+  <tdml:defineSchema name="HexBinaryComparisons">
+    <xs:include 
schemaLocation="/org/apache/daffodil/xsd/DFDLGeneralFormat.dfdl.xsd"/>
+    <dfdl:format ref="ex:GeneralFormat" />
+    <xs:element name="equalsNotEquals">
+      <xs:complexType>
+        <xs:sequence>
+          <xs:element name="data1" dfdl:lengthKind="explicit" dfdl:length="2" 
type="xs:hexBinary"/>
+          <xs:element name="data2" dfdl:lengthKind="explicit" dfdl:length="2" 
type="xs:hexBinary"/>
+          <xs:element name="equals" type="xs:boolean"
+                      dfdl:inputValueCalc="{ ../ex:data1 eq ../ex:data2 }"/>
+          <xs:element name="notEquals" type="xs:boolean"
+                      dfdl:inputValueCalc="{ ../ex:data1 ne ../ex:data2  }"/>
+        </xs:sequence>
+      </xs:complexType>
+    </xs:element>
+
+    <xs:element name="lessThan">
+      <xs:complexType>
+        <xs:sequence>
+          <xs:element name="data1" dfdl:lengthKind="explicit" dfdl:length="2" 
type="xs:hexBinary"/>
+          <xs:element name="data2" dfdl:lengthKind="explicit" dfdl:length="2" 
type="xs:hexBinary"/>
+          <xs:element name="lt" type="xs:boolean"
+                      dfdl:inputValueCalc="{ ../ex:data1 lt ../ex:data2 }"/>
+        </xs:sequence>
+      </xs:complexType>
+    </xs:element>
+
+    <xs:element name="lessThanOrEquals">
+      <xs:complexType>
+        <xs:sequence>
+          <xs:element name="data1" dfdl:lengthKind="explicit" dfdl:length="2" 
type="xs:hexBinary"/>
+          <xs:element name="data2" dfdl:lengthKind="explicit" dfdl:length="2" 
type="xs:hexBinary"/>
+          <xs:element name="le" type="xs:boolean"
+                      dfdl:inputValueCalc="{ ../ex:data1 le ../ex:data2 }"/>
+        </xs:sequence>
+      </xs:complexType>
+    </xs:element>
+
+    <xs:element name="greaterThan">
+      <xs:complexType>
+        <xs:sequence>
+          <xs:element name="data1" dfdl:lengthKind="explicit" dfdl:length="2" 
type="xs:hexBinary"/>
+          <xs:element name="data2" dfdl:lengthKind="explicit" dfdl:length="2" 
type="xs:hexBinary"/>
+          <xs:element name="gt" type="xs:boolean"
+                      dfdl:inputValueCalc="{ ../ex:data1 gt ../ex:data2 }"/>
+        </xs:sequence>
+      </xs:complexType>
+    </xs:element>
+
+    <xs:element name="greaterThanOrEquals">
+      <xs:complexType>
+        <xs:sequence>
+          <xs:element name="data1" dfdl:lengthKind="explicit" dfdl:length="2" 
type="xs:hexBinary"/>
+          <xs:element name="data2" dfdl:lengthKind="explicit" dfdl:length="2" 
type="xs:hexBinary"/>
+          <xs:element name="ge" type="xs:boolean"
+                      dfdl:inputValueCalc="{ ../ex:data1 ge ../ex:data2 }"/>
+        </xs:sequence>
+      </xs:complexType>
+    </xs:element>
+  </tdml:defineSchema>
+
+  <tdml:parserTestCase name="hexBinaryComparison_01" validation="on"
+                       root="equalsNotEquals" model="HexBinaryComparisons">
+    <tdml:document>
+      <tdml:documentPart type="byte">deadbeef</tdml:documentPart>
+    </tdml:document>
+    <tdml:infoset>
+      <tdml:dfdlInfoset>
+        <equalsNotEquals>
+          <data1>DEAD</data1>
+          <data2>BEEF</data2>
+          <equals>false</equals>
+          <notEquals>true</notEquals>
+        </equalsNotEquals>
+      </tdml:dfdlInfoset>
+    </tdml:infoset>
+  </tdml:parserTestCase>
+
+  <tdml:parserTestCase name="hexBinaryComparison_02" validation="on"
+                       root="lessThan" model="HexBinaryComparisons">
+    <tdml:document>
+      <tdml:documentPart type="byte">deadbeef</tdml:documentPart>
+    </tdml:document>
+    <tdml:errors>
+      <tdml:error>Schema Definition Error</tdml:error>
+      <tdml:error>Unsupported operation 'lt'</tdml:error>
+      <tdml:error>on type hexBinary</tdml:error>
+    </tdml:errors>
+  </tdml:parserTestCase>
+
+  <tdml:parserTestCase name="hexBinaryComparison_03" validation="on"
+                       root="lessThanOrEquals" model="HexBinaryComparisons">
+    <tdml:document>
+      <tdml:documentPart type="byte">deadbeef</tdml:documentPart>
+    </tdml:document>
+    <tdml:errors>
+      <tdml:error>Schema Definition Error</tdml:error>
+      <tdml:error>Unsupported operation 'le'</tdml:error>
+      <tdml:error>on type hexBinary</tdml:error>
+    </tdml:errors>
+  </tdml:parserTestCase>
+
+  <tdml:parserTestCase name="hexBinaryComparison_04" validation="on"
+                       root="greaterThan" model="HexBinaryComparisons">
+    <tdml:document>
+      <tdml:documentPart type="byte">deadbeef</tdml:documentPart>
+    </tdml:document>
+    <tdml:errors>
+      <tdml:error>Schema Definition Error</tdml:error>
+      <tdml:error>Unsupported operation 'gt'</tdml:error>
+      <tdml:error>on type hexBinary</tdml:error>
+    </tdml:errors>
+  </tdml:parserTestCase>
+
+  <tdml:parserTestCase name="hexBinaryComparison_05" validation="on"
+                       root="greaterThanOrEquals" model="HexBinaryComparisons">
+    <tdml:document>
+      <tdml:documentPart type="byte">deadbeef</tdml:documentPart>
+    </tdml:document>
+    <tdml:errors>
+      <tdml:error>Schema Definition Error</tdml:error>
+      <tdml:error>Unsupported operation 'ge'</tdml:error>
+      <tdml:error>on type hexBinary</tdml:error>
+    </tdml:errors>
+  </tdml:parserTestCase>
+
 </tdml:testSuite>
diff --git 
a/daffodil-test/src/test/scala/org/apache/daffodil/section23/dfdl_expressions/TestDFDLExpressions2.scala
 
b/daffodil-test/src/test/scala/org/apache/daffodil/section23/dfdl_expressions/TestDFDLExpressions2.scala
index 6988e4c70..1ae315299 100644
--- 
a/daffodil-test/src/test/scala/org/apache/daffodil/section23/dfdl_expressions/TestDFDLExpressions2.scala
+++ 
b/daffodil-test/src/test/scala/org/apache/daffodil/section23/dfdl_expressions/TestDFDLExpressions2.scala
@@ -17,9 +17,13 @@
 
 package org.apache.daffodil.section23.dfdl_expressions
 
+import org.apache.daffodil.lib.Implicits.intercept
+import org.apache.daffodil.lib.exceptions.UsageException
 import org.apache.daffodil.tdml.Runner
 
 import org.junit.AfterClass
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
 import org.junit.Test
 
 object TestDFDLExpressions2 {
@@ -135,6 +139,84 @@ class TestDFDLExpressions2 {
   @Test def test_idiv18(): Unit = { runner.runOneTest("idiv18") }
   @Test def test_idiv19(): Unit = { runner.runOneTest("idiv19") }
   @Test def test_idiv20(): Unit = { runner.runOneTest("idiv20") }
+  @Test def test_DFDLCheckRange_01(): Unit = {
+    runner.runOneTest("DFDLCheckRange_01")
+  }
+
+  @Test def test_DFDLCheckRange_02(): Unit = {
+    runner.runOneTest("DFDLCheckRange_02")
+  }
+
+  @Test def test_DFDLCheckRange_03(): Unit = {
+    runner.runOneTest("DFDLCheckRange_03")
+  }
+
+  @Test def test_DFDLCheckRange_04(): Unit = {
+    runner.runOneTest("DFDLCheckRange_04")
+  }
+
+  @Test def test_DFDLCheckRange_05(): Unit = {
+    runner.runOneTest("DFDLCheckRange_05")
+  }
+
+  @Test def test_DFDLCheckRange_06(): Unit = {
+    runner.runOneTest("DFDLCheckRange_06")
+  }
+
+  @Test def test_DFDLCheckRange_07(): Unit = {
+    runner.runOneTest("DFDLCheckRange_07")
+  }
+
+  @Test def test_DFDLCheckRange_08(): Unit = {
+    runner.runOneTest("DFDLCheckRange_08")
+  }
+
+  @Test def test_hexBinaryComparison_01(): Unit = {
+    runner.runOneTest("hexBinaryComparison_01")
+  }
+
+  @Test def test_hexBinaryComparison_02(): Unit = {
+    runner.runOneTest("hexBinaryComparison_02")
+  }
+  @Test def test_hexBinaryComparison_03(): Unit = {
+    runner.runOneTest("hexBinaryComparison_03")
+  }
+  @Test def test_hexBinaryComparison_04(): Unit = {
+    runner.runOneTest("hexBinaryComparison_04")
+  }
+  @Test def test_hexBinaryComparison_05(): Unit = {
+    runner.runOneTest("hexBinaryComparison_05")
+  }
+
+  @Test def test_hexBinaryComparison_06(): Unit = {
+    import org.apache.daffodil.runtime1.dpath.ComparisonOps
+    import org.apache.daffodil.runtime1.dpath.NodeInfo
+    val compOps = ComparisonOps.forType(NodeInfo.HexBinary)
+
+    val ba1 = Array[Byte](0xde.toByte, 0xad.toByte)
+    val ba2 = Array[Byte](0xbe.toByte, 0xef.toByte)
+
+    val eEQ = compOps.eq.operate(ba1, ba2).getBoolean
+    val eNE = compOps.ne.operate(ba1, ba2).getBoolean
+    val eLT = intercept[UsageException] {
+      compOps.lt.operate(ba1, ba2)
+    }
+    val eLE = intercept[UsageException] {
+      compOps.le.operate(ba1, ba2)
+    }
+    val eGT = intercept[UsageException] {
+      compOps.gt.operate(ba1, ba2)
+    }
+    val eGE = intercept[UsageException] {
+      compOps.ge.operate(ba1, ba2)
+    }
+    assertFalse(eEQ)
+    assertTrue(eNE)
+    assertTrue(eLT.getMessage.contains("Unsupported operation LT"))
+    assertTrue(eLE.getMessage.contains("Unsupported operation LE"))
+    assertTrue(eGT.getMessage.contains("Unsupported operation GT"))
+    assertTrue(eGE.getMessage.contains("Unsupported operation GE"))
+  }
 
   @Test def test_add01(): Unit = { runner.runOneTest("add01") }
 


Reply via email to