stevedlawrence commented on code in PR #1652:
URL: https://github.com/apache/daffodil/pull/1652#discussion_r3305635266


##########
daffodil-core/src/main/scala/org/apache/daffodil/core/dsom/SchemaSet.scala:
##########
@@ -116,6 +117,7 @@ final class SchemaSet private (
 
   requiredEvaluationsAlways(root)
   requiredEvaluationsAlways(checkForDuplicateTopLevels())
+  
requiredEvaluationsAlways(root.checkEndOfParentRestrictions(Some(LengthUnits.Characters)))

Review Comment:
   Is it possible to put this on the `Root` class? It feels like that' the 
right place for this check and any checks that algorithmically need to start 
from the root like this one. 



##########
daffodil-core/src/main/scala/org/apache/daffodil/core/dsom/Term.scala:
##########
@@ -269,6 +267,26 @@ trait Term
       .getOrElse(false)
   }
 
+  final lazy val immediatelyEnclosingParentForEOPElem: Option[Term] = {

Review Comment:
   Can this be removed? It doesn't look like it's used anywhere, and it is has 
subtle edge cases where it doesn't necessarily return what one might think it 
would because it only knows about lexical parents.



##########
daffodil-core/src/main/scala/org/apache/daffodil/core/dsom/Term.scala:
##########
@@ -562,4 +580,21 @@ trait Term
     }
   }
 
+  final lazy val realElementChildren: Seq[ElementBase] = {
+    termChildren.flatMap {
+      case eb: ElementBase => Seq(eb)
+      case c: Choice => Nil
+      case mg: ModelGroup => mg.realElementChildren
+    }
+  }
+
+  lazy val flattenedChildren: IndexedSeq[Term] = termChildren.flatMap { c =>

Review Comment:
   Does an IndexedSeq provide much benefit with how this is used? I don't think 
we will usually know what index we an element is, so we'll likely have to do a 
linear scans regardless. And the way this is used it looks like we do indexOf 
and then get everything after that index. If this were a normal Seq, I think 
similar logic  could be implemented like this:
   
   ```scala
   val elementPlusNextSiblings = flattendChildren.dropWhile(_ != 
specificEopChild)
   Assert.invariant(!elementPlusNextSibligns.isEmpty)
   val nextSiblings = elementPlusNextSiblings.tail
   if (nextSiblings.isDefined) {
     // SDE, children exist afterEOPelement
   }
   ```
   
   We don't really use the indexability, so this might avoid some overheads.



##########
daffodil-core/src/main/scala/org/apache/daffodil/core/grammar/ElementBaseGrammarMixin.scala:
##########
@@ -252,6 +253,49 @@ trait ElementBaseGrammarMixin
   }
   final lazy val prefixedLengthBody = prefixedLengthElementDecl.parsedValue
 
+  def checkEndOfParentRestrictionsOnCurrentElement(optParentELU: 
Option[LengthUnits]): Unit = {
+    val currentElement: ElementBase = this
+    if (currentElement.lengthKind != LengthKind.EndOfParent) {} else {

Review Comment:
   I think this function is only called on elements with lengthKind=EOP. I 
would suggest we change this to an assert that lengthKind == EOP and remove the 
condition.



##########
daffodil-core/src/main/scala/org/apache/daffodil/core/dsom/Term.scala:
##########
@@ -562,4 +580,21 @@ trait Term
     }
   }
 
