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 2021/11/24 22:18:59 UTC

[daffodil] branch main updated: Fix some code generation corner cases

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 ebe7523  Fix some code generation corner cases
ebe7523 is described below

commit ebe75238207e6643e47a9d674ac6174ce2ebd869
Author: John Interrante <in...@research.ge.com>
AuthorDate: Fri Nov 12 13:10:57 2021 -0500

    Fix some code generation corner cases
    
    Also make "sbt compile" start generating runtime2's example files.
    
    Corner cases fixed:
    
    1. Elements used in multiple places within a schema were causing
       redundant code generation.
    
    2. Empty complex type elements without any child elements were causing
       compiler warnings.
    
    3. Certain choice dispatch key expressions were causing compiler
       errors.
    
    .gitattributes - Add comment explaining what KEYS line is for.
    
    main.yml - Bump mxml from 3.2 to 3.3.  Add a GitHub action that runs
    `git diff --exit-code` to check a pull request has updated the
    examples files.
    
    BUILD.md - Bump mxml from 3.2 to 3.3 and Fedora from 34 to 35.
    
    build.sbt - Make genManaged show generated files if you run `sbt 'show
    genManaged'`.  Remove examples directory from cIncludeDirectories
    (hadn't been needed there for a long time).  Update other generators
    to be consistent and less verbose about logging.  Add genExamples and
    genExamplesSettings to generate example files by watching runtime2
    scala files and runtime2 example schemas for changes and calling
    CodeGenerator.main using runtime2's test classpath, which avoids log4j
    ERROR message but still gets log4j WARNING message which we filter
    out.  Integrate genExamples into `sbt compile` so it will run
    automatically.
    
    Makefile - Add "format" and "iwyu" targets for maintainers' use.
    
    cli_errors.c - Print program version on stdout, not stderr.  Update includes.
    
    xml_reader.c - Fix a lint warning.  Update includes.
    
    xml_writer.c - Update includes.
    
    infoset.c - Fix some lint warnings.
    
    parsers.h - Update includes.
    
    unparsers.h - Update includes.
    
    CodeGenerator.scala - Add object CodeGenerator with main method to
    generate all example files.  Make main method receive examples
    directory's location as a mandatory parameter and create example
    files' folders if necessary (usually not needed but nice to do in case
    we move the examples directory).  Also print the example files to
    ensure `sbt 'show genExamples'` will list their locations.
    
    CodeGeneratorState.scala - Fix some corner cases in generated files:
    1) elements used in multiple places within a schema causing redundant
    code generation, 2) empty complex type elements without any child
    elements causing compiler warnings, 3) certain choice dispatch key
    expressions causing compiler errors.  Add comment to explain why
    choiceDispatchField returns "".  Update generated files' includes.
    Write generated files with System.lineSeparator (LF on Unix, CRLF on
    Windows) to be consistent with rest of Daffodil's Scala & C files.
    
    ElementParseAndUnspecifiedLengthCodeGenerator.scala - Fix code
    generation for one corner case (elements used in multiple places
    within a schema causing redundant code generation).
    
    examples/*/generated_code.[ch] - Move from old location to new
    location.  Generate with build.sbt's genExamples task.
    
    TestCodeGenerator.scala - Change tempDir from /tmp/NNN... to
    /tmp/daffodil-runtime2-NNN... to make its purpose clearer.  Add new
    test_CodeGenerator_main for code coverage because "sbt coverage
    compile" doesn't include genExamples in coverage data.
    
    Rat.scala - Change examples directory to new location.
    
    DAFFODIL-2585
---
 .gitattributes                                     |  1 +
 .github/workflows/main.yml                         |  5 +-
 BUILD.md                                           |  6 +-
 build.sbt                                          | 62 +++++++++++---
 .../org/apache/daffodil/runtime2/c/Makefile        | 13 ++-
 .../apache/daffodil/runtime2/c/libcli/cli_errors.c |  5 +-
 .../apache/daffodil/runtime2/c/libcli/xml_reader.c | 67 +++++++--------
 .../apache/daffodil/runtime2/c/libcli/xml_writer.c |  6 +-
 .../daffodil/runtime2/c/libruntime/infoset.c       | 26 ++----
 .../daffodil/runtime2/c/libruntime/parsers.h       |  2 +-
 .../daffodil/runtime2/c/libruntime/unparsers.h     |  2 +-
 .../apache/daffodil/runtime2/CodeGenerator.scala   | 65 +++++++++++++++
 .../runtime2/generators/CodeGeneratorState.scala   | 95 ++++++++++++++++------
 ...entParseAndUnspecifiedLengthCodeGenerator.scala |  2 +-
 .../examples/NestedUnion/generated_code.c          |  4 +-
 .../examples/NestedUnion/generated_code.h          |  2 +-
 .../examples/ex_nums/generated_code.c              |  4 +-
 .../examples/ex_nums/generated_code.h              |  2 +-
 .../daffodil/runtime2/TestCodeGenerator.scala      | 18 +++-
 project/Rat.scala                                  |  2 +-
 20 files changed, 279 insertions(+), 110 deletions(-)

diff --git a/.gitattributes b/.gitattributes
index 7196c32..b49c277 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -13,4 +13,5 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+# Do not include KEYS in archived source releases
 /KEYS export-ignore 
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index b2c13d0..1d8a27c 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -86,7 +86,7 @@ jobs:
         uses: actions/checkout@v2.4.0
         with:
           repository: michaelrsweet/mxml
-          ref: v3.2
+          ref: v3.3
           path: mxml
 
       - name: Install mxml library (Windows)
@@ -149,6 +149,9 @@ jobs:
       - name: Run Integration Tests
         run: $SBT coverage IntegrationTest/test
 
+      - name: Run Modified Example Files Check
+        run: git diff --color --exit-code
+
       - name: Generate Coverage Report
         run: $SBT coverageAggregate
 
diff --git a/BUILD.md b/BUILD.md
index 913273c..ddbfe5f 100644
--- a/BUILD.md
+++ b/BUILD.md
@@ -60,7 +60,7 @@ in its own repositories.  You'll have to install the latest [SBT]
 version following its website's instructions and you'll have to build
 the [Mini-XML] library from source:
 
-    git clone -b v3.2 https://github.com/michaelrsweet/mxml.git
+    git clone -b v3.3 https://github.com/michaelrsweet/mxml.git
     # ./configure fails if you use CC=clang
     unset CC AR
     cd mxml
@@ -71,7 +71,7 @@ the [Mini-XML] library from source:
 Now you can build Daffodil from source and the sbt and daffodil
 commands you type will be able to call the C compiler.
 
-## Fedora 34
+## Fedora 35
 
 You can use the `dnf` package manager to install most of the tools
 needed to build Daffodil:
@@ -131,7 +131,7 @@ environment variables `CC` and `AR` to the clang binaries' names:
 However, MSYS2 has no [libmxml-devel][Mini-XML] package so you'll have
 to build the [Mini-XML] library from source:
 
-    git clone -b v3.2 https://github.com/michaelrsweet/mxml.git
+    git clone -b v3.3 https://github.com/michaelrsweet/mxml.git
     # some daffodil tests fail if you build mxml with clang
     unset CC AR
     cd mxml
diff --git a/build.sbt b/build.sbt
index e998557..748fa00 100644
--- a/build.sbt
+++ b/build.sbt
@@ -29,14 +29,15 @@ Global / excludeLintKeys ++= Set(
   EclipseKeys.classpathTransformerFactories,
 )
 
-lazy val genManaged = taskKey[Unit]("Generate managed sources and resources")
+lazy val genManaged = taskKey[Seq[File]]("Generate managed sources and resources")
 lazy val genProps = taskKey[Seq[File]]("Generate properties scala source")
 lazy val genSchemas = taskKey[Seq[File]]("Generated DFDL schemas")
+lazy val genExamples = taskKey[Seq[File]]("Generate runtime2 example files")
 
 lazy val daffodil         = project.in(file(".")).configs(IntegrationTest)
                               .enablePlugins(JavaUnidocPlugin, ScalaUnidocPlugin)
                               .aggregate(macroLib, propgen, lib, io, runtime1, runtime1Unparser, runtime1Layers, runtime2, core, japi, sapi, tdmlLib, tdmlProc, cli, udf, schematron, test, testIBM1, tutorials, testStdLayout)
-                              .settings(commonSettings, nopublish, ratSettings, unidocSettings)
+                              .settings(commonSettings, nopublish, ratSettings, unidocSettings, genExamplesSettings)
 
 lazy val macroLib         = Project("daffodil-macro-lib", file("daffodil-macro-lib")).configs(IntegrationTest)
                               .settings(commonSettings, nopublish)
@@ -83,7 +84,6 @@ lazy val runtime2         = Project("daffodil-runtime2", file("daffodil-runtime2
                                   runtime2CFiles -> Seq(
                                     (Compile / resourceDirectory).value / "org" / "apache" / "daffodil" / "runtime2" / "c" / "libcli",
                                     (Compile / resourceDirectory).value / "org" / "apache" / "daffodil" / "runtime2" / "c" / "libruntime",
-                                    (Compile / resourceDirectory).value / "org" / "apache" / "daffodil" / "runtime2" / "examples"
                                   )
                                 ),
                                 Compile / cFlags := (Compile / cFlags).value.withDefaultValue(Seq("-Wall", "-Wextra", "-Wpedantic", "-std=gnu11"))
@@ -259,9 +259,7 @@ lazy val usesMacros = Seq(
 
 lazy val libManagedSettings = Seq(
   genManaged := {
-    (Compile / genProps).value
-    (Compile / genSchemas).value
-    ()
+    (Compile / genProps).value ++ (Compile / genSchemas).value
   },
   Compile / genProps := {
     val cp = (propgen / Runtime / dependencyClasspath).value
@@ -269,14 +267,15 @@ lazy val libManagedSettings = Seq(
     val inRSrc = (propgen / Compile / resources).value
     val stream = (propgen / streams).value
     val outdir = (Compile / sourceManaged).value
+    val mainClass = "org.apache.daffodil.propGen.PropertyGenerator"
+    val args = Seq(mainClass, outdir.toString)
     val filesToWatch = (inSrc ++ inRSrc).toSet
-    val cachedFun = FileFunction.cached(stream.cacheDirectory / "propgen") { (in: Set[File]) =>
-      val mainClass = "org.apache.daffodil.propGen.PropertyGenerator"
+    val cachedFun = FileFunction.cached(stream.cacheDirectory / "propgen") { _ =>
       val out = new java.io.ByteArrayOutputStream()
       val forkOpts = ForkOptions()
                        .withOutputStrategy(Some(CustomOutput(out)))
                        .withBootJars(cp.files.toVector)
-      val ret = new Fork("java", Some(mainClass)).fork(forkOpts, Seq(outdir.toString)).exitValue()
+      val ret = Fork.java(forkOpts, args)
       if (ret != 0) {
         sys.error("Failed to generate code")
       }
@@ -285,9 +284,9 @@ lazy val libManagedSettings = Seq(
       val br = new java.io.BufferedReader(isr)
       val iterator = Iterator.continually(br.readLine()).takeWhile(_ != null)
       val files = iterator.map { f =>
-        stream.log.info("generated %s".format(f))
         new File(f)
       }.toSet
+      stream.log.info(s"generated ${files.size} Scala sources to ${outdir}")
       files
     }
     cachedFun(filesToWatch).toSeq
@@ -298,12 +297,13 @@ lazy val libManagedSettings = Seq(
     val outdir = (Compile / resourceManaged).value
     val filesToWatch = inRSrc.filter{_.isFile}.toSet
     val cachedFun = FileFunction.cached(stream.cacheDirectory / "schemasgen") { (schemas: Set[File]) =>
-      schemas.map { schema =>
+      val files = schemas.map { schema =>
         val out = outdir / "org" / "apache" / "daffodil" / "xsd" / schema.getName
         IO.copyFile(schema, out)
-        stream.log.info("generated %s".format(out))
         out
       }
+      stream.log.info(s"generated ${files.size} XML schemas to ${outdir}")
+      files
     }
     cachedFun(filesToWatch).toSeq
   },
@@ -342,3 +342,41 @@ lazy val unidocSettings = Seq(
     }
   },
 )
+
+lazy val genExamplesSettings = Seq(
+  Compile / genExamples := {
+    val cp = (runtime2 / Test / dependencyClasspath).value
+    val inSrc = (runtime2 / Compile / sources).value
+    val inRSrc = (runtime2 / Test / resources).value
+    val stream = (runtime2 / streams).value
+    val filesToWatch = (inSrc ++ inRSrc).toSet
+    val cachedFun = FileFunction.cached(stream.cacheDirectory / "genExamples") { _ =>
+      val out = new java.io.ByteArrayOutputStream()
+      val forkOpts = ForkOptions()
+                       .withOutputStrategy(Some(CustomOutput(out)))
+                       .withBootJars(cp.files.toVector)
+      val mainClass = "org.apache.daffodil.runtime2.CodeGenerator"
+      val outdir = (runtime2 / Test / sourceDirectory).value / "c" / "examples"
+      val args = Seq(mainClass, outdir.toString)
+      val ret = Fork.java(forkOpts, args)
+      if (ret != 0) {
+        stream.log.error(s"failed to generate example files")
+      }
+      val bis = new java.io.ByteArrayInputStream(out.toByteArray)
+      val isr = new java.io.InputStreamReader(bis)
+      val br = new java.io.BufferedReader(isr)
+      val iterator = Iterator.continually(br.readLine()).takeWhile(_ != null).filterNot(_.startsWith("WARN"))
+      val files = iterator.map { f =>
+        new File(f)
+      }.toSet
+      stream.log.info(s"generated ${files.size} runtime2 example files to ${outdir}")
+      files
+    }
+    cachedFun(filesToWatch).toSeq
+  },
+  Compile / compile := {
+    val res = (Compile / compile).value
+    (Compile / genExamples).value
+    res
+  }
+)
diff --git a/daffodil-runtime2/src/main/resources/org/apache/daffodil/runtime2/c/Makefile b/daffodil-runtime2/src/main/resources/org/apache/daffodil/runtime2/c/Makefile
index 2a5fd6a..5f1c951 100644
--- a/daffodil-runtime2/src/main/resources/org/apache/daffodil/runtime2/c/Makefile
+++ b/daffodil-runtime2/src/main/resources/org/apache/daffodil/runtime2/c/Makefile
@@ -66,4 +66,15 @@ unparse-check: $(PROGRAM)
 clean:
 	rm -f $(PROGRAM) $(TEST_DAT).tmp $(TEST_DAT_XML).tmp
 
-.PHONY: check parse-check unparse-check clean
+# Maintainer only: Format C source files or check includes.
+
+FMT = clang-format -i
+IWYU = iwyu -Xiwyu --max_line_length=999 -Xiwyu --update_comments
+
+format:
+	$(FMT) $(HEADERS) $(SOURCES)
+
+iwyu:
+	-for f in $(SOURCES); do $(IWYU) $(CFLAGS) $(INCLUDES) $$f; done
+
+.PHONY: check parse-check unparse-check clean format iwyu
diff --git a/daffodil-runtime2/src/main/resources/org/apache/daffodil/runtime2/c/libcli/cli_errors.c b/daffodil-runtime2/src/main/resources/org/apache/daffodil/runtime2/c/libcli/cli_errors.c
index 4a72416..0a6345f 100644
--- a/daffodil-runtime2/src/main/resources/org/apache/daffodil/runtime2/c/libcli/cli_errors.c
+++ b/daffodil-runtime2/src/main/resources/org/apache/daffodil/runtime2/c/libcli/cli_errors.c
@@ -18,9 +18,8 @@
 // clang-format off
 #include "cli_errors.h"
 #include <assert.h>    // for assert
-#include <inttypes.h>  // for PRId64
+#include <inttypes.h>  // for PRId64, uint8_t
 #include <stddef.h>    // for NULL
-#include <stdint.h>    // for uint8_t
 // clang-format on
 
 // USAGE - second line to append to CLI usage messages
@@ -65,7 +64,7 @@ error_lookup(uint8_t code)
          "unexpected getopt code %" PRId64 "\n"
          "Check for program error\n",
          FIELD_D64},
-        {CLI_PROGRAM_VERSION, "%s\n", FIELD_S},
+        {CLI_PROGRAM_VERSION, "%s\n", FIELD_S_ON_STDOUT},
         {CLI_STACK_EMPTY, "stack empty, stopping program\n", FIELD_ZZZ},
         {CLI_STACK_OVERFLOW, "stack overflow, stopping program\n", FIELD_ZZZ},
         {CLI_STACK_UNDERFLOW, "stack underflow, stopping program\n", FIELD_ZZZ},
diff --git a/daffodil-runtime2/src/main/resources/org/apache/daffodil/runtime2/c/libcli/xml_reader.c b/daffodil-runtime2/src/main/resources/org/apache/daffodil/runtime2/c/libcli/xml_reader.c
index 5dc6e9a..af8ad19 100644
--- a/daffodil-runtime2/src/main/resources/org/apache/daffodil/runtime2/c/libcli/xml_reader.c
+++ b/daffodil-runtime2/src/main/resources/org/apache/daffodil/runtime2/c/libcli/xml_reader.c
@@ -22,11 +22,11 @@
 #include <inttypes.h>    // for strtoimax, strtoumax
 #include <mxml.h>        // for mxmlWalkNext, mxmlGetElement, mxmlGetType, MXML_DESCEND, MXML_OPAQUE, mxmlDelete, mxmlGetOpaque, mxmlLoadFile, MXML_OPAQUE_CALLBACK
 #include <stdbool.h>     // for bool, false, true
-#include <stdint.h>      // for intmax_t, uintmax_t, int16_t, int32_t, int64_t, int8_t, uint16_t, uint32_t, uint64_t, uint8_t, INT16_MAX, INT16_MIN, INT32_MAX, INT32_MIN, INT64_MAX, INT64_MIN, INT8_MAX, INT8_MIN, UINT16_MAX, UINT32_MAX, UINT64_MAX, UINT8_MAX
+#include <stdint.h>      // for int64_t, intmax_t, uint8_t, uintmax_t, int16_t, int32_t, int8_t, uint16_t, uint32_t, uint64_t, INT16_MAX, INT16_MIN, INT32_MAX, INT32_MIN, INT64_MAX, INT64_MIN, INT8_MAX, INT8_MIN, UINT16_MAX, UINT32_MAX, UINT64_MAX, UINT8_MAX
 #include <stdlib.h>      // for free, malloc, strtod, strtof
-#include <string.h>      // for memset, strcmp, strlen, strncmp
-#include "cli_errors.h"  // for CLI_STRTONUM_EMPTY, CLI_STRTONUM_NOT, CLI_XML_GONE, CLI_STRTOD_ERRNO, CLI_STRTOI_ERRNO, CLI_STRTONUM_RANGE, CLI_XML_MISMATCH, CLI_STRTOBOOL, CLI_XML_ERD, CLI_XML_INPUT, CLI_XML_LEFT
-#include "errors.h"      // for Error, Error::(anonymous), UNUSED
+#include <string.h>      // for strcmp, strlen, strncmp, memset
+#include "cli_errors.h"  // for CLI_STRTONUM_EMPTY, CLI_STRTONUM_NOT, CLI_XML_GONE, CLI_STRTOD_ERRNO, CLI_STRTOI_ERRNO, CLI_STRTONUM_RANGE, CLI_XML_MISMATCH, CLI_HEXBINARY_LENGTH, CLI_HEXBINARY_PARSE, CLI_HEXBINARY_SIZE, CLI_STRTOBOOL, CLI_XML_ERD, CLI_XML_INPUT, CLI_XML_LEFT
+#include "errors.h"      // for Error, Error::(anonymous), ERR_HEXBINARY_ALLOC, UNUSED
 // clang-format on
 
 // Convert an XML element's text to a boolean with error checking
@@ -267,35 +267,38 @@ strtohexbinary(const char *text, HexBinary *hexBinary)
     }
 
     // Store hexadecimal characters into byte array
-    if (hexBinary->array) memset(hexBinary->array, 0, hexBinary->lengthInBytes);
-    for (size_t i = 0; i < numNibbles; i++)
+    if (hexBinary->array)
     {
-        char    c = text[i];
-        uint8_t value = 0;
-
-        // Check whether c is valid hexadecimal character
-        if (c >= '0' && c <= '9')
-        {
-            value = (c - '0');
-        }
-        else if (c >= 'A' && c <= 'F')
-        {
-            value = (c - 'A') + 10;
-        }
-        else if (c >= 'a' && c <= 'f')
+        memset(hexBinary->array, 0, hexBinary->lengthInBytes);
+        for (size_t i = 0; i < numNibbles; i++)
         {
-            value = (c - 'a') + 10;
-        }
-        else
-        {
-            static Error error = {CLI_HEXBINARY_PARSE, {0}};
-            error.arg.c = c;
-            return &error;
-        }
+            char    c = text[i];
+            uint8_t value = 0;
 
-        // Shift high nibble, add low nibble on next iteration
-        value <<= (((i + 1) % 2) * 4);
-        hexBinary->array[i / 2] += value;
+            // Check whether c is valid hexadecimal character
+            if (c >= '0' && c <= '9')
+            {
+                value = (c - '0');
+            }
+            else if (c >= 'A' && c <= 'F')
+            {
+                value = (c - 'A') + 10;
+            }
+            else if (c >= 'a' && c <= 'f')
+            {
+                value = (c - 'a') + 10;
+            }
+            else
+            {
+                static Error error = {CLI_HEXBINARY_PARSE, {0}};
+                error.arg.c = c;
+                return &error;
+            }
+
+            // Shift high nibble, add low nibble on next iteration
+            value <<= (((i + 1) % 2) * 4);
+            hexBinary->array[i / 2] += value;
+        }
     }
 
     return NULL;
@@ -431,8 +434,8 @@ xmlSimpleElem(XMLReader *reader, const ERD *erd, void *valueptr)
         {
             // Check for any errors calling strtonum or strtounum
             const Error *error = NULL;
-            intmax_t num = 0;
-            uintmax_t unum = 0;
+            intmax_t     num = 0;
+            uintmax_t    unum = 0;
 
             // Handle various types of values
             const enum TypeCode typeCode = erd->typeCode;
diff --git a/daffodil-runtime2/src/main/resources/org/apache/daffodil/runtime2/c/libcli/xml_writer.c b/daffodil-runtime2/src/main/resources/org/apache/daffodil/runtime2/c/libcli/xml_writer.c
index a146a31..bdab382 100644
--- a/daffodil-runtime2/src/main/resources/org/apache/daffodil/runtime2/c/libcli/xml_writer.c
+++ b/daffodil-runtime2/src/main/resources/org/apache/daffodil/runtime2/c/libcli/xml_writer.c
@@ -18,9 +18,9 @@
 // clang-format off
 #include "xml_writer.h"
 #include <assert.h>      // for assert
-#include <mxml.h>        // for mxmlNewOpaquef, mxml_node_t, mxmlElementSetAttr, mxmlGetOpaque, mxmlNewElement, mxmlDelete, mxmlGetElement, mxmlNewXML, mxmlSaveFile, MXML_NO_CALLBACK
-#include <stdbool.h>     // for bool
-#include <stdint.h>      // for int16_t, int32_t, int64_t, int8_t, uint16_t, uint32_t, uint64_t, uint8_t
+#include <mxml.h>        // for mxmlNewOpaquef, mxml_node_t, mxmlElementSetAttr, mxmlGetOpaque, mxmlNewElement, mxmlDelete, mxmlGetElement, mxmlNewOpaque, mxmlNewXML, mxmlSaveFile, MXML_NO_CALLBACK
+#include <stdbool.h>     // for bool, false, true
+#include <stdint.h>      // for uint8_t, int16_t, int32_t, int64_t, int8_t, uint16_t, uint32_t, uint64_t
 #include <stdlib.h>      // for free, malloc
 #include <string.h>      // for strcmp
 #include "cli_errors.h"  // for CLI_XML_DECL, CLI_XML_ELEMENT, CLI_XML_WRITE, LIMIT_XML_NESTING
diff --git a/daffodil-runtime2/src/main/resources/org/apache/daffodil/runtime2/c/libruntime/infoset.c b/daffodil-runtime2/src/main/resources/org/apache/daffodil/runtime2/c/libruntime/infoset.c
index 35ab348..17a4d50 100644
--- a/daffodil-runtime2/src/main/resources/org/apache/daffodil/runtime2/c/libruntime/infoset.c
+++ b/daffodil-runtime2/src/main/resources/org/apache/daffodil/runtime2/c/libruntime/infoset.c
@@ -28,10 +28,11 @@ const char *
 get_erd_name(const ERD *erd)
 {
     static char name[LIMIT_NAME_LENGTH];
-    char *      next = name;
-    char *      last = name + sizeof(name) - 1;
 
-    if (next && erd->namedQName.prefix)
+    char *next = name;
+    char *last = name + sizeof(name) - 1;
+
+    if (erd->namedQName.prefix)
     {
         next = memccpy(next, erd->namedQName.prefix, 0, last - next);
         if (next)
@@ -119,19 +120,14 @@ get_erd_ns(const ERD *erd)
 static const Error *
 walkInfosetNode(const VisitEventHandler *handler, const InfosetBase *infoNode)
 {
-    const Error *error = NULL;
-
-    // Start visiting the node
-    if (!error)
-    {
-        error = handler->visitStartComplex(handler, infoNode);
-    }
-
-    // Walk the node's children recursively
     const size_t      count = infoNode->erd->numChildren;
     const ERD **const childrenERDs = infoNode->erd->childrenERDs;
     const size_t *    offsets = infoNode->erd->offsets;
 
+    // Start visiting the node
+    const Error *error = handler->visitStartComplex(handler, infoNode);
+
+    // Walk the node's children recursively
     for (size_t i = 0; i < count && !error; i++)
     {
         const size_t offset = offsets[i];
@@ -182,14 +178,10 @@ walkInfosetNode(const VisitEventHandler *handler, const InfosetBase *infoNode)
 const Error *
 walkInfoset(const VisitEventHandler *handler, const InfosetBase *infoset)
 {
-    const Error *error = NULL;
+    const Error *error = handler->visitStartDocument(handler);
 
     if (!error)
     {
-        error = handler->visitStartDocument(handler);
-    }
-    if (!error)
-    {
         error = walkInfosetNode(handler, infoset);
     }
     if (!error)
diff --git a/daffodil-runtime2/src/main/resources/org/apache/daffodil/runtime2/c/libruntime/parsers.h b/daffodil-runtime2/src/main/resources/org/apache/daffodil/runtime2/c/libruntime/parsers.h
index 41f124d..65ed5cc 100644
--- a/daffodil-runtime2/src/main/resources/org/apache/daffodil/runtime2/c/libruntime/parsers.h
+++ b/daffodil-runtime2/src/main/resources/org/apache/daffodil/runtime2/c/libruntime/parsers.h
@@ -22,7 +22,7 @@
 #include <stdbool.h>  // for bool
 #include <stddef.h>   // for size_t
 #include <stdint.h>   // for int64_t, uint32_t, int16_t, int32_t, int8_t, uint16_t, uint64_t, uint8_t
-#include "infoset.h"  // for PState
+#include "infoset.h"  // for PState, HexBinary
 // clang-format on
 
 // Parse binary booleans, real numbers, and integers
diff --git a/daffodil-runtime2/src/main/resources/org/apache/daffodil/runtime2/c/libruntime/unparsers.h b/daffodil-runtime2/src/main/resources/org/apache/daffodil/runtime2/c/libruntime/unparsers.h
index 54c119f..5f7a9cb 100644
--- a/daffodil-runtime2/src/main/resources/org/apache/daffodil/runtime2/c/libruntime/unparsers.h
+++ b/daffodil-runtime2/src/main/resources/org/apache/daffodil/runtime2/c/libruntime/unparsers.h
@@ -22,7 +22,7 @@
 #include <stdbool.h>  // for bool
 #include <stddef.h>   // for size_t
 #include <stdint.h>   // for uint32_t, int16_t, int32_t, int64_t, int8_t, uint16_t, uint64_t, uint8_t
-#include "infoset.h"  // for UState
+#include "infoset.h"  // for UState, HexBinary
 // clang-format on
 
 // Unparse binary booleans, real numbers, and integers
diff --git a/daffodil-runtime2/src/main/scala/org/apache/daffodil/runtime2/CodeGenerator.scala b/daffodil-runtime2/src/main/scala/org/apache/daffodil/runtime2/CodeGenerator.scala
index 2f25133..e88c632 100644
--- a/daffodil-runtime2/src/main/scala/org/apache/daffodil/runtime2/CodeGenerator.scala
+++ b/daffodil-runtime2/src/main/scala/org/apache/daffodil/runtime2/CodeGenerator.scala
@@ -24,10 +24,12 @@ import java.nio.file.Paths
 import java.util.Collections
 import org.apache.daffodil.api.DFDL
 import org.apache.daffodil.api.Diagnostic
+import org.apache.daffodil.compiler.Compiler
 import org.apache.daffodil.dsom.Root
 import org.apache.daffodil.dsom.SchemaDefinitionError
 import org.apache.daffodil.runtime2.generators.CodeGeneratorState
 import org.apache.daffodil.util.Misc
+import org.apache.daffodil.xml.QName
 import org.apache.daffodil.xml.RefQName
 
 import scala.util.Properties.isWin
@@ -198,3 +200,66 @@ class CodeGenerator(root: Root) extends DFDL.CodeGenerator {
   override def getDiagnostics: Seq[Diagnostic] = diagnostics
   override def isError: Boolean = errorStatus
 }
+
+/** Runs from "sbt compile" to keep all example generated code files up to date */
+object CodeGenerator {
+  // Update one set of example generated code files from an example schema
+  private def updateGeneratedCodeExample(schemaFile: os.Path, rootName: Option[String],
+                                         exampleCodeHeader: os.Path, exampleCodeFile: os.Path): Unit = {
+    // Generate code from the example schema file
+    val pf = Compiler().compileFile(schemaFile.toIO, rootName)
+    assert(!pf.isError, pf.getDiagnostics.map(_.getMessage()).mkString("\n"))
+    val cg = pf.forLanguage("c")
+    val rootNS = QName.refQNameFromExtendedSyntax(rootName.getOrElse("")).toOption
+    val tempDir = os.temp.dir(dir = null, prefix = "daffodil-runtime2-")
+    val codeDir = cg.generateCode(rootNS, tempDir.toString)
+    assert(!cg.isError, cg.getDiagnostics.map(_.getMessage()).mkString("\n"))
+
+    // Replace the example generated files with the newly generated files
+    val generatedCodeHeader = codeDir/"libruntime"/"generated_code.h"
+    val generatedCodeFile = codeDir/"libruntime"/"generated_code.c"
+    os.copy(generatedCodeHeader, exampleCodeHeader, replaceExisting = true, createFolders = true)
+    os.copy(generatedCodeFile, exampleCodeFile, replaceExisting = true, createFolders = true)
+
+    // Print the example generated files' names so "sbt 'show genExamples'" can list them
+    System.out.println(exampleCodeHeader)
+    System.out.println(exampleCodeFile)
+
+    // tempDir should be removed automatically after main exits; this is just in case
+    os.remove.all(tempDir)
+  }
+
+  // Make sure "sbt compile" calls this main method
+  def main(args: Array[String]): Unit = {
+    // We expect one mandatory parameter, the examples directory's absolute location.
+    if (args.length != 1) {
+      System.err.println(s"Usage: ${CodeGenerator} <examples directory location>")
+      System.exit(1);
+    }
+
+    // Get paths to our example schemas and example generated code files
+    val rootDir = if (os.exists(os.pwd/"src")) os.pwd/os.up else os.pwd
+
+    val schemaDir = rootDir/"daffodil-runtime2"/"src"/"test"/"resources"/"org"/"apache"/"daffodil"/"runtime2"
+    val exNumsSchema = schemaDir/"ex_nums.dfdl.xsd"
+    val exNumsRootName = None
+    val nestedSchema = schemaDir/"nested.dfdl.xsd"
+    val nestedRootName = Some("NestedUnion")
+
+    val examplesDir = os.Path(args(0))
+    val exNumsCodeHeader = examplesDir/"ex_nums"/"generated_code.h"
+    val exNumsCodeFile = examplesDir/"ex_nums"/"generated_code.c"
+    val nestedCodeHeader = examplesDir/"NestedUnion"/"generated_code.h"
+    val nestedCodeFile = examplesDir/"NestedUnion"/"generated_code.c"
+
+    // Update each set of example generated code files
+    try {
+      updateGeneratedCodeExample(exNumsSchema, exNumsRootName, exNumsCodeHeader, exNumsCodeFile)
+      updateGeneratedCodeExample(nestedSchema, nestedRootName, nestedCodeHeader, nestedCodeFile)
+    } catch {
+      case e: Throwable =>
+        System.err.println(s"Error generating example code files: $e")
+        System.exit(1);
+    }
+  }
+}
diff --git a/daffodil-runtime2/src/main/scala/org/apache/daffodil/runtime2/generators/CodeGeneratorState.scala b/daffodil-runtime2/src/main/scala/org/apache/daffodil/runtime2/generators/CodeGeneratorState.scala
index 9de6062..be8b3be 100644
--- a/daffodil-runtime2/src/main/scala/org/apache/daffodil/runtime2/generators/CodeGeneratorState.scala
+++ b/daffodil-runtime2/src/main/scala/org/apache/daffodil/runtime2/generators/CodeGeneratorState.scala
@@ -25,6 +25,7 @@ import org.apache.daffodil.dsom.Choice
 import org.apache.daffodil.dsom.ElementBase
 import org.apache.daffodil.dsom.GlobalComplexTypeDef
 import org.apache.daffodil.dsom.GlobalElementDecl
+import org.apache.daffodil.dsom.Root
 import org.apache.daffodil.dsom.SchemaComponent
 import org.apache.daffodil.exceptions.ThrowsSDE
 import org.apache.daffodil.schema.annotation.props.gen.OccursCountKind
@@ -36,12 +37,33 @@ import scala.collection.mutable
  * Builds up the state of generated code.
  */
 class CodeGeneratorState {
+  private val elementsAlreadySeen = mutable.Map[String, ElementBase]()
   private val structs = mutable.Stack[ComplexCGState]()
   private val prototypes = mutable.ArrayBuffer[String]()
   private val erds = mutable.ArrayBuffer[String]()
   private val finalStructs = mutable.ArrayBuffer[String]()
   private val finalImplementation = mutable.ArrayBuffer[String]()
 
+  // Returns true if the global 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 = context.namedQName.toString
+    val alreadySeen = elementsAlreadySeen.contains(key)
+    if (!alreadySeen) elementsAlreadySeen += (key -> context)
+    !alreadySeen
+  }
+
+  // Specially handles an element reference by returning only the first
+  // element reference seen in order to build the correct ERD name
+  private def firstElementSeen(context: ElementBase): ElementBase = {
+    if (context.isSimpleType) {
+      context
+    } else {
+      val aContext = elementsAlreadySeen.getOrElse(context.namedQName.toString, context)
+      aContext
+    }
+  }
+
   // Builds an ERD name for the given element that needs to be unique in C file scope
   private def erdName(context: ElementBase): String = {
     def buildName(sc: SchemaComponent, sb: StringBuilder): StringBuilder = {
@@ -56,8 +78,9 @@ class CodeGeneratorState {
       }
       sb
     }
-    val sb = buildName(context, new StringBuilder) ++= "ERD"
-    sb.toString()
+    val aContext = firstElementSeen(context)
+    val sb = buildName(aContext, new StringBuilder) ++= "ERD"
+    sb.toString
   }
 
   // Returns the given element's local name (doesn't have to be unique)
@@ -71,14 +94,12 @@ class CodeGeneratorState {
       structs.top.parserStatements.mkString("\n")
     else
       s"""    // Empty struct, but need to prevent compiler warnings
-         |    UNUSED(${C}_compute_offsets);
          |    UNUSED(instance);
          |    UNUSED(pstate);""".stripMargin
     val unparserStatements = if (structs.top.unparserStatements.nonEmpty)
       structs.top.unparserStatements.mkString("\n")
     else
       s"""    // Empty struct, but need to prevent compiler warnings
-         |    UNUSED(${C}_compute_offsets);
          |    UNUSED(instance);
          |    UNUSED(ustate);""".stripMargin
     val hasChoice = structs.top.initChoiceStatements.nonEmpty
@@ -157,30 +178,41 @@ class CodeGeneratorState {
    * - we can store the accessed value in an int64_t local variable safely
    */
   private def choiceDispatchField(context: ElementBase): String = {
-    // We want to use SchemaComponent.scPath but it's private so duplicate it here (for now...)
+    // 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
-    val localNames = scPath(context).map {
-      case er: AbstractElementRef => er.refQName.local
-      case e: ElementBase => e.namedQName.local
-      case ed: GlobalElementDecl => ed.namedQName.local
-      case _ => ""
-    }
-    val absoluteSlashPath = localNames.mkString("/")
-    val dispatchSlashPath = context.complexType.modelGroup match {
+
+    // 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 e: ElementBase => e.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()
         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
-        val normalizedURI = new URI(absoluteSlashPath + "/" + relativePath).normalize
-        normalizedURI.getPath.substring(1)
+
+        // 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("/[^/:]+:", "/")
+
+        // 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 _ => ""
     }
-    // Strip namespace prefixes since C code uses only local names (for now...)
-    val localDispatchSlashPath = dispatchSlashPath.replaceAll("/[^:]+:", "/")
-    val res = localDispatchSlashPath.replace('/', '.')
-    res
   }
 
   // We know context is a complex type.  We need to 1) support choice groups; 2) support
@@ -321,7 +353,7 @@ class CodeGeneratorState {
     val hasChoice = structs.top.initChoiceStatements.nonEmpty
     val numChildren = if (hasChoice) 2 else count
     val initChoice = if (hasChoice) s"(InitChoiceRD)&${C}_initChoice" else "NULL"
-    val complexERD =
+    val complexERD = if (numChildren > 0)
       s"""static const $C ${C}_compute_offsets;
          |
          |static const size_t ${C}_offsets[$count] = {
@@ -344,6 +376,19 @@ class CodeGeneratorState {
          |    $initChoice // initChoice
          |};
          |""".stripMargin
+    else
+      s"""static const ERD $erd = {
+         |$qnameInit
+         |    COMPLEX, // typeCode
+         |    $numChildren, // numChildren
+         |    NULL, // offsets
+         |    NULL, // childrenERDs
+         |    (ERDInitSelf)&${C}_initSelf, // initSelf
+         |    (ERDParseSelf)&${C}_parseSelf, // parseSelf
+         |    (ERDUnparseSelf)&${C}_unparseSelf, // unparseSelf
+         |    $initChoice // initChoice
+         |};
+         |""".stripMargin
 
     erds += complexERD
   }
@@ -570,10 +615,10 @@ class CodeGeneratorState {
          |#define GENERATED_CODE_H
          |
          |// clang-format off
-         |#include "infoset.h"  // for HexBinary, InfosetBase
          |#include <stdbool.h>  // for bool
          |#include <stddef.h>   // for size_t
          |#include <stdint.h>   // for uint8_t, int16_t, int32_t, int64_t, uint32_t, int8_t, uint16_t, uint64_t
+         |#include "infoset.h"  // for InfosetBase, HexBinary
          |// clang-format on
          |
          |// Define infoset structures
@@ -581,7 +626,7 @@ class CodeGeneratorState {
          |$structs
          |#endif // GENERATED_CODE_H
          |""".stripMargin
-    header
+    header.replace("\r\n", "\n").replace("\n", System.lineSeparator)
   }
 
   def generateCodeFile(rootElementName: String): String = {
@@ -594,10 +639,10 @@ class CodeGeneratorState {
       s"""// clang-format off
          |#include "generated_code.h"
          |#include <math.h>       // for NAN
-         |#include <stdbool.h>    // for true, false, bool
+         |#include <stdbool.h>    // for false, bool, true
          |#include <stddef.h>     // for NULL, size_t
          |#include <string.h>     // for memset, memcmp
-         |#include "errors.h"     // for Error, PState, UState, ERR_CHOICE_KEY, UNUSED
+         |#include "errors.h"     // for Error, PState, UState, ERR_CHOICE_KEY, Error::(anonymous), UNUSED
          |#include "parsers.h"    // for alloc_hexBinary, parse_hexBinary, parse_be_float, parse_be_int16, parse_validate_fixed, parse_be_bool32, parse_be_bool16, parse_be_int32, parse_be_uint16, parse_be_uint32, parse_le_bool32, parse_le_int64, parse_le_uint16, parse_le_uint8, parse_be_bool8, parse_be_double, parse_be_int64, parse_be_int8, parse_be_uint64, parse_be_uint8, parse_le_bool16, parse_le_bool8, parse_le_double, parse_le_float, parse_le_int16, parse_le_int32, parse_le_int8, par [...]
          |#include "unparsers.h"  // for unparse_hexBinary, unparse_be_float, unparse_be_int16, unparse_validate_fixed, unparse_be_bool32, unparse_be_bool16, unparse_be_int32, unparse_be_uint16, unparse_be_uint32, unparse_le_bool32, unparse_le_int64, unparse_le_uint16, unparse_le_uint8, unparse_be_bool8, unparse_be_double, unparse_be_int64, unparse_be_int8, unparse_be_uint64, unparse_be_uint8, unparse_le_bool16, unparse_le_bool8, unparse_le_double, unparse_le_float, unparse_le_int16, unp [...]
          |// clang-format on
@@ -632,7 +677,7 @@ class CodeGeneratorState {
          |
          |$finalImplementation
          |""".stripMargin
-    code
+    code.replace("\r\n", "\n").replace("\n", System.lineSeparator)
   }
 }
 
diff --git a/daffodil-runtime2/src/main/scala/org/apache/daffodil/runtime2/generators/ElementParseAndUnspecifiedLengthCodeGenerator.scala b/daffodil-runtime2/src/main/scala/org/apache/daffodil/runtime2/generators/ElementParseAndUnspecifiedLengthCodeGenerator.scala
index 553d2c7..a0c361f 100644
--- a/daffodil-runtime2/src/main/scala/org/apache/daffodil/runtime2/generators/ElementParseAndUnspecifiedLengthCodeGenerator.scala
+++ b/daffodil-runtime2/src/main/scala/org/apache/daffodil/runtime2/generators/ElementParseAndUnspecifiedLengthCodeGenerator.scala
@@ -38,7 +38,7 @@ trait ElementParseAndUnspecifiedLengthCodeGenerator {
     if (context.isSimpleType) {
       cgState.addSimpleTypeERD(context) // ERD static initializer
       Runtime2CodeGenerator.generateCode(elementContentGram, cgState) // initSelf, parseSelf, unparseSelf
-    } else {
+    } else if (cgState.elementNotSeenYet(context)) {
       cgState.pushComplexElement(context)
       cgState.addBeforeSwitchStatements(context) // switch statements for choices
       context.elementChildren.foreach { child =>
diff --git a/daffodil-runtime2/src/test/resources/org/apache/daffodil/runtime2/examples/NestedUnion/generated_code.c b/daffodil-runtime2/src/test/c/examples/NestedUnion/generated_code.c
similarity index 99%
rename from daffodil-runtime2/src/test/resources/org/apache/daffodil/runtime2/examples/NestedUnion/generated_code.c
rename to daffodil-runtime2/src/test/c/examples/NestedUnion/generated_code.c
index 8f1b538..1c5ee4d 100644
--- a/daffodil-runtime2/src/test/resources/org/apache/daffodil/runtime2/examples/NestedUnion/generated_code.c
+++ b/daffodil-runtime2/src/test/c/examples/NestedUnion/generated_code.c
@@ -1,10 +1,10 @@
 // clang-format off
 #include "generated_code.h"
 #include <math.h>       // for NAN
-#include <stdbool.h>    // for true, false, bool
+#include <stdbool.h>    // for false, bool, true
 #include <stddef.h>     // for NULL, size_t
 #include <string.h>     // for memset, memcmp
-#include "errors.h"     // for Error, PState, UState, ERR_CHOICE_KEY, UNUSED
+#include "errors.h"     // for Error, PState, UState, ERR_CHOICE_KEY, Error::(anonymous), UNUSED
 #include "parsers.h"    // for alloc_hexBinary, parse_hexBinary, parse_be_float, parse_be_int16, parse_validate_fixed, parse_be_bool32, parse_be_bool16, parse_be_int32, parse_be_uint16, parse_be_uint32, parse_le_bool32, parse_le_int64, parse_le_uint16, parse_le_uint8, parse_be_bool8, parse_be_double, parse_be_int64, parse_be_int8, parse_be_uint64, parse_be_uint8, parse_le_bool16, parse_le_bool8, parse_le_double, parse_le_float, parse_le_int16, parse_le_int32, parse_le_int8, parse_le_uint [...]
 #include "unparsers.h"  // for unparse_hexBinary, unparse_be_float, unparse_be_int16, unparse_validate_fixed, unparse_be_bool32, unparse_be_bool16, unparse_be_int32, unparse_be_uint16, unparse_be_uint32, unparse_le_bool32, unparse_le_int64, unparse_le_uint16, unparse_le_uint8, unparse_be_bool8, unparse_be_double, unparse_be_int64, unparse_be_int8, unparse_be_uint64, unparse_be_uint8, unparse_le_bool16, unparse_le_bool8, unparse_le_double, unparse_le_float, unparse_le_int16, unparse_le_in [...]
 // clang-format on
diff --git a/daffodil-runtime2/src/test/resources/org/apache/daffodil/runtime2/examples/NestedUnion/generated_code.h b/daffodil-runtime2/src/test/c/examples/NestedUnion/generated_code.h
similarity index 94%
rename from daffodil-runtime2/src/test/resources/org/apache/daffodil/runtime2/examples/NestedUnion/generated_code.h
rename to daffodil-runtime2/src/test/c/examples/NestedUnion/generated_code.h
index 0ee034b..a5b531c 100644
--- a/daffodil-runtime2/src/test/resources/org/apache/daffodil/runtime2/examples/NestedUnion/generated_code.h
+++ b/daffodil-runtime2/src/test/c/examples/NestedUnion/generated_code.h
@@ -2,10 +2,10 @@
 #define GENERATED_CODE_H
 
 // clang-format off
-#include "infoset.h"  // for HexBinary, InfosetBase
 #include <stdbool.h>  // for bool
 #include <stddef.h>   // for size_t
 #include <stdint.h>   // for uint8_t, int16_t, int32_t, int64_t, uint32_t, int8_t, uint16_t, uint64_t
+#include "infoset.h"  // for InfosetBase, HexBinary
 // clang-format on
 
 // Define infoset structures
diff --git a/daffodil-runtime2/src/test/resources/org/apache/daffodil/runtime2/examples/ex_nums/generated_code.c b/daffodil-runtime2/src/test/c/examples/ex_nums/generated_code.c
similarity index 99%
rename from daffodil-runtime2/src/test/resources/org/apache/daffodil/runtime2/examples/ex_nums/generated_code.c
rename to daffodil-runtime2/src/test/c/examples/ex_nums/generated_code.c
index 2cfd717..3587657 100644
--- a/daffodil-runtime2/src/test/resources/org/apache/daffodil/runtime2/examples/ex_nums/generated_code.c
+++ b/daffodil-runtime2/src/test/c/examples/ex_nums/generated_code.c
@@ -1,10 +1,10 @@
 // clang-format off
 #include "generated_code.h"
 #include <math.h>       // for NAN
-#include <stdbool.h>    // for true, false, bool
+#include <stdbool.h>    // for false, bool, true
 #include <stddef.h>     // for NULL, size_t
 #include <string.h>     // for memset, memcmp
-#include "errors.h"     // for Error, PState, UState, ERR_CHOICE_KEY, UNUSED
+#include "errors.h"     // for Error, PState, UState, ERR_CHOICE_KEY, Error::(anonymous), UNUSED
 #include "parsers.h"    // for alloc_hexBinary, parse_hexBinary, parse_be_float, parse_be_int16, parse_validate_fixed, parse_be_bool32, parse_be_bool16, parse_be_int32, parse_be_uint16, parse_be_uint32, parse_le_bool32, parse_le_int64, parse_le_uint16, parse_le_uint8, parse_be_bool8, parse_be_double, parse_be_int64, parse_be_int8, parse_be_uint64, parse_be_uint8, parse_le_bool16, parse_le_bool8, parse_le_double, parse_le_float, parse_le_int16, parse_le_int32, parse_le_int8, parse_le_uint [...]
 #include "unparsers.h"  // for unparse_hexBinary, unparse_be_float, unparse_be_int16, unparse_validate_fixed, unparse_be_bool32, unparse_be_bool16, unparse_be_int32, unparse_be_uint16, unparse_be_uint32, unparse_le_bool32, unparse_le_int64, unparse_le_uint16, unparse_le_uint8, unparse_be_bool8, unparse_be_double, unparse_be_int64, unparse_be_int8, unparse_be_uint64, unparse_be_uint8, unparse_le_bool16, unparse_le_bool8, unparse_le_double, unparse_le_float, unparse_le_int16, unparse_le_in [...]
 // clang-format on
diff --git a/daffodil-runtime2/src/test/resources/org/apache/daffodil/runtime2/examples/ex_nums/generated_code.h b/daffodil-runtime2/src/test/c/examples/ex_nums/generated_code.h
similarity index 97%
rename from daffodil-runtime2/src/test/resources/org/apache/daffodil/runtime2/examples/ex_nums/generated_code.h
rename to daffodil-runtime2/src/test/c/examples/ex_nums/generated_code.h
index e3c6ab6..849f788 100644
--- a/daffodil-runtime2/src/test/resources/org/apache/daffodil/runtime2/examples/ex_nums/generated_code.h
+++ b/daffodil-runtime2/src/test/c/examples/ex_nums/generated_code.h
@@ -2,10 +2,10 @@
 #define GENERATED_CODE_H
 
 // clang-format off
-#include "infoset.h"  // for HexBinary, InfosetBase
 #include <stdbool.h>  // for bool
 #include <stddef.h>   // for size_t
 #include <stdint.h>   // for uint8_t, int16_t, int32_t, int64_t, uint32_t, int8_t, uint16_t, uint64_t
+#include "infoset.h"  // for InfosetBase, HexBinary
 // clang-format on
 
 // Define infoset structures
diff --git a/daffodil-runtime2/src/test/scala/org/apache/daffodil/runtime2/TestCodeGenerator.scala b/daffodil-runtime2/src/test/scala/org/apache/daffodil/runtime2/TestCodeGenerator.scala
index 78c81a9..f3c2943 100644
--- a/daffodil-runtime2/src/test/scala/org/apache/daffodil/runtime2/TestCodeGenerator.scala
+++ b/daffodil-runtime2/src/test/scala/org/apache/daffodil/runtime2/TestCodeGenerator.scala
@@ -39,8 +39,7 @@ import org.junit.Test
  */
 class TestCodeGenerator {
   // Ensure all tests remove tempDir after creating it
-  val tempDir: os.Path = os.temp.dir()
-
+  val tempDir: os.Path = os.temp.dir(dir = null, prefix = "daffodil-runtime2-")
   @After def after(): Unit = {
     os.remove.all(tempDir)
   }
@@ -86,9 +85,14 @@ class TestCodeGenerator {
 
     // Generate code from the test schema successfully
     val codeDir = cg.generateCode(None, tempDir.toString)
+    val daffodilMain = codeDir/"libcli"/"daffodil_main.c"
+    val generatedCodeHeader = codeDir/"libruntime"/"generated_code.h"
+    val generatedCodeFile = codeDir/"libruntime"/"generated_code.c"
     assert(!cg.isError, cg.getDiagnostics.map(_.getMessage()).mkString("\n"))
     assert(os.exists(codeDir))
-    assert(os.exists(codeDir/"libruntime"/"generated_code.c"))
+    assert(os.exists(daffodilMain))
+    assert(os.exists(generatedCodeHeader))
+    assert(os.exists(generatedCodeFile))
   }
 
   @Test def test_compileCode_success(): Unit = {
@@ -168,4 +172,12 @@ class TestCodeGenerator {
     assert(ur.isError, "expected ur.isError to be true")
     assert(ur.getDiagnostics.nonEmpty, "expected ur.getDiagnostics to be non-empty")
   }
+
+  // Test added for code coverage because "sbt coverage compile" doesn't include genExamples
+  @Test def test_CodeGenerator_main(): Unit = {
+    val rootDir = if (os.exists(os.pwd/"src")) os.pwd/os.up else os.pwd
+    val examplesDir = rootDir/"daffodil-runtime2"/"target"/"test_CodeGenerator_main"
+    val args = Array(examplesDir.toString)
+    CodeGenerator.main(args)
+  }
 }
diff --git a/project/Rat.scala b/project/Rat.scala
index 4ff775d..4330f0b 100644
--- a/project/Rat.scala
+++ b/project/Rat.scala
@@ -36,7 +36,7 @@ object Rat {
     file("daffodil-cli/src/windows/dialog.bmp"),
 
     // generated_code.[ch] examples
-    file("daffodil-runtime2/src/test/resources/org/apache/daffodil/runtime2/examples"),
+    file("daffodil-runtime2/src/test/c/examples"),
 
     // test files that cannot include the Apache license without breaking tests
     file("daffodil-cli/src/it/resources/org/apache/daffodil/CLI/debugger/982"),