From 74347a32cb378d5614d8ce1d6333bd8a28caa05d Mon Sep 17 00:00:00 2001 From: olabusayoT <50379531+olabusayoT@users.noreply.github.com> Date: Tue, 9 Jun 2026 10:50:34 -0400 Subject: [PATCH 1/2] Fix postfix-sep handling for optional elements with discriminators MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Primary bug (seq2): in a postfix-separated anyEmpty sequence, when an optional complex element contains a dfdl:discriminator that evaluates to true (committing to that element) and the child parses successfully, but the postfix separator is subsequently not found, Daffodil aborted with an internal Assert.invariant failure instead of producing a clean "Failed to find postfix separator" parse error. The discriminator had already resolved the outer PoU, so the subsequent unconditional Assert.invariant(!pstate.isPointOfUncertaintyResolved(pou)) in the child-successful+sep-failed path fired. This was fixed by checking isPointOfUncertaintyResolved before deciding to resetToPointOfUncertainty (undo the entire parse attempt since the mark was created) vs resolvePointOfUncertainty (commit to whatever was just parsed, just clean up the checkpoint). * Related bug (seq3): in the same postfix+anyEmpty configuration, an optional simple-delimited element with zero-length content followed by its separator which should be AbsentRep per DFDL spec 9.2.4 was not recognized as absent, causing the sequence to stop prematurely instead of continuing to the next child. Two necessary fixes: - PostfixSeparatorHelper's child-failed+sep-found path called computeParseAttemptStatus with the post-separator bit position, making the isZL flag incorrect. Switch to computeFailedParseAttemptStatus with hasZLChildAttempt (captured before sep.parse1). A second Assert.invariant in the isSimpleDelimited guard of the same path is replaced by folding the PoU-resolved check into the condition. Then promote MissingItem to AbsentRep for non-positional optional elements per the spec. - The outer scalar loop's AbsentRep case unconditionally set isDone=true. Check Separated.isPositional, and for non-positional (anyEmpty) elements we call moveOverOneGroupIndexOnly and continue, while for positional elements we keep the existing trailing-suppression isDone=true behavior. * removed an erroneous setBitPos0b(currentPos) from the MissingItem+optional+pou branch in parseOneInstanceWithMaybePoU that incorrectly skipped past a prefix/infix separator needed by the next element. * added tests seq2–seq7: discriminator+missing-sep error, absent-then-present non-positional, required-element-absent error, positional trailingEmpty isDone path, both-absent non-positional, and present-then-absent non-positional. DAFFODIL-3085 --- .../parsers/SeparatedParseHelper.scala | 47 +++-- .../parsers/SequenceParserBases.scala | 33 +-- .../sequence_groups/SequenceGroup.tdml | 195 ++++++++++++++++++ .../sequence_groups/TestSequenceGroups.scala | 8 + 4 files changed, 246 insertions(+), 37 deletions(-) diff --git a/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/processors/parsers/SeparatedParseHelper.scala b/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/processors/parsers/SeparatedParseHelper.scala index 6919cb369e..578c585ec2 100644 --- a/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/processors/parsers/SeparatedParseHelper.scala +++ b/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/processors/parsers/SeparatedParseHelper.scala @@ -185,7 +185,6 @@ final class PostfixSeparatorHelper( scParserArg: Separated, isSimpleDelimited: Boolean ) extends SeparatorParseHelper(sep, child, scParserArg) { - import ParseAttemptStatus.* override def parseOneWithSeparator( pstate: PState, @@ -227,8 +226,13 @@ final class PostfixSeparatorHelper( // usually (except first one in infix case) don't run the child parser val failure = pstate.processorStatus.asInstanceOf[Failure] - Assert.invariant(!pstate.isPointOfUncertaintyResolved(pou)) - pstate.resetToPointOfUncertainty(pou) + if (!pstate.isPointOfUncertaintyResolved(pou)) { + pstate.resetToPointOfUncertainty(pou) + } else { + // pou was resolved by a discriminator somewhere, so + // we need to resolve the outer POU here + pstate.resolvePointOfUncertainty() + } pstate.setFailed(failure.cause) failedSeparator(pstate, "postfix") @@ -257,33 +261,34 @@ final class PostfixSeparatorHelper( // If the type is simple, and lengthKind 'delimited' we can proceed. // Otherwise we punt and call it missing. // - if (isSimpleDelimited) { + if (isSimpleDelimited && !pstate.isPointOfUncertaintyResolved(pou)) { val failure = pstate.processorStatus.asInstanceOf[Failure] - Assert.invariant(!pstate.isPointOfUncertaintyResolved(pou)) pstate.resetToPointOfUncertainty(pou) sep.parse1(pstate) if (pstate.isSuccess) { - val posAfterSep = pstate.bitPos0b - // failed child, but success on postfix sep. - // behave here just like a prefix separator that was then followed by a child failure + // child failed, but postfix sep found immediately after reset + // marking this as ZL content. + // Use hasZLChildAttempt (captured before sep.parse1) so isZL reflects + // the child bit position, not the post-separator position. pstate.setFailed(failure.cause) - val pas = - prh.computeParseAttemptStatus( - scParser, - prevBitPosBeforeChild, - pstate, - requiredOptional - ) - val res = pas match { - case AbsentRep => { - pstate.dataInputStream.setBitPos0b(posAfterSep) - MissingSeparator - } + val pas = prh.computeFailedParseAttemptStatus( + scParser, + prevBitPosBeforeChild, + pstate, + hasZLChildAttempt, + requiredOptional + ) + // For non-positional (anyEmpty) sequences, the child result helper returns + // MissingItem for optional ZL elements, but since the postfix separator WAS + // found this is AbsentRep per DFDL spec 9.2.4. + pas match { + case ParseAttemptStatus.MissingItem + if requiredOptional.isInstanceOf[RequiredOptionalStatus.Optional] => + ParseAttemptStatus.AbsentRep case _ => pas } - res } else { // the separator failed on ZL data // so no chance on a ZL representation here. diff --git a/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/processors/parsers/SequenceParserBases.scala b/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/processors/parsers/SequenceParserBases.scala index a0366cf812..509b88aaf1 100644 --- a/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/processors/parsers/SequenceParserBases.scala +++ b/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/processors/parsers/SequenceParserBases.scala @@ -258,22 +258,23 @@ abstract class SequenceParserBase( resultOfTry = nextResultOfTry resultOfTry match { case AbsentRep => { - // a scalar element, or a model group is absent. That means no separator - // was found for it. - // - // That means were at the end of the representation of this sequence, - // This is only returned as resultOfTry if it is - // OK for us to act on it. I.e., we know that the situation is - // Positional trailing, with a group that can have zero-length representation. - // and no separator was found for it. - // - // So we mask the failure, and exit the sequence successfully - pstate.setSuccess() - isDone = true - // If we're masking the failure, we don't want the error dianostics - // to flow up. Restore the diagnostics from before the parse - // attempt - pstate.diagnostics = diagnosticsBeforeAttempt + // A scalar element is absent. For positional trailing, this means no + // separator was found, so we mask the failure and exit the sequence + // successfully. For non-positional separated sequences (e.g., anyEmpty + // postfix), the postfix separator was found and position has already + // been advanced past it by parseOneInstanceWithMaybePoU, so we + // continue parsing the next sequence child instead. + val continueSequence = scalarParser match { + case sep: Separated => !sep.isPositional + case _ => false + } + if (continueSequence) { + pstate.mpstate.moveOverOneGroupIndexOnly() + } else { + pstate.setSuccess() + isDone = true + pstate.diagnostics = diagnosticsBeforeAttempt + } } // This child alternative of an unordered sequence failed, and that diff --git a/daffodil-test/src/test/resources/org/apache/daffodil/section14/sequence_groups/SequenceGroup.tdml b/daffodil-test/src/test/resources/org/apache/daffodil/section14/sequence_groups/SequenceGroup.tdml index f58c28f327..13f6f43395 100644 --- a/daffodil-test/src/test/resources/org/apache/daffodil/section14/sequence_groups/SequenceGroup.tdml +++ b/daffodil-test/src/test/resources/org/apache/daffodil/section14/sequence_groups/SequenceGroup.tdml @@ -1780,5 +1780,200 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + separator + not found + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + hello + + + + + + + + + + + + + + unable to parse + integer from empty string + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 42 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 42 + + + + + diff --git a/daffodil-test/src/test/scala/org/apache/daffodil/section14/sequence_groups/TestSequenceGroups.scala b/daffodil-test/src/test/scala/org/apache/daffodil/section14/sequence_groups/TestSequenceGroups.scala index b661c5dc9f..94615234fe 100644 --- a/daffodil-test/src/test/scala/org/apache/daffodil/section14/sequence_groups/TestSequenceGroups.scala +++ b/daffodil-test/src/test/scala/org/apache/daffodil/section14/sequence_groups/TestSequenceGroups.scala @@ -121,4 +121,12 @@ class TestSequenceGroup extends TdmlTests { @Test def choice_group_with_annotation_01 = test @Test def similar_model_groups_01 = test + + // DAFFODIL-3085 + @Test def seq2 = test + @Test def seq3 = test + @Test def seq4 = test + @Test def seq5 = test + @Test def seq6 = test + @Test def seq7 = test } From fd3e4b0d396ec738d330e992f07ba66d6b5f4c71 Mon Sep 17 00:00:00 2001 From: olabusayoT <50379531+olabusayoT@users.noreply.github.com> Date: Tue, 16 Jun 2026 10:49:56 -0400 Subject: [PATCH 2/2] Add support for restoring state to a point of uncertainty without discarding it - Introduced `restoreToPointOfUncertainty` method in `PState`. - Updated separator helpers and sequence parsers to accept an optional `PointOfUncertainty` (POU) for speculative parsing. - Adjusted postfix separator logic to handle speculative rewinds while retaining the POU for outer handlers. - Refactored logic to safely manage POU states, ensuring correct handling of resolved and unresolved checkpoints. DAFFODIL-3085 --- .../runtime1/processors/parsers/PState.scala | 22 ++ .../parsers/SeparatedParseHelper.scala | 256 +++++++++--------- .../parsers/SeparatedSequenceParsers.scala | 6 +- .../parsers/SequenceChildBases.scala | 12 +- .../parsers/SequenceParserBases.scala | 7 +- .../parsers/UnseparatedSequenceParsers.scala | 4 +- 6 files changed, 174 insertions(+), 133 deletions(-) diff --git a/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/processors/parsers/PState.scala b/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/processors/parsers/PState.scala index fa64d628bb..7154d890d0 100644 --- a/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/processors/parsers/PState.scala +++ b/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/processors/parsers/PState.scala @@ -429,6 +429,28 @@ final class PState private ( reset(pouPop) } + /** + * Restore state to the point of uncertainty WITHOUT discarding it. The POU + * remains on the stack and can still be used for a subsequent reset or + * discard by the outer handler. Used when a separator helper needs to + * speculatively rewind state (e.g., postfix absent-rep detection) while + * leaving the outer POU available for the outer handler to discard normally. + * + * Unlike resetToPointOfUncertainty, this does not pop the POU from the + * stack, decrement the infoset walker block count, or return the mark to + * the pool. Because dataInputStream.reset discards its mark argument, a + * fresh data stream mark is captured at the restored position so that the + * outer handler can still call resetToPointOfUncertainty or + * discardPointOfUncertainty. + */ + def restoreToPointOfUncertainty(pou: PState.Mark): Unit = { + Assert.usage(!isPointOfUncertaintyResolved(pou)) + pou.restoreInto(this) + // dataInputStream.reset (called inside restoreInto) discards pou.disMark. + // Re-capture at this (= outer POU) position so the POU remains valid. + pou.disMark = dataInputStream.mark(pou.poolDebugLabel) + } + /** * Discard the point of uncertainty created by withPointOfUncertainty. Once * called, the pou variable should no longer be used. If the pou is not diff --git a/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/processors/parsers/SeparatedParseHelper.scala b/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/processors/parsers/SeparatedParseHelper.scala index 578c585ec2..c2eea03cb9 100644 --- a/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/processors/parsers/SeparatedParseHelper.scala +++ b/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/processors/parsers/SeparatedParseHelper.scala @@ -17,6 +17,7 @@ package org.apache.daffodil.runtime1.processors.parsers import org.apache.daffodil.lib.exceptions.Assert +import org.apache.daffodil.lib.util.Maybe import org.apache.daffodil.runtime1.processors.ElementRuntimeData import org.apache.daffodil.runtime1.processors.Failure import org.apache.daffodil.runtime1.processors.Success @@ -44,7 +45,8 @@ sealed abstract class SeparatorParseHelper( def parseOneWithSeparator( state: PState, - requiredOptional: RequiredOptionalStatus + requiredOptional: RequiredOptionalStatus, + maybePOU: Maybe[PState.Mark] ): ParseAttemptStatus /** @@ -76,9 +78,10 @@ final class PrefixSeparatorHelper(sep: Parser, childParser: Parser, scParserArg: override def parseOneWithSeparator( pstate: PState, - requiredOptional: RequiredOptionalStatus + requiredOptional: RequiredOptionalStatus, + maybePOU: Maybe[PState.Mark] ): ParseAttemptStatus = - parseOneWithInfixOrPrefixSeparator(true, pstate, requiredOptional) + parseOneWithInfixOrPrefixSeparator(true, pstate, requiredOptional, maybePOU) } final class InfixSeparatorHelper(sep: Parser, childParser: Parser, scParserArg: Separated) @@ -89,13 +92,19 @@ final class InfixSeparatorHelper(sep: Parser, childParser: Parser, scParserArg: override def parseOneWithSeparator( pstate: PState, - requiredOptional: RequiredOptionalStatus + requiredOptional: RequiredOptionalStatus, + maybePOU: Maybe[PState.Mark] ): ParseAttemptStatus = { val infixSepShouldBePresent = pstate.mpstate.groupPos > 1 - parseOneWithInfixOrPrefixSeparator(infixSepShouldBePresent, pstate, requiredOptional) + parseOneWithInfixOrPrefixSeparator( + infixSepShouldBePresent, + pstate, + requiredOptional, + maybePOU + ) } } @@ -106,7 +115,8 @@ trait InfixPrefixSeparatorHelperMixin { self: SeparatorParseHelper => final protected def parseOneWithInfixOrPrefixSeparator( shouldParseTheSep: Boolean, pstate: PState, - requiredOptional: RequiredOptionalStatus + requiredOptional: RequiredOptionalStatus, + maybePOU: Maybe[PState.Mark] ): ParseAttemptStatus = { val sepStatus = @@ -116,23 +126,22 @@ trait InfixPrefixSeparatorHelperMixin { self: SeparatorParseHelper => // while searching for it which should not be propagated upward requiredOptional match { case _: RequiredOptionalStatus.Optional => { - // Create a point of uncertainty with which to reset if we find errors - pstate.withPointOfUncertainty( - "parseOneWithInfixOrPrefixSeparator", - childParser.context - ) { pou => - sep.parse1(pstate) - - val rv = if (pstate.processorStatus eq Success) { - SeparatorParseStatus.SeparatorFound - } else { - // reset to the point of uncertainty to discard the errors - pstate.resetToPointOfUncertainty(pou) - SeparatorParseStatus.SeparatorFailed - } - rv + // Optional elements require arrayIndexStatus to return OptionalMiddle/OptionalLast, + // which requires minRepeats < maxRepeats, which only happens in MinMaxOccursCountParser + // and MinMaxOccursCountParser has HasPoU, so needsPoU = true, so One(pou) is always passed + // it is a bug if maybePOU is not defined then + Assert.invariant(maybePOU.isDefined) + val pou = maybePOU.get + sep.parse1(pstate) + val rv = if (pstate.processorStatus eq Success) { + SeparatorParseStatus.SeparatorFound + } else { + // reset to the point of uncertainty to discard the errors + pstate.resetToPointOfUncertainty(pou) + SeparatorParseStatus.SeparatorFailed } + rv } case _ => { sep.parse1(pstate) @@ -188,126 +197,109 @@ final class PostfixSeparatorHelper( override def parseOneWithSeparator( pstate: PState, - requiredOptional: RequiredOptionalStatus + requiredOptional: RequiredOptionalStatus, + maybePOU: Maybe[PState.Mark] ): ParseAttemptStatus = { val prevBitPosBeforeChild = pstate.bitPos0b - pstate.withPointOfUncertainty("PostfixSeparatorHelper", childParser.context) { pou => - childParser.parse1(pstate) - val childSuccessful = pstate.processorStatus eq Success - val childFailure = !childSuccessful // just makes later logic easier to read - val bitPosAfterChildAttempt = pstate.bitPos0b - val prh = scParser.parseResultHelper - // This seems odd, but there are cases where after a failure - // the bitPos has not yet been repositioned back to where it started. - // This does happen for complex types. - val hasZLChildAttempt = prevBitPosBeforeChild == bitPosAfterChildAttempt - - if (childSuccessful) { - val dataOnlyRep = - prh.computeParseAttemptStatus( - scParser, - prevBitPosBeforeChild, - pstate, - requiredOptional - ) - sep.parse1(pstate) - if (pstate.processorStatus eq Success) { - // we got the postfix sep after successful parse of the data item - // so whatever the status of the item was, that's the status overall. - dataOnlyRep - } else { - // child successful, but - // no postfix sep found. - // capture failure reason, but reset position to before the child, - // and remove any elements. That way this behaves like a prefix - // or infix separator, where if you don't get the sep, then you - // usually (except first one in infix case) don't run the child parser - val failure = pstate.processorStatus.asInstanceOf[Failure] + childParser.parse1(pstate) + val childSuccessful = pstate.processorStatus eq Success + val childFailure = !childSuccessful // just makes later logic easier to read + val bitPosAfterChildAttempt = pstate.bitPos0b + val prh = scParser.parseResultHelper + // This seems odd, but there are cases where after a failure + // the bitPos has not yet been repositioned back to where it started. + // This does happen for complex types. + val hasZLChildAttempt = prevBitPosBeforeChild == bitPosAfterChildAttempt + + if (childSuccessful) { + val dataOnlyRep = + prh.computeParseAttemptStatus( + scParser, + prevBitPosBeforeChild, + pstate, + requiredOptional + ) + sep.parse1(pstate) + if (pstate.processorStatus eq Success) { + // we got the postfix sep after successful parse of the data item + // so whatever the status of the item was, that's the status overall. + dataOnlyRep + } else { + // child successful, but no postfix sep found. + // The outer handler (parseOneInstanceWithMaybePoU) resets state back + // to before the child for optional elements without a discriminator, or + // propagates as a hard failure if a discriminator fired (spec 9.3.3.1). + failedSeparator(pstate, "postfix") + prh.computeFailedSeparatorParseAttemptStatus( + scParser, + prevBitPosBeforeChild, + pstate, + hasZLChildAttempt, + requiredOptional + ) + } + } else { + Assert.invariant(childFailure) + // We might be tempted to just try the postfix sep now, + // to see if the postfix sep is there immediately. + // This is, however, problematic. + // + // We're depending on this item being an element with lengthKind="delimited" + // which can't have a zero-length representation, so that if there is zero-data before + // the postfix separator, we can declare the item to be absentRep. + // + // But what if the element is not lengthKind 'delimited'. + // Or what if it has complex type? But has a non-zero-length structure where this + // same delimiter is used at a more deeply nested depth? + // + // If the type is simple, and lengthKind 'delimited' we can proceed. + // Otherwise we punt and call it missing. + // + // required elements within an ordered sequence get Nope passed + // as maybePOU (because needsPoU = HasPoU && !Required = false). + // Without a POU to restore to, there's no position to reset back + // and try the bare postfix separator, so the guard rightfully skips + // that optimization and falls through to the else below + // (treating the child failure as unresolvable missing). + if ( + isSimpleDelimited && maybePOU.isDefined && !pstate.isPointOfUncertaintyResolved( + maybePOU.get + ) + ) { + val failure = pstate.processorStatus.asInstanceOf[Failure] - if (!pstate.isPointOfUncertaintyResolved(pou)) { - pstate.resetToPointOfUncertainty(pou) - } else { - // pou was resolved by a discriminator somewhere, so - // we need to resolve the outer POU here - pstate.resolvePointOfUncertainty() - } + pstate.restoreToPointOfUncertainty(maybePOU.get) + sep.parse1(pstate) + if (pstate.isSuccess) { + // child failed, but postfix sep found immediately after reset + // marking this as ZL content. + // Use hasZLChildAttempt (captured before sep.parse1) so isZL reflects + // the child bit position, not the post-separator position. pstate.setFailed(failure.cause) - failedSeparator(pstate, "postfix") - prh.computeFailedSeparatorParseAttemptStatus( + val pas = prh.computeFailedParseAttemptStatus( scParser, prevBitPosBeforeChild, pstate, hasZLChildAttempt, requiredOptional ) - } - } else { - Assert.invariant(childFailure) - // We might be tempted to just try the postfix sep now, - // to see if the postfix sep is there immediately. - // This is, however, problematic. - // - // We're depending on this item being an element with lengthKind="delimited" - // which can't have a zero-length representation, so that if there is zero-data before - // the postfix separator, we can declare the item to be absentRep. - // - // But what if the element is not lengthKind 'delimited'. - // Or what if it has complex type? But has a non-zero-length structure where this - // same delimiter is used at a more deeply nested depth? - // - // If the type is simple, and lengthKind 'delimited' we can proceed. - // Otherwise we punt and call it missing. - // - if (isSimpleDelimited && !pstate.isPointOfUncertaintyResolved(pou)) { - val failure = pstate.processorStatus.asInstanceOf[Failure] - - pstate.resetToPointOfUncertainty(pou) - - sep.parse1(pstate) - if (pstate.isSuccess) { - // child failed, but postfix sep found immediately after reset - // marking this as ZL content. - // Use hasZLChildAttempt (captured before sep.parse1) so isZL reflects - // the child bit position, not the post-separator position. - pstate.setFailed(failure.cause) - val pas = prh.computeFailedParseAttemptStatus( - scParser, - prevBitPosBeforeChild, - pstate, - hasZLChildAttempt, - requiredOptional - ) - // For non-positional (anyEmpty) sequences, the child result helper returns - // MissingItem for optional ZL elements, but since the postfix separator WAS - // found this is AbsentRep per DFDL spec 9.2.4. - pas match { - case ParseAttemptStatus.MissingItem - if requiredOptional.isInstanceOf[RequiredOptionalStatus.Optional] => - ParseAttemptStatus.AbsentRep - case _ => pas - } - } else { - // the separator failed on ZL data - // so no chance on a ZL representation here. - val isZL = false - prh.computeFailedSeparatorParseAttemptStatus( - scParser, - prevBitPosBeforeChild, - pstate, - isZL, - requiredOptional - ) + // For non-positional (anyEmpty) sequences, the child result helper returns + // MissingItem for optional ZL elements, but since the postfix separator WAS + // found this is AbsentRep per DFDL spec 9.2.4. + pas match { + case ParseAttemptStatus.MissingItem + if requiredOptional.isInstanceOf[RequiredOptionalStatus.Optional] => + ParseAttemptStatus.AbsentRep + case _ => pas } } else { - // not simple delimited. We really have no way of knowing - // if trying to parse just the sep now makes any sense at all. - // (ex: the child could be failing because it is fixed length, with asserts that check the value - // that fail.) + // the separator failed on ZL data + // so no chance on a ZL representation here. val isZL = false - prh.computeFailedParseAttemptStatus( + prh.computeFailedSeparatorParseAttemptStatus( scParser, prevBitPosBeforeChild, pstate, @@ -315,8 +307,20 @@ final class PostfixSeparatorHelper( requiredOptional ) } + } else { + // not simple delimited. We really have no way of knowing + // if trying to parse just the sep now makes any sense at all. + // (ex: the child could be failing because it is fixed length, with asserts that check the value + // that fail.) + val isZL = false + prh.computeFailedParseAttemptStatus( + scParser, + prevBitPosBeforeChild, + pstate, + isZL, + requiredOptional + ) } - } } } diff --git a/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/processors/parsers/SeparatedSequenceParsers.scala b/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/processors/parsers/SeparatedSequenceParsers.scala index 8ff39edb08..dff35f0998 100644 --- a/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/processors/parsers/SeparatedSequenceParsers.scala +++ b/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/processors/parsers/SeparatedSequenceParsers.scala @@ -17,6 +17,7 @@ package org.apache.daffodil.runtime1.processors.parsers import org.apache.daffodil.lib.schema.annotation.props.gen.SeparatorPosition +import org.apache.daffodil.lib.util.Maybe import org.apache.daffodil.runtime1.processors.* trait Separated { self: SequenceChildParser => @@ -43,9 +44,10 @@ trait Separated { self: SequenceChildParser => final def parseOne( pstate: PState, - requiredOptional: RequiredOptionalStatus + requiredOptional: RequiredOptionalStatus, + maybePOU: Maybe[PState.Mark] ): ParseAttemptStatus = { - separatorHelper.parseOneWithSeparator(pstate, requiredOptional) + separatorHelper.parseOneWithSeparator(pstate, requiredOptional, maybePOU) } final override def arrayCompleteChecks( diff --git a/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/processors/parsers/SequenceChildBases.scala b/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/processors/parsers/SequenceChildBases.scala index cce424fdfc..23e8ab12a4 100644 --- a/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/processors/parsers/SequenceChildBases.scala +++ b/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/processors/parsers/SequenceChildBases.scala @@ -207,7 +207,11 @@ abstract class SequenceChildParser( final override def parse(pstate: PState): Unit = Assert.usageError("Not to be called on sequence child parsers") - def parseOne(pstate: PState, requiredOptional: RequiredOptionalStatus): ParseAttemptStatus + def parseOne( + pstate: PState, + requiredOptional: RequiredOptionalStatus, + maybePOU: Maybe[PState.Mark] + ): ParseAttemptStatus def maybeStaticRequiredOptionalStatus: Maybe[RequiredOptionalStatus] @@ -264,7 +268,11 @@ final class NonRepresentedSequenceChildParser( def maybeStaticRequiredOptionalStatus: Maybe[RequiredOptionalStatus] = Assert.usageError("not to be used for non-represented terms.") - def parseOne(pstate: PState, ignored_roStatus: RequiredOptionalStatus): ParseAttemptStatus = { + def parseOne( + pstate: PState, + ignored_roStatus: RequiredOptionalStatus, + maybePOU: Maybe[PState.Mark] + ): ParseAttemptStatus = { childParser.parse1(pstate) if (pstate.processorStatus eq Success) ParseAttemptStatus.NormalRep diff --git a/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/processors/parsers/SequenceParserBases.scala b/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/processors/parsers/SequenceParserBases.scala index 509b88aaf1..e339d77d77 100644 --- a/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/processors/parsers/SequenceParserBases.scala +++ b/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/processors/parsers/SequenceParserBases.scala @@ -226,7 +226,10 @@ abstract class SequenceParserBase( // should never have non-represented children in unordered sequences Assert.invariant(isOrdered) - nonRepresentedParser.parseOne(pstate, null) + // non-represented children by definition can never be absent or + // optional, so there is no speculative parsing and no need for + // a point of uncertainty. + nonRepresentedParser.parseOne(pstate, null, Nope) // don't need to digest result from this. All // information about success/failure is in the pstate. // @@ -386,7 +389,7 @@ abstract class SequenceParserBase( checkN(pstate, parser) // check if occursIndex exceeds tunable limit. val priorPos = pstate.bitPos0b - var resultOfTry = parser.parseOne(pstate, roStatus) + var resultOfTry = parser.parseOne(pstate, roStatus, maybePoU) val currentPos = pstate.bitPos0b diff --git a/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/processors/parsers/UnseparatedSequenceParsers.scala b/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/processors/parsers/UnseparatedSequenceParsers.scala index ebb82983c5..65c7ffdca7 100644 --- a/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/processors/parsers/UnseparatedSequenceParsers.scala +++ b/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/processors/parsers/UnseparatedSequenceParsers.scala @@ -16,6 +16,7 @@ */ package org.apache.daffodil.runtime1.processors.parsers +import org.apache.daffodil.lib.util.Maybe import org.apache.daffodil.runtime1.processors.ElementRuntimeData import org.apache.daffodil.runtime1.processors.OccursCountEv import org.apache.daffodil.runtime1.processors.Processor @@ -27,7 +28,8 @@ trait Unseparated { self: SequenceChildParser => final def parseOne( pstate: PState, - requiredOptional: RequiredOptionalStatus + requiredOptional: RequiredOptionalStatus, + maybePOU: Maybe[PState.Mark] ): ParseAttemptStatus = { val prevBitPosBeforeChild = pstate.bitPos0b self.childParser.parse1(pstate)