+  final lazy val realElementChildren: Seq[ElementBase] = {
+    termChildren.flatMap {
+      case eb: ElementBase => Seq(eb)
+      case c: Choice => Nil
+      case mg: ModelGroup => mg.realElementChildren
+    }
+  }
+
+  lazy val flattenedChildren: IndexedSeq[Term] = termChildren.flatMap { c =>
+    c match {
+      case eb: ElementBase => IndexedSeq(eb)
+      case mg: ModelGroup if mg.groupMembers.isEmpty => IndexedSeq(mg)
+      case s: SequenceTermBase => s.flattenedChildren
+      case c: ChoiceTermBase => c.flattenedChildren

Review Comment:
   I'm not sure this is correct for ChoiceTermBase, at least in the context 
where this is used to detect if there are siblings after this. For example, say 
we have a schema below, where the first branch of a choice contains an EOP 
element:
   
   ```xml
   <element name="foo">
     <complexType>
       <choice>
         <element name="bar" dfdl:lengthKind="endOfParent" ... />
         <element name="baz" dfdl:lenghtKind="explicit" ... />
       </choice>
     </complexType>
   </element>
   ```
   
   In this case, I think the choices `flattenedChildren` will look like:
   
   ```
   IndexedSeq(bar, baz)
   ```
   And so it will look like baz follows an EOP element and would SDE. But I 
think this this schema is legal?
   
   Since choiceLengthKind doesn't allow EOP, maybe we just return Nil here, 
similar to realElementChildren? And maybe choices need to run their checks on 
individual branches? I'm not sure we can just flatten all branches of a choice 
into a single list of children.
   
   Or maybe we need a different way to determine if there are things after an 
EOP element that doesn't require flattening. Maybe checkEndOfParentRestrictions 
returns a boolean that indicates if it saw an EOP element, and then as we roll 
up from the recursion we check the result? For xample, maybe something like:
   
   ```scala
   def checkEndOfParentRestrcitions(...) {
     ...
   
     val sawEOP = term.termChildren.foldLeft(false) { case (child, sawEOP)) =>
       if (sawEOP) {
        // the previous child saw an EOP element, and this child is after it, 
so error
        SDE(...)
       }
       child.checkEndOfParentRestrictions(...)
     }
     sawEOP
   }
   ```
   
   This doesn't handle non-represented elements or choice branches, but maybe 
something along those lines? This is also nice in that it doesn't require 
allocating any new lists like the flattenedChildren. We're already having to 
walking the entire tree, so if we can do logic just be return values or 
something that would be preferred. It's not the standard way we do things, but 
this parent/sibling logic this reuires kindof makes the normal way difficult.



##########
daffodil-core/src/main/scala/org/apache/daffodil/core/grammar/AlignedMixin.scala:
##########
@@ -456,7 +456,11 @@ trait AlignedMixin extends GrammarMixin { self: Term =>
           }
           case LengthKind.Delimited => encodingLengthApprox
           case LengthKind.Pattern => encodingLengthApprox
-          case LengthKind.EndOfParent => LengthMultipleOf(1) // NYI
+          case LengthKind.EndOfParent =>
+            // technically the alignment of an EndOfParent element would be the
+            // alignment of its parent minus our current alignment (i.e 
alignment of
+            // prior sibs) but since nothing can follow

Review Comment:
   This looks like a fragment sentence? Also this comment talks about alignment 
but this function is about calculating the approximate length. I think 
`LengthMultipleOf(1)` is correct, but we might want to update the comment as to 
why.



##########
daffodil-core/src/main/scala/org/apache/daffodil/core/grammar/TermGrammarMixin.scala:
##########
@@ -84,4 +96,181 @@ trait TermGrammarMixin extends AlignedMixin with 
BitOrderMixin with TermRuntime1
     MandatoryTextAlignment(this, knownEncodingAlignmentInBits, true)
   }
 
+  def myEffectiveLengthUnits(optLastNonEOPLU: Option[LengthUnits]): 
Option[LengthUnits] = {
+    val elu = this match {
+      case e: ElementBase =>
+        e.lengthKind match {
+          case LengthKind.EndOfParent => optLastNonEOPLU
+          case LengthKind.Explicit | LengthKind.Prefixed => Some(e.lengthUnits)
+          case LengthKind.Pattern => Some(LengthUnits.Characters)
+          case _ => None
+        }
+      case c: ChoiceTermBase if c.choiceLengthKind == 
ChoiceLengthKind.Explicit =>
+        Some(LengthUnits.Bytes)
+      // the spec doesn't actually account for this case, but logically it 
makes
+      // sense that ELU of a sequence is the ELU of its parent that 
technically is the
+      // last non-EndOfParent ELU.
+      case s: SequenceTermBase => optLastNonEOPLU
+      case _ => None
+    }
+    elu
+  }
+
+  final lazy val childrenEndOfParent: Seq[Term] = 
LV(Symbol("childrenEndOfParent")) {
+    val gms = termChildren
+    val chls = gms.flatMap {
+      case eb: ElementBase if eb.lengthKind == LengthKind.EndOfParent => 
Seq(eb)
+      case eb: ElementBase => Nil
+      case c: ChoiceTermBase => Nil
+      case mg: ModelGroup => mg.childrenEndOfParent
+    }
+    chls
+  }.value
+
+  def checkEndOfParentRestrictions(optLastNonEOPLU: Option[LengthUnits]): Unit 
= {
+    val term = this
+    lazy val eopChildren = this.childrenEndOfParent
+    // checks
+    term match {
+      case rootElem: Root if rootElem.lengthKind == LengthKind.EndOfParent => {
+        
rootElem.checkEndOfParentRestrictionsOnCurrentElement(Some(LengthUnits.Characters))
+        eopChildren.foreach {
+          case e: ElementBase => {
+            rootElem.checkChildrenForSiblingsAfterEOPElement(e)
+            Assert.invariant(
+              optLastNonEOPLU.isDefined,
+              "Effective Length Units of parent should not be None"
+            )
+            e.checkEndOfParentRestrictionsOnCurrentElement(optLastNonEOPLU)
+          }
+          case _ => // do nothing
+        }
+      }
+      case parent: ElementBase if eopChildren.nonEmpty => {
+        eopChildren.foreach {
+          case e: ElementBase => {
+            val optParentELU = parent.myEffectiveLengthUnits(optLastNonEOPLU)
+            parent.lengthKind match {
+              case LengthKind.Implicit | LengthKind.Delimited =>
+                schemaDefinitionError(
+                  "element is specified as dfdl:lengthKind=\"endOfParent\", 
but its parent is an element with dfdl:lengthKind 'implicit' or 'delimited'."
+                )
+              case _ => // do nothing
+            }
+            parent.checkChildrenForSiblingsAfterEOPElement(e)
+            Assert.invariant(
+              optParentELU.isDefined,
+              "Effective Length Units of parent should not be None"
+            )
+            e.checkEndOfParentRestrictionsOnCurrentElement(optParentELU)
+          }
+          case _ => // do nothing
+        }
+      }
+      case s: SequenceTermBase if eopChildren.nonEmpty => {
+        schemaDefinitionWhen(
+          s.separatorPosition == SeparatorPosition.Postfix,
+          "element is specified as dfdl:lengthKind=\"endOfParent\", but is in 
a sequence with dfdl:separatorPosition defined as 'postfix'."
+        )
+        schemaDefinitionWhen(
+          s.sequenceKind != SequenceKind.Ordered,
+          "element is specified as dfdl:lengthKind=\"endOfParent\", but is in 
a sequence with dfdl:sequenceKind defined as 'unordered'."
+        )
+        schemaDefinitionWhen(
+          s.hasTerminator,
+          "element is specified as dfdl:lengthKind=\"endOfParent\", but is in 
a sequence with a dfdl:terminator."
+        )
+        schemaDefinitionWhen(
+          s.realElementChildren.exists(e => e.floating == YesNo.Yes),
+          "element is specified as dfdl:lengthKind=\"endOfParent\", but is in 
a sequence with elements defining dfdl:floating='yes'."
+        )
+        schemaDefinitionWhen(
+          s.trailingSkip != 0,
+          "element is specified as dfdl:lengthKind=\"endOfParent\", but is in 
a sequence with a non-zero dfdl:trailingSkip."
+        )
+        eopChildren.foreach {
+          case e: ElementBase => {
+            s.checkChildrenForSiblingsAfterEOPElement(e)
+            e.checkEndOfParentRestrictionsOnCurrentElement(optLastNonEOPLU)
+          }
+          case _ => // do nothing
+        }
+      }
+      case c: ChoiceTermBase if eopChildren.nonEmpty => {
+        // TODO: The DFDL spec (12.3.6) explicitly mentions that an 
EndOfParent element
+        // can be terminated by a choice with choiceLengthKind='explicit', 
with no reference
+        // to implicit length choices. Later on in 12.3.6, it specified what 
the parent Effective
+        // Length Units would be for an explicit length choice, again with no 
reference to
+        // implicit length choices. The only way to get an EndOfParent element 
within an
+        // implicit length choice would be to have it actually be terminated 
by the choice's
+        // parent, similar to how we treat elements within an element that is 
also EndOfParent.
+        // The only mention of implicit length choices in the DFDL Spec is to 
mention SDEs when
+        // an EndOfParent element is enclosed by a choice with 
choiceLengthKind='implicit'. We
+        // think that may be a typo, so for now we disallow 
ChoiceLengthKind.Implicit
+        // enclosing an EndOfParent element.
+        // See Daffodil-3080
+        schemaDefinitionWhen(
+          c.choiceLengthKind == ChoiceLengthKind.Implicit,
+          "element is specified as dfdl:lengthKind=\"endOfParent\", but its 
parent is a choice with dfdl:choiceLengthKind 'implicit'."
+        )
+        schemaDefinitionWhen(
+          c.hasTerminator,
+          "element is specified as dfdl:lengthKind=\"endOfParent\", but is in 
a choice with a dfdl:terminator."
+        )
+        schemaDefinitionWhen(
+          c.trailingSkip != 0,
+          "element is specified as dfdl:lengthKind=\"endOfParent\", but is in 
a choice with a non-zero dfdl:trailingSkip."
+        )
+        val optParentELU = c.myEffectiveLengthUnits(optLastNonEOPLU)
+        eopChildren.foreach {
+          case e: ElementBase => {
+            c.checkChildrenForSiblingsAfterEOPElement(e)
+            Assert.invariant(
+              optParentELU.isDefined,
+              "Effective Length Units of parent should not be None"
+            )
+            e.checkEndOfParentRestrictionsOnCurrentElement(optParentELU)
+          }
+          case _ => // do nothing
+        }
+      }
+      case _ => // do nothing
+    }
+    // end checks
+    term.termChildren.foreach { e =>
+      lazy val optParentELU = term.myEffectiveLengthUnits(optLastNonEOPLU)
+      term match {
+        case parent @ (_: ElementBase | _: ChoiceTermBase | _: 
SequenceTermBase) => {
+          e.checkEndOfParentRestrictions(optParentELU)
+        }
+        case _ => // do nothing
+      }
+    }
+  }
+
+  def checkChildrenForSiblingsAfterEOPElement(specificChild: ElementBase) = {

Review Comment:
   One thing worth considering is this line in the spec:
   
   > It is a Schema Definition Error if:
   > ...
   > *  any other represented element is defined between this element and the 
end of the enclosing component.
   
   I think the implication it is that it is legal to have non-represented 
elements (i.e. IVC's) that appear after a an EOP element. So for example, this 
is legal:
   
   ```xml
   <element name="foo" dfdl:lengthKind="explicit" ...>
     <complexType>
       <sequence>
         <element name="bar" dfdl:lengthKind="endOfParent" ... />
         <element name="baz" dfdl:inputValueCalc="{ ... }" ... />
       </sequence>
     </complexType>
   </element>
   ```
   
   Maybe this can be done by just excluding IVC elements form 
flattenedChildren, since we essentially ignore them in the context of checking 
for siblings after EOP?



##########
daffodil-core/src/main/scala/org/apache/daffodil/core/grammar/ElementBaseGrammarMixin.scala:
##########
@@ -605,7 +649,19 @@ trait ElementBaseGrammarMixin
     case LengthKind.Pattern =>
       schemaDefinitionError("Binary data elements cannot have 
lengthKind='pattern'.")
     case LengthKind.EndOfParent =>
-      schemaDefinitionError("Binary data elements cannot have 
lengthKind='endOfParent'.")
+      // only for packed binary data, length must be computed at runtime.
+      if (
+        representation == Representation.Binary
+        && optionBinaryCalendarRep.isDefined
+        && Seq(BinaryCalendarRep.Packed, BinaryCalendarRep.Bcd, 
BinaryCalendarRep.Ibm4690Packed)
+          .contains(binaryCalendarRep)
+        || representation == Representation.Binary
+        && optionBinaryNumberRep.isDefined
+        && Seq(BinaryNumberRep.Packed, BinaryNumberRep.Bcd, 
BinaryNumberRep.Ibm4690Packed)
+          .contains(binaryNumberRep)

Review Comment:
   I feel like we don't usually use contains for this kind of thing and instead 
just do a match case. I have no strong preference either way, but it might 
match our existing conventions in this file a bit better. Maybe it gets too 
messy though...
   
   



##########
daffodil-core/src/main/scala/org/apache/daffodil/core/grammar/ElementBaseGrammarMixin.scala:
##########
@@ -605,7 +649,19 @@ trait ElementBaseGrammarMixin
     case LengthKind.Pattern =>
       schemaDefinitionError("Binary data elements cannot have 
lengthKind='pattern'.")
     case LengthKind.EndOfParent =>
-      schemaDefinitionError("Binary data elements cannot have 
lengthKind='endOfParent'.")
+      // only for packed binary data, length must be computed at runtime.
+      if (
+        representation == Representation.Binary

Review Comment:
   Do we need representation checks here? I would assume that if 
binaryNumberKnownLengthInBits is called then that implies we're already 
determined this is some kind of binary representation. For example, we don't 
check representation in any of the other cases.



##########
daffodil-core/src/main/scala/org/apache/daffodil/core/grammar/ElementBaseGrammarMixin.scala:
##########
@@ -922,6 +998,8 @@ trait ElementBaseGrammarMixin
       byteOrderRaw // must be defined or SDE
     }
     (binaryNumberRep, lengthKind, binaryNumberKnownLengthInBits) match {
+      case (BinaryNumberRep.Binary, LengthKind.EndOfParent, _) =>
+        SDE("lengthKind='endOfParent' is not allowed with binary number 
representation")

Review Comment:
   Another example of and SDE that feels like we already check somewhere else. 
Is it possible to only have one place?



##########
daffodil-core/src/main/scala/org/apache/daffodil/core/grammar/ElementBaseGrammarMixin.scala:
##########
@@ -1396,11 +1490,9 @@ trait ElementBaseGrammarMixin
         case LengthKind.Implicit
             if isSimpleType && impliedRepresentation == Representation.Binary 
=>
           new SpecifiedLengthImplicit(this, body, implicitBinaryLengthInBits)
-        case LengthKind.EndOfParent if isComplexType =>
-          notYetImplemented("lengthKind='endOfParent' for complex type")
-        case LengthKind.EndOfParent =>
-          notYetImplemented("lengthKind='endOfParent' for simple type")
-        case LengthKind.Delimited | LengthKind.Implicit =>
+        case LengthKind.EndOfParent if (isComplexType || 
this.isInstanceOf[Root]) =>
+          new SpecifiedLengthEndOfParent(this, body)

Review Comment:
   Why is this needed for simple Roots? It feels like the `body` and simple 
root and non-root elements would both be the same thing, so it's not clear why 
root simples need this SpecifiedLengthEndOfParent wrapper. Maybe add a comment 
explaining why it's needed for root?



##########
daffodil-core/src/main/scala/org/apache/daffodil/core/grammar/ElementBaseGrammarMixin.scala:
##########
@@ -1396,11 +1490,9 @@ trait ElementBaseGrammarMixin
         case LengthKind.Implicit
             if isSimpleType && impliedRepresentation == Representation.Binary 
=>
           new SpecifiedLengthImplicit(this, body, implicitBinaryLengthInBits)
-        case LengthKind.EndOfParent if isComplexType =>
-          notYetImplemented("lengthKind='endOfParent' for complex type")
-        case LengthKind.EndOfParent =>
-          notYetImplemented("lengthKind='endOfParent' for simple type")
-        case LengthKind.Delimited | LengthKind.Implicit =>
+        case LengthKind.EndOfParent if (isComplexType || 
this.isInstanceOf[Root]) =>
+          new SpecifiedLengthEndOfParent(this, body)
+        case LengthKind.Delimited | LengthKind.Implicit | 
LengthKind.EndOfParent =>
           Assert.impossibleCase(
             "Delimited and ComplexType Implicit cases should not be reached"

Review Comment:
   Should probably update this comment to mention EOP, or maybe reword it to be 
more generic.



##########
daffodil-core/src/main/scala/org/apache/daffodil/core/grammar/TermGrammarMixin.scala:
##########
@@ -84,4 +96,181 @@ trait TermGrammarMixin extends AlignedMixin with 
BitOrderMixin with TermRuntime1
     MandatoryTextAlignment(this, knownEncodingAlignmentInBits, true)
   }
 
+  def myEffectiveLengthUnits(optLastNonEOPLU: Option[LengthUnits]): 
Option[LengthUnits] = {
+    val elu = this match {
+      case e: ElementBase =>
+        e.lengthKind match {
+          case LengthKind.EndOfParent => optLastNonEOPLU
+          case LengthKind.Explicit | LengthKind.Prefixed => Some(e.lengthUnits)
+          case LengthKind.Pattern => Some(LengthUnits.Characters)
+          case _ => None
+        }
+      case c: ChoiceTermBase if c.choiceLengthKind == 
ChoiceLengthKind.Explicit =>
+        Some(LengthUnits.Bytes)
+      // the spec doesn't actually account for this case, but logically it 
makes
+      // sense that ELU of a sequence is the ELU of its parent that 
technically is the
+      // last non-EndOfParent ELU.
+      case s: SequenceTermBase => optLastNonEOPLU
+      case _ => None
+    }
+    elu
+  }
+
+  final lazy val childrenEndOfParent: Seq[Term] = 
LV(Symbol("childrenEndOfParent")) {
+    val gms = termChildren
+    val chls = gms.flatMap {
+      case eb: ElementBase if eb.lengthKind == LengthKind.EndOfParent => 
Seq(eb)
+      case eb: ElementBase => Nil
+      case c: ChoiceTermBase => Nil
+      case mg: ModelGroup => mg.childrenEndOfParent
+    }
+    chls
+  }.value
+
+  def checkEndOfParentRestrictions(optLastNonEOPLU: Option[LengthUnits]): Unit 
= {
+    val term = this
+    lazy val eopChildren = this.childrenEndOfParent
+    // checks
+    term match {
+      case rootElem: Root if rootElem.lengthKind == LengthKind.EndOfParent => {
+        
rootElem.checkEndOfParentRestrictionsOnCurrentElement(Some(LengthUnits.Characters))
+        eopChildren.foreach {
+          case e: ElementBase => {
+            rootElem.checkChildrenForSiblingsAfterEOPElement(e)
+            Assert.invariant(
+              optLastNonEOPLU.isDefined,
+              "Effective Length Units of parent should not be None"
+            )
+            e.checkEndOfParentRestrictionsOnCurrentElement(optLastNonEOPLU)
+          }
+          case _ => // do nothing
+        }
+      }
+      case parent: ElementBase if eopChildren.nonEmpty => {
+        eopChildren.foreach {
+          case e: ElementBase => {
+            val optParentELU = parent.myEffectiveLengthUnits(optLastNonEOPLU)
+            parent.lengthKind match {
+              case LengthKind.Implicit | LengthKind.Delimited =>
+                schemaDefinitionError(
+                  "element is specified as dfdl:lengthKind=\"endOfParent\", 
but its parent is an element with dfdl:lengthKind 'implicit' or 'delimited'."
+                )
+              case _ => // do nothing

Review Comment:
   I feel like in general if we have a donothing case, we usually opt for 
something like
   
   ```scala
   schemaDefinitionWhen(
     lengthKind == LengthKind.Implicit || lengthKind == LengthKind.Delimited,
     "..."
   )
   ```
   Since it only two things, it feels a bit cleaner



##########
daffodil-core/src/main/scala/org/apache/daffodil/core/grammar/TermGrammarMixin.scala:
##########
@@ -84,4 +96,181 @@ trait TermGrammarMixin extends AlignedMixin with 
BitOrderMixin with TermRuntime1
     MandatoryTextAlignment(this, knownEncodingAlignmentInBits, true)
   }
 
+  def myEffectiveLengthUnits(optLastNonEOPLU: Option[LengthUnits]): 
Option[LengthUnits] = {
+    val elu = this match {
+      case e: ElementBase =>
+        e.lengthKind match {
+          case LengthKind.EndOfParent => optLastNonEOPLU
+          case LengthKind.Explicit | LengthKind.Prefixed => Some(e.lengthUnits)
+          case LengthKind.Pattern => Some(LengthUnits.Characters)
+          case _ => None
+        }
+      case c: ChoiceTermBase if c.choiceLengthKind == 
ChoiceLengthKind.Explicit =>
+        Some(LengthUnits.Bytes)
+      // the spec doesn't actually account for this case, but logically it 
makes
+      // sense that ELU of a sequence is the ELU of its parent that 
technically is the
+      // last non-EndOfParent ELU.
+      case s: SequenceTermBase => optLastNonEOPLU
+      case _ => None
+    }
+    elu
+  }
+
+  final lazy val childrenEndOfParent: Seq[Term] = 
LV(Symbol("childrenEndOfParent")) {
+    val gms = termChildren
+    val chls = gms.flatMap {
+      case eb: ElementBase if eb.lengthKind == LengthKind.EndOfParent => 
Seq(eb)
+      case eb: ElementBase => Nil
+      case c: ChoiceTermBase => Nil
+      case mg: ModelGroup => mg.childrenEndOfParent
+    }
+    chls
+  }.value
+
+  def checkEndOfParentRestrictions(optLastNonEOPLU: Option[LengthUnits]): Unit 
= {
+    val term = this
+    lazy val eopChildren = this.childrenEndOfParent
+    // checks
+    term match {
+      case rootElem: Root if rootElem.lengthKind == LengthKind.EndOfParent => {
+        
rootElem.checkEndOfParentRestrictionsOnCurrentElement(Some(LengthUnits.Characters))
+        eopChildren.foreach {
+          case e: ElementBase => {
+            rootElem.checkChildrenForSiblingsAfterEOPElement(e)
+            Assert.invariant(
+              optLastNonEOPLU.isDefined,
+              "Effective Length Units of parent should not be None"
+            )
+            e.checkEndOfParentRestrictionsOnCurrentElement(optLastNonEOPLU)
+          }
+          case _ => // do nothing
+        }
+      }
+      case parent: ElementBase if eopChildren.nonEmpty => {
+        eopChildren.foreach {
+          case e: ElementBase => {
+            val optParentELU = parent.myEffectiveLengthUnits(optLastNonEOPLU)
+            parent.lengthKind match {
+              case LengthKind.Implicit | LengthKind.Delimited =>
+                schemaDefinitionError(
+                  "element is specified as dfdl:lengthKind=\"endOfParent\", 
but its parent is an element with dfdl:lengthKind 'implicit' or 'delimited'."
+                )
+              case _ => // do nothing
+            }
+            parent.checkChildrenForSiblingsAfterEOPElement(e)
+            Assert.invariant(
+              optParentELU.isDefined,
+              "Effective Length Units of parent should not be None"
+            )
+            e.checkEndOfParentRestrictionsOnCurrentElement(optParentELU)
+          }
+          case _ => // do nothing
+        }
+      }
+      case s: SequenceTermBase if eopChildren.nonEmpty => {
+        schemaDefinitionWhen(
+          s.separatorPosition == SeparatorPosition.Postfix,
+          "element is specified as dfdl:lengthKind=\"endOfParent\", but is in 
a sequence with dfdl:separatorPosition defined as 'postfix'."
+        )
+        schemaDefinitionWhen(
+          s.sequenceKind != SequenceKind.Ordered,
+          "element is specified as dfdl:lengthKind=\"endOfParent\", but is in 
a sequence with dfdl:sequenceKind defined as 'unordered'."
+        )
+        schemaDefinitionWhen(
+          s.hasTerminator,
+          "element is specified as dfdl:lengthKind=\"endOfParent\", but is in 
a sequence with a dfdl:terminator."
+        )
+        schemaDefinitionWhen(
+          s.realElementChildren.exists(e => e.floating == YesNo.Yes),
+          "element is specified as dfdl:lengthKind=\"endOfParent\", but is in 
a sequence with elements defining dfdl:floating='yes'."
+        )
+        schemaDefinitionWhen(
+          s.trailingSkip != 0,
+          "element is specified as dfdl:lengthKind=\"endOfParent\", but is in 
a sequence with a non-zero dfdl:trailingSkip."
+        )
+        eopChildren.foreach {
+          case e: ElementBase => {
+            s.checkChildrenForSiblingsAfterEOPElement(e)
+            e.checkEndOfParentRestrictionsOnCurrentElement(optLastNonEOPLU)
+          }
+          case _ => // do nothing
+        }
+      }
+      case c: ChoiceTermBase if eopChildren.nonEmpty => {
+        // TODO: The DFDL spec (12.3.6) explicitly mentions that an 
EndOfParent element
+        // can be terminated by a choice with choiceLengthKind='explicit', 
with no reference
+        // to implicit length choices. Later on in 12.3.6, it specified what 
the parent Effective
+        // Length Units would be for an explicit length choice, again with no 
reference to
+        // implicit length choices. The only way to get an EndOfParent element 
within an
+        // implicit length choice would be to have it actually be terminated 
by the choice's
+        // parent, similar to how we treat elements within an element that is 
also EndOfParent.
+        // The only mention of implicit length choices in the DFDL Spec is to 
mention SDEs when
+        // an EndOfParent element is enclosed by a choice with 
choiceLengthKind='implicit'. We
+        // think that may be a typo, so for now we disallow 
ChoiceLengthKind.Implicit
+        // enclosing an EndOfParent element.
+        // See Daffodil-3080
+        schemaDefinitionWhen(
+          c.choiceLengthKind == ChoiceLengthKind.Implicit,
+          "element is specified as dfdl:lengthKind=\"endOfParent\", but its 
parent is a choice with dfdl:choiceLengthKind 'implicit'."
+        )
+        schemaDefinitionWhen(
+          c.hasTerminator,
+          "element is specified as dfdl:lengthKind=\"endOfParent\", but is in 
a choice with a dfdl:terminator."
+        )
+        schemaDefinitionWhen(
+          c.trailingSkip != 0,
+          "element is specified as dfdl:lengthKind=\"endOfParent\", but is in 
a choice with a non-zero dfdl:trailingSkip."
+        )
+        val optParentELU = c.myEffectiveLengthUnits(optLastNonEOPLU)
+        eopChildren.foreach {
+          case e: ElementBase => {
+            c.checkChildrenForSiblingsAfterEOPElement(e)
+            Assert.invariant(
+              optParentELU.isDefined,
+              "Effective Length Units of parent should not be None"
+            )
+            e.checkEndOfParentRestrictionsOnCurrentElement(optParentELU)
+          }
+          case _ => // do nothing
+        }
+      }
+      case _ => // do nothing
+    }
+    // end checks
+    term.termChildren.foreach { e =>
+      lazy val optParentELU = term.myEffectiveLengthUnits(optLastNonEOPLU)

Review Comment:
   I think we can move term.myEffectiveLengthUnits out of this foreach since it 
doesn't depend on anything but term which doesn't change in the foreach.
   
   In fact, I think we can move this to the top and then reference optParentELU 
everywhere it's needed. We reference `this.myEffectiveLengthUnits` in a number 
of places so we should just do it at the beginning to avoid the duplication. 
And that function shoudl work in all circumstances, it just sometimes returns 
None to indicate situations where using it isn't valid because we're EOP 
children should be legal and we should error somewhere else.



##########
daffodil-core/src/main/scala/org/apache/daffodil/core/grammar/TermGrammarMixin.scala:
##########
@@ -84,4 +96,181 @@ trait TermGrammarMixin extends AlignedMixin with 
BitOrderMixin with TermRuntime1
     MandatoryTextAlignment(this, knownEncodingAlignmentInBits, true)
   }
 
+  def myEffectiveLengthUnits(optLastNonEOPLU: Option[LengthUnits]): 
Option[LengthUnits] = {
+    val elu = this match {
+      case e: ElementBase =>
+        e.lengthKind match {
+          case LengthKind.EndOfParent => optLastNonEOPLU
+          case LengthKind.Explicit | LengthKind.Prefixed => Some(e.lengthUnits)
+          case LengthKind.Pattern => Some(LengthUnits.Characters)
+          case _ => None
+        }
+      case c: ChoiceTermBase if c.choiceLengthKind == 
ChoiceLengthKind.Explicit =>
+        Some(LengthUnits.Bytes)
+      // the spec doesn't actually account for this case, but logically it 
makes
+      // sense that ELU of a sequence is the ELU of its parent that 
technically is the
+      // last non-EndOfParent ELU.
+      case s: SequenceTermBase => optLastNonEOPLU
+      case _ => None

Review Comment:
   Suggest we change this default case to case `c: ChoiceTermBase => None` 
which I think is the only other case, and scala should error if we are missing 
any other cases.
   
   It's also maybe worth adding a comment about these None cases, that they 
indicate elements where EOP children should be illegal children, and so it 
doesn't make sense to make any decision that require this elements ELU. And we 
use None ensure that doesn't happen.



##########
daffodil-core/src/main/scala/org/apache/daffodil/core/grammar/TermGrammarMixin.scala:
##########
@@ -84,4 +96,181 @@ trait TermGrammarMixin extends AlignedMixin with 
BitOrderMixin with TermRuntime1
     MandatoryTextAlignment(this, knownEncodingAlignmentInBits, true)
   }
 
+  def myEffectiveLengthUnits(optLastNonEOPLU: Option[LengthUnits]): 
Option[LengthUnits] = {
+    val elu = this match {
+      case e: ElementBase =>
+        e.lengthKind match {
+          case LengthKind.EndOfParent => optLastNonEOPLU
+          case LengthKind.Explicit | LengthKind.Prefixed => Some(e.lengthUnits)
+          case LengthKind.Pattern => Some(LengthUnits.Characters)
+          case _ => None
+        }
+      case c: ChoiceTermBase if c.choiceLengthKind == 
ChoiceLengthKind.Explicit =>
+        Some(LengthUnits.Bytes)
+      // the spec doesn't actually account for this case, but logically it 
makes
+      // sense that ELU of a sequence is the ELU of its parent that 
technically is the
+      // last non-EndOfParent ELU.
+      case s: SequenceTermBase => optLastNonEOPLU
+      case _ => None
+    }
+    elu
+  }
+
+  final lazy val childrenEndOfParent: Seq[Term] = 
LV(Symbol("childrenEndOfParent")) {
+    val gms = termChildren
+    val chls = gms.flatMap {
+      case eb: ElementBase if eb.lengthKind == LengthKind.EndOfParent => 
Seq(eb)
+      case eb: ElementBase => Nil
+      case c: ChoiceTermBase => Nil
+      case mg: ModelGroup => mg.childrenEndOfParent
+    }
+    chls
+  }.value
+
+  def checkEndOfParentRestrictions(optLastNonEOPLU: Option[LengthUnits]): Unit 
= {
+    val term = this
+    lazy val eopChildren = this.childrenEndOfParent
+    // checks
+    term match {
+      case rootElem: Root if rootElem.lengthKind == LengthKind.EndOfParent => {
+        
rootElem.checkEndOfParentRestrictionsOnCurrentElement(Some(LengthUnits.Characters))
+        eopChildren.foreach {
+          case e: ElementBase => {
+            rootElem.checkChildrenForSiblingsAfterEOPElement(e)
+            Assert.invariant(
+              optLastNonEOPLU.isDefined,
+              "Effective Length Units of parent should not be None"
+            )
+            e.checkEndOfParentRestrictionsOnCurrentElement(optLastNonEOPLU)
+          }
+          case _ => // do nothing
+        }
+      }
+      case parent: ElementBase if eopChildren.nonEmpty => {
+        eopChildren.foreach {
+          case e: ElementBase => {
+            val optParentELU = parent.myEffectiveLengthUnits(optLastNonEOPLU)
+            parent.lengthKind match {
+              case LengthKind.Implicit | LengthKind.Delimited =>
+                schemaDefinitionError(
+                  "element is specified as dfdl:lengthKind=\"endOfParent\", 
but its parent is an element with dfdl:lengthKind 'implicit' or 'delimited'."
+                )
+              case _ => // do nothing
+            }
+            parent.checkChildrenForSiblingsAfterEOPElement(e)
+            Assert.invariant(
+              optParentELU.isDefined,
+              "Effective Length Units of parent should not be None"
+            )
+            e.checkEndOfParentRestrictionsOnCurrentElement(optParentELU)
+          }
+          case _ => // do nothing
+        }
+      }
+      case s: SequenceTermBase if eopChildren.nonEmpty => {
+        schemaDefinitionWhen(
+          s.separatorPosition == SeparatorPosition.Postfix,
+          "element is specified as dfdl:lengthKind=\"endOfParent\", but is in 
a sequence with dfdl:separatorPosition defined as 'postfix'."
+        )
+        schemaDefinitionWhen(
+          s.sequenceKind != SequenceKind.Ordered,
+          "element is specified as dfdl:lengthKind=\"endOfParent\", but is in 
a sequence with dfdl:sequenceKind defined as 'unordered'."
+        )
+        schemaDefinitionWhen(
+          s.hasTerminator,
+          "element is specified as dfdl:lengthKind=\"endOfParent\", but is in 
a sequence with a dfdl:terminator."
+        )
+        schemaDefinitionWhen(
+          s.realElementChildren.exists(e => e.floating == YesNo.Yes),
+          "element is specified as dfdl:lengthKind=\"endOfParent\", but is in 
a sequence with elements defining dfdl:floating='yes'."
+        )
+        schemaDefinitionWhen(
+          s.trailingSkip != 0,
+          "element is specified as dfdl:lengthKind=\"endOfParent\", but is in 
a sequence with a non-zero dfdl:trailingSkip."
+        )
+        eopChildren.foreach {
+          case e: ElementBase => {
+            s.checkChildrenForSiblingsAfterEOPElement(e)
+            e.checkEndOfParentRestrictionsOnCurrentElement(optLastNonEOPLU)
+          }
+          case _ => // do nothing
+        }

Review Comment:
   I'm not sure we need to do these checks here? I'm pretty sure we don't need 
the `e.checkEndOfParentRestrictionsOnCurrentElement` since these `eopChildren` 
are all the same as what whatever contains this sequence, so we would just be 
duplicating checks.
   
   I feel like the way we flatten children for checking siblings also kindof 
ignores sequences, but I'm not positive.  



##########
daffodil-core/src/main/scala/org/apache/daffodil/core/grammar/TermGrammarMixin.scala:
##########
@@ -84,4 +96,181 @@ trait TermGrammarMixin extends AlignedMixin with 
BitOrderMixin with TermRuntime1
     MandatoryTextAlignment(this, knownEncodingAlignmentInBits, true)
   }
 
+  def myEffectiveLengthUnits(optLastNonEOPLU: Option[LengthUnits]): 
Option[LengthUnits] = {
+    val elu = this match {
+      case e: ElementBase =>
+        e.lengthKind match {
+          case LengthKind.EndOfParent => optLastNonEOPLU
+          case LengthKind.Explicit | LengthKind.Prefixed => Some(e.lengthUnits)
+          case LengthKind.Pattern => Some(LengthUnits.Characters)
+          case _ => None
+        }
+      case c: ChoiceTermBase if c.choiceLengthKind == 
ChoiceLengthKind.Explicit =>
+        Some(LengthUnits.Bytes)
+      // the spec doesn't actually account for this case, but logically it 
makes
+      // sense that ELU of a sequence is the ELU of its parent that 
technically is the
+      // last non-EndOfParent ELU.
+      case s: SequenceTermBase => optLastNonEOPLU
+      case _ => None
+    }
+    elu
+  }
+
+  final lazy val childrenEndOfParent: Seq[Term] = 
LV(Symbol("childrenEndOfParent")) {
+    val gms = termChildren
+    val chls = gms.flatMap {
+      case eb: ElementBase if eb.lengthKind == LengthKind.EndOfParent => 
Seq(eb)
+      case eb: ElementBase => Nil
+      case c: ChoiceTermBase => Nil
+      case mg: ModelGroup => mg.childrenEndOfParent
+    }
+    chls
+  }.value
+
+  def checkEndOfParentRestrictions(optLastNonEOPLU: Option[LengthUnits]): Unit 
= {
+    val term = this
+    lazy val eopChildren = this.childrenEndOfParent
+    // checks
+    term match {
+      case rootElem: Root if rootElem.lengthKind == LengthKind.EndOfParent => {
+        
rootElem.checkEndOfParentRestrictionsOnCurrentElement(Some(LengthUnits.Characters))
+        eopChildren.foreach {
+          case e: ElementBase => {
+            rootElem.checkChildrenForSiblingsAfterEOPElement(e)
+            Assert.invariant(
+              optLastNonEOPLU.isDefined,
+              "Effective Length Units of parent should not be None"
+            )
+            e.checkEndOfParentRestrictionsOnCurrentElement(optLastNonEOPLU)
+          }
+          case _ => // do nothing
+        }
+      }
+      case parent: ElementBase if eopChildren.nonEmpty => {
+        eopChildren.foreach {
+          case e: ElementBase => {
+            val optParentELU = parent.myEffectiveLengthUnits(optLastNonEOPLU)
+            parent.lengthKind match {
+              case LengthKind.Implicit | LengthKind.Delimited =>
+                schemaDefinitionError(

Review Comment:
   I think this SDE (and maybe most of these SDEs?) want to be something like 
`child.SchemaDefintionError` so that the context of the SDE is the element that 
specifies EOP instead of the parent. Once a user knows where the EOP elment is, 
it should be easy to find the offending parent. But the other way is maybe 
difficult, it could be hard to find the sole EOP child if there a bunch of 
children.



##########
daffodil-core/src/main/scala/org/apache/daffodil/io/InputSource.scala:
##########
@@ -181,6 +181,12 @@ abstract class InputSource {
    */
   def releasePosition(bytePos0b: Long): Unit
 
+  /**
+   * Get the number of bytes that are available to be read until the end of
+   * the data stream.
+   */
+  lazy val bytesTillEndOfDataStream: Long = -1L

Review Comment:
   Suggest we make this a def so we require classes that extend InputSource 
create implement this function.
   
   Also, I'm wondering if we could run into any issues with this being a lazy 
val? My concern is that `bytesUntilEndOfDataStream` is only correct at a single 
point in the stream. So if we calcuulate this lazy val at one position, move 
the bit position, and then access it again, then stored 
bytesUntilEndOfDataStream is no longer correct. I can't think of an example 
where this is actually possible, but we could avoid this entirely the API is 
about just the EOD position. For example, what if instead we do something like 
this:
   
   ```scala
   /**
     * Getteh byte position of End Of Data, using zero-based indexing.
     * 
     * Throws some exception if end of data could not be found within an 
implementation defined limit.
     */
   def endOfDataPosition(): Long
   ```
   
   This way implementations can try to find and store the EOD byte position 
without needing to know the current byte position. And if anything like 
endOfParent parsers need to calculate a length to the EOD they can get this 
position and subtract their current position.



##########
daffodil-core/src/main/scala/org/apache/daffodil/io/InputSource.scala:
##########
@@ -564,6 +570,22 @@ class BucketingInputSource(
     headBucketBytePosition0b += bytesRemoved
     oldestBucketIndex = 0
   }
+
+  override lazy val bytesTillEndOfDataStream: Long = {
+    val initialBytePosition = position()
+    val endOfDataNotReached = fillBucketsToIndex(maxNumberOfNonNullBuckets, 8)

Review Comment:
   Two things:
   
   1. Instead of passing in 8 for the second parameter for the 
bytesNeededInBucket, I'd suggest we go with `bucketSize`. That way the goal is 
to fill all of the last bucket instead of just the first 8 bytes and so we are 
guaranteed to try have all `maxNumberOfNonNullBuckets` filled instead of the 
last one just being possibly 8 bytes full.
   2. The first parameter of `fillBucketsToIndex` isn't the number of buckets 
to fill, it is the goal index of the bucket to fill up to. So for example, if 
our current position was in bucket 100 and lets say `maxNumberOfNonNullBuckets` 
was 2, then I think we want to try to fill up buckets 100 and 101 (so that we 
have 2 full buckets, including our current bucket). So in this example the 
first parameter would want to be 101 instead of `maxNumberOfNonNullBuckets`.
   
   So I think the math for that all looks something like this:
   ```scala
   val curBucketIndex = getBucketIndex(curBytePosition0b)
   val goalBucketIndex = curBucketIndex + maxNumberOfNonNullBuckets - 1
   val endOfDataNotReached = fillBucketsToIndex(goalBucket, bucketSize)
   ```



##########
daffodil-core/src/main/scala/org/apache/daffodil/io/InputSource.scala:
##########
@@ -645,4 +667,11 @@ class ByteBufferInputSource(byteBuffer: ByteBuffer) 
extends InputSource {
   override def close(): Unit = {
     // do nothing. No resources to release.
   }
+
+  override lazy val bytesTillEndOfDataStream: Long = {
+    val initialPosition = position()
+    val br = byteBuffer.remaining
+    position(initialPosition)
+    br
+  }

Review Comment:
   `byterBuffer.remaining` doesn't change the position so we don't need to 
capture and restore it.
   
   Note that if we switch to `endOfDataPosition`, that could be calculated when 
the class is initialized, and it would just be something like `override val 
endOfDataPostion = bb.remaining()`



##########
daffodil-core/src/main/scala/org/apache/daffodil/io/InputSource.scala:
##########
@@ -564,6 +570,22 @@ class BucketingInputSource(
     headBucketBytePosition0b += bytesRemoved
     oldestBucketIndex = 0
   }
+
+  override lazy val bytesTillEndOfDataStream: Long = {
+    val initialBytePosition = position()
+    val endOfDataNotReached = fillBucketsToIndex(maxNumberOfNonNullBuckets, 8)
+    if (!endOfDataNotReached) {
+      val numBytesFilled = totalBytesBucketed - initialBytePosition
+      // reset the byte position to the initial byte position
+      position(initialBytePosition)
+      numBytesFilled
+    } else {
+      position(initialBytePosition)
+      throw new Exception(
+        s"Attempted to fill to end of data stream, but did not reach end of 
data stream before maxCacheSizeInBytes: ${maxCacheSizeInBytes}."
+      )

Review Comment:
   I'm wondering if we should return a Option/Maybe here instead of throwing an 
exception. That way we can let callers decide how to handle the cases where an 
implementation couldn't find EOD. I imagine in most cases they'll want to 
create an SDE, but maybe there are other options. And we can just document that 
the function might return None if the end of data stream couldn't be found.



##########
daffodil-core/src/main/scala/org/apache/daffodil/runtime1/processors/parsers/ParserTraits.scala:
##########
@@ -181,7 +181,14 @@ trait BitLengthFromBitLimitMixin {
   }
 
   def getLengthInBits(pstate: PState): Long = {
-    val len = pstate.bitLimit0b.get - pstate.bitPos0b
-    len
+    if (pstate.bitLimit0b.isDefined) {
+      val len = pstate.bitLimit0b.get - pstate.bitPos0b
+      len
+    } else if (isEndOfParent) {
+      val byteLimit = pstate.dataInputStream.bytesTillEndOfDataStream
+      byteLimit * 8

Review Comment:
   Is it possible for our current bitPosition to not on a byte boundary? Or do 
all EOP types required byte alignment?
   
   I'm not sure if packed decimals can by half-byte aligned or not (I think 
they can?), but I'm pretty xs:hexBinary does not require a specific alignment. 
For example:
   
   ```xml
   <element name="root" dfdl:lengthKind="endOfParent">
     <complexType>
       <sequene>
         <element name="foo" type="xs:int" dfdl:lengthKind="4" 
dfdl:lengthUnits="bits" ... />
         <element name="leftOver" type="xs:hexBinary" 
dfdl:lengthKind="endOfParent" ... />
       </sequence>
     </complexType>
   </element>
   ```
   This feels like a reasonable way to capture leftOver data, even if we aren't 
byte aligned.
   
   I think this is maybe another reason in favor for switching to something 
like endOfDataPosition, then this becomes something like
   
   ```scala
   val dis = pstate.dataInputStream
   val len = (dis.endOfDataPosition * 8) - dis.curBitPos0b
   ```
   Which works as expected even if curBitPosition is not byte aligned.



##########
daffodil-core/src/main/scala/org/apache/daffodil/io/InputSourceDataInputStream.scala:
##########
@@ -136,6 +136,8 @@ final class InputSourceDataInputStream private (val 
inputSource: InputSource)
    */
   def hasReachedEndOfData: Boolean = inputSource.hasReachedEndOfData
 
+  lazy val bytesTillEndOfDataStream: Long = 
inputSource.bytesTillEndOfDataStream

Review Comment:
   For consistency, I'd suggest we make this a def. This is essentially a 
delegate to the underlying implementation, and we can let them decide best how 
the result show be stored or recalcuated. 



##########
daffodil-core/src/main/scala/org/apache/daffodil/core/grammar/ElementBaseGrammarMixin.scala:
##########
@@ -252,6 +253,49 @@ trait ElementBaseGrammarMixin
   }
   final lazy val prefixedLengthBody = prefixedLengthElementDecl.parsedValue
 
+  def checkEndOfParentRestrictionsOnCurrentElement(optParentELU: 
Option[LengthUnits]): Unit = {
+    val currentElement: ElementBase = this
+    if (currentElement.lengthKind != LengthKind.EndOfParent) {} else {
+      schemaDefinitionWhen(
+        currentElement.hasTerminator,
+        "element is specified as dfdl:lengthKind=\"endOfParent\", but 
specifies a dfdl:terminator."
+      )
+      schemaDefinitionWhen(
+        currentElement.trailingSkip != 0,
+        "element is specified as dfdl:lengthKind=\"endOfParent\", but 
specifies a non-zero dfdl:trailingSkip."
+      )
+      schemaDefinitionWhen(
+        currentElement.maxOccurs > 1,
+        "element is specified as dfdl:lengthKind=\"endOfParent\", but 
specifies a maxOccurs greater than 1."
+      )
+      schemaDefinitionWhen(
+        currentElement.impliedRepresentation == Representation.Text
+          && (!currentElement.isKnownEncoding || 
!currentElement.knownEncodingIsFixedWidth || 
currentElement.knownEncodingWidthInBits != 8)
+          && (!optParentELU.contains(LengthUnits.Characters)),

Review Comment:
   Using contains will return false if optParentELU is None, which will make 
this a non-SDE.
   
   Though, in theory we should never have a None here since if this is an EOP 
element it must be in something that has an ELU. So maybe assert in this 
function that opParentELU is defined. Or maybe better woudl be to change 
optParentELU in this function not be an Option, so you can't even pass in an 
invalid ELU. And it requires that callers are careful to make sure there is an 
ELU.



##########
daffodil-core/src/main/scala/org/apache/daffodil/core/grammar/ElementBaseGrammarMixin.scala:
##########
@@ -252,6 +253,49 @@ trait ElementBaseGrammarMixin
   }
   final lazy val prefixedLengthBody = prefixedLengthElementDecl.parsedValue
 
+  def checkEndOfParentRestrictionsOnCurrentElement(optParentELU: 
Option[LengthUnits]): Unit = {
+    val currentElement: ElementBase = this
+    if (currentElement.lengthKind != LengthKind.EndOfParent) {} else {
+      schemaDefinitionWhen(
+        currentElement.hasTerminator,
+        "element is specified as dfdl:lengthKind=\"endOfParent\", but 
specifies a dfdl:terminator."
+      )
+      schemaDefinitionWhen(
+        currentElement.trailingSkip != 0,
+        "element is specified as dfdl:lengthKind=\"endOfParent\", but 
specifies a non-zero dfdl:trailingSkip."
+      )
+      schemaDefinitionWhen(
+        currentElement.maxOccurs > 1,
+        "element is specified as dfdl:lengthKind=\"endOfParent\", but 
specifies a maxOccurs greater than 1."
+      )
+      schemaDefinitionWhen(
+        currentElement.impliedRepresentation == Representation.Text
+          && (!currentElement.isKnownEncoding || 
!currentElement.knownEncodingIsFixedWidth || 
currentElement.knownEncodingWidthInBits != 8)
+          && (!optParentELU.contains(LengthUnits.Characters)),
+        "element is specified as dfdl:lengthKind=\"endOfParent\", but the 
element has text representation, and does not have a single-byte character set 
encoding, and the effective length units of the parent is not 'characters'."
+      )
+      if (currentElement.isSimpleType) {
+        schemaDefinitionUnless(
+          (currentElement.primType eq PrimType.String)
+            || (currentElement.representation == Representation.Text)
+            || (currentElement.primType eq PrimType.HexBinary)
+            || (currentElement.representation == Representation.Binary
+              && Seq(BinaryNumberRep.Packed, BinaryNumberRep.Bcd, 
BinaryNumberRep.Ibm4690Packed)
+                .contains(currentElement.binaryNumberRep))
+            || (currentElement.representation == Representation.Binary
+              && currentElement.optionBinaryCalendarRep.isDefined
+              && Seq(
+                BinaryCalendarRep.Packed,
+                BinaryCalendarRep.Bcd,
+                BinaryCalendarRep.Ibm4690Packed
+              )
+                .contains(currentElement.binaryCalendarRep)),
+          "element is a simple type specified as 
dfdl:lengthKind=\"endOfParent\", but isn't a string type, doesn't have text 
representation, isn't a hexbinary type, or doesn't have binary representation 
with packed decimal representation."
+        )

Review Comment:
   I'm wondering if this last check as already handled by below changes? FOr 
example, just below this you have a SDE for
   
   ```
           SDE("lengthKind='endOfParent' only supported for packed binary 
formats.")
   ```
   
   Seems like it would only be possible to hit one of these SDEs. I don't think 
we necessarily need to have all checks in this 
`checkEndOfParentRestrictionsOnCurrentElement` if they are handled in other 
places with potentially more specific error messages.



##########
daffodil-core/src/main/scala/org/apache/daffodil/core/grammar/ElementBaseGrammarMixin.scala:
##########
@@ -1072,6 +1163,7 @@ trait ElementBaseGrammarMixin
       case PrimType.Boolean => {
         lengthKind match {
           case LengthKind.Prefixed => new BinaryBooleanPrefixedLength(this)
+          case LengthKind.EndOfParent => new 
BinaryBooleanEndOfParentLength(this)

Review Comment:
   It looks like the spec only allows EOP with packed binary elements. Is 
boolean allowed?



##########
daffodil-core/src/main/scala/org/apache/daffodil/core/grammar/TermGrammarMixin.scala:
##########
@@ -84,4 +96,181 @@ trait TermGrammarMixin extends AlignedMixin with 
BitOrderMixin with TermRuntime1
     MandatoryTextAlignment(this, knownEncodingAlignmentInBits, true)
   }
 
+  def myEffectiveLengthUnits(optLastNonEOPLU: Option[LengthUnits]): 
Option[LengthUnits] = {

Review Comment:
   Should we drop `my`? We don't really use that convention very often, and I 
think in the context it's clear that calling `effectiveLengthUnits()` returns 
the ELU of the current element.



##########
daffodil-core/src/main/scala/org/apache/daffodil/core/grammar/TermGrammarMixin.scala:
##########
@@ -84,4 +96,181 @@ trait TermGrammarMixin extends AlignedMixin with 
BitOrderMixin with TermRuntime1
     MandatoryTextAlignment(this, knownEncodingAlignmentInBits, true)
   }
 
+  def myEffectiveLengthUnits(optLastNonEOPLU: Option[LengthUnits]): 
Option[LengthUnits] = {
+    val elu = this match {
+      case e: ElementBase =>
+        e.lengthKind match {
+          case LengthKind.EndOfParent => optLastNonEOPLU
+          case LengthKind.Explicit | LengthKind.Prefixed => Some(e.lengthUnits)
+          case LengthKind.Pattern => Some(LengthUnits.Characters)
+          case _ => None
+        }
+      case c: ChoiceTermBase if c.choiceLengthKind == 
ChoiceLengthKind.Explicit =>
+        Some(LengthUnits.Bytes)
+      // the spec doesn't actually account for this case, but logically it 
makes
+      // sense that ELU of a sequence is the ELU of its parent that 
technically is the
+      // last non-EndOfParent ELU.
+      case s: SequenceTermBase => optLastNonEOPLU
+      case _ => None
+    }
+    elu
+  }
+
+  final lazy val childrenEndOfParent: Seq[Term] = 
LV(Symbol("childrenEndOfParent")) {
+    val gms = termChildren
+    val chls = gms.flatMap {
+      case eb: ElementBase if eb.lengthKind == LengthKind.EndOfParent => 
Seq(eb)
+      case eb: ElementBase => Nil
+      case c: ChoiceTermBase => Nil
+      case mg: ModelGroup => mg.childrenEndOfParent
+    }
+    chls
+  }.value
+
+  def checkEndOfParentRestrictions(optLastNonEOPLU: Option[LengthUnits]): Unit 
= {
+    val term = this
+    lazy val eopChildren = this.childrenEndOfParent
+    // checks
+    term match {
+      case rootElem: Root if rootElem.lengthKind == LengthKind.EndOfParent => {
+        
rootElem.checkEndOfParentRestrictionsOnCurrentElement(Some(LengthUnits.Characters))
+        eopChildren.foreach {
+          case e: ElementBase => {
+            rootElem.checkChildrenForSiblingsAfterEOPElement(e)
+            Assert.invariant(
+              optLastNonEOPLU.isDefined,
+              "Effective Length Units of parent should not be None"
+            )
+            e.checkEndOfParentRestrictionsOnCurrentElement(optLastNonEOPLU)
+          }
+          case _ => // do nothing
+        }
+      }
+      case parent: ElementBase if eopChildren.nonEmpty => {

Review Comment:
   I think this case is almost exactly the same as the Root case except the 
root case does a `rootElem.checkEndOfParentResriciontiosnOfCurrrentElement`. 
Can we merge the two and do something like:
   
   ```scala
   if (parent.isInstanceOf[Root]) {
     parent.checkEndOfParentRestrictionsOnCurrentElement(optParentELU)
   }
   ```
   
   Then everything else can remain the same?



##########
daffodil-core/src/main/scala/org/apache/daffodil/core/grammar/TermGrammarMixin.scala:
##########
@@ -84,4 +96,181 @@ trait TermGrammarMixin extends AlignedMixin with 
BitOrderMixin with TermRuntime1
     MandatoryTextAlignment(this, knownEncodingAlignmentInBits, true)
   }
 
+  def myEffectiveLengthUnits(optLastNonEOPLU: Option[LengthUnits]): 
Option[LengthUnits] = {
+    val elu = this match {
+      case e: ElementBase =>
+        e.lengthKind match {
+          case LengthKind.EndOfParent => optLastNonEOPLU
+          case LengthKind.Explicit | LengthKind.Prefixed => Some(e.lengthUnits)
+          case LengthKind.Pattern => Some(LengthUnits.Characters)
+          case _ => None
+        }
+      case c: ChoiceTermBase if c.choiceLengthKind == 
ChoiceLengthKind.Explicit =>
+        Some(LengthUnits.Bytes)
+      // the spec doesn't actually account for this case, but logically it 
makes
+      // sense that ELU of a sequence is the ELU of its parent that 
technically is the
+      // last non-EndOfParent ELU.
+      case s: SequenceTermBase => optLastNonEOPLU
+      case _ => None
+    }
+    elu
+  }
+
+  final lazy val childrenEndOfParent: Seq[Term] = 
LV(Symbol("childrenEndOfParent")) {
+    val gms = termChildren
+    val chls = gms.flatMap {
+      case eb: ElementBase if eb.lengthKind == LengthKind.EndOfParent => 
Seq(eb)
+      case eb: ElementBase => Nil
+      case c: ChoiceTermBase => Nil
+      case mg: ModelGroup => mg.childrenEndOfParent
+    }
+    chls
+  }.value
+
+  def checkEndOfParentRestrictions(optLastNonEOPLU: Option[LengthUnits]): Unit 
= {
+    val term = this
+    lazy val eopChildren = this.childrenEndOfParent
+    // checks
+    term match {
+      case rootElem: Root if rootElem.lengthKind == LengthKind.EndOfParent => {
+        
rootElem.checkEndOfParentRestrictionsOnCurrentElement(Some(LengthUnits.Characters))
+        eopChildren.foreach {
+          case e: ElementBase => {
+            rootElem.checkChildrenForSiblingsAfterEOPElement(e)
+            Assert.invariant(
+              optLastNonEOPLU.isDefined,
+              "Effective Length Units of parent should not be None"
+            )
+            e.checkEndOfParentRestrictionsOnCurrentElement(optLastNonEOPLU)
+          }
+          case _ => // do nothing
+        }
+      }
+      case parent: ElementBase if eopChildren.nonEmpty => {
+        eopChildren.foreach {
+          case e: ElementBase => {
+            val optParentELU = parent.myEffectiveLengthUnits(optLastNonEOPLU)

Review Comment:
   Reading over this, I have to keep reminding myself that parent is not 
reaching up to this elements parent, it's just `this`. I know using `parent` 
better matches the wording in the spec, but would it make sense to name this as 
`elem` or `e` or something, and then this case would be `case child: 
ELemenetBase`? I think that still makes the parent children relationship clear, 
but doesn't confuse with `parent`, which I tihnk in some instances we do use to 
actually reach up to the parent element. 



##########
daffodil-core/src/main/scala/org/apache/daffodil/core/grammar/TermGrammarMixin.scala:
##########
@@ -84,4 +96,181 @@ trait TermGrammarMixin extends AlignedMixin with 
BitOrderMixin with TermRuntime1
     MandatoryTextAlignment(this, knownEncodingAlignmentInBits, true)
   }
 
+  def myEffectiveLengthUnits(optLastNonEOPLU: Option[LengthUnits]): 
Option[LengthUnits] = {
+    val elu = this match {
+      case e: ElementBase =>
+        e.lengthKind match {
+          case LengthKind.EndOfParent => optLastNonEOPLU
+          case LengthKind.Explicit | LengthKind.Prefixed => Some(e.lengthUnits)
+          case LengthKind.Pattern => Some(LengthUnits.Characters)
+          case _ => None
+        }
+      case c: ChoiceTermBase if c.choiceLengthKind == 
ChoiceLengthKind.Explicit =>
+        Some(LengthUnits.Bytes)
+      // the spec doesn't actually account for this case, but logically it 
makes
+      // sense that ELU of a sequence is the ELU of its parent that 
technically is the
+      // last non-EndOfParent ELU.
+      case s: SequenceTermBase => optLastNonEOPLU
+      case _ => None
+    }
+    elu
+  }
+
+  final lazy val childrenEndOfParent: Seq[Term] = 
LV(Symbol("childrenEndOfParent")) {
+    val gms = termChildren
+    val chls = gms.flatMap {
+      case eb: ElementBase if eb.lengthKind == LengthKind.EndOfParent => 
Seq(eb)
+      case eb: ElementBase => Nil
+      case c: ChoiceTermBase => Nil
+      case mg: ModelGroup => mg.childrenEndOfParent
+    }
+    chls
+  }.value
+
+  def checkEndOfParentRestrictions(optLastNonEOPLU: Option[LengthUnits]): Unit 
= {
+    val term = this
+    lazy val eopChildren = this.childrenEndOfParent
+    // checks
+    term match {
+      case rootElem: Root if rootElem.lengthKind == LengthKind.EndOfParent => {
+        
rootElem.checkEndOfParentRestrictionsOnCurrentElement(Some(LengthUnits.Characters))
+        eopChildren.foreach {
+          case e: ElementBase => {
+            rootElem.checkChildrenForSiblingsAfterEOPElement(e)
+            Assert.invariant(
+              optLastNonEOPLU.isDefined,
+              "Effective Length Units of parent should not be None"
+            )
+            e.checkEndOfParentRestrictionsOnCurrentElement(optLastNonEOPLU)
+          }
+          case _ => // do nothing
+        }
+      }
+      case parent: ElementBase if eopChildren.nonEmpty => {
+        eopChildren.foreach {
+          case e: ElementBase => {
+            val optParentELU = parent.myEffectiveLengthUnits(optLastNonEOPLU)
+            parent.lengthKind match {
+              case LengthKind.Implicit | LengthKind.Delimited =>
+                schemaDefinitionError(
+                  "element is specified as dfdl:lengthKind=\"endOfParent\", 
but its parent is an element with dfdl:lengthKind 'implicit' or 'delimited'."
+                )
+              case _ => // do nothing
+            }
+            parent.checkChildrenForSiblingsAfterEOPElement(e)
+            Assert.invariant(
+              optParentELU.isDefined,
+              "Effective Length Units of parent should not be None"
+            )
+            e.checkEndOfParentRestrictionsOnCurrentElement(optParentELU)
+          }
+          case _ => // do nothing

Review Comment:
   Is this case possible? I think `childrenEndOfParent` is always Elements? Do 
we want to change `childrenEndOfParent` to be a `Seq[ElementBase]`, then we can 
avoid these match cases?



##########
daffodil-core/src/main/scala/org/apache/daffodil/core/grammar/TermGrammarMixin.scala:
##########
@@ -84,4 +96,181 @@ trait TermGrammarMixin extends AlignedMixin with 
BitOrderMixin with TermRuntime1
     MandatoryTextAlignment(this, knownEncodingAlignmentInBits, true)
   }
 
+  def myEffectiveLengthUnits(optLastNonEOPLU: Option[LengthUnits]): 
Option[LengthUnits] = {
+    val elu = this match {
+      case e: ElementBase =>
+        e.lengthKind match {
+          case LengthKind.EndOfParent => optLastNonEOPLU
+          case LengthKind.Explicit | LengthKind.Prefixed => Some(e.lengthUnits)
+          case LengthKind.Pattern => Some(LengthUnits.Characters)
+          case _ => None
+        }
+      case c: ChoiceTermBase if c.choiceLengthKind == 
ChoiceLengthKind.Explicit =>
+        Some(LengthUnits.Bytes)
+      // the spec doesn't actually account for this case, but logically it 
makes
+      // sense that ELU of a sequence is the ELU of its parent that 
technically is the
+      // last non-EndOfParent ELU.
+      case s: SequenceTermBase => optLastNonEOPLU
+      case _ => None
+    }
+    elu
+  }
+
+  final lazy val childrenEndOfParent: Seq[Term] = 
LV(Symbol("childrenEndOfParent")) {
+    val gms = termChildren
+    val chls = gms.flatMap {
+      case eb: ElementBase if eb.lengthKind == LengthKind.EndOfParent => 
Seq(eb)
+      case eb: ElementBase => Nil
+      case c: ChoiceTermBase => Nil
+      case mg: ModelGroup => mg.childrenEndOfParent
+    }
+    chls
+  }.value
+
+  def checkEndOfParentRestrictions(optLastNonEOPLU: Option[LengthUnits]): Unit 
= {
+    val term = this
+    lazy val eopChildren = this.childrenEndOfParent
+    // checks
+    term match {
+      case rootElem: Root if rootElem.lengthKind == LengthKind.EndOfParent => {
+        
rootElem.checkEndOfParentRestrictionsOnCurrentElement(Some(LengthUnits.Characters))
+        eopChildren.foreach {
+          case e: ElementBase => {
+            rootElem.checkChildrenForSiblingsAfterEOPElement(e)
+            Assert.invariant(
+              optLastNonEOPLU.isDefined,
+              "Effective Length Units of parent should not be None"
+            )
+            e.checkEndOfParentRestrictionsOnCurrentElement(optLastNonEOPLU)
+          }
+          case _ => // do nothing
+        }
+      }
+      case parent: ElementBase if eopChildren.nonEmpty => {
+        eopChildren.foreach {
+          case e: ElementBase => {
+            val optParentELU = parent.myEffectiveLengthUnits(optLastNonEOPLU)
+            parent.lengthKind match {
+              case LengthKind.Implicit | LengthKind.Delimited =>
+                schemaDefinitionError(
+                  "element is specified as dfdl:lengthKind=\"endOfParent\", 
but its parent is an element with dfdl:lengthKind 'implicit' or 'delimited'."
+                )
+              case _ => // do nothing
+            }
+            parent.checkChildrenForSiblingsAfterEOPElement(e)
+            Assert.invariant(
+              optParentELU.isDefined,
+              "Effective Length Units of parent should not be None"
+            )
+            e.checkEndOfParentRestrictionsOnCurrentElement(optParentELU)
+          }
+          case _ => // do nothing
+        }
+      }
+      case s: SequenceTermBase if eopChildren.nonEmpty => {
+        schemaDefinitionWhen(
+          s.separatorPosition == SeparatorPosition.Postfix,
+          "element is specified as dfdl:lengthKind=\"endOfParent\", but is in 
a sequence with dfdl:separatorPosition defined as 'postfix'."
+        )
+        schemaDefinitionWhen(
+          s.sequenceKind != SequenceKind.Ordered,
+          "element is specified as dfdl:lengthKind=\"endOfParent\", but is in 
a sequence with dfdl:sequenceKind defined as 'unordered'."
+        )
+        schemaDefinitionWhen(
+          s.hasTerminator,
+          "element is specified as dfdl:lengthKind=\"endOfParent\", but is in 
a sequence with a dfdl:terminator."
+        )
+        schemaDefinitionWhen(
+          s.realElementChildren.exists(e => e.floating == YesNo.Yes),
+          "element is specified as dfdl:lengthKind=\"endOfParent\", but is in 
a sequence with elements defining dfdl:floating='yes'."
+        )
+        schemaDefinitionWhen(
+          s.trailingSkip != 0,
+          "element is specified as dfdl:lengthKind=\"endOfParent\", but is in 
a sequence with a non-zero dfdl:trailingSkip."
+        )
+        eopChildren.foreach {
+          case e: ElementBase => {
+            s.checkChildrenForSiblingsAfterEOPElement(e)
+            e.checkEndOfParentRestrictionsOnCurrentElement(optLastNonEOPLU)
+          }
+          case _ => // do nothing
+        }
+      }
+      case c: ChoiceTermBase if eopChildren.nonEmpty => {
+        // TODO: The DFDL spec (12.3.6) explicitly mentions that an 
EndOfParent element
+        // can be terminated by a choice with choiceLengthKind='explicit', 
with no reference
+        // to implicit length choices. Later on in 12.3.6, it specified what 
the parent Effective
+        // Length Units would be for an explicit length choice, again with no 
reference to
+        // implicit length choices. The only way to get an EndOfParent element 
within an
+        // implicit length choice would be to have it actually be terminated 
by the choice's
+        // parent, similar to how we treat elements within an element that is 
also EndOfParent.
+        // The only mention of implicit length choices in the DFDL Spec is to 
mention SDEs when
+        // an EndOfParent element is enclosed by a choice with 
choiceLengthKind='implicit'. We
+        // think that may be a typo, so for now we disallow 
ChoiceLengthKind.Implicit
+        // enclosing an EndOfParent element.
+        // See Daffodil-3080
+        schemaDefinitionWhen(
+          c.choiceLengthKind == ChoiceLengthKind.Implicit,
+          "element is specified as dfdl:lengthKind=\"endOfParent\", but its 
parent is a choice with dfdl:choiceLengthKind 'implicit'."
+        )
+        schemaDefinitionWhen(
+          c.hasTerminator,
+          "element is specified as dfdl:lengthKind=\"endOfParent\", but is in 
a choice with a dfdl:terminator."
+        )
+        schemaDefinitionWhen(
+          c.trailingSkip != 0,
+          "element is specified as dfdl:lengthKind=\"endOfParent\", but is in 
a choice with a non-zero dfdl:trailingSkip."
+        )
+        val optParentELU = c.myEffectiveLengthUnits(optLastNonEOPLU)
+        eopChildren.foreach {
+          case e: ElementBase => {
+            c.checkChildrenForSiblingsAfterEOPElement(e)
+            Assert.invariant(
+              optParentELU.isDefined,
+              "Effective Length Units of parent should not be None"
+            )
+            e.checkEndOfParentRestrictionsOnCurrentElement(optParentELU)
+          }
+          case _ => // do nothing
+        }
+      }
+      case _ => // do nothing
+    }
+    // end checks
+    term.termChildren.foreach { e =>
+      lazy val optParentELU = term.myEffectiveLengthUnits(optLastNonEOPLU)
+      term match {
+        case parent @ (_: ElementBase | _: ChoiceTermBase | _: 
SequenceTermBase) => {
+          e.checkEndOfParentRestrictions(optParentELU)
+        }
+        case _ => // do nothing

Review Comment:
   What is this default case match and so not recursing into? It feels like we 
should recurse into everything so this just wants to be 
`term.checkEndOfParentRestrictions(optParentELU)` 



##########
daffodil-core/src/main/scala/org/apache/daffodil/io/InputSource.scala:
##########
@@ -564,6 +570,22 @@ class BucketingInputSource(
     headBucketBytePosition0b += bytesRemoved
     oldestBucketIndex = 0
   }
+
+  override lazy val bytesTillEndOfDataStream: Long = {
+    val initialBytePosition = position()

Review Comment:
   I don't think `fillBucketsToIndex` changes the position so I don't think you 
need to capture this and restore the position.



##########
daffodil-core/src/main/scala/org/apache/daffodil/runtime1/processors/parsers/SpecifiedLengthParsers.scala:
##########
@@ -132,6 +132,17 @@ class SpecifiedLengthPatternParser(
   }
 }
 
+class SpecifiedLengthEndOfParentParser(
+  eParser: Parser,
+  erd: ElementRuntimeData
+) extends SpecifiedLengthParserBase(eParser, erd),
+    BitLengthFromBitLimitMixin(true) {
+
+  override protected def getBitLength(s: PState): MaybeULong = {
+    MaybeULong(super[BitLengthFromBitLimitMixin].getBitLength(s))
+  }

Review Comment:
   I'm wondering if we need something different for 
SpecifiedLengthEndOfParentParser, at least the context of complex types. If we 
look at SpecifiedLengthParserBase, then logic it follows for complex types is 
essentially
   
   1. getBitLength
   2. ignored checking isDefinedForLength
   3. withBitLengthLimit { children.parse() }
   4. skip any remaining bits not consumed by children
   
   Note that the reason for step 2 to ignore isDefinedForLength is because 
isDefinedForLength will cause the underlying input source too fill a bunch of 
buckets for the full length of the complex types, but there are limits to how 
much we can bucket.
   
   Instead, we just call all the children, have them slowly read all the bytes 
as needed, and then skip whatever is left over at the end. So we don't require 
the full length of the complex type until we finish parsing the children.
   
   But say we have an EOP complex root element. The first thing this does is 
call getBiLength which will  to scan the entire stream for the EOD, which does 
exactly what we were trying to avoid by skipping isDefinedForLength in step 2.
   
   So it feels like we need special logic for complex EOP elements so that they 
do something different if there is no current bit limit. For example, the logic 
wants to be more like this for complex EOP elements:
   
   ```scala
   if (bitLimit.isDefined) {
     val len = getBitLength  // will use bitLimit
     withBitLengthLength { children.parse }
     val reminaingBits = ...
     dis.skip(remaiingBits)
   } else {
      children.parse()
      val len = getBitLength // will scan for EOD
      val reminaingBits = ...
      dis.skip(remainingBits)
   }
   ```
   So we kindof need to reverse the order of getBitLengthand children.parse.
   
   I'm not sure if it makes sense to change SpecifiedLengthParserBase to know 
about EOP and check for bitLimit or SpecifiedLengthEndOfParentParser wants to 
be it's own thing, maybe with helper functions that the Base parser has for the 
common things like skipping remaining bits.
   
   Note this is only an issue for complex types, simple types need the length 
no matter what.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to