You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@daffodil.apache.org by mm...@apache.org on 2023/03/02 15:42:26 UTC

[daffodil] branch main updated: Update occursIndex to skip absent elements

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

mmcgann 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 16c738c38 Update occursIndex to skip absent elements
16c738c38 is described below

commit 16c738c38ffb41a27709119cb3ffa84e3dedd74c
Author: Mike McGann <mm...@owlcyberdefense.com>
AuthorDate: Fri Feb 24 10:43:33 2023 -0500

    Update occursIndex to skip absent elements
    
    The occursIndex function should return the current index of the element
    in the infoset. Daffodil was using the current array position as this
    value. When the emptyElementParsePolicy is set to treatAsAbsent, empty
    elements are not included in the final infoset and therefore the
    current array position may not match the index in the final infoset.
    
    This change introduces a new variable in the parser and unparser states
    for tracking the current occusIndex value which is not incremented when
    an absent element is seen.
    
    DAFFODIL-2791
---
 .../runtime1/SeparatedSequenceUnparsers.scala      | 59 +++++++++++++++-------
 .../runtime1/UnseparatedSequenceUnparsers.scala    | 13 +++--
 .../runtime1/debugger/InteractiveDebugger.scala    |  2 +-
 .../daffodil/runtime1/dpath/ArrayRelated.scala     |  7 ++-
 .../daffodil/runtime1/dpath/DPathRuntime.scala     |  2 +-
 .../apache/daffodil/runtime1/dpath/DState.scala    |  9 ++--
 .../apache/daffodil/runtime1/infoset/Infoset.scala |  1 -
 .../daffodil/runtime1/infoset/InfosetImpl.scala    |  1 -
 .../runtime1/processors/DataProcessor.scala        |  3 +-
 .../runtime1/processors/ProcessorStateBases.scala  | 15 ++++--
 .../daffodil/runtime1/processors/RuntimeData.scala |  2 +-
 .../parsers/InitiatedContentParsers.scala          |  8 +--
 .../runtime1/processors/parsers/PState.scala       | 34 +++++++++----
 .../processors/parsers/SeparatedParseHelper.scala  |  2 +-
 .../processors/parsers/SequenceChildBases.scala    |  8 +--
 .../processors/parsers/SequenceParserBases.scala   | 16 ++++--
 .../runtime1/processors/unparsers/UState.scala     | 35 +++++++++----
 .../apache/daffodil/usertests/TestSepTests.scala   |  4 +-
 18 files changed, 142 insertions(+), 79 deletions(-)

