You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@daffodil.apache.org by ji...@apache.org on 2023/06/09 15:05:59 UTC
[daffodil] branch main updated: Add codegen-c support for runtime length hexBinary
This is an automated email from the ASF dual-hosted git repository.
jinterrante pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/daffodil.git
The following commit(s) were added to refs/heads/main by this push:
new 47f385ef4 Add codegen-c support for runtime length hexBinary
47f385ef4 is described below
commit 47f385ef4fee938662c54619d08595824de587fb
Author: John Interrante <in...@research.ge.com>
AuthorDate: Thu Jun 8 11:00:45 2023 -0400
Add codegen-c support for runtime length hexBinary
Extend Daffodil's C code generator to support runtime length hexBinary
elements. Also make some cleanups for better consistency.
DAFFODIL-2819
dependency-scan.yml: Saw a better example somewhere (it was called
dependency-graph.yml) so modify and rename to match that example.
DaffodilCCodeGenerator.scala: Change comments and names in compileCode
and pickCommand to be more similar to another codegen. Extend
generateCode to handle RepOrderedWithMinMaxSequenceChild.
BinaryValueCodeGenerator.scala: Remove restriction on runtime lengths.
CodeGeneratorState.scala: Add cExpression to implement a poor man's C
code expression generator until Daffodil adds custom code generation
functionality to compiled DFDL expressions. Remove namespace prefixes
in makeLegalForC to ensure they don't appear in C code. Extend
arrayMaxOccurs to handle more cases. Make cStructFieldAccess do a
better job of converting a DFDL expression written as a path to a C
struct field dereference.
HexBinaryCodeGenerator.scala: Allow specified length hexBinary
elements to use runtime lengths as well as constant lengths.
ex_nums.tdml: Add a new test to check support for runtime length
hexBinary elements.
Evaluatable.scala: Allow codegen-c to get compiled expression from
EvaluatableExpression.
TestExNums.scala: Make both daffodil and daffodilC run new test.
---
.../{dependency-scan.yml => dependency-graph.yml} | 27 +++----
.../codegen/c/DaffodilCCodeGenerator.scala | 50 ++++++++-----
.../c/generators/BinaryValueCodeGenerator.scala | 5 --
.../codegen/c/generators/CodeGeneratorState.scala | 87 ++++++++++++++++++----
.../c/generators/HexBinaryCodeGenerator.scala | 47 ++++++++++--
.../org/apache/daffodil/codegen/c/ex_nums.tdml | 42 ++++++++++-
.../daffodil/runtime1/processors/Evaluatable.scala | 2 +-
.../org/apache/daffodil/codegen/c/TestExNums.scala | 2 +
8 files changed, 196 insertions(+), 66 deletions(-)
diff --git a/.github/workflows/dependency-scan.yml b/.github/workflows/dependency-graph.yml
similarity index 50%
rename from .github/workflows/dependency-scan.yml
rename to .github/workflows/dependency-graph.yml
index 3a1fd3252..2308d5b65 100644
--- a/.github/workflows/dependency-scan.yml
+++ b/.github/workflows/dependency-graph.yml
@@ -13,30 +13,23 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-name: Dependency scan
+name: Dependency Graph
+
+# Run after pushing a commit to the main branch.
-# Controls when the workflow will run
on:
- # Triggers the workflow on push or pull request events but only for the "main" branch
push:
- branches: [ "main" ]
-
- # Allows you to run this workflow manually from the Actions tab
- workflow_dispatch:
+ branches:
+ - main
-# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
- # This workflow contains a single job called "build"
- build:
- # The type of runner that the job will run on
+ dependency-graph:
+ name: Dependency Graph
runs-on: ubuntu-22.04
- # Steps represent a sequence of tasks that will be executed as part of the job
steps:
- # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
- - uses: actions/checkout@v3.5.2
+ - name: Check out Repository
+ uses: actions/checkout@v3.5.2
- - name: Sbt Dependency Submission
- # You may pin to the exact commit or the version.
- # uses: scalacenter/sbt-dependency-submission@ab086b50c947c9774b70f39fc7f6e20ca2706c91
+ - name: Submit Dependency Graph
uses: scalacenter/sbt-dependency-submission@v2.1.2
diff --git a/daffodil-codegen-c/src/main/scala/org/apache/daffodil/codegen/c/DaffodilCCodeGenerator.scala b/daffodil-codegen-c/src/main/scala/org/apache/daffodil/codegen/c/DaffodilCCodeGenerator.scala
index 3f438fc5f..fc92e1134 100644
--- a/daffodil-codegen-c/src/main/scala/org/apache/daffodil/codegen/c/DaffodilCCodeGenerator.scala
+++ b/daffodil-codegen-c/src/main/scala/org/apache/daffodil/codegen/c/DaffodilCCodeGenerator.scala
@@ -52,6 +52,7 @@ import org.apache.daffodil.core.grammar.primitives.HexBinarySpecifiedLength
import org.apache.daffodil.core.grammar.primitives.OrderedSequence
import org.apache.daffodil.core.grammar.primitives.RepOrderedExactlyNSequenceChild
import org.apache.daffodil.core.grammar.primitives.RepOrderedExpressionOccursCountSequenceChild
+import org.apache.daffodil.core.grammar.primitives.RepOrderedWithMinMaxSequenceChild
import org.apache.daffodil.core.grammar.primitives.RightFill
import org.apache.daffodil.core.grammar.primitives.ScalarOrderedSequenceChild
import org.apache.daffodil.core.grammar.primitives.SpecifiedLengthExplicit
@@ -143,21 +144,21 @@ class DaffodilCCodeGenerator(root: Root) extends DFDL.CodeGenerator {
val exe = if (isWin) codeDir / "daffodil.exe" else codeDir / "daffodil"
try {
- // Assemble the compiler's command line arguments
- val compiler = pickCompiler
+ // Assemble the compilation command line arguments
+ val command = pickCommand
val cFlags = Seq("-std=gnu11")
val includes = Seq("-Ilibcli", "-Ilibruntime")
val absFiles = os.walk(codeDir, skip = _.last == "tests").filter(_.ext == "c")
val relFiles = Seq("libcli/*.c", "libruntime/*.c")
val libs = Seq("-lmxml")
- // Run the compiler within the code directory
- if (compiler.nonEmpty) {
+ // Run the compilation command within the code directory
+ if (command.nonEmpty) {
val result = os
- .proc(compiler, cFlags, includes, if (isWin) relFiles else absFiles, libs, "-o", exe)
+ .proc(command, cFlags, includes, if (isWin) relFiles else absFiles, libs, "-o", exe)
.call(cwd = codeDir, stderr = os.Pipe)
if (result.chunks.nonEmpty) {
- // Report any compiler output as a warning
+ // Report any compilation output as a warning
warning(result.toString())
}
}
@@ -179,26 +180,27 @@ class DaffodilCCodeGenerator(root: Root) extends DFDL.CodeGenerator {
}
/**
- * Searches for any available C compiler on the system. Tries to find the
- * compiler given by `CC` if `CC` exists in the environment, then tries to
- * find any compiler from the following list:
+ * Searches for any available C compilation command on the system.
+ * Tries to find the command given by `CC` if `CC` exists in the
+ * environment, then tries to find any command from the following
+ * list:
*
* - zig cc
* - cc
* - clang
* - gcc
*
- * Returns the first compiler found as a sequence of strings in case the
- * compiler is a program with a subcommand argument. Returns the empty
- * sequence if no compiler could be found in the user's PATH.
+ * Returns the first command found as a sequence of strings in case
+ * the command has a subcommand argument. Returns the empty
+ * sequence if no command could be found in the user's PATH.
*/
- private lazy val pickCompiler: Seq[String] = {
+ private lazy val pickCommand: Seq[String] = {
val ccEnv = sys.env.getOrElse("CC", "zig cc")
- val compilers = Seq(ccEnv, "zig cc", "cc", "clang", "gcc")
+ val commands = Seq(ccEnv, "zig cc", "cc", "clang", "gcc")
val path = sys.env.getOrElse("PATH", ".").split(File.pathSeparatorChar)
- def inPath(compiler: String): Boolean = {
- (compiler != null) && {
- val exec = compiler.takeWhile(_ != ' ')
+ def inPath(command: String): Boolean = {
+ (command != null) && {
+ val exec = command.takeWhile(_ != ' ')
val exec2 = exec + ".exe"
path.exists(dir =>
Files.isExecutable(Paths.get(dir, exec))
@@ -206,9 +208,9 @@ class DaffodilCCodeGenerator(root: Root) extends DFDL.CodeGenerator {
)
}
}
- val compiler = compilers.find(inPath)
- if (compiler.isDefined)
- compiler.get.split(' ').toSeq
+ val command = commands.find(inPath)
+ if (command.isDefined)
+ command.get.split(' ').toSeq
else
Seq.empty[String]
}
@@ -278,6 +280,7 @@ object DaffodilCCodeGenerator
case g: RepOrderedExactlyNSequenceChild => repOrderedExactlyNSequenceChild(g, cgState)
case g: RepOrderedExpressionOccursCountSequenceChild =>
repOrderedExpressionOccursCountSequenceChild(g, cgState)
+ case g: RepOrderedWithMinMaxSequenceChild => repOrderedWithMinMaxSequenceChild(g, cgState)
case g: RightFill => noop(g)
case g: ScalarOrderedSequenceChild => scalarOrderedSequenceChild(g, cgState)
case g: SeqComp => seqCompGenerateCode(g, cgState)
@@ -343,6 +346,13 @@ object DaffodilCCodeGenerator
cgState.popArray(g.context)
}
+ private def repOrderedWithMinMaxSequenceChild(
+ g: RepOrderedWithMinMaxSequenceChild,
+ cgState: CodeGeneratorState,
+ ): Unit = {
+ DaffodilCCodeGenerator.generateCode(g.term.termContentBody, cgState)
+ }
+
private def scalarOrderedSequenceChild(
g: ScalarOrderedSequenceChild,
cgState: CodeGeneratorState,
diff --git a/daffodil-codegen-c/src/main/scala/org/apache/daffodil/codegen/c/generators/BinaryValueCodeGenerator.scala b/daffodil-codegen-c/src/main/scala/org/apache/daffodil/codegen/c/generators/BinaryValueCodeGenerator.scala
index 374e08e5d..3d6bd871a 100644
--- a/daffodil-codegen-c/src/main/scala/org/apache/daffodil/codegen/c/generators/BinaryValueCodeGenerator.scala
+++ b/daffodil-codegen-c/src/main/scala/org/apache/daffodil/codegen/c/generators/BinaryValueCodeGenerator.scala
@@ -20,7 +20,6 @@ package org.apache.daffodil.codegen.c.generators
import org.apache.daffodil.core.dsom.ElementBase
import org.apache.daffodil.lib.schema.annotation.props.gen.BitOrder
import org.apache.daffodil.lib.schema.annotation.props.gen.ByteOrder
-import org.apache.daffodil.lib.schema.annotation.props.gen.LengthKind
import org.apache.daffodil.lib.util.Maybe.Nope
// Base trait which provides common code to generate C code for primitive value elements
@@ -44,10 +43,6 @@ trait BinaryValueCodeGenerator {
e.maybeByteOrderEv == Nope || e.byteOrderEv.isConstant,
"Runtime dfdl:byteOrder expressions not supported.",
)
- e.schemaDefinitionUnless(
- e.lengthKind == LengthKind.Prefixed || e.elementLengthInBitsEv.isConstant,
- "Runtime dfdl:length expressions not supported.",
- )
// Call the given partially applied function values with their remaining unbound argument (deref)
val deref = if (cgState.hasArray) "[i]" else ""
diff --git a/daffodil-codegen-c/src/main/scala/org/apache/daffodil/codegen/c/generators/CodeGeneratorState.scala b/daffodil-codegen-c/src/main/scala/org/apache/daffodil/codegen/c/generators/CodeGeneratorState.scala
index 107305a58..ed7e6ec00 100644
--- a/daffodil-codegen-c/src/main/scala/org/apache/daffodil/codegen/c/generators/CodeGeneratorState.scala
+++ b/daffodil-codegen-c/src/main/scala/org/apache/daffodil/codegen/c/generators/CodeGeneratorState.scala
@@ -17,6 +17,7 @@
package org.apache.daffodil.codegen.c.generators
+import java.util.regex.Pattern
import scala.collection.immutable
import scala.collection.mutable
@@ -387,7 +388,7 @@ class CodeGeneratorState(private val root: ElementBase) {
code.replace("\r\n", "\n").replace("\n", System.lineSeparator)
}
- // Returns a name for the given element's C struct identifier
+ // Returns a C name for the given element's C struct identifier
private def cStructName(context: ElementBase): String = {
val sb = buildName(context, new StringBuilder)
makeLegalForC(sb)
@@ -395,7 +396,7 @@ class CodeGeneratorState(private val root: ElementBase) {
name
}
- // Returns a name for the given element's element runtime data
+ // Returns a C name for the given element's element runtime data
private def erdName(context: ElementBase): String = {
val sb = buildName(context, new StringBuilder) ++= "ERD"
makeLegalForC(sb)
@@ -403,7 +404,7 @@ class CodeGeneratorState(private val root: ElementBase) {
name
}
- // Returns a name for the given element's local name
+ // Returns a C name for the given element's local name.
def cName(context: ElementBase): String = {
val sb = new StringBuilder(context.namedQName.local)
makeLegalForC(sb)
@@ -411,6 +412,44 @@ class CodeGeneratorState(private val root: ElementBase) {
name
}
+ // Converts a dfdl:length expression to a C expression. We make some
+ // simplifying assumptions to make converting the expression easier:
+ // - all field names start with ../ or /rootName
+ // - all field names end at the first non-XML-identifier character
+ // - all field names can be converted to C identifiers using cStructFieldAccess
+ // - the expression performs only arithmetic (with no casts) and computes a length
+ // - the expression is almost C-ready but may contain some non-C named operators
+ // - may need to replace any div, idiv, mod with / and % instead
+ // Eventually we should make the compiled expression generate the C code itself
+ // instead of using this superficial replaceAllIn approach.
+ def cExpression(expr: String): String = {
+ // Match each field name and replace it with a C struct field dereference
+ val field = """(((\.\./)+|/)[\p{L}_][\p{L}:_\-.0-9/]*)""".r
+ val exprWithFields = field.replaceAllIn(
+ expr,
+ m => {
+ val fieldName = m.group(1).stripPrefix("../")
+ val cFieldName = cStructFieldAccess(fieldName)
+ cFieldName
+ },
+ )
+ // Match each named operator and replace it with a C operator
+ val operator = """(\bidiv\b|\bdiv\b|\bmod\b)""".r
+ val exprWithOperators = operator.replaceAllIn(
+ exprWithFields,
+ m => {
+ val operatorName = m.group(1)
+ val cOperatorSymbol = operatorName match {
+ case "idiv" => "/"
+ case "div" => "/"
+ case "mod" => "%"
+ }
+ cOperatorSymbol
+ },
+ )
+ exprWithOperators
+ }
+
// Adds an ERD definition for the given complex element
private def addComplexTypeERD(context: ElementBase): Unit = {
val C = cStructName(context)
@@ -792,9 +831,14 @@ class CodeGeneratorState(private val root: ElementBase) {
// Makes any XML identifier a legal C identifier
private def makeLegalForC(sb: StringBuilder): Unit = {
+ // Remove the local name's namespace prefix if there is one; with
+ // luck it won't be needed and the C code will look cleaner
+ val matcher = Pattern.compile("^[^/:]+:").matcher(sb)
+ if (matcher.lookingAt()) sb.replace(matcher.start, matcher.end, "")
+
+ // Replace illegal characters with '_' to form a legal C name
lazy val legalCharsForC: immutable.Set[Char] =
Set('_') ++ ('a' to 'z') ++ ('A' to 'Z') ++ ('0' to '9')
- // Ensure sb contains only legal characters for C identifiers
for (i <- sb.indices) {
if (!legalCharsForC.contains(sb.charAt(i))) {
sb.setCharAt(i, '_')
@@ -880,6 +924,7 @@ class CodeGeneratorState(private val root: ElementBase) {
case OccursCountKind.Fixed if e.maxOccurs > 1 => e.maxOccurs
case OccursCountKind.Fixed if e.maxOccurs == 1 => 0
case OccursCountKind.Implicit if e.minOccurs == 1 && e.maxOccurs == 1 => 0
+ case OccursCountKind.Implicit if e.maxOccurs > 0 => e.maxOccurs
case OccursCountKind.Expression if e.maxOccurs > 0 => e.maxOccurs
case _ =>
e.SDE(
@@ -938,18 +983,31 @@ class CodeGeneratorState(private val root: ElementBase) {
// - we can convert a relative path without any up dirs to an instance-> indirection
// - we can convert slashes in the path to dots in a C struct field access notation
private def cStructFieldAccess(expr: String): String = {
- // Strip any namespace prefixes from expr and the root element's local name since
- // C code uses only struct fields' names.
- val rootName = cName(root)
- val exprPath = expr.replaceAll("/[^/:]+:", "/").stripPrefix(s"/$rootName")
- val fieldAccess = if (exprPath.startsWith("/")) {
+ // If expr is an absolute path, strip the root element's local name
+ val rootName = root.namedQName.local
+ val exprWOPrefix = expr.stripPrefix(s"/$rootName")
+
+ // Turn all DFDL local names into legal C names
+ val localName = """([\p{L}_][\p{L}:_\-.0-9]*)""".r
+ val exprWithFields = localName.replaceAllIn(
+ exprWOPrefix,
+ m => {
+ // Make each DFDL local name a legal C name
+ val sb = new StringBuilder(m.group(1))
+ makeLegalForC(sb)
+ sb.mkString
+ },
+ )
+
+ // Convert exprPath to the appropriate field access indirection
+ val fieldAccess = if (exprWithFields.startsWith("/")) {
// Convert exprPath to a get_infoset()-> indirection
val C = cStructName(root)
- s"""(($C *)get_infoset(false))->${exprPath.stripPrefix("/")}"""
- } else if (exprPath.startsWith("../")) {
+ s"""(($C *)get_infoset(false))->${exprWithFields.stripPrefix("/")}"""
+ } else if (exprWithFields.startsWith("../")) {
// Split exprPath into the up dirs and after the up dirs
- val afterUpDirs = exprPath.split("\\.\\./").mkString
- val upDirs = exprPath.stripSuffix(afterUpDirs)
+ val afterUpDirs = exprWithFields.split("\\.\\./").mkString
+ val upDirs = exprWithFields.stripSuffix(afterUpDirs)
// Count how many up dirs there are
val nUpDirs = upDirs.split('/').length
// Go up the stack that many times to get that struct's C type
@@ -960,8 +1018,9 @@ class CodeGeneratorState(private val root: ElementBase) {
s"""(($C *)instance->_base.$parents)->$afterUpDirs"""
} else {
// Convert exprPath to an instance-> indirection
- s"""instance->$exprPath"""
+ s"""instance->$exprWithFields"""
}
+
// Finally, convert the field access to C struct dot notation
val notation = fieldAccess.replace('/', '.')
notation
diff --git a/daffodil-codegen-c/src/main/scala/org/apache/daffodil/codegen/c/generators/HexBinaryCodeGenerator.scala b/daffodil-codegen-c/src/main/scala/org/apache/daffodil/codegen/c/generators/HexBinaryCodeGenerator.scala
index 96d90897a..44d521a6b 100644
--- a/daffodil-codegen-c/src/main/scala/org/apache/daffodil/codegen/c/generators/HexBinaryCodeGenerator.scala
+++ b/daffodil-codegen-c/src/main/scala/org/apache/daffodil/codegen/c/generators/HexBinaryCodeGenerator.scala
@@ -20,6 +20,7 @@ package org.apache.daffodil.codegen.c.generators
import org.apache.daffodil.core.dsom.ElementBase
import org.apache.daffodil.lib.schema.annotation.props.gen.ByteOrder
import org.apache.daffodil.runtime1.dpath.NodeInfo.PrimType
+import org.apache.daffodil.runtime1.processors.ExplicitLengthEv
trait HexBinaryCodeGenerator extends BinaryValueCodeGenerator {
@@ -96,20 +97,50 @@ trait HexBinaryCodeGenerator extends BinaryValueCodeGenerator {
val localName = cgState.cName(e)
val field = s"instance->$localName$deref"
val fieldArray = s"instance->_a_$localName$deref"
- val specifiedLength = e.elementLengthInBitsEv.constValue.get
+ val specifiedLength =
+ if (e.elementLengthInBitsEv.isConstant)
+ e.elementLengthInBitsEv.constValue.get
+ else
+ -1
+ val primType = s"size_t"
+ val lenVar = s"_l_$localName"
+ val expression =
+ if (e.elementLengthInBitsEv.isConstant)
+ e.elementLengthInBitsEv.constValue.get.toString
+ else {
+ val expr = e.elementLengthInBitsEv.lengthEv
+ .asInstanceOf[ExplicitLengthEv]
+ .expr
+ .toBriefXML()
+ .stripPrefix("'{")
+ .stripSuffix("}'")
+ .trim()
+ // Convert DFDL expression to a C expression
+ val cExpr = cgState.cExpression(expr)
+ cExpr
+ }
val initERDStatement =
if (specifiedLength > 0)
s"""$indent1$indent2 $field.array = $fieldArray;
- |$indent1$indent2 $field.lengthInBytes = sizeof($fieldArray);
- |$indent1$indent2 $field.dynamic = false;""".stripMargin
- else
+ |$indent1$indent2 $field.lengthInBytes = sizeof($fieldArray);
+ |$indent1$indent2 $field.dynamic = false;""".stripMargin
+ else if (specifiedLength == 0)
s"""$indent1$indent2 $field.array = NULL;
- |$indent1$indent2 $field.lengthInBytes = 0;
- |$indent1$indent2 $field.dynamic = false;""".stripMargin
+ |$indent1$indent2 $field.lengthInBytes = 0;
+ |$indent1$indent2 $field.dynamic = false;""".stripMargin
+ else
+ s"""$indent1$indent2 $field.dynamic = true;""".stripMargin
val parseStatement =
- s"""$indent1$indent2 parse_hexBinary(&$field, pstate);
- |$indent1$indent2 if (pstate->error) return;""".stripMargin
+ if (specifiedLength >= 0)
+ s"""$indent1$indent2 parse_hexBinary(&$field, pstate);
+ |$indent1$indent2 if (pstate->error) return;""".stripMargin
+ else
+ s"""$indent1$indent2 $primType $lenVar = $expression;
+ |$indent1$indent2 alloc_hexBinary(&$field, $lenVar, pstate);
+ |$indent1$indent2 if (pstate->error) return;
+ |$indent1$indent2 parse_hexBinary(&$field, pstate);
+ |$indent1$indent2 if (pstate->error) return;""".stripMargin
val unparseStatement =
s"""$indent1$indent2 unparse_hexBinary($field, ustate);
|$indent1$indent2 if (ustate->error) return;""".stripMargin
diff --git a/daffodil-codegen-c/src/test/resources/org/apache/daffodil/codegen/c/ex_nums.tdml b/daffodil-codegen-c/src/test/resources/org/apache/daffodil/codegen/c/ex_nums.tdml
index 94e81bc08..419d5ef7a 100644
--- a/daffodil-codegen-c/src/test/resources/org/apache/daffodil/codegen/c/ex_nums.tdml
+++ b/daffodil-codegen-c/src/test/resources/org/apache/daffodil/codegen/c/ex_nums.tdml
@@ -21,7 +21,9 @@
description="TDML tests for ex_nums.dfdl.xsd"
xmlns:daf="urn:ogf:dfdl:2013:imp:daffodil.apache.org:2018:ext"
xmlns:dfdl="http://www.ogf.org/dfdl/dfdl-1.0/"
- xmlns:tdml="http://www.ibm.com/xmlns/dfdl/testData">
+ xmlns:ex="http://example.com"
+ xmlns:tdml="http://www.ibm.com/xmlns/dfdl/testData"
+ xmlns:xs="http://www.w3.org/2001/XMLSchema">
<!--
Run all tests:
@@ -52,6 +54,44 @@
</tdml:infoset>
</tdml:parserTestCase>
+ <!--
+ Test runtime length hexBinary works
+ -->
+
+ <tdml:defineSchema name="length">
+ <xs:include schemaLocation="org/apache/daffodil/xsd/DFDLGeneralFormat.dfdl.xsd"/>
+ <dfdl:format ref="GeneralFormat" representation="binary"/>
+ <xs:element name="e1">
+ <xs:complexType>
+ <xs:sequence>
+ <xs:element name="len" type="xs:int"/>
+ <xs:element name="hex" type="xs:hexBinary"
+ dfdl:length="{ (../ex:len idiv 4) * 4 }"
+ dfdl:lengthKind="explicit"/>
+ </xs:sequence>
+ </xs:complexType>
+ </xs:element>
+ </tdml:defineSchema>
+
+ <tdml:parserTestCase
+ model="length"
+ name="length"
+ root="e1"
+ roundTrip="onePass">
+ <tdml:document>
+ <tdml:documentPart type="byte">00000004</tdml:documentPart>
+ <tdml:documentPart type="byte">00000004</tdml:documentPart>
+ </tdml:document>
+ <tdml:infoset>
+ <tdml:dfdlInfoset>
+ <e1>
+ <len>4</len>
+ <hex>00000004</hex>
+ </e1>
+ </tdml:dfdlInfoset>
+ </tdml:infoset>
+ </tdml:parserTestCase>
+
<!--
In Daffodil, parse with validation="off" or "limited" reads
non-fixed values with no validation diagnostics, while parse
diff --git a/daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/processors/Evaluatable.scala b/daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/processors/Evaluatable.scala
index aa65adcc2..435d5bfbd 100644
--- a/daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/processors/Evaluatable.scala
+++ b/daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/processors/Evaluatable.scala
@@ -451,7 +451,7 @@ trait ExprEvalMixin[T <: AnyRef]
* of the property.
*/
abstract class EvaluatableExpression[ExprType <: AnyRef](
- override protected val expr: CompiledExpression[ExprType],
+ override val expr: CompiledExpression[ExprType],
ci: DPathCompileInfo,
) extends Evaluatable[ExprType](ci)
with ExprEvalMixin[ExprType] {
diff --git a/daffodil-test/src/test/scala/org/apache/daffodil/codegen/c/TestExNums.scala b/daffodil-test/src/test/scala/org/apache/daffodil/codegen/c/TestExNums.scala
index d1686e9c0..b8e421138 100644
--- a/daffodil-test/src/test/scala/org/apache/daffodil/codegen/c/TestExNums.scala
+++ b/daffodil-test/src/test/scala/org/apache/daffodil/codegen/c/TestExNums.scala
@@ -38,6 +38,7 @@ class TestExNums {
import TestExNums._
@Test def s_ex_nums(): Unit = { runnerS.runOneTest("ex_nums") }
+ @Test def s_length(): Unit = { runnerS.runOneTest("length") }
@Test def s_parse_error_off(): Unit = { runnerS.runOneTest("parse_error_off") }
@Test def s_parse_error_limited(): Unit = { runnerS.runOneTest("parse_error_limited") }
@Test def s_parse_error_on(): Unit = { runnerS.runOneTest("parse_error_on") }
@@ -46,6 +47,7 @@ class TestExNums {
@Test def s_unparse_error_on(): Unit = { runnerS.runOneTest("unparse_error_on") }
@Test def c_ex_nums(): Unit = { runnerC.runOneTest("ex_nums") }
+ @Test def c_length(): Unit = { runnerC.runOneTest("length") }
@Test def c_parse_error_off(): Unit = { runnerC.runOneTest("parse_error_off") }
@Test def c_parse_error_limited(): Unit = { runnerC.runOneTest("parse_error_limitedC") }
@Test def c_parse_error_on(): Unit = { runnerC.runOneTest("parse_error_on") }