From d3266d642f55430c7fda9aba2b65a88dc22ab4ed Mon Sep 17 00:00:00 2001 From: olabusayoT <50379531+olabusayoT@users.noreply.github.com> Date: Mon, 24 Nov 2025 17:17:59 -0500 Subject: [PATCH 1/2] Add logic for separator/initiator/prefixed/terminator alignment - Implement logic to calculate prefix, infix, and postfix separator alignments and lengths in sequence terms. - Added new alignment logic/test cases covering initiator alignment, prefixed length elements, terminator alignment and value MTA (for optimization) behavior. - Enhanced alignment calculation by excluding prior alignment from the contentStart, as alignmentApprox is a good place to start the contentStartAlignment. - Updated schemas and TDML files to include test data for new alignment scenarios. DAFFODIL-2295, DAFFODIL-3056, DAFFODIL-3057 --- .../daffodil/core/grammar/AlignedMixin.scala | 236 +++++-- .../section12/aligned_data/Aligned_Data.tdml | 601 ++++++++++++++++++ .../apache/daffodil/usertests/SepTests.tdml | 185 +++++- .../aligned_data/TestAlignedData.scala | 21 + .../daffodil/usertests/TestSepTests.scala | 7 + 5 files changed, 1001 insertions(+), 49 deletions(-) diff --git a/daffodil-core/src/main/scala/org/apache/daffodil/core/grammar/AlignedMixin.scala b/daffodil-core/src/main/scala/org/apache/daffodil/core/grammar/AlignedMixin.scala index a6d52b8a5c..9ef92560ce 100644 --- a/daffodil-core/src/main/scala/org/apache/daffodil/core/grammar/AlignedMixin.scala +++ b/daffodil-core/src/main/scala/org/apache/daffodil/core/grammar/AlignedMixin.scala @@ -21,17 +21,24 @@ import org.apache.daffodil.core.dsom.ElementBase import org.apache.daffodil.core.dsom.ModelGroup import org.apache.daffodil.core.dsom.QuasiElementDeclBase import org.apache.daffodil.core.dsom.Root +import org.apache.daffodil.core.dsom.SequenceTermBase import org.apache.daffodil.core.dsom.Term import org.apache.daffodil.lib.exceptions.Assert import org.apache.daffodil.lib.schema.annotation.props.gen.AlignmentKind import org.apache.daffodil.lib.schema.annotation.props.gen.AlignmentUnits import org.apache.daffodil.lib.schema.annotation.props.gen.LengthKind +import org.apache.daffodil.lib.schema.annotation.props.gen.LengthKind.Explicit +import org.apache.daffodil.lib.schema.annotation.props.gen.LengthKind.Prefixed import org.apache.daffodil.lib.schema.annotation.props.gen.LengthUnits +import org.apache.daffodil.lib.schema.annotation.props.gen.SeparatorPosition import org.apache.daffodil.lib.util.Math case class AlignmentMultipleOf(nBits: Long) { + // To combine AlignmentMultipleOfs, use * def *(that: AlignmentMultipleOf) = AlignmentMultipleOf(Math.gcd(nBits, that.nBits)) def %(that: AlignmentMultipleOf) = nBits % that.nBits + + // To combine AlignmentMultipleOfs and lengths, use + def +(that: LengthApprox) = AlignmentMultipleOf(Math.gcd(nBits, nBits + that.nBits)) } @@ -134,17 +141,17 @@ trait AlignedMixin extends GrammarMixin { self: Term => LengthExact(trailingSkipInBits) } - // FIXME: DAFFODIL-2295 - // Does not take into account that in a sequence, what may be prior may be a separator. - // The separator is text in some encoding, might not be the same as this term's encoding, and - // the alignment will be left where that text leaves it. - // + /** + * The priorAlignmentApprox doesn't need to take into account the separator + * alignment/length as that comes after the priorAlignmentApprox, but before + * the leadingSkip. + */ private lazy val priorAlignmentApprox: AlignmentMultipleOf = LV(Symbol("priorAlignmentApprox")) { if (this.isInstanceOf[Root] || this.isInstanceOf[QuasiElementDeclBase]) { AlignmentMultipleOf( 0 - ) // root and quasi elements are aligned with anything // TODO: really? Why quasi-elements - they should have implicit alignment ? + ) // root and quasi elements are aligned with anything (quasi elements are always 1 aligned) } else { val priorSibs = potentialPriorTerms val arraySelfAlignment = @@ -201,7 +208,7 @@ trait AlignedMixin extends GrammarMixin { self: Term => // Return 0 here, unordered alignment will be handled by unorderedSequenceSelfAlignment AlignmentMultipleOf(0) } else { - ps.endingAlignmentApprox + ps.endingAlignmentWithRightFramingApprox } eaa } @@ -228,29 +235,161 @@ trait AlignedMixin extends GrammarMixin { self: Term => } }.value + private lazy val separatorPrefixMTAApprox = + this.optLexicalParent match { + case Some(s: SequenceTermBase) if s.hasSeparator => + import SeparatorPosition.* + s.separatorPosition match { + case Prefix | Infix => LengthMultipleOf(s.knownEncodingAlignmentInBits) + case Postfix => LengthMultipleOf(0) + } + case _ => LengthMultipleOf(0) + } + + private lazy val separatorPostfixMTAApprox = + this.optLexicalParent match { + case Some(s: SequenceTermBase) if s.hasSeparator => + import SeparatorPosition.* + s.separatorPosition match { + case Prefix | Infix => LengthMultipleOf(0) + case Postfix => LengthMultipleOf(s.knownEncodingAlignmentInBits) + } + case _ => LengthMultipleOf(0) + } + + private lazy val separatorLengthApprox = this.optLexicalParent match { + case Some(s: SequenceTermBase) if s.hasSeparator => + getEncodingLengthApprox(s) + case _ => LengthMultipleOf(0) + } + + private lazy val initiatorMTAApprox = + if (this.hasInitiator) { + AlignmentMultipleOf(this.knownEncodingAlignmentInBits) + } else { + AlignmentMultipleOf(0) + } + + private lazy val initiatorLengthApprox = if (this.hasInitiator) { + getEncodingLengthApprox(this) + } else { + LengthMultipleOf(0) + } + + private lazy val terminatorMTAApprox = + if (this.hasTerminator) { + AlignmentMultipleOf(this.knownEncodingAlignmentInBits) + } else { + AlignmentMultipleOf(0) + } + + private lazy val terminatorLengthApprox = if (this.hasTerminator) { + getEncodingLengthApprox(this) + } else { + LengthMultipleOf(0) + } + + private def getEncodingLengthApprox(t: Term) = { + if (t.isKnownEncoding) { + val dfdlCharset = t.charsetEv.optConstant.get + val fixedWidth = + if (dfdlCharset.maybeFixedWidth.isDefined) dfdlCharset.maybeFixedWidth.get else 8 + LengthMultipleOf(fixedWidth) + } else { + // runtime encoding, which must be a byte-length encoding + LengthMultipleOf(8) + } + } + + /** + * prior alignment with leading skip includes the prefix/infix separator MTA and length, + * any leading skips + */ private lazy val priorAlignmentWithLeadingSkipApprox: AlignmentMultipleOf = { - priorAlignmentApprox + leadingSkipApprox + val priorAlignmentWithSeparatorApprox = priorAlignmentApprox + + separatorPrefixMTAApprox + + separatorLengthApprox + val leadingAlignmentApprox = (priorAlignmentWithSeparatorApprox + + leadingSkipApprox) + leadingAlignmentApprox + } + + /** + * The length of the prefix length quasi element. + * + * This is only relevant for quasi elements, or prefixed terms. + */ + private lazy val prefixLengthElementLength = { + this match { + case eb: ElementBase => { + if (eb.isInstanceOf[QuasiElementDeclBase]) + LengthExact( + eb.asInstanceOf[QuasiElementDeclBase].elementLengthInBitsEv.constValue.get + ) + else if (eb.lengthKind == Prefixed) + LengthExact( + this + .asInstanceOf[ElementBase] + .prefixedLengthElementDecl + .elementLengthInBitsEv + .constValue + .get + ) + else + LengthExact(0) + } + case _ => LengthExact(0) + } } + /** + * The alignment of the value of the term. + * + * This is only relevant for simple elements. + */ + private lazy val valueMTAApprox = { + this match { + case eb: ElementBase if eb.isSimpleType => { + AlignmentMultipleOf(eb.knownEncodingAlignmentInBits) + } + case _ => AlignmentMultipleOf(0) + } + } + + /** + * This alignment is made up of the alignment of the term itself, + * the initiator MTA and length, the prefix length quasi + * element length, and the value MTA (we add it for optimization + * but is extremely difficult to test, as other things such + * as unparsers will provide MTA, even elementSpecifiedLengthApprox + * considers the encoding also). + */ protected lazy val contentStartAlignment: AlignmentMultipleOf = { - if (priorAlignmentWithLeadingSkipApprox % alignmentApprox == 0) { - // alignment won't be needed, continue using prior alignment as start alignment - priorAlignmentWithLeadingSkipApprox - } else { - // alignment will be needed, it will be forced to be aligned to alignmentApprox + val leftFramingApprox = alignmentApprox - } + * initiatorMTAApprox + + initiatorLengthApprox + val csa = (leftFramingApprox + prefixLengthElementLength) * valueMTAApprox + csa } + /** + * Accounts for the start of the content alignment, element length, + * terminator MTA/Length and trailing skip + */ protected lazy val endingAlignmentApprox: AlignmentMultipleOf = { this match { case eb: ElementBase => { - if (eb.isComplexType && eb.lengthKind == LengthKind.Implicit) { - eb.complexType.group.endingAlignmentApprox + trailingSkipApprox + val res = if (eb.isComplexType && eb.lengthKind == LengthKind.Implicit) { + eb.complexType.group.endingAlignmentApprox } else { // simple type or complex type with specified length - contentStartAlignment + (elementSpecifiedLengthApprox + trailingSkipApprox) + contentStartAlignment + elementSpecifiedLengthApprox } + val cea = res * terminatorMTAApprox + + terminatorLengthApprox + + trailingSkipApprox + cea } case mg: ModelGroup => { // @@ -260,37 +399,53 @@ trait AlignedMixin extends GrammarMixin { self: Term => // val (lastChildren, couldBeLast) = mg.potentialLastChildren val lastApproxesConsideringChildren: Seq[AlignmentMultipleOf] = lastChildren.map { lc => - // - // for each possible last child, add its ending alignment - // to our trailing skip to get where it would leave off were - // it the actual last child. - // + // for each possible last child, gather its endingAlignmentApprox val lceaa = lc.endingAlignmentApprox - val res = lceaa + trailingSkipApprox - res + lceaa } val optApproxIfNoChildren = // // gather possibilities for this item itself // - if (couldBeLast) + if (couldBeLast) { // // if this model group could be last, then consider // if none of its content was present. - // We'd just have the contentStart, nothing, and the trailing Skip. + // We'd just have the contentStart // - Some(contentStartAlignment + trailingSkipApprox) - else + val res = Some(contentStartAlignment) + res + } else // can't be last, no possibilities to gather. None val lastApproxes = lastApproxesConsideringChildren ++ optApproxIfNoChildren - Assert.invariant(lastApproxes.nonEmpty) - val res = lastApproxes.reduce { _ * _ } + // take all the gathered possibilities and add the ending alignment + // to terminator MTA/length and our trailing skip to get where it would leave off were + // each the actual last child. + val lastApproxesFinal = lastApproxes.map { + _ * terminatorMTAApprox + + terminatorLengthApprox + + trailingSkipApprox + } + Assert.invariant(lastApproxesFinal.nonEmpty) + val res = lastApproxesFinal.reduce { + _ * _ + } res } } } + /** + * The postfix separator MTA/length needs to be added after the trailing skip + */ + protected lazy val endingAlignmentWithRightFramingApprox: AlignmentMultipleOf = { + val res = endingAlignmentApprox + + separatorPostfixMTAApprox + + separatorLengthApprox + res + } + protected lazy val elementSpecifiedLengthApprox: LengthApprox = { this match { case eb: ElementBase => { @@ -321,7 +476,16 @@ trait AlignedMixin extends GrammarMixin { self: Term => case LengthKind.Delimited => encodingLengthApprox case LengthKind.Pattern => encodingLengthApprox case LengthKind.EndOfParent => LengthMultipleOf(1) // NYI - case LengthKind.Prefixed => LengthMultipleOf(1) // NYI + case LengthKind.Prefixed => { + val prefixElem = eb.prefixedLengthElementDecl + if (prefixElem.lengthKind == Explicit) { + LengthExact( + prefixElem.elementLengthInBitsEv.optConstant.get.get + ) + prefixLengthElementLength + } else { + getEncodingLengthApprox(prefixElem) + } + } } } case _: ModelGroup => Assert.usageError("Only for elements") @@ -329,15 +493,7 @@ trait AlignedMixin extends GrammarMixin { self: Term => } private lazy val encodingLengthApprox: LengthApprox = { - if (isKnownEncoding) { - val dfdlCharset = charsetEv.optConstant.get - val fixedWidth = - if (dfdlCharset.maybeFixedWidth.isDefined) dfdlCharset.maybeFixedWidth.get else 8 - LengthMultipleOf(fixedWidth) - } else { - // runtime encoding, which must be a byte-length encoding - LengthMultipleOf(8) - } + getEncodingLengthApprox(this) } protected lazy val isKnownToBeByteAlignedAndByteLength: Boolean = { diff --git a/daffodil-test/src/test/resources/org/apache/daffodil/section12/aligned_data/Aligned_Data.tdml b/daffodil-test/src/test/resources/org/apache/daffodil/section12/aligned_data/Aligned_Data.tdml index 119c799854..4a1b7ec896 100644 --- a/daffodil-test/src/test/resources/org/apache/daffodil/section12/aligned_data/Aligned_Data.tdml +++ b/daffodil-test/src/test/resources/org/apache/daffodil/section12/aligned_data/Aligned_Data.tdml @@ -603,6 +603,377 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IaaBbb + + + + + + aa + bb + + + + + + + + IAaabbee + + + + + + aa + bb + + ee + + + + + + + aaTBbb + + + + + + aa + bb + + + + + + + + aabbT2ee + + + + + + aa + bb + + ee + + + + + + + + + + + Schema Definition Error + 1 bits + must be a multiple + 8 bits + US-ASCII + + + + + + + cc + aa + bb + + + + + + + + cc2aaBbb + + + + + + cc + aa + bb + + + + + + + + cc16aabb + + + + + + cc + aa + bb + + + + + + + + + 1110 + 1111 + FFFFFFFF + 0111 + 0000 + + + + + + + 1110 + + 0111 + + + + + + + + eEaBb + + + + + e + + a + b + + + + + + + + + 0011 + 1111 + 0000 + 0100 + + + + + + + 3 + 4 + + + + + + + + + 0011 + 0000 + - + 00000101 + 00000100 + + + + + + + 3 + 5 + 4 + + + + + + + + eE-ob + + + + + e + + o + b + + + + + + + + eEb + + + + + e + + b + + + + + diff --git a/daffodil-test/src/test/resources/org/apache/daffodil/usertests/SepTests.tdml b/daffodil-test/src/test/resources/org/apache/daffodil/usertests/SepTests.tdml index 0ab4ea916e..61d08de6b9 100644 --- a/daffodil-test/src/test/resources/org/apache/daffodil/usertests/SepTests.tdml +++ b/daffodil-test/src/test/resources/org/apache/daffodil/usertests/SepTests.tdml @@ -143,14 +143,6 @@ separatorPosition="infix" emptyElementParsePolicy="treatAsEmpty"/> - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + aa/Bbb + + + + + aa + bb + + + + + + + + /Aaa/Bbb + + + + + aa + bb + + + + + + + + aa/Bbb/ + + + + + + aa + bb + + + + + + + + cc-Aaa/Bbb/-dd- + + + + + + cc + aa + bb + dd + + + + + + diff --git a/daffodil-test/src/test/scala/org/apache/daffodil/section12/aligned_data/TestAlignedData.scala b/daffodil-test/src/test/scala/org/apache/daffodil/section12/aligned_data/TestAlignedData.scala index 23967c96a8..0aaeade57c 100644 --- a/daffodil-test/src/test/scala/org/apache/daffodil/section12/aligned_data/TestAlignedData.scala +++ b/daffodil-test/src/test/scala/org/apache/daffodil/section12/aligned_data/TestAlignedData.scala @@ -186,6 +186,27 @@ class TestAlignedData extends TdmlTests { @Test def alignmentFillByteDefined = test @Test def separatorMTA_01 = test + + // DAFFODIL-3056 + @Test def test_init_alignment_1 = test + @Test def test_init_alignment_2 = test + @Test def test_prefix_alignment_1 = test + @Test def test_prefix_alignment_2 = test + @Test def test_valueMTA_alignment_1 = test + + // DAFFODIL-3057 + @Test def test_term_alignment_1 = test + @Test def test_term_alignment_2 = test + + // DAFFODIL-3059 + @Test def test_prefix_alignment_0 = test + + // DAFFODIL-3060 + @Test def prior_siblings_1 = test + @Test def prior_siblings_2 = test + @Test def prior_siblings_3 = test + @Test def prior_siblings_4 = test + @Test def prior_siblings_5 = test } class TestBinaryInput extends TdmlTests { diff --git a/daffodil-test/src/test/scala/org/apache/daffodil/usertests/TestSepTests.scala b/daffodil-test/src/test/scala/org/apache/daffodil/usertests/TestSepTests.scala index 3ffd036121..9c27ba7261 100644 --- a/daffodil-test/src/test/scala/org/apache/daffodil/usertests/TestSepTests.scala +++ b/daffodil-test/src/test/scala/org/apache/daffodil/usertests/TestSepTests.scala @@ -55,4 +55,11 @@ class TestSepTests extends TdmlTests { // DAFFODIL-2791 @Test def test_treatAsAbsent_occursIndex = test + + // DAFFODIL-2295 + @Test def test_sep_alignment_1 = test + @Test def test_sep_alignment_2 = test + @Test def test_sep_alignment_3 = test + + @Test def test_sep_alignment_4 = test } From 583cdf4407a9ee2d3c04cd0895af6f94d13d5955 Mon Sep 17 00:00:00 2001 From: olabusayoT <50379531+olabusayoT@users.noreply.github.com> Date: Thu, 8 Jan 2026 13:17:12 -0500 Subject: [PATCH 2/2] fixup! - Implemented logic to handle nested prefixed alignment scenarios. - Updated alignment calculations for nested prefixed elements. - Added `test_prefix_alignment_3` in TestAlignedData to verify new behavior. - Updated TDML schemas with test data for nested prefixed alignment validation. - Fixed valueMTA to only apply for Representation.Text. - Add new variables to more accurately represent alignment, such as contentEndAlignment, which is useful for delimiter text alignment before the terminator and trailing skip is considered. DAFFODIL-2295, DAFFODIL-3056, DAFFODIL-3057 --- .../daffodil/core/grammar/AlignedMixin.scala | 185 ++++++++++-------- .../section12/aligned_data/Aligned_Data.tdml | 64 ++++++ .../aligned_data/TestAlignedData.scala | 1 + 3 files changed, 169 insertions(+), 81 deletions(-) diff --git a/daffodil-core/src/main/scala/org/apache/daffodil/core/grammar/AlignedMixin.scala b/daffodil-core/src/main/scala/org/apache/daffodil/core/grammar/AlignedMixin.scala index 9ef92560ce..f2d3ceb4ed 100644 --- a/daffodil-core/src/main/scala/org/apache/daffodil/core/grammar/AlignedMixin.scala +++ b/daffodil-core/src/main/scala/org/apache/daffodil/core/grammar/AlignedMixin.scala @@ -28,16 +28,20 @@ import org.apache.daffodil.lib.schema.annotation.props.gen.AlignmentKind import org.apache.daffodil.lib.schema.annotation.props.gen.AlignmentUnits import org.apache.daffodil.lib.schema.annotation.props.gen.LengthKind import org.apache.daffodil.lib.schema.annotation.props.gen.LengthKind.Explicit +import org.apache.daffodil.lib.schema.annotation.props.gen.LengthKind.Implicit import org.apache.daffodil.lib.schema.annotation.props.gen.LengthKind.Prefixed import org.apache.daffodil.lib.schema.annotation.props.gen.LengthUnits +import org.apache.daffodil.lib.schema.annotation.props.gen.Representation import org.apache.daffodil.lib.schema.annotation.props.gen.SeparatorPosition import org.apache.daffodil.lib.util.Math case class AlignmentMultipleOf(nBits: Long) { - // To combine AlignmentMultipleOfs, use * + // To combine alignments that could all happen at the same point in data, use *, + // To add new alignment to an existing approximate alignment, use + def *(that: AlignmentMultipleOf) = AlignmentMultipleOf(Math.gcd(nBits, that.nBits)) def %(that: AlignmentMultipleOf) = nBits % that.nBits - + def +(that: AlignmentMultipleOf) = + if (this.nBits % that.nBits == 0) this else that // To combine AlignmentMultipleOfs and lengths, use + def +(that: LengthApprox) = AlignmentMultipleOf(Math.gcd(nBits, nBits + that.nBits)) } @@ -70,13 +74,15 @@ trait AlignedMixin extends GrammarMixin { self: Term => * Hence we are guaranteed to be properly aligned. */ final lazy val isKnownToBeAligned: Boolean = LV(Symbol("isKnownToBeAligned")) { - if (!isRepresented || (alignmentKindDefaulted == AlignmentKind.Manual)) true - else { - val pa = priorAlignmentWithLeadingSkipApprox - val aa = alignmentApprox - val res = (pa % aa) == 0 - res - } + val res = + if (!isRepresented || (alignmentKindDefaulted == AlignmentKind.Manual)) true + else { + val pa = priorAlignmentWithLeadingSkipApprox + val aa = alignmentApprox + val res = (pa % aa) == 0 + res + } + res }.value /** @@ -88,33 +94,37 @@ trait AlignedMixin extends GrammarMixin { self: Term => * considers the surrounding context meeting the alignment needs. */ final lazy val isKnownToBeTextAligned: Boolean = LV(Symbol("isKnownToBeTextAligned")) { - if (alignmentKindDefaulted == AlignmentKind.Manual) true // manual alignment - else if (isKnownEncoding) { - if (knownEncodingAlignmentInBits == 1) - true - else if (priorAlignmentWithLeadingSkipApprox.nBits % knownEncodingAlignmentInBits == 0) + val res = + if (alignmentKindDefaulted == AlignmentKind.Manual) true // manual alignment + else if (isKnownEncoding) { + if (knownEncodingAlignmentInBits == 1) + true + else if (priorAlignmentWithLeadingSkipApprox.nBits % knownEncodingAlignmentInBits == 0) + true + else + false + } else if (schemaSet.root.isScannable) true else false - } else if (schemaSet.root.isScannable) - true - else - false + res }.value final lazy val isDelimiterKnownToBeTextAligned: Boolean = { - if (alignmentKindDefaulted == AlignmentKind.Manual) true // manual alignment - else if (isKnownEncoding) { - if (knownEncodingAlignmentInBits == 1) - true - else if (endingAlignmentApprox.nBits % knownEncodingAlignmentInBits == 0) + val res = + if (alignmentKindDefaulted == AlignmentKind.Manual) true // manual alignment + else if (isKnownEncoding) { + if (knownEncodingAlignmentInBits == 1) + true + else if (contentEndAlignment.nBits % knownEncodingAlignmentInBits == 0) + true + else + false + } else if (schemaSet.root.isScannable) true else false - } else if (schemaSet.root.isScannable) - true - else - false + res } final lazy val hasNoSkipRegions = LV(Symbol("hasNoSkipRegions")) { @@ -240,10 +250,10 @@ trait AlignedMixin extends GrammarMixin { self: Term => case Some(s: SequenceTermBase) if s.hasSeparator => import SeparatorPosition.* s.separatorPosition match { - case Prefix | Infix => LengthMultipleOf(s.knownEncodingAlignmentInBits) - case Postfix => LengthMultipleOf(0) + case Prefix | Infix => AlignmentMultipleOf(s.knownEncodingAlignmentInBits) + case Postfix => AlignmentMultipleOf(1) } - case _ => LengthMultipleOf(0) + case _ => AlignmentMultipleOf(1) } private lazy val separatorPostfixMTAApprox = @@ -251,42 +261,42 @@ trait AlignedMixin extends GrammarMixin { self: Term => case Some(s: SequenceTermBase) if s.hasSeparator => import SeparatorPosition.* s.separatorPosition match { - case Prefix | Infix => LengthMultipleOf(0) - case Postfix => LengthMultipleOf(s.knownEncodingAlignmentInBits) + case Prefix | Infix => AlignmentMultipleOf(1) + case Postfix => AlignmentMultipleOf(s.knownEncodingAlignmentInBits) } - case _ => LengthMultipleOf(0) + case _ => AlignmentMultipleOf(1) } private lazy val separatorLengthApprox = this.optLexicalParent match { case Some(s: SequenceTermBase) if s.hasSeparator => getEncodingLengthApprox(s) - case _ => LengthMultipleOf(0) + case _ => LengthExact(0) } private lazy val initiatorMTAApprox = if (this.hasInitiator) { AlignmentMultipleOf(this.knownEncodingAlignmentInBits) } else { - AlignmentMultipleOf(0) + AlignmentMultipleOf(1) } private lazy val initiatorLengthApprox = if (this.hasInitiator) { getEncodingLengthApprox(this) } else { - LengthMultipleOf(0) + LengthExact(0) } private lazy val terminatorMTAApprox = if (this.hasTerminator) { AlignmentMultipleOf(this.knownEncodingAlignmentInBits) } else { - AlignmentMultipleOf(0) + AlignmentMultipleOf(1) } private lazy val terminatorLengthApprox = if (this.hasTerminator) { getEncodingLengthApprox(this) } else { - LengthMultipleOf(0) + LengthExact(0) } private def getEncodingLengthApprox(t: Term) = { @@ -319,40 +329,44 @@ trait AlignedMixin extends GrammarMixin { self: Term => * * This is only relevant for quasi elements, or prefixed terms. */ - private lazy val prefixLengthElementLength = { + protected lazy val prefixLengthElementLength: LengthApprox = { this match { case eb: ElementBase => { - if (eb.isInstanceOf[QuasiElementDeclBase]) - LengthExact( - eb.asInstanceOf[QuasiElementDeclBase].elementLengthInBitsEv.constValue.get - ) - else if (eb.lengthKind == Prefixed) - LengthExact( - this - .asInstanceOf[ElementBase] - .prefixedLengthElementDecl - .elementLengthInBitsEv - .constValue - .get - ) - else + if (eb.lengthKind == Prefixed) { + val prefixTypeElem = eb.prefixedLengthElementDecl + getElementLength(prefixTypeElem) + } else { LengthExact(0) + } } case _ => LengthExact(0) } } + private def getElementLength(elem: ElementBase) = { + val currentLengthKind = elem.lengthKind + if (currentLengthKind == Explicit || currentLengthKind == Implicit) { + LengthExact( + elem.elementLengthInBitsEv.constValue.get + ) + } else if (currentLengthKind == Prefixed) { + elem.prefixLengthElementLength + } else { + LengthExact(0) + } + } + /** - * The alignment of the value of the term. + * The alignment of the term's value. * * This is only relevant for simple elements. */ private lazy val valueMTAApprox = { this match { - case eb: ElementBase if eb.isSimpleType => { + case eb: ElementBase if eb.isSimpleType && eb.representation == Representation.Text => { AlignmentMultipleOf(eb.knownEncodingAlignmentInBits) } - case _ => AlignmentMultipleOf(0) + case _ => AlignmentMultipleOf(1) } } @@ -365,31 +379,28 @@ trait AlignedMixin extends GrammarMixin { self: Term => * considers the encoding also). */ protected lazy val contentStartAlignment: AlignmentMultipleOf = { - val leftFramingApprox = - alignmentApprox - * initiatorMTAApprox - + initiatorLengthApprox - val csa = (leftFramingApprox + prefixLengthElementLength) * valueMTAApprox + val leftFramingApprox = priorAlignmentWithLeadingSkipApprox + + alignmentApprox + + initiatorMTAApprox + + initiatorLengthApprox + val csa = (leftFramingApprox + prefixLengthElementLength) + valueMTAApprox csa } /** - * Accounts for the start of the content alignment, element length, - * terminator MTA/Length and trailing skip + * Accounts for the start of the content start alignment and element length. + * This is used to determine the alignment before the terminator and trailing skip */ - protected lazy val endingAlignmentApprox: AlignmentMultipleOf = { + protected lazy val contentEndAlignment: AlignmentMultipleOf = { this match { case eb: ElementBase => { val res = if (eb.isComplexType && eb.lengthKind == LengthKind.Implicit) { - eb.complexType.group.endingAlignmentApprox + eb.complexType.group.contentEndAlignment } else { // simple type or complex type with specified length contentStartAlignment + elementSpecifiedLengthApprox } - val cea = res * terminatorMTAApprox - + terminatorLengthApprox - + trailingSkipApprox - cea + res } case mg: ModelGroup => { // @@ -419,16 +430,10 @@ trait AlignedMixin extends GrammarMixin { self: Term => // can't be last, no possibilities to gather. None val lastApproxes = lastApproxesConsideringChildren ++ optApproxIfNoChildren - // take all the gathered possibilities and add the ending alignment - // to terminator MTA/length and our trailing skip to get where it would leave off were + // take all the gathered possibilities to get where it would leave off were // each the actual last child. - val lastApproxesFinal = lastApproxes.map { - _ * terminatorMTAApprox - + terminatorLengthApprox - + trailingSkipApprox - } - Assert.invariant(lastApproxesFinal.nonEmpty) - val res = lastApproxesFinal.reduce { + Assert.invariant(lastApproxes.nonEmpty) + val res = lastApproxes.reduce { _ * _ } res @@ -436,6 +441,18 @@ trait AlignedMixin extends GrammarMixin { self: Term => } } + /** + * Add the ending alignment of the term + * to terminator MTA/length and our trailing skip + */ + protected lazy val endingAlignmentApprox: AlignmentMultipleOf = { + val res = this.contentEndAlignment + + terminatorMTAApprox + + terminatorLengthApprox + + trailingSkipApprox + res + } + /** * The postfix separator MTA/length needs to be added after the trailing skip */ @@ -478,12 +495,18 @@ trait AlignedMixin extends GrammarMixin { self: Term => case LengthKind.EndOfParent => LengthMultipleOf(1) // NYI case LengthKind.Prefixed => { val prefixElem = eb.prefixedLengthElementDecl - if (prefixElem.lengthKind == Explicit) { + if (prefixElem.lengthKind == Explicit || prefixElem.lengthKind == Implicit) { LengthExact( prefixElem.elementLengthInBitsEv.optConstant.get.get - ) + prefixLengthElementLength - } else { + ) + } else if (prefixElem.representation == Representation.Text) { getEncodingLengthApprox(prefixElem) + } else { + eb.lengthUnits match { + case LengthUnits.Bits => LengthMultipleOf(1) + case LengthUnits.Bytes => LengthMultipleOf(8) + case LengthUnits.Characters => encodingLengthApprox + } } } } diff --git a/daffodil-test/src/test/resources/org/apache/daffodil/section12/aligned_data/Aligned_Data.tdml b/daffodil-test/src/test/resources/org/apache/daffodil/section12/aligned_data/Aligned_Data.tdml index 4a1b7ec896..3ddb07887c 100644 --- a/daffodil-test/src/test/resources/org/apache/daffodil/section12/aligned_data/Aligned_Data.tdml +++ b/daffodil-test/src/test/resources/org/apache/daffodil/section12/aligned_data/Aligned_Data.tdml @@ -798,6 +798,17 @@ + + + + + + + + @@ -828,6 +839,37 @@ + + + + + + + + + + + + + + + + @@ -4400,6 +4442,28 @@ + + cc16aabb + + + + + + cc + aa + bb + + + + + + + Nested + prefixed + not supported + + + 1110 diff --git a/daffodil-test/src/test/scala/org/apache/daffodil/section12/aligned_data/TestAlignedData.scala b/daffodil-test/src/test/scala/org/apache/daffodil/section12/aligned_data/TestAlignedData.scala index 0aaeade57c..e22069d77f 100644 --- a/daffodil-test/src/test/scala/org/apache/daffodil/section12/aligned_data/TestAlignedData.scala +++ b/daffodil-test/src/test/scala/org/apache/daffodil/section12/aligned_data/TestAlignedData.scala @@ -192,6 +192,7 @@ class TestAlignedData extends TdmlTests { @Test def test_init_alignment_2 = test @Test def test_prefix_alignment_1 = test @Test def test_prefix_alignment_2 = test + @Test def test_prefix_alignment_3 = test @Test def test_valueMTA_alignment_1 = test // DAFFODIL-3057