Skip to content

Implement occursCountKind="stopValue" - #1728

Open
jadams-tresys wants to merge 9 commits into
apache:mainfrom
jadams-tresys:DAFFODIL-501
Open

jadams-tresys wants to merge 9 commits into
apache:mainfrom
jadams-tresys:DAFFODIL-501

Conversation

@jadams-tresys

Copy link
Copy Markdown
Contributor

Bulk of this was done with AI. I tried to give it a thorough review and cleaned up a few things that seemed at odds with our usual coding style (see commit bf40370) but I'll admit I'm not super familiar with all the parts of Daffodil that were involved in this change.

Test coverage seems pretty good, but I think a few more need to be added to verify the various SDE/ProcessingError conditions outlined in the spec. I will add those soon, but wanted to get this up for review to make sure things are headed in the right direction.

DAFFODIL-501

@olabusayoT olabusayoT left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some comments about style.

Comment thread daffodil-core/src/main/scala/org/apache/daffodil/core/dsom/ParticleMixin.scala Outdated
Comment thread daffodil-core/src/main/scala/org/apache/daffodil/core/dsom/ParticleMixin.scala Outdated
Comment thread daffodil-core/src/main/scala/org/apache/daffodil/core/dsom/ParticleMixin.scala Outdated
Comment on lines +249 to +255
<!--
Codegen backends cannot parse stopValueElement; generating C code for it fails with
"Code generation not supported for: RepOrderedStopValueSequenceChild"
daffodil generate c -r stopValueElement -s variablelen.dfdl.xsd
-->

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks pretty straightfoward to add the ReoOrderedStopValueSequenceChild support to the C generator, any reason why we're not doing this?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd suggest we hold off for now, if we do want it we should do it as a separate PR.

Josh Adams added 9 commits October 19, 2026 07:48
For now only implemented parse support
Also remove some unused test files
One of the requirements in the DFDL spec is that if a stopValue shows up
in the infoset, it should result in a processing error.
- ParticleMixin: restore LV(Symbol(...)) for hasStopValue; cook
  dfdl:occursStopValue separately for parsing and unparsing
- SequenceChild: use if/else instead of matching on a boolean
- SeparatedSequenceParsers: comment explaining why checkParseAttemptStatus is
  applied after the separator has been parsed
- Remove DAFFODIL-501 from source comments and shorten long ones
- Clarify in the variablelen test files that only the Daffodil runtime, not the C
  code generator, supports stopValue
Comment on lines +249 to +255
<!--
Codegen backends cannot parse stopValueElement; generating C code for it fails with
"Code generation not supported for: RepOrderedStopValueSequenceChild"
daffodil generate c -r stopValueElement -s variablelen.dfdl.xsd
-->

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd suggest we hold off for now, if we do want it we should do it as a separate PR.

// while building the grammar of an element whose required evaluations have not run yet
schemaDefinitionWhen(
!isSimpleType,
"Property occursCountKind='stopValue' requires a simple type. The value of each occurrence is compared with the dfdl:occursStopValue."

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The value of each occurrence is compared with the dfdl:occursStopValue.

I don't think we need this diagnostic sentence, we don't usually explain why certain restrictions exist. Users can look to the spec if they need justification for our SDEs.


// Unparsing an occursCountKind='stopValue' array emits all of the infoset occurrences
// followed by one synthesized terminating occurrence whose value is the first
// dfdl:occursStopValue (that occurrence consumes data but is not part of the infoset,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SHould this be something like "(that occurrence produces data but ...)"? Nothing in unparse consumes data


Assert.invariant(stopValues.nonEmpty)

final override def pouStatus = PoUStatus.HasPoU

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Section 9.3.3 says that StopValues do not have points of uncertainties:

When parsing an array or optional element, points of uncertainty only occur for certain values of dfdl:occursCountKind[23], as follows:
...
stopValue No point of uncertainty (The stop value must always be present, even when XSD minOccurs is 0).

Is this correct, or does pouStatus related to something other this table?

if (!status.isSuccess || !pstate.isSuccess) return status
val maybeElem = pstate.infosetLastChild
maybeElem.get match {
case elem: DISimple if (elem.erd eq erd) && !elem.isNilled && elem.hasValue => {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we are in checkStopValue and the parse attempt was successful doesn't that mean we must have parsed at least one element, it must have been a DISImple, and that simple must have the erd we are looking for?

So it feels like the only conditions that really matter are if it's nilled (which does not match StopValue) and, if it's not nilled it must have a value. So this could be simplified to:

if (!status.isSuccess || !pstate.isSuccess) return status
val maybeElem =pstate.infosetLastChild
val simple = maybeElement.get.asSimple
if (!simple.isNilled && stopValues.exists(stopValueMatch(elem.dataValue.getAnyRef, _)) {
  ParseAttemptStatus.StopValueRep
} else status

)
} else {
Assert.invariant(ev.isEnd && ev.erd.isComplexType)
// Zero infoset occurrences, but an occursCountKind='stopValue'

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here, does this want to happen after the checkFinalOccursCOunt, or it doesn't matter if we don't change some state?

*/
final def checkStopValueInInfoset(state: UState, maybeElem: Option[DISimple]): Unit = {
maybeElem.foreach { elem =>
if (!elem.isNilled && elem.hasValue) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need elem.hasValue? Once we've gotten if it isn't nilled doesn't it have to have a value? Similarly, don't we have to have an element so maybeElement doesn't need to be optional?

if (stopValues.exists { sv => stopValueMatch(dataValue, sv.getAnyRef) }) {
UE(
state,
"Found stop value '%s' in the infoset for array element %s. Occurrences of a dfdl:occursCountKind='stopValue' array must not have a value equal to one of the dfdl:occursStopValue values; the terminating occurrence is added automatically when unparsing.",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we make this a little less verbose? Maybe something like "Stop value %s cannot appear in the infoset". That's mabye a bit too terse, but it does convey the same information.

Note we also don't need to include "array element %s", since that should appear in the SDE context.

<y>8</y>
<y>9</y>
<z>some string</z>
<z>,some string</z>

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm wondering if the change to this test is correct? The comma after 10 is definitely a delimiter since it is needed to determine where the 10 ends. So it's it feels wrong for it to appear in the data of the next element--we already determined it's a delimiter and not data.

It almost feels like delimited stopValues do not play well with infix separators? Usually delimited infix separated arrays end by seeing an out of scope delimiter. So if you see an in-scope delimiter, you know there must be at least one more element. But with stopValue, an in-scope delimiter doesn't necessarily indicate there is another value because the stopValue could end the array. So the question is what do you do with the delimiter? If we consume it as a delimiter it feels like we're treating it as if separatorPosition was postifx. But if we don't consume it we're treating it as data, but we already identified it as a delimiter.

I think the same issue exists with prefix separators to, since infix and prefiex are essentially the same thing, there just isn't a separate at the very begnning.

I'm not sure what the right answer is. Maybe we say that lengthKind="delimited" and occursCountKind="stopValue" cannot be in a sequence with infix/prefix separator position? I'm not sure if that's too strict, maybe there are causes where this can work?

@Ignore @Test def BB000 = test
// DAFFODIL-501: occursCountKind='stopValue' is now implemented; this test was
// enabled with its expected infoset updated to match the DFDL specification (see
// BB.tdml for details).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we need this comment.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants