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


##########
daffodil-runtime2/src/main/scala/org/apache/daffodil/runtime2/generators/CodeGeneratorState.scala:
##########
@@ -58,38 +56,209 @@ class CodeGeneratorState(private val root: ElementBase) {
     sb
   }
 
+  // Returns a name for the given element's element resource descriptor

Review Comment:
   ERD in the context of runtime1 stands for ElementRuntimeData, since it's all 
the data we might need during runtime1. But maybe something like resource 
descriptor is less runtime specific?



##########
daffodil-runtime2/src/main/scala/org/apache/daffodil/runtime2/generators/CodeGeneratorState.scala:
##########
@@ -58,38 +56,209 @@ class CodeGeneratorState(private val root: ElementBase) {
     sb
   }
 
+  // Returns a name for the given element's element resource descriptor
+  private def erdName(context: ElementBase): String = {
+    val sb = buildName(context, new StringBuilder) ++= "ERD"
+    val name = sb.toString
+    name
+  }
+
   // Returns a name for the given element's C struct identifier
   private def cStructName(context: ElementBase): String = {
     val sb = buildName(context, new StringBuilder)
     val name = sb.toString
     name
   }
 
-  // Returns a name for the given element's element resource descriptor
-  private def erdName(context: ElementBase): String = {
-    val sb = buildName(context, new StringBuilder) ++= "ERD"
-    val name = sb.toString
-    name
+  // Returns the notation needed to access a C struct field.  We make some 
simplifying
+  // assumptions to make generating the field access easier:
+  // - the expression contains only a relative or absolute path, nothing else 
(e.g.,
+  //   the expression doesn't call any functions or perform any computation)
+  // - we can convert an absolute path to a rootElement()-> indirection
+  // - we can convert a relative path beginning with up dirs to a parents-> 
indirection
+  // - 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 = root.namedQName.local
+    val exprPath = expr.replaceAll("/[^/:]+:", "/").stripPrefix(s"/$rootName")
+    val fieldAccess = if (exprPath.startsWith("/")) {
+      // Convert exprPath to a rootElement()-> indirection
+      val C = cStructName(root)
+      s"""(($C *)rootElement())->${exprPath.stripPrefix("/")}"""
+    } else if (exprPath.startsWith("../")) {
+      // Split exprPath into the up dirs and after the up dirs
+      val afterUpDirs = exprPath.split("\\.\\./").mkString
+      val upDirs = exprPath.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
+      val C = structs(nUpDirs).C
+      // Convert the up dirs to parents
+      val parents = upDirs.replaceAllLiterally("../", 
"parent->").stripSuffix("->")
+      // Convert exprPath to a parents-> indirection
+      s"""(($C *)instance->_base.$parents)->$afterUpDirs"""
+    } else {
+      // Convert exprPath to an instance-> indirection
+      s"""instance->$exprPath"""
+    }
+    // Finally, convert the field access to C struct dot notation
+    val notation = fieldAccess.replace('/', '.')
+    notation
   }
 
-  // Returns true if the element has not been seen before (checking if a
-  // map already contains the element, otherwise adding it to the map)
-  def elementNotSeenYet(context: ElementBase): Boolean = {
-    val key = cStructName(context)
-    val alreadySeen = elementsAlreadySeen.contains(key)
-    if (!alreadySeen)
-      elementsAlreadySeen += (key -> context)
-    !alreadySeen
+  // Returns the code needed to get the size of an array of elements, which
+  // may be either a constant (maxOccurs) or an expression (occursCount)
+  // which accesses a particular C struct field.
+  private def getOccursCount(e: ElementBase): String = {
+    val occursCount = e.occursCountKind match {
+      case OccursCountKind.Fixed =>
+        s"""    UNUSED(instance);
+           |    return ${e.maxOccurs};""".stripMargin
+      case OccursCountKind.Expression =>
+        // Extract expression from {...} in element's occursCount attribute
+        val xml = e.occursCountEv.toBriefXML()

Review Comment:
   toBriefXML is really only intended for debugging. I think this is more 
evidence that we really need some kind of expression walker.



##########
daffodil-runtime2/src/main/resources/org/apache/daffodil/runtime2/c/libcli/daffodil_main.c:
##########
@@ -88,6 +88,15 @@ main(int argc, char *argv[])
         XMLWriter xmlWriter = {xmlWriterMethods, output, {NULL, NULL, 0}};
         error = walkInfoset((VisitEventHandler *)&xmlWriter, root);
         continue_or_exit(error);
+
+        // Any diagnostics will fail the parse if validate mode is on
+        if (daffodil_parse.validate && pstate.diagnostics)
+        {
+            static Error error = {CLI_DIAGNOSTICS, {0}};
+
+            error.arg.d64 = pstate.diagnostics->length;
+            continue_or_exit(&error);
+        }

Review Comment:
   In runtime1, diagnostics can be warnings or errors (and can also be 
validation vs processor errors as well). In runtime2, are all diagnostics 
validation errors, and doesn't have anything like warnings? Seems like we 
wouldn't want to error if there were warnings.



##########
daffodil-runtime2/src/main/scala/org/apache/daffodil/runtime2/generators/CodeGeneratorState.scala:
##########
@@ -404,76 +542,117 @@ class CodeGeneratorState(private val root: ElementBase) {
     finalStructs += struct
   }
 
-  def addSimpleTypeStatements(initERDStatement: String, initSelfStatement: 
String, parseStatement: String, unparseStatement: String): Unit = {
-    if (initERDStatement.nonEmpty) structs.top.initERDStatements += 
initERDStatement
-    if (initSelfStatement.nonEmpty) structs.top.initSelfStatements += 
initSelfStatement
+  // Add C code to initialize, parse, and unparse a primitive value element
+  def addSimpleTypeStatements(initERDStatement: String, parseStatement: 
String, unparseStatement: String): Unit = {
+    if (initERDStatement.nonEmpty) {
+      if (hasChoice)
+        structs.top.initChoiceStatements += initERDStatement
+      else
+        structs.top.initERDStatements += initERDStatement
+    }
     if (parseStatement.nonEmpty) structs.top.parserStatements += parseStatement
     if (unparseStatement.nonEmpty) structs.top.unparserStatements += 
unparseStatement
   }
 
-  def addComplexTypeStatements(child: ElementBase): Unit = {
+  // Generate C code to initialize, parse, and unparse a child element
+  def addPerChildStatements(child: ElementBase): Unit = {
     val C = cStructName(child)
-    val e = child.name
-    val hasChoice = structs.top.initChoiceStatements.nonEmpty
-    val arraySize = if (child.occursCountKind == OccursCountKind.Fixed) 
child.maxOccurs else 0
+    val e = child.namedQName.local
 
+    // Generate a choice statement case if the child is in a choice element
+    val hasChoice = structs.top.initChoiceStatements.nonEmpty
     if (hasChoice) {
+      val position = child.position
       structs.top.initChoiceStatements ++= 
ChoiceBranchKeyCooker.convertConstant(
         child.choiceBranchKey, child, forUnparse = false).map { key => s"    
case $key:"}
-
-      val offset = child.position - 1
-      val initChoiceStatement = s"        instance->_choice = $offset;"
-      val parseStatement = s"    case $offset:"
-      val unparseStatement = s"    case $offset:"
-
-      structs.top.initChoiceStatements += initChoiceStatement
-      structs.top.parserStatements += parseStatement
-      structs.top.unparserStatements += unparseStatement
+      structs.top.initChoiceStatements += s"        instance->_choice = 
$position;"
+      structs.top.parserStatements += s"    case $position:"
+      structs.top.unparserStatements += s"    case $position:"
     }
+    val indent1 = if (hasChoice) "    " else ""
 
-    def addStatements(deref: String): Unit = {
-      val initChoiceStatement = s"        ${C}_initERD(&instance->$e$deref);"
-      val initERDStatement = s"    ${C}_initERD(&instance->$e$deref);"
-      val initSelfStatement = s"    ${C}_initSelf(&instance->$e$deref);"
-      val moreIndent = if (hasChoice) "    " else ""
-      val parseStatement =
-        s"""$moreIndent    ${C}_parseSelf(&instance->$e$deref, pstate);
-           |$moreIndent    if (pstate->error) return;""".stripMargin
-      val unparseStatement =
-        s"""$moreIndent    ${C}_unparseSelf(&instance->$e$deref, ustate);
-           |$moreIndent    if (ustate->error) return;""".stripMargin
+    // Add an array if the child occurs multiple times
+    val hasArray = arrayMaxOccurs(child) > 0
+    val deref = if (hasArray) "[i]" else ""
+    val indent2 = if (hasArray) "    " else ""
+    if (hasArray) pushArray(child)
 
+    // Generate statements to initialize, parse, and unparse the child
+    Runtime2CodeGenerator.generateCode(child.enclosedElement, this)
+    if (!child.isSimpleType) {
       if (hasChoice)
-        structs.top.initChoiceStatements += initChoiceStatement
+        structs.top.initChoiceStatements += s"$indent2        
${C}_initERD(&instance->$e$deref, (InfosetBase *)instance);"
       else
-        structs.top.initERDStatements += initERDStatement
-      structs.top.initSelfStatements += initSelfStatement
-      structs.top.parserStatements += parseStatement
-      structs.top.unparserStatements += unparseStatement
+        structs.top.initERDStatements += s"$indent2    
${C}_initERD(&instance->$e$deref, (InfosetBase *)instance);"
+      structs.top.parserStatements +=
+        s"""$indent1$indent2    ${C}_parseSelf(&instance->$e$deref, pstate);
+           |$indent1$indent2    if (pstate->error) return;""".stripMargin
+      structs.top.unparserStatements +=
+        s"""$indent1$indent2    ${C}_unparseSelf(&instance->$e$deref, ustate);
+           |$indent1$indent2    if (ustate->error) return;""".stripMargin
     }
-    if (arraySize > 0)
-      for (i <- 0 until arraySize)
-        addStatements(s"[$i]")
-    else
-      addStatements("")
 
-    if (hasChoice) {
-      val initChoiceStatement = s"        break;"
-      val parseStatement = s"        break;"
-      val unparseStatement = s"        break;"
+    // Generate statements to align the next element
+    val mustAlign = child.endingAlignmentApprox.nBits > 
child.priorAlignmentApprox.nBits

Review Comment:
   I'm not sure this is the right logic to know if you need to add alignment 
bits. The `isKnownToBeAligned` lets you know if we were able to statically 
figure out if any alignment is needed after an element. So if that is false, 
you *might* need alignment, but you might not. We don't know, and we also don't 
know how many bits are needed.
   
   If you might need alignment, I don't think `endingAlignmentApprox` is the 
right field to read. It think the below code wants to just access the 
`alignment` field on the ElementBase and do some modular arithmetic at runtime 
to calculate how many bits are needed. I think something like this, very 
similar to what our `align` function does in DataInputStreamImplMixin.scala
   
   ```scala
   if (!child.isKnownToBeAligned) {
     structs.top.parserStatements +=
       s"""   // Fill to closest alignment
          |   parse_fill_bits(${child.alignment} - (pstate->bitPos0b % 
${child.alignment}), pstate);
          |   if (pstate->error) return;""".stripMargin
       // same idea for unparse
   }
   ```
   
   In fact, it might even be more clear to add a parse_align() function, which 
has this logic, then this simplifies to just
   ```scala
   if (!child.isKnownToBeAligned) {
     structs.top.parserStatements +=
       s"""   // Fill to closest alignment
          |   parse_align(${child.alignment}, pstate);
          |   if (pstate->error) return;""".stripMargin
       // same idea for unparse
   }
   ```
   That would just be a wrapper around parse_fill_bits, but maybe is more clear 
what's happening.
   
   Note that even though we might not be able to statically determine if 
alignment is needed, it still could be aligned at runtime, so `parse_fill_bits` 
needs to be able to support filling zero bits, which I think it does.



##########
daffodil-runtime2/src/main/resources/org/apache/daffodil/runtime2/c/libcli/daffodil_getopt.c:
##########
@@ -85,6 +86,18 @@ parse_daffodil_cli(int argc, char *argv[])
             daffodil_unparse.outfile = optarg;
             break;
         case 'V':
+            if (strcmp("limited", optarg) == 0 || strcmp("on", optarg) == 0)
+            {
+                daffodil_parse.validate = true;

Review Comment:
   Just to be clear, this is a slight difference than the daffodil runtime1 
validate option. `limited` there means for Daffodil to check individual fields 
itself as it parses, and `on` means to validation the entire infoset with 
Xerces once parsing completes. I assume the C validation is more like the 
former. I think it's fine for both `on` and `limited` to do the same thing for 
runtime2, but just thought it was worth make it clear that they aren't the same 
in runtime1. Maybe eventually there might be a desire for `on` to validate with 
an external tool like Daffodil does, like via xmllint or something.



##########
daffodil-runtime2/src/main/scala/org/apache/daffodil/runtime2/generators/CodeGeneratorState.scala:
##########
@@ -58,38 +56,209 @@ class CodeGeneratorState(private val root: ElementBase) {
     sb
   }
 
+  // Returns a name for the given element's element resource descriptor
+  private def erdName(context: ElementBase): String = {
+    val sb = buildName(context, new StringBuilder) ++= "ERD"
+    val name = sb.toString
+    name
+  }
+
   // Returns a name for the given element's C struct identifier
   private def cStructName(context: ElementBase): String = {
     val sb = buildName(context, new StringBuilder)
     val name = sb.toString
     name
   }
 
-  // Returns a name for the given element's element resource descriptor
-  private def erdName(context: ElementBase): String = {
-    val sb = buildName(context, new StringBuilder) ++= "ERD"
-    val name = sb.toString
-    name
+  // Returns the notation needed to access a C struct field.  We make some 
simplifying
+  // assumptions to make generating the field access easier:
+  // - the expression contains only a relative or absolute path, nothing else 
(e.g.,
+  //   the expression doesn't call any functions or perform any computation)
+  // - we can convert an absolute path to a rootElement()-> indirection
+  // - we can convert a relative path beginning with up dirs to a parents-> 
indirection
+  // - 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 = root.namedQName.local
+    val exprPath = expr.replaceAll("/[^/:]+:", "/").stripPrefix(s"/$rootName")
+    val fieldAccess = if (exprPath.startsWith("/")) {
+      // Convert exprPath to a rootElement()-> indirection
+      val C = cStructName(root)
+      s"""(($C *)rootElement())->${exprPath.stripPrefix("/")}"""
+    } else if (exprPath.startsWith("../")) {
+      // Split exprPath into the up dirs and after the up dirs
+      val afterUpDirs = exprPath.split("\\.\\./").mkString
+      val upDirs = exprPath.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
+      val C = structs(nUpDirs).C
+      // Convert the up dirs to parents
+      val parents = upDirs.replaceAllLiterally("../", 
"parent->").stripSuffix("->")
+      // Convert exprPath to a parents-> indirection
+      s"""(($C *)instance->_base.$parents)->$afterUpDirs"""
+    } else {
+      // Convert exprPath to an instance-> indirection
+      s"""instance->$exprPath"""
+    }
+    // Finally, convert the field access to C struct dot notation
+    val notation = fieldAccess.replace('/', '.')
+    notation
   }

Review Comment:
   I wonder if we need a DFDL Expression walker that would allow you to walk a 
compiled expression and convert it to C code (and error if there's something in 
an expression you don't support. We already have a lot of logic to parse and 
validate expressions so it's  a shame for you to have to do it all again.
   
   This also feels very fragile and difficult to ensure correctness. For 
example, I *think* there might be an edge case where expressions with up-paths 
in the middle (e.g. ../../foo/bar/../baz) might fail? I'm not positive (again, 
it's hard to ensure correctness). I'm not sure I've actaully seen something 
like that and it's probably not worth worrying about considering the supported 
expressions are very limited, but weird like this is possible (and I think our 
expression compiler optimizes this kind of thing out).



##########
daffodil-runtime2/src/main/scala/org/apache/daffodil/runtime2/generators/BinaryValueCodeGenerator.scala:
##########
@@ -21,81 +21,65 @@ import org.apache.daffodil.dsom.ElementBase
 import org.apache.daffodil.schema.annotation.props.gen.BitOrder
 import org.apache.daffodil.schema.annotation.props.gen.ByteOrder
 import org.apache.daffodil.schema.annotation.props.gen.LengthKind
-import org.apache.daffodil.schema.annotation.props.gen.OccursCountKind
 import org.apache.daffodil.util.Maybe.Nope
 
 // Base trait which provides common code to generate C code for primitive 
value elements
 trait BinaryValueCodeGenerator {
 
-  // Generate C code for a primitive value element by calculating how many 
times the element occurs
-  // and applying the given partially applied function values the necessary 
number of times.  Intended
-  // to be called by other traits which extend this trait, not directly by 
Runtime2CodeGenerator.
-  def binaryValueGenerateCode(e: ElementBase, addField: String => Unit, 
validateFixed: String => Unit): Unit = {
+  // Generate C code for a primitive value element differently depending on 
how many times the element occurs.
+  // Intended to be called by other traits which extend this trait, not 
directly by Runtime2CodeGenerator.
+  def binaryValueGenerateCode(e: ElementBase, addField: String => Unit, 
validateFixed: String => Unit, cgState: CodeGeneratorState): Unit = {
     // For the time being this is a very limited back end.
     // So there are some restrictions to enforce.
     e.schemaDefinitionUnless(e.bitOrder eq BitOrder.MostSignificantBitFirst, 
"Only dfdl:bitOrder 'mostSignificantBitFirst' is supported.")
     e.schemaDefinitionUnless(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.")
 
-    // Calculate how many times the element occurs in the data.
-    val arraySize = e.occursCountKind match {
-      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 _ => e.SDE("occursCountKind %s minOccurs %d maxOccurs %d is not 
supported in C code generator",
-        e.occursCountKind.toString, e.minOccurs, e.maxOccurs)
+    // Call the given partially applied function values with their remaining 
unbound argument (deref)
+    val hasArray = cgState.arrayMaxOccurs(e) > 0

Review Comment:
   I found this `arrayMaxOccurs` function a bit confusing at first especially 
since we already have an isArray function, which does things like returns false 
for scalars. Would it be possible to just SDE on the specific array kinds that 
runtime2 doesn't support like OccursCountKind.Parse, so as to avoid another 
function that has a unique definition of what an array is?



##########
daffodil-runtime2/src/main/scala/org/apache/daffodil/runtime2/generators/CodeGeneratorState.scala:
##########
@@ -168,64 +330,42 @@ class CodeGeneratorState(private val root: ElementBase) {
     qNameInit
   }
 
-  /**
-   * We want to convert a choiceDispatchKey expression into C struct dot
-   * notation (rootElement->[subElement.field]) which will access the C
-   * struct field containing the choiceDispatchKey's runtime value.
-   *
-   * We make some assumptions to make generating the dot notation easier:
-   * - the expression starts with '{xs:string( and ends with )}'
-   * - the expression returns the value of a previous element without
-   *   changing the value in any way (except converting it to xs:string)
-   * - both the expression and the C code use only local names (for now...)
-   * - we can map the context node's path to a Unix-like slash path
-   * - all dpath operations look like Unix-like relative paths (../tag)
-   * - we can normalize the new path and convert it to C struct dot notation
-   * - we can store the accessed value in an int64_t local variable safely
-   */
-  private def choiceDispatchField(context: ElementBase): String = {
-    // We want to call SchemaComponent.scPath but it's private so duplicate it 
here for now
-    def scPath(sc: SchemaComponent): Seq[SchemaComponent] = 
sc.optLexicalParent.map { scPath }.getOrElse(Nil) :+ sc
-
-    // We handle only direct dispatch choices, so ignore other elements
-    context.complexType.modelGroup match {
-      case choice: Choice if choice.isDirectDispatch =>
-        // Get parent path against which to perform up paths
-        val parentNames = scPath(context).map {
-          case _: Root => ""
-          case er: AbstractElementRef => er.refQName.local
-          case eb: ElementBase => eb.namedQName.local
-          case ed: GlobalElementDecl => ed.namedQName.local
-          case _ => ""
-        }
-        val parentPath = parentNames.mkString("/")
-
-        // Convert expression to a relative path (may have up paths)
-        val expr = 
choice.choiceDispatchKeyEv.expr.toBriefXML().filterNot(_.isWhitespace)
-        val before = "'{xs:string("
-        val after = ")}'"
-        val relativePath = if (expr.startsWith(before) && expr.endsWith(after))
-          expr.substring(before.length, expr.length - after.length) else expr
-
-        // Remove redundant slashes (//) and up paths (../)
-        val normalizedURI = new URI(parentPath + "/" + relativePath).normalize
-
-        // Strip namespace prefixes since C code uses only local names (for 
now)
-        val dispatchPath = normalizedURI.getPath.replaceAll("/[^/:]+:", "/")
+  // Returns true if the element has not been seen before (checking if a
+  // map already contains the element, otherwise adding it to the map)
+  def elementNotSeenYet(context: ElementBase): Boolean = {
+    val key = cStructName(context)
+    val alreadySeen = elementsAlreadySeen.contains(key)
+    if (!alreadySeen)
+      elementsAlreadySeen += (key -> context)
+    !alreadySeen
+  }
 
-        // Convert to C struct dot notation without any leading dot
-        val notation = dispatchPath.replace('/', '.').substring(1)
-        notation
-      // We get called on every group element, so we need to return "" for 
non-choice elements
-      case _ => ""
+  // Returns the given element's maxOccurs if it is an array element
+  // with a finite maxOccurs > 0, otherwise returns zero for scalar
+  // elements and array elements with unbounded maxOccurs (we don't
+  // support unbounded arrays in C right now)
+  def arrayMaxOccurs(e: ElementBase): Int = {

Review Comment:
   Mentioned elsewhere, but I find this confusing. Suggested one alternative 
elsewhere, but maybe at least change this so this returns a `Maybe[Int]`, where 
it is `One[Int]` when it is an array and `Nope` when it's not an array. I think 
that's more clear than needing to check the special zero value.



##########
daffodil-runtime2/src/main/scala/org/apache/daffodil/runtime2/generators/CodeGeneratorState.scala:
##########
@@ -404,76 +542,117 @@ class CodeGeneratorState(private val root: ElementBase) {
     finalStructs += struct
   }
 
-  def addSimpleTypeStatements(initERDStatement: String, initSelfStatement: 
String, parseStatement: String, unparseStatement: String): Unit = {
-    if (initERDStatement.nonEmpty) structs.top.initERDStatements += 
initERDStatement
-    if (initSelfStatement.nonEmpty) structs.top.initSelfStatements += 
initSelfStatement
+  // Add C code to initialize, parse, and unparse a primitive value element
+  def addSimpleTypeStatements(initERDStatement: String, parseStatement: 
String, unparseStatement: String): Unit = {
+    if (initERDStatement.nonEmpty) {
+      if (hasChoice)
+        structs.top.initChoiceStatements += initERDStatement
+      else
+        structs.top.initERDStatements += initERDStatement
+    }
     if (parseStatement.nonEmpty) structs.top.parserStatements += parseStatement
     if (unparseStatement.nonEmpty) structs.top.unparserStatements += 
unparseStatement
   }
 
-  def addComplexTypeStatements(child: ElementBase): Unit = {
+  // Generate C code to initialize, parse, and unparse a child element
+  def addPerChildStatements(child: ElementBase): Unit = {
     val C = cStructName(child)
-    val e = child.name
-    val hasChoice = structs.top.initChoiceStatements.nonEmpty
-    val arraySize = if (child.occursCountKind == OccursCountKind.Fixed) 
child.maxOccurs else 0
+    val e = child.namedQName.local
 
+    // Generate a choice statement case if the child is in a choice element
+    val hasChoice = structs.top.initChoiceStatements.nonEmpty
     if (hasChoice) {
+      val position = child.position
       structs.top.initChoiceStatements ++= 
ChoiceBranchKeyCooker.convertConstant(
         child.choiceBranchKey, child, forUnparse = false).map { key => s"    
case $key:"}
-
-      val offset = child.position - 1
-      val initChoiceStatement = s"        instance->_choice = $offset;"
-      val parseStatement = s"    case $offset:"
-      val unparseStatement = s"    case $offset:"
-
-      structs.top.initChoiceStatements += initChoiceStatement
-      structs.top.parserStatements += parseStatement
-      structs.top.unparserStatements += unparseStatement
+      structs.top.initChoiceStatements += s"        instance->_choice = 
$position;"
+      structs.top.parserStatements += s"    case $position:"
+      structs.top.unparserStatements += s"    case $position:"
     }
+    val indent1 = if (hasChoice) "    " else ""
 
-    def addStatements(deref: String): Unit = {
-      val initChoiceStatement = s"        ${C}_initERD(&instance->$e$deref);"
-      val initERDStatement = s"    ${C}_initERD(&instance->$e$deref);"
-      val initSelfStatement = s"    ${C}_initSelf(&instance->$e$deref);"
-      val moreIndent = if (hasChoice) "    " else ""
-      val parseStatement =
-        s"""$moreIndent    ${C}_parseSelf(&instance->$e$deref, pstate);
-           |$moreIndent    if (pstate->error) return;""".stripMargin
-      val unparseStatement =
-        s"""$moreIndent    ${C}_unparseSelf(&instance->$e$deref, ustate);
-           |$moreIndent    if (ustate->error) return;""".stripMargin
+    // Add an array if the child occurs multiple times
+    val hasArray = arrayMaxOccurs(child) > 0
+    val deref = if (hasArray) "[i]" else ""
+    val indent2 = if (hasArray) "    " else ""
+    if (hasArray) pushArray(child)
 
+    // Generate statements to initialize, parse, and unparse the child
+    Runtime2CodeGenerator.generateCode(child.enclosedElement, this)
+    if (!child.isSimpleType) {
       if (hasChoice)
-        structs.top.initChoiceStatements += initChoiceStatement
+        structs.top.initChoiceStatements += s"$indent2        
${C}_initERD(&instance->$e$deref, (InfosetBase *)instance);"
       else
-        structs.top.initERDStatements += initERDStatement
-      structs.top.initSelfStatements += initSelfStatement
-      structs.top.parserStatements += parseStatement
-      structs.top.unparserStatements += unparseStatement
+        structs.top.initERDStatements += s"$indent2    
${C}_initERD(&instance->$e$deref, (InfosetBase *)instance);"
+      structs.top.parserStatements +=
+        s"""$indent1$indent2    ${C}_parseSelf(&instance->$e$deref, pstate);
+           |$indent1$indent2    if (pstate->error) return;""".stripMargin
+      structs.top.unparserStatements +=
+        s"""$indent1$indent2    ${C}_unparseSelf(&instance->$e$deref, ustate);
+           |$indent1$indent2    if (ustate->error) return;""".stripMargin
     }
-    if (arraySize > 0)
-      for (i <- 0 until arraySize)
-        addStatements(s"[$i]")
-    else
-      addStatements("")
 
-    if (hasChoice) {
-      val initChoiceStatement = s"        break;"
-      val parseStatement = s"        break;"
-      val unparseStatement = s"        break;"
+    // Generate statements to align the next element
+    val mustAlign = child.endingAlignmentApprox.nBits > 
child.priorAlignmentApprox.nBits
+    if (mustAlign) {
+      val nBits = child.endingAlignmentApprox.nBits
+      structs.top.parserStatements +=
+        s"""$indent1$indent2    // Fill to closest alignment
+           |$indent1$indent2    parse_fill_bits((pstate->bitPos0b + ${nBits - 
1}) / $nBits * $nBits, pstate);
+           |$indent1$indent2    if (pstate->error) return;""".stripMargin
+      structs.top.unparserStatements +=
+        s"""$indent1$indent2    // Fill to closest alignment
+           |$indent1$indent2    unparse_fill_bits((ustate->bitPos0b + ${nBits 
- 1}) / $nBits * $nBits, '\\0', ustate);
+           |$indent1$indent2    if (ustate->error) return;""".stripMargin
+    }
 
-      structs.top.initChoiceStatements += initChoiceStatement
-      structs.top.parserStatements += parseStatement
-      structs.top.unparserStatements += unparseStatement
+    // Terminate the array if the child occurs multiple times
+    if (hasArray) popArray(child)
+
+    // Generate a choice statement break if the child is in a choice element
+    if (hasChoice) {
+      val break = s"        break;"
+      structs.top.initChoiceStatements += break
+      structs.top.parserStatements += break
+      structs.top.unparserStatements += break
     }
   }
 
+  private def pushArray(e: ElementBase): Unit = {
+    val C = structs.top.C
+    val name = "%5Bi%5D"

Review Comment:
   What is this name? Looks like a format string, but are B and D format 
patterns?



##########
daffodil-runtime2/src/main/scala/org/apache/daffodil/runtime2/generators/HexBinaryCodeGenerator.scala:
##########
@@ -60,76 +62,70 @@ trait HexBinaryCodeGenerator extends 
BinaryValueCodeGenerator {
     val primType = s"$intType$intLen"
     val conv = if (e.prefixedLengthElementDecl.byteOrderEv.constValue == 
ByteOrder.BigEndian) "be" else "le"
     val function = s"${conv}_$primType"
-    val i = if (deref.length > 2) deref.substring(1, deref.length - 1) else ""
-    val lenVar = s"_l_$localName$i"
+    val lenVar = s"_l_$localName"
 
     val initERDStatement =
-      s"""    $field.array = NULL;
-         |    $field.lengthInBytes = 0;
-         |    $field.dynamic = true;""".stripMargin
-    val initSelfStatement = ""
+      s"""$indent1$indent2    $field.dynamic = true;""".stripMargin
     val parseStatement =
-      s"""    ${primType}_t $lenVar;
-         |    parse_$function(&$lenVar, $intLen, pstate);
-         |    if (pstate->error) return;
-         |    alloc_hexBinary(&$field, $lenVar, pstate);
-         |    if (pstate->error) return;
-         |    parse_hexBinary(&$field, pstate);
-         |    if (pstate->error) return;""".stripMargin
+      s"""$indent1$indent2    ${primType}_t $lenVar;
+         |$indent1$indent2    parse_$function(&$lenVar, $intLen, pstate);
+         |$indent1$indent2    if (pstate->error) return;
+         |$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"""    unparse_$function($field.lengthInBytes, $intLen, ustate);
-         |    if (ustate->error) return;
-         |    unparse_hexBinary($field, ustate);
-         |    if (ustate->error) return;""".stripMargin
-    cgState.addSimpleTypeStatements(initERDStatement, initSelfStatement, 
parseStatement, unparseStatement)
+      s"""$indent1$indent2    unparse_$function($field.lengthInBytes, $intLen, 
ustate);
+         |$indent1$indent2    if (ustate->error) return;
+         |$indent1$indent2    unparse_hexBinary($field, ustate);
+         |$indent1$indent2    if (ustate->error) return;""".stripMargin
+    cgState.addSimpleTypeStatements(initERDStatement, parseStatement, 
unparseStatement)
   }
 
   // Generate C code to initialize, parse, and unparse a hexBinary specified 
length element
   private def hexBinarySpecifiedLengthAddField(e: ElementBase, deref: String, 
cgState: CodeGeneratorState): Unit = {
+    val indent1 = if (cgState.hasChoice) "    " else ""
+    val indent2 = if (deref.nonEmpty) "    " else ""
     val localName = e.namedQName.local
     val field = s"instance->$localName$deref"
     val fieldArray = s"instance->_a_$localName$deref"
     val specifiedLength = e.elementLengthInBitsEv.constValue.get
 
     val initERDStatement = if (specifiedLength > 0)
-      s"""    $field.array = $fieldArray;
-         |    $field.lengthInBytes = sizeof($fieldArray);
-         |    $field.dynamic = false;""".stripMargin
+      s"""$indent1$indent2    $field.array = $fieldArray;
+         |$indent1$indent2    $field.lengthInBytes = sizeof($fieldArray);
+         |$indent1$indent2    $field.dynamic = false;""".stripMargin
     else
-      s"""    $field.array = NULL;
-         |    $field.lengthInBytes = 0;
-         |    $field.dynamic = false;""".stripMargin
-    val initSelfStatement = if (specifiedLength > 0)
-      s"    memset($fieldArray, 0x77, sizeof($fieldArray));"
-    else
-      ""
+      s"""$indent1$indent2    $field.array = NULL;
+         |$indent1$indent2    $field.lengthInBytes = 0;
+         |$indent1$indent2    $field.dynamic = false;""".stripMargin
     val parseStatement =
-      s"""    parse_hexBinary(&$field, pstate);
-         |    if (pstate->error) return;""".stripMargin
+      s"""$indent1$indent2    parse_hexBinary(&$field, pstate);
+         |$indent1$indent2    if (pstate->error) return;""".stripMargin
     val unparseStatement =
-      s"""    unparse_hexBinary($field, ustate);
-         |    if (ustate->error) return;""".stripMargin
-    cgState.addSimpleTypeStatements(initERDStatement, initSelfStatement, 
parseStatement, unparseStatement)
+      s"""$indent1$indent2    unparse_hexBinary($field, ustate);
+         |$indent1$indent2    if (ustate->error) return;""".stripMargin
+    cgState.addSimpleTypeStatements(initERDStatement, parseStatement, 
unparseStatement)
   }
 
   // Generate C code to validate a hexBinary element against its fixed value
   private def hexBinaryValidateFixed(e: ElementBase, deref: String, cgState: 
CodeGeneratorState): Unit = {
+    val indent1 = if (cgState.hasChoice) "    " else ""
+    val indent2 = if (deref.nonEmpty) "    " else ""
     val localName = e.namedQName.local
     val field = s"instance->$localName$deref"
-    val i = if (deref.length > 2) deref.substring(1, deref.length - 1) else ""
-    val fixed = s"${localName}_fixed$i"
+    val fixed = s"${localName}_fixed"
     val array = e.fixedValueAsString.grouped(2).mkString("0x", ", 0x", "")
 
     val initERDStatement = ""
-    val initSelfStatement = ""
     val parseStatement =
-      s"""    uint8_t $fixed[] = {$array};
-         |    parse_validate_fixed(memcmp($field.array, $fixed, 
sizeof($fixed)) == 0, "$localName", pstate);
-         |    if (pstate->error) return;""".stripMargin
+      s"""$indent1$indent2    uint8_t $fixed[] = {$array};
+         |$indent1$indent2    parse_validate_fixed(memcmp($field.array, 
$fixed, sizeof($fixed)) == 0, "$localName", pstate);
+         |$indent1$indent2    if (pstate->error) return;""".stripMargin
     val unparseStatement =
-      s"""    uint8_t $fixed[] = {$array};
-         |    unparse_validate_fixed(memcmp($field.array, $fixed, 
sizeof($fixed)) == 0, "$localName", ustate);
-         |    if (ustate->error) return;""".stripMargin
-    cgState.addSimpleTypeStatements(initERDStatement, initSelfStatement, 
parseStatement, unparseStatement)
+      s"""$indent1$indent2    uint8_t $fixed[] = {$array};
+         |$indent1$indent2    unparse_validate_fixed(memcmp($field.array, 
$fixed, sizeof($fixed)) == 0, "$localName", ustate);
+         |$indent1$indent2    if (ustate->error) return;""".stripMargin

Review Comment:
   I wonder if there's a better way to handle all this indentation? Perhaps we 
could create a custom function similar to stripMargin but strips margin + 
indents to some level.
   
   ```scala
   val indentLevel = 1 + Seq(cgState.hasChoice, deref.nonEmpty).count(_ == true)
   
   s"""|uint8_t $fixed[] = {$array};
       |unparse_validate_fixed(memcmp($field.array, $fixed, sizeof($fixed)) == 
0, "$localName", ustate);
       |if (ustate->error) return;
       |""".indent(indentLevel)
   ```
   
   Not sure I like the indentLevel logic one-liner, but it something along 
those lines could be a bit cleaner and make it easier to see the real code and 
reduce duplication.



##########
daffodil-runtime2/src/main/scala/org/apache/daffodil/runtime2/generators/CodeGeneratorState.scala:
##########
@@ -404,76 +542,117 @@ class CodeGeneratorState(private val root: ElementBase) {
     finalStructs += struct
   }
 
-  def addSimpleTypeStatements(initERDStatement: String, initSelfStatement: 
String, parseStatement: String, unparseStatement: String): Unit = {
-    if (initERDStatement.nonEmpty) structs.top.initERDStatements += 
initERDStatement
-    if (initSelfStatement.nonEmpty) structs.top.initSelfStatements += 
initSelfStatement
+  // Add C code to initialize, parse, and unparse a primitive value element
+  def addSimpleTypeStatements(initERDStatement: String, parseStatement: 
String, unparseStatement: String): Unit = {
+    if (initERDStatement.nonEmpty) {
+      if (hasChoice)
+        structs.top.initChoiceStatements += initERDStatement
+      else
+        structs.top.initERDStatements += initERDStatement
+    }
     if (parseStatement.nonEmpty) structs.top.parserStatements += parseStatement
     if (unparseStatement.nonEmpty) structs.top.unparserStatements += 
unparseStatement
   }
 
-  def addComplexTypeStatements(child: ElementBase): Unit = {
+  // Generate C code to initialize, parse, and unparse a child element
+  def addPerChildStatements(child: ElementBase): Unit = {
     val C = cStructName(child)
-    val e = child.name
-    val hasChoice = structs.top.initChoiceStatements.nonEmpty
-    val arraySize = if (child.occursCountKind == OccursCountKind.Fixed) 
child.maxOccurs else 0
+    val e = child.namedQName.local
 
+    // Generate a choice statement case if the child is in a choice element
+    val hasChoice = structs.top.initChoiceStatements.nonEmpty
     if (hasChoice) {
+      val position = child.position
       structs.top.initChoiceStatements ++= 
ChoiceBranchKeyCooker.convertConstant(
         child.choiceBranchKey, child, forUnparse = false).map { key => s"    
case $key:"}
-
-      val offset = child.position - 1
-      val initChoiceStatement = s"        instance->_choice = $offset;"
-      val parseStatement = s"    case $offset:"
-      val unparseStatement = s"    case $offset:"
-
-      structs.top.initChoiceStatements += initChoiceStatement
-      structs.top.parserStatements += parseStatement
-      structs.top.unparserStatements += unparseStatement
+      structs.top.initChoiceStatements += s"        instance->_choice = 
$position;"
+      structs.top.parserStatements += s"    case $position:"
+      structs.top.unparserStatements += s"    case $position:"
     }
+    val indent1 = if (hasChoice) "    " else ""
 
-    def addStatements(deref: String): Unit = {
-      val initChoiceStatement = s"        ${C}_initERD(&instance->$e$deref);"
-      val initERDStatement = s"    ${C}_initERD(&instance->$e$deref);"
-      val initSelfStatement = s"    ${C}_initSelf(&instance->$e$deref);"
-      val moreIndent = if (hasChoice) "    " else ""
-      val parseStatement =
-        s"""$moreIndent    ${C}_parseSelf(&instance->$e$deref, pstate);
-           |$moreIndent    if (pstate->error) return;""".stripMargin
-      val unparseStatement =
-        s"""$moreIndent    ${C}_unparseSelf(&instance->$e$deref, ustate);
-           |$moreIndent    if (ustate->error) return;""".stripMargin
+    // Add an array if the child occurs multiple times
+    val hasArray = arrayMaxOccurs(child) > 0
+    val deref = if (hasArray) "[i]" else ""
+    val indent2 = if (hasArray) "    " else ""
+    if (hasArray) pushArray(child)
 
+    // Generate statements to initialize, parse, and unparse the child
+    Runtime2CodeGenerator.generateCode(child.enclosedElement, this)
+    if (!child.isSimpleType) {
       if (hasChoice)
-        structs.top.initChoiceStatements += initChoiceStatement
+        structs.top.initChoiceStatements += s"$indent2        
${C}_initERD(&instance->$e$deref, (InfosetBase *)instance);"
       else
-        structs.top.initERDStatements += initERDStatement
-      structs.top.initSelfStatements += initSelfStatement
-      structs.top.parserStatements += parseStatement
-      structs.top.unparserStatements += unparseStatement
+        structs.top.initERDStatements += s"$indent2    
${C}_initERD(&instance->$e$deref, (InfosetBase *)instance);"
+      structs.top.parserStatements +=
+        s"""$indent1$indent2    ${C}_parseSelf(&instance->$e$deref, pstate);
+           |$indent1$indent2    if (pstate->error) return;""".stripMargin
+      structs.top.unparserStatements +=
+        s"""$indent1$indent2    ${C}_unparseSelf(&instance->$e$deref, ustate);
+           |$indent1$indent2    if (ustate->error) return;""".stripMargin
     }
-    if (arraySize > 0)
-      for (i <- 0 until arraySize)
-        addStatements(s"[$i]")
-    else
-      addStatements("")
 
-    if (hasChoice) {
-      val initChoiceStatement = s"        break;"
-      val parseStatement = s"        break;"
-      val unparseStatement = s"        break;"
+    // Generate statements to align the next element
+    val mustAlign = child.endingAlignmentApprox.nBits > 
child.priorAlignmentApprox.nBits
+    if (mustAlign) {
+      val nBits = child.endingAlignmentApprox.nBits
+      structs.top.parserStatements +=
+        s"""$indent1$indent2    // Fill to closest alignment
+           |$indent1$indent2    parse_fill_bits((pstate->bitPos0b + ${nBits - 
1}) / $nBits * $nBits, pstate);
+           |$indent1$indent2    if (pstate->error) return;""".stripMargin
+      structs.top.unparserStatements +=
+        s"""$indent1$indent2    // Fill to closest alignment
+           |$indent1$indent2    unparse_fill_bits((ustate->bitPos0b + ${nBits 
- 1}) / $nBits * $nBits, '\\0', ustate);
+           |$indent1$indent2    if (ustate->error) return;""".stripMargin
+    }
 
-      structs.top.initChoiceStatements += initChoiceStatement
-      structs.top.parserStatements += parseStatement
-      structs.top.unparserStatements += unparseStatement
+    // Terminate the array if the child occurs multiple times
+    if (hasArray) popArray(child)
+
+    // Generate a choice statement break if the child is in a choice element
+    if (hasChoice) {
+      val break = s"        break;"
+      structs.top.initChoiceStatements += break
+      structs.top.parserStatements += break
+      structs.top.unparserStatements += break
     }
   }
 
+  private def pushArray(e: ElementBase): Unit = {
+    val C = structs.top.C
+    val name = "%5Bi%5D"
+    structs.push(new ComplexCGState(C, name))
+  }
+
+  private def popArray(e: ElementBase): Unit = {
+    addArrayImplementation(e)
+    structs.pop()
+
+    // Now call the array's methods instead of the array's element's methods
+    val C = structs.top.C
+    val arrayName = s"array_${cStructName(e)}$C"
+    val indent = if (hasChoice) "    " else ""
+    if (hasChoice)
+      structs.top.initChoiceStatements += s"$indent    
${arrayName}_initERD(instance, parent);"
+    else
+      structs.top.initERDStatements += s"$indent    
${arrayName}_initERD(instance, parent);"
+    structs.top.parserStatements +=
+      s"""$indent    ${arrayName}_parseSelf(instance, pstate);
+         |$indent    if (pstate->error) return;""".stripMargin
+    structs.top.unparserStatements +=
+      s"""$indent    ${arrayName}_unparseSelf(instance, ustate);
+         |$indent    if (ustate->error) return;""".stripMargin
+  }
+
   def pushComplexElement(context: ElementBase): Unit = {
     val C = cStructName(context)
-    structs.push(new ComplexCGState(C))
+    val deref = if (arrayMaxOccurs(context) > 0) "%5B0%5D" else ""

Review Comment:
   Same here, is this a magic string?



-- 
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