diff --git a/daffodil-runtime1-unparser/src/main/scala/org/apache/daffodil/unparsers/runtime1/SeparatedSequenceUnparsers.scala b/daffodil-runtime1-unparser/src/main/scala/org/apache/daffodil/unparsers/runtime1/SeparatedSequenceUnparsers.scala
index 1d5d0ee83..d98a9d008 100644
--- a/daffodil-runtime1-unparser/src/main/scala/org/apache/daffodil/unparsers/runtime1/SeparatedSequenceUnparsers.scala
+++ b/daffodil-runtime1-unparser/src/main/scala/org/apache/daffodil/unparsers/runtime1/SeparatedSequenceUnparsers.scala
@@ -111,7 +111,7 @@ class RepOrderedSeparatedSequenceChildUnparser(
   with Separated {
 
   override def checkArrayPosAgainstMaxOccurs(state: UState) =
-    state.arrayPos <= maxRepeats(state)
+    state.arrayIterationPos <= maxRepeats(state)
 }
 
 class OrderedSeparatedSequenceUnparser(
@@ -316,7 +316,8 @@ class OrderedSeparatedSequenceUnparser(
       val zlDetector = childUnparser.zeroLengthDetector
       childUnparser match {
         case unparser: RepOrderedSeparatedSequenceChildUnparser => {
-          state.arrayIndexStack.push(1L)
+          state.arrayIterationIndexStack.push(1L)
+          state.occursIndexStack.push(1L)
           val erd = unparser.erd
           var numOccurrences = 0
           val maxReps = unparser.maxRepeats(state)
@@ -357,8 +358,11 @@ class OrderedSeparatedSequenceUnparser(
                   // These are so we can check invariants on these stacks being
                   // pushed and popped reliably, and incremented only once
                   //
-                  val arrayIndexBefore = state.arrayPos
-                  val arrayIndexStackDepthBefore = state.arrayIndexStack.length
+                  val arrayIterationIndexBefore = state.arrayIterationPos
+                  val arrayIterationIndexStackDepthBefore =
+                    state.arrayIterationIndexStack.length
+                  val occursIndexBefore = state.occursPos
+                  val occursIndexStackDepthBefore = state.occursIndexStack.length
                   val groupIndexBefore = state.groupPos
                   val groupIndexStackDepthBefore = state.groupIndexStack.length
 
@@ -388,9 +392,15 @@ class OrderedSeparatedSequenceUnparser(
                     )
                   }
                   numOccurrences += 1
-                  Assert.invariant(state.arrayIndexStack.length == arrayIndexStackDepthBefore)
-                  state.moveOverOneArrayIndexOnly()
-                  Assert.invariant(state.arrayPos == arrayIndexBefore + 1)
+                  Assert.invariant(
+                    state.arrayIterationIndexStack.length == arrayIterationIndexStackDepthBefore,
+                  )
+                  state.moveOverOneArrayIterationIndexOnly()
+                  Assert.invariant(state.arrayIterationPos == arrayIterationIndexBefore + 1)
+
+                  Assert.invariant(state.occursIndexStack.length == occursIndexStackDepthBefore)
+                  state.moveOverOneOccursIndexOnly()
+                  Assert.invariant(state.occursPos == occursIndexBefore + 1)
 
                   Assert.invariant(state.groupIndexStack.length == groupIndexStackDepthBefore)
                   state.moveOverOneGroupIndexOnly() // array elements are always represented.
@@ -413,7 +423,7 @@ class OrderedSeparatedSequenceUnparser(
                   unparser,
                   numOccurrences,
                   maxReps,
-                  state.arrayPos - 1,
+                  state.arrayIterationPos - 1,
                 )
                 unparser.endArrayOrOptional(erd, state)
               } else {
@@ -467,7 +477,8 @@ class OrderedSeparatedSequenceUnparser(
             // no event (state.inspect returned false)
             Assert.invariantFailed("No event for unparsing.")
           }
-          state.arrayIndexStack.pop()
+          state.arrayIterationIndexStack.pop()
+          state.occursIndexStack.pop()
         }
         case scalarUnparser =>
           trd match {
@@ -564,7 +575,8 @@ class OrderedSeparatedSequenceUnparser(
               trailingSuspendedOps,
               onlySeparatorFlag = true,
             )
-            state.moveOverOneArrayIndexOnly()
+            state.moveOverOneArrayIterationIndexOnly()
+            state.moveOverOneOccursIndexOnly()
             state.moveOverOneGroupIndexOnly()
             numOccurrences += 1
           }
@@ -595,7 +607,8 @@ class OrderedSeparatedSequenceUnparser(
       //
       childUnparser match {
         case unparser: RepOrderedSeparatedSequenceChildUnparser => {
-          state.arrayIndexStack.push(1L)
+          state.arrayIterationIndexStack.push(1L)
+          state.occursIndexStack.push(1L)
           val erd = unparser.erd
           var numOccurrences = 0
           val maxReps = unparser.maxRepeats(state)
@@ -628,8 +641,11 @@ class OrderedSeparatedSequenceUnparser(
                   // These are so we can check invariants on these stacks being
                   // pushed and popped reliably, and incremented only once
                   //
-                  val arrayIndexBefore = state.arrayPos
-                  val arrayIndexStackDepthBefore = state.arrayIndexStack.length
+                  val arrayIterationIndexBefore = state.arrayIterationPos
+                  val arrayIterationIndexStackDepthBefore =
+                    state.arrayIterationIndexStack.length
+                  val occursIndexBefore = state.occursPos
+                  val occursIndexStackDepthBefore = state.occursIndexStack.length
                   val groupIndexBefore = state.groupPos
                   val groupIndexStackDepthBefore = state.groupIndexStack.length
 
@@ -643,9 +659,15 @@ class OrderedSeparatedSequenceUnparser(
 
                   unparseOne(unparser, erd, state)
                   numOccurrences += 1
-                  Assert.invariant(state.arrayIndexStack.length == arrayIndexStackDepthBefore)
-                  state.moveOverOneArrayIndexOnly()
-                  Assert.invariant(state.arrayPos == arrayIndexBefore + 1)
+                  Assert.invariant(
+                    state.arrayIterationIndexStack.length == arrayIterationIndexStackDepthBefore,
+                  )
+                  state.moveOverOneArrayIterationIndexOnly()
+                  Assert.invariant(state.arrayIterationPos == arrayIterationIndexBefore + 1)
+
+                  Assert.invariant(state.occursIndexStack.length == occursIndexStackDepthBefore)
+                  state.moveOverOneOccursIndexOnly()
+                  Assert.invariant(state.occursPos == occursIndexBefore + 1)
 
                   Assert.invariant(state.groupIndexStack.length == groupIndexStackDepthBefore)
                   state.moveOverOneGroupIndexOnly() // array elements are always represented.
@@ -670,7 +692,7 @@ class OrderedSeparatedSequenceUnparser(
                   unparser,
                   numOccurrences,
                   maxReps,
-                  state.arrayPos - 1,
+                  state.arrayIterationPos - 1,
                 )
                 unparser.endArrayOrOptional(erd, state)
               } else {
@@ -704,7 +726,8 @@ class OrderedSeparatedSequenceUnparser(
             // no event (state.inspect returned false)
             Assert.invariantFailed("No event for unparsing.")
           }
-          state.arrayIndexStack.pop()
+          state.arrayIterationIndexStack.pop()
+          state.occursIndexStack.pop()
         }
         case scalarUnparser => {
           unparseOne(scalarUnparser, trd, state)
diff --git a/daffodil-runtime1-unparser/src/main/scala/org/apache/daffodil/unparsers/runtime1/UnseparatedSequenceUnparsers.scala b/daffodil-runtime1-unparser/src/main/scala/org/apache/daffodil/unparsers/runtime1/UnseparatedSequenceUnparsers.scala
index 4b534ac9e..903ae07a6 100644
--- a/daffodil-runtime1-unparser/src/main/scala/org/apache/daffodil/unparsers/runtime1/UnseparatedSequenceUnparsers.scala
+++ b/daffodil-runtime1-unparser/src/main/scala/org/apache/daffodil/unparsers/runtime1/UnseparatedSequenceUnparsers.scala
@@ -46,7 +46,7 @@ class RepOrderedUnseparatedSequenceChildUnparser(
 
   override def checkArrayPosAgainstMaxOccurs(state: UState): Boolean = {
     if (ock eq OccursCountKind.Implicit)
-      state.arrayPos <= maxRepeats(state)
+      state.arrayIterationPos <= maxRepeats(state)
     else
       true
   }
@@ -92,7 +92,8 @@ class OrderedUnseparatedSequenceUnparser(
       //
       childUnparser match {
         case unparser: RepeatingChildUnparser => {
-          state.arrayIndexStack.push(1L)
+          state.arrayIterationIndexStack.push(1L)
+          state.occursIndexStack.push(1L)
           val erd = unparser.erd
           var numOccurrences = 0
           val maxReps = unparser.maxRepeats(state)
@@ -127,7 +128,8 @@ class OrderedUnseparatedSequenceUnparser(
 
                   unparseOne(unparser, erd, state)
                   numOccurrences += 1
-                  state.moveOverOneArrayIndexOnly()
+                  state.moveOverOneArrayIterationIndexOnly()
+                  state.moveOverOneOccursIndexOnly()
                   state.moveOverOneGroupIndexOnly() // array elements are always represented.
 
                   if (isArr)
@@ -140,7 +142,7 @@ class OrderedUnseparatedSequenceUnparser(
                   unparser,
                   numOccurrences,
                   maxReps,
-                  state.arrayPos - 1,
+                  state.arrayIterationPos - 1,
                 )
                 unparser.endArrayOrOptional(erd, state)
               } else {
@@ -179,7 +181,8 @@ class OrderedUnseparatedSequenceUnparser(
             Assert.invariantFailed("No event for unparing.")
           }
 
-          state.arrayIndexStack.pop()
+          state.arrayIterationIndexStack.pop()
+          state.occursIndexStack.pop()
         }
         //
         case scalarUnparser => {
diff --git a/daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/debugger/InteractiveDebugger.scala b/daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/debugger/InteractiveDebugger.scala
index 06bec11b3..0935a94c8 100644
--- a/daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/debugger/InteractiveDebugger.scala
+++ b/daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/debugger/InteractiveDebugger.scala
@@ -1837,7 +1837,7 @@ class InteractiveDebugger(
         val longDesc = desc
 
         def getSomeValue(state: StateForDebugger): Option[Long] = {
-          if (state.arrayPos != -1) Some(state.arrayPos) else None
+          if (state.occursPos != -1) Some(state.occursPos) else None
         }
       }
 
diff --git a/daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/dpath/ArrayRelated.scala b/daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/dpath/ArrayRelated.scala
index 6c33f903e..26a0029fb 100644
--- a/daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/dpath/ArrayRelated.scala
+++ b/daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/dpath/ArrayRelated.scala
@@ -17,7 +17,6 @@
 
 package org.apache.daffodil.runtime1.dpath
 
-import org.apache.daffodil.lib.exceptions.Assert
 import org.apache.daffodil.lib.util.Maybe.Nope
 import org.apache.daffodil.runtime1.infoset.InfosetNoInfosetException
 
@@ -79,9 +78,9 @@ case object FNExactlyOne extends RecipeOp {
 
 case object DFDLOccursIndex extends RecipeOp {
   override def run(dstate: DState): Unit = {
-    Assert.invariant(dstate.arrayPos >= 1)
-    if (dstate.isCompile)
+    if (dstate.isCompile) {
       throw new InfosetNoInfosetException(Nope)
-    dstate.setCurrentValue(dstate.arrayPos)
+    }
+    dstate.setCurrentValue(dstate.occursIndex)
   }
 }
diff --git a/daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/dpath/DPathRuntime.scala b/daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/dpath/DPathRuntime.scala
index 8123ab010..de4443e5c 100644
--- a/daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/dpath/DPathRuntime.scala
+++ b/daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/dpath/DPathRuntime.scala
@@ -63,7 +63,7 @@ class CompiledDPath(val ops: RecipeOp*) extends Serializable {
       dstate.setContextNode(state.thisElement.asInstanceOf[DINode]) // used for diagnostics
     }
 
-    dstate.setArrayPos(state.arrayPos)
+    dstate.setOccursIndex(state.occursPos)
     dstate.setErrorOrWarn(state)
     dstate.resetValue
     dstate.isCompile = state match {
diff --git a/daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/dpath/DState.scala b/daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/dpath/DState.scala
index 61260991a..d8e6719fc 100644
--- a/daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/dpath/DState.scala
+++ b/daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/dpath/DState.scala
@@ -367,10 +367,10 @@ case class DState(
     _savesErrorsAndWarnings = One(s)
   }
 
-  private var _arrayPos: Long = -1L // init to -1L so that we must set before use.
-  def arrayPos = _arrayPos
-  def setArrayPos(arrayPos1b: Long): Unit = {
-    _arrayPos = arrayPos1b
+  private var _occursIndex: Long = -1
+  def occursIndex = _occursIndex
+  def setOccursIndex(index: Long): Unit = {
+    _occursIndex = index
   }
 
   def SDE(formatString: String, args: Any*) = {
@@ -450,7 +450,6 @@ class DStateForConstantFolding(
   override def runtimeData = die
   override def selfMove() = die
   override def fnExists() = die
-  override def arrayPos = die
   override def arrayLength = die
   override val typeCalculators = compileInfo.typeCalcMap
 
diff --git a/daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/infoset/Infoset.scala b/daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/infoset/Infoset.scala
index 29806a76c..85ff6ae6a 100644
--- a/daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/infoset/Infoset.scala
+++ b/daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/infoset/Infoset.scala
@@ -31,7 +31,6 @@ trait InfosetArray {
   def append(ie: InfosetElement): Unit
   def getOccurrence(occursIndex: Long): InfosetElement
   def length: Long
-
 }
 
 trait InfosetElement extends InfosetItem {
diff --git a/daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/infoset/InfosetImpl.scala b/daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/infoset/InfosetImpl.scala
index 3ea88c5f4..486225df4 100644
--- a/daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/infoset/InfosetImpl.scala
+++ b/daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/infoset/InfosetImpl.scala
@@ -23,7 +23,6 @@ import java.math.{ BigDecimal => JBigDecimal }
 import java.util.HashMap
 import java.util.concurrent.atomic.AtomicInteger
 import java.util.concurrent.atomic.AtomicLong
-import scala.collection.IndexedSeq
 import scala.collection.mutable.ArrayBuffer
 
 import org.apache.daffodil.io.DataOutputStream
diff --git a/daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/processors/DataProcessor.scala b/daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/processors/DataProcessor.scala
index b2c8180b4..4f5d27a22 100644
--- a/daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/processors/DataProcessor.scala
+++ b/daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/processors/DataProcessor.scala
@@ -587,7 +587,8 @@ class DataProcessor(
     state.setProcessor(rootUnparser)
 
     // Verify that all stacks are empty
-    Assert.invariant(state.arrayIndexStack.length == 1)
+    Assert.invariant(state.arrayIterationIndexStack.length == 1)
+    Assert.invariant(state.occursIndexStack.length == 1)
     Assert.invariant(state.groupIndexStack.length == 1)
     Assert.invariant(state.childIndexStack.length == 1)
     Assert.invariant(state.currentInfosetNodeMaybe.isEmpty)
diff --git a/daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/processors/ProcessorStateBases.scala b/daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/processors/ProcessorStateBases.scala
index ec2dd340e..5d8639541 100644
--- a/daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/processors/ProcessorStateBases.scala
+++ b/daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/processors/ProcessorStateBases.scala
@@ -70,7 +70,8 @@ trait StateForDebugger {
   def bitLimit0b: MaybeULong
   def childPos: Long
   def groupPos: Long
-  def arrayPos: Long
+  def arrayIterationPos: Long
+  def occursPos: Long
   def variableMapForDebugger: VariableMap
   def delimitedParseResult: Maybe[dfa.ParseResult]
   def withinHiddenNest: Boolean
@@ -83,7 +84,8 @@ case class TupleForDebugger(
   val bitLimit0b: MaybeULong,
   val childPos: Long,
   val groupPos: Long,
-  val arrayPos: Long,
+  val arrayIterationPos: Long,
+  val occursPos: Long,
   val variableMapForDebugger: VariableMap,
   val delimitedParseResult: Maybe[dfa.ParseResult],
   val withinHiddenNest: Boolean,
@@ -473,7 +475,8 @@ abstract class ParseOrUnparseState protected (
       bitLimit0b,
       childPos,
       groupPos,
-      arrayPos,
+      arrayIterationPos,
+      occursPos,
       variableMap.copy(), // deep copy since variableMap is mutable
       delimitedParseResult,
       withinHiddenNest,
@@ -506,7 +509,8 @@ abstract class ParseOrUnparseState protected (
   final def bytePos = bytePos0b
 
   def groupPos: Long
-  def arrayPos: Long
+  def arrayIterationPos: Long
+  def occursPos: Long
   def childPos: Long
 
   def hasInfoset: Boolean
@@ -604,7 +608,8 @@ final class CompileState(
   tunable: DaffodilTunables,
 ) extends ParseOrUnparseState(tci.variableMap, Nil, maybeDataProc, tunable) {
 
-  def arrayPos: Long = 1L
+  def arrayIterationPos: Long = 1L
+  def occursPos: Long = 1L
   def bitLimit0b: MaybeULong = MaybeULong.Nope
   def bitPos0b: Long = 0L
   def childPos: Long = 0L
diff --git a/daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/processors/RuntimeData.scala b/daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/processors/RuntimeData.scala
index 369f7c2fa..30cd8ac71 100644
--- a/daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/processors/RuntimeData.scala
+++ b/daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/processors/RuntimeData.scala
@@ -390,7 +390,7 @@ final class SimpleTypeRuntimeData(
       }
     }
 
-    // Note: dont check occurs counts // if(!checkMinMaxOccurs(e, pstate.arrayPos)) { return java.lang.Boolean.FALSE }
+    // Note: dont check occurs counts // if(!checkMinMaxOccurs(e, pstate.arrayIterationPos)) { return java.lang.Boolean.FALSE }
     OK
   }
 
diff --git a/daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/processors/parsers/InitiatedContentParsers.scala b/daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/processors/parsers/InitiatedContentParsers.scala
index adbaebb85..36657b1b9 100644
--- a/daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/processors/parsers/InitiatedContentParsers.scala
+++ b/daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/processors/parsers/InitiatedContentParsers.scala
@@ -27,7 +27,7 @@ final class InitiatedContentDiscrimOnIndexGreaterThanMinParser(
   override lazy val runtimeDependencies = Vector()
 
   final def parse(start: PState): Unit = {
-    if (start.arrayPos > min)
+    if (start.arrayIterationPos > min)
       start.resolvePointOfUncertainty()
   }
 }
@@ -47,7 +47,7 @@ final class InitiatedContentDiscrimChoiceOnlyOnFirstIndexParser(
   override lazy val runtimeDependencies = Vector()
 
   final def parse(start: PState): Unit = {
-    if (start.arrayPos == 1)
+    if (start.arrayIterationPos == 1)
       start.resolvePointOfUncertainty()
   }
 }
@@ -71,7 +71,7 @@ final class InitiatedContentDiscrimChoiceAndIndexGreaterThanMinParser(
     // branch and not to try any other branches if something in the array
     // fails.
 
-    if (start.arrayPos > min) {
+    if (start.arrayIterationPos > min) {
       // resolve the point of uncertainty associated with array elements once
       // we have parsed the required number of min occurrences. There should
       // not be a point of uncertainty associated with this array until we have
@@ -79,7 +79,7 @@ final class InitiatedContentDiscrimChoiceAndIndexGreaterThanMinParser(
       start.resolvePointOfUncertainty()
     }
 
-    if (start.arrayPos == 1) {
+    if (start.arrayIterationPos == 1) {
       // resolve the point of uncertainty associated with the initiated content
       // choice so we do not attempt to parse other branches if something fails
       start.resolvePointOfUncertainty()
diff --git a/daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/processors/parsers/PState.scala b/daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/processors/parsers/PState.scala
index 59d9a2e08..adf085ff1 100644
--- a/daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/processors/parsers/PState.scala
+++ b/daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/processors/parsers/PState.scala
@@ -78,7 +78,8 @@ object MPState {
 
   final class Mark {
 
-    var arrayIndexStackMark: MStack.Mark = _
+    var arrayIterationIndexStackMark: MStack.Mark = _
+    var occursIndexStackMark: MStack.Mark = _
     var groupIndexStackMark: MStack.Mark = _
     var childIndexStackMark: MStack.Mark = _
     var occursBoundsStackMark: MStack.Mark = _
@@ -86,19 +87,23 @@ object MPState {
     clear()
 
     def clear(): Unit = {
-      arrayIndexStackMark = MStack.nullMark
+      arrayIterationIndexStackMark = MStack.nullMark
+      occursIndexStackMark = MStack.nullMark
       groupIndexStackMark = MStack.nullMark
       childIndexStackMark = MStack.nullMark
       occursBoundsStackMark = MStack.nullMark
     }
 
     def captureFrom(mp: MPState): Unit = {
-      arrayIndexStackMark = mp.arrayIndexStack.mark
+      arrayIterationIndexStackMark = mp.arrayIterationIndexStack.mark
+      occursIndexStackMark = mp.occursIndexStack.mark
       groupIndexStackMark = mp.groupIndexStack.mark
       childIndexStackMark = mp.childIndexStack.mark
     }
+
     def restoreInto(mp: MPState): Unit = {
-      mp.arrayIndexStack.reset(this.arrayIndexStackMark)
+      mp.arrayIterationIndexStack.reset(this.arrayIterationIndexStackMark)
+      mp.occursIndexStack.reset(this.occursIndexStackMark)
       mp.groupIndexStack.reset(this.groupIndexStackMark)
       mp.childIndexStack.reset(this.childIndexStackMark)
     }
@@ -107,12 +112,16 @@ object MPState {
 
 class MPState private () {
 
-  val arrayIndexStack = MStackOfLong()
+  val arrayIterationIndexStack = MStackOfLong()
+  val occursIndexStack = MStackOfLong()
+
+  def moveOverOneArrayIterationIndexOnly() =
+    arrayIterationIndexStack.push(arrayIterationIndexStack.pop + 1)
 
-  def moveOverOneArrayIndexOnly() = arrayIndexStack.push(arrayIndexStack.pop + 1)
-  def moveBackOneArrayIndexOnly() = arrayIndexStack.push(arrayIndexStack.pop - 1)
+  def arrayIterationPos = arrayIterationIndexStack.top
 
-  def arrayPos = arrayIndexStack.top
+  def moveOverOneOccursIndexOnly() = occursIndexStack.push(occursIndexStack.pop + 1)
+  def occursPos = occursIndexStack.top
 
   val groupIndexStack = MStackOfLong()
   def moveOverOneGroupIndexOnly() = groupIndexStack.push(groupIndexStack.pop + 1)
@@ -134,7 +143,8 @@ class MPState private () {
   val escapeSchemeEVCache = new MStackOfMaybe[EscapeSchemeParserHelper]
 
   private def init(): Unit = {
-    arrayIndexStack.push(1L)
+    arrayIterationIndexStack.push(1L)
+    occursIndexStack.push(1L)
     groupIndexStack.push(1L)
     childIndexStack.push(1L)
     delimitersLocalIndexStack.push(-1)
@@ -143,7 +153,8 @@ class MPState private () {
   def verifyFinalState(): Unit = {
     // The current values of the top of these stacks might have
     // changed, but the fact that they are just 1 deep should be restored.
-    Assert.invariant(arrayIndexStack.length == 1)
+    Assert.invariant(arrayIterationIndexStack.length == 1)
+    Assert.invariant(occursIndexStack.length == 1)
     Assert.invariant(groupIndexStack.length == 1)
     Assert.invariant(childIndexStack.length == 1)
     Assert.invariant(delimitersLocalIndexStack.length == 1)
@@ -230,7 +241,8 @@ final class PState private (
   def thisElement = infoset
 
   override def groupPos = mpstate.groupPos
-  override def arrayPos = mpstate.arrayPos
+  override def arrayIterationPos = mpstate.arrayIterationPos
+  override def occursPos = mpstate.occursPos
   override def childPos = mpstate.childPos
 
   private val markPool = new PState.MarkPool
diff --git a/daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/processors/parsers/SeparatedParseHelper.scala b/daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/processors/parsers/SeparatedParseHelper.scala
index 5010725c1..b120d5463 100644
--- a/daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/processors/parsers/SeparatedParseHelper.scala
+++ b/daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/processors/parsers/SeparatedParseHelper.scala
@@ -58,7 +58,7 @@ sealed abstract class SeparatorParseHelper(
           pstate,
           "Failed to populate %s[%s]. Missing %s separator. Cause: %s",
           erd.prefixedName,
-          pstate.mpstate.arrayPos,
+          pstate.mpstate.arrayIterationPos,
           kind,
           cause,
         )
diff --git a/daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/processors/parsers/SequenceChildBases.scala b/daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/processors/parsers/SequenceChildBases.scala
index 5abe0b5a3..85323885c 100644
--- a/daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/processors/parsers/SequenceChildBases.scala
+++ b/daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/processors/parsers/SequenceChildBases.scala
@@ -291,7 +291,7 @@ abstract class RepeatingChildParser(
   def arrayIndexStatus(minRepeats: Long, maxRepeats: Long, pstate: PState): ArrayIndexStatus = {
     import ArrayIndexStatus._
     Assert.invariant(pstate.processorStatus eq Success)
-    val apos = pstate.arrayPos
+    val apos = pstate.arrayIterationPos
     val result: ArrayIndexStatus =
       if (apos <= minRepeats)
         Required
@@ -331,7 +331,8 @@ abstract class RepeatingChildParser(
    * there can be more than 1 occurrence.
    */
   def startArray(state: PState): Unit = {
-    state.mpstate.arrayIndexStack.push(1L) // one-based indexing
+    state.mpstate.arrayIterationIndexStack.push(1L) // one-based indexing
+    state.mpstate.occursIndexStack.push(1L)
   }
 
   /**
@@ -344,7 +345,8 @@ abstract class RepeatingChildParser(
    * by way of index: e.g., fn:exists( optElement[dfdl:currentIndex()]  )
    */
   def endArray(state: PState): Unit = {
-    state.mpstate.arrayIndexStack.pop()
+    state.mpstate.arrayIterationIndexStack.pop()
+    state.mpstate.occursIndexStack.pop()
     super.endArray(state)
   }
 
diff --git a/daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/processors/parsers/SequenceParserBases.scala b/daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/processors/parsers/SequenceParserBases.scala
index 8ad505d5a..59c741c5e 100644
--- a/daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/processors/parsers/SequenceParserBases.scala
+++ b/daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/processors/parsers/SequenceParserBases.scala
@@ -46,7 +46,7 @@ abstract class SequenceParserBase(
   import ParseAttemptStatus._
 
   final protected def checkN(pstate: PState, childParser: SequenceChildParser): Unit = {
-    if (pstate.arrayPos > pstate.tunable.maxOccursBounds) {
+    if (pstate.arrayIterationPos > pstate.tunable.maxOccursBounds) {
       throw new TunableLimitExceededError(
         childParser.trd.schemaFileLocation,
         "Array occurrences excceeds the maxOccursBounds tunable limit of %s",
@@ -187,11 +187,17 @@ abstract class SequenceParserBase(
               // advance array position.
               // Done unconditionally, as some failures get converted into successes
               //
-              // If ultimately this is a real failure, then mothing cares about this, it is
-              // about to get poppped/cleared anyway.
+              // If ultimately this is a real failure, then nothing cares about this, it is
+              // about to get popped/cleared anyway.
               //
               if (ais ne Done) {
-                pstate.mpstate.moveOverOneArrayIndexOnly()
+                pstate.mpstate.moveOverOneArrayIterationIndexOnly()
+
+                // If the emptyElementParsePolicy is set to treatAsAbsent, don't
+                // increment the occursIndex if the element is absent
+                if (resultOfTry != AbsentRep) {
+                  pstate.mpstate.moveOverOneOccursIndexOnly()
+                }
               }
 
               if (
@@ -509,7 +515,7 @@ abstract class SequenceParserBase(
                 pstate,
                 "Failed to populate %s[%s]. Cause: %s",
                 erd.prefixedName,
-                pstate.mpstate.arrayPos,
+                pstate.mpstate.arrayIterationPos,
                 cause,
               )
             }
diff --git a/daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/processors/unparsers/UState.scala b/daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/processors/unparsers/UState.scala
index 60726198d..fe99f1659 100644
--- a/daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/processors/unparsers/UState.scala
+++ b/daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/processors/unparsers/UState.scala
@@ -145,10 +145,12 @@ abstract class UState(
   def popDelimiters(): Unit
 
   def currentInfosetNodeStack: MStackOfMaybe[DINode]
-  def arrayIndexStack: MStackOfLong
+  def arrayIterationIndexStack: MStackOfLong
+  def occursIndexStack: MStackOfLong
   def childIndexStack: MStackOfLong
   def groupIndexStack: MStackOfLong
-  def moveOverOneArrayIndexOnly(): Unit
+  def moveOverOneArrayIterationIndexOnly(): Unit
+  def moveOverOneOccursIndexOnly(): Unit
   def moveOverOneGroupIndexOnly(): Unit
   def moveOverOneElementChildOnly(): Unit
 
@@ -166,7 +168,7 @@ abstract class UState(
     Assert.invariant(Maybe.WithNulls.isDefined(currentInfosetNode))
     currentInfosetNode match {
       case a: DIArray => {
-        a.getOccurrence(arrayPos)
+        a.getOccurrence(arrayIterationPos)
       }
       case e: DIElement => thisElement
     }
@@ -413,6 +415,7 @@ final class UStateForSuspension(
   override var dataOutputStream: DirectOrBufferedDataOutputStream,
   vbox: VariableBox,
   override val currentInfosetNode: DINode,
+  arrayIterationIndex: Long,
   occursIndex: Long,
   escapeSchemeEVCacheMaybe: Maybe[MStackOfMaybe[EscapeSchemeUnparserHelper]],
   delimiterStackMaybe: Maybe[MStackOf[DelimiterStackUnparseNode]],
@@ -447,8 +450,10 @@ final class UStateForSuspension(
   override def advanceOrError = die
   override def isInspectArrayEnd = die
   override def currentInfosetNodeStack = die
-  override def arrayIndexStack = die
-  override def moveOverOneArrayIndexOnly() = die
+  override def arrayIterationIndexStack = die
+  override def moveOverOneArrayIterationIndexOnly() = die
+  override def occursIndexStack = die
+  override def moveOverOneOccursIndexOnly() = die
   override def groupIndexStack = die
   override def moveOverOneGroupIndexOnly() = die
   override def childIndexStack = die
@@ -459,7 +464,8 @@ final class UStateForSuspension(
 
   override def groupPos = 0 // was die, but this is called when copying state during debugging
   override def currentInfosetNodeMaybe = Maybe(currentInfosetNode)
-  override def arrayPos = occursIndex
+  override def arrayIterationPos = arrayIterationIndex
+  override def occursPos = occursIndex
   override def childPos = 0 // was die, but this is called when copying state during debugging.
 
   override def localDelimiters = delimiterStackMaybe.get.top
@@ -559,7 +565,8 @@ final class UStateMain private (
       suspendedDOS,
       variableBox.cloneForSuspension,
       currentInfosetNodeStack.top.get, // only need the to of the stack, not the whole thing
-      arrayIndexStack.top, // only need the top of the stack, not the whole thing
+      arrayIterationIndexStack.top, // only need the top of the stack, not the whole thing
+      occursIndexStack.top,
       es,
       ds,
       tunable,
@@ -647,10 +654,16 @@ final class UStateMain private (
 
   override val currentInfosetNodeStack = new MStackOfMaybe[DINode]
 
-  override val arrayIndexStack = MStackOfLong()
-  arrayIndexStack.push(1L)
-  override def moveOverOneArrayIndexOnly() = arrayIndexStack.push(arrayIndexStack.pop + 1)
-  override def arrayPos = arrayIndexStack.top
+  override val arrayIterationIndexStack = MStackOfLong()
+  arrayIterationIndexStack.push(1L)
+  override def moveOverOneArrayIterationIndexOnly() =
+    arrayIterationIndexStack.push(arrayIterationIndexStack.pop + 1)
+  override def arrayIterationPos = arrayIterationIndexStack.top
+
+  override val occursIndexStack = MStackOfLong()
+  occursIndexStack.push(1L)
+  override def moveOverOneOccursIndexOnly() = occursIndexStack.push(occursIndexStack.pop + 1)
+  override def occursPos = occursIndexStack.top
 
   override val groupIndexStack = MStackOfLong()
   groupIndexStack.push(1L)
diff --git a/daffodil-test/src/test/scala/org/apache/daffodil/usertests/TestSepTests.scala b/daffodil-test/src/test/scala/org/apache/daffodil/usertests/TestSepTests.scala
index 092465ca7..fbc82c851 100644
--- a/daffodil-test/src/test/scala/org/apache/daffodil/usertests/TestSepTests.scala
+++ b/daffodil-test/src/test/scala/org/apache/daffodil/usertests/TestSepTests.scala
@@ -70,6 +70,8 @@ class TestSepTests {
   @Test def test_sep_ssp_never_5(): Unit = { runner.runOneTest("test_sep_ssp_never_5") }
 
   // DAFFODIL-2791
-  // @Test def test_treatAsMissing_occursIndex(): Unit = { runner.runOneTest("test_treatAsMissing_occursIndex") }
+  @Test def test_treatAsMissing_occursIndex(): Unit = {
+    runner.runOneTest("test_treatAsMissing_occursIndex")
+  }
 
 }