tuxji commented on a change in pull request #488:
URL: https://github.com/apache/incubator-daffodil/pull/488#discussion_r575289097



##########
File path: 
daffodil-runtime2/src/main/scala/org/apache/daffodil/runtime2/generators/CodeGeneratorState.scala
##########
@@ -105,41 +129,178 @@ class CodeGeneratorState {
     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 use 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 {
+      case choice: Choice if choice.isDirectDispatch =>
+        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)
+      case _ => ""
+    }
+    // Strip namespace prefixes since C code uses only local names (for now...)
+    val localDispatchSlashPath = dispatchSlashPath.replaceAll("/[^:]+:", "/")
+    val res = localDispatchSlashPath.replace('/', '.')
+    res
+  }
+
+  def addBeforeSwitchStatements(context: ElementBase): Unit = {
+    val erd = erdName(context)
+    val initStatement = s"    instance->_base.erd = &$erd;"
+
+    structs.top.initStatements += initStatement
+
+    val dispatchField = choiceDispatchField(context)
+    if (dispatchField.nonEmpty) {
+      val C = localName(context)
+      val declaration =
+        s"""    size_t      _choice; // choice of which union field to use
+           |    union
+           |    {""".stripMargin
+      val erdDef =
+        s"""static const ERD _choice_$erd = {
+           |    {
+           |        NULL, // namedQName.prefix
+           |        "_choice", // namedQName.local
+           |        NULL, // namedQName.ns
+           |    },
+           |    CHOICE, // typeCode
+           |    0, NULL, NULL, NULL, NULL, NULL, NULL
+           |};
+           |""".stripMargin
+      val offsetComputation = s"    (const char 
*)&${C}_compute_offsets._choice - (const char *)&${C}_compute_offsets"
+      val erdComputation = s"    &_choice_$erd"
+      val initStatement = s"    instance->_choice = NO_CHOICE;"
+      val initChoiceStatement =
+        s"""    int64_t key = rootElement->$dispatchField;
+           |    switch (key)
+           |    {""".stripMargin
+      val parseStatement =
+        s"""    instance->_base.erd->initChoice(&instance->_base, 
rootElement());
+           |    switch (instance->_choice)
+           |    {""".stripMargin
+      val unparseStatement =
+        s"""    instance->_base.erd->initChoice(&instance->_base, 
rootElement());
+           |    switch (instance->_choice)
+           |    {""".stripMargin
+
+      erds += erdDef
+      structs.top.declarations += declaration
+      structs.top.offsetComputations += offsetComputation
+      structs.top.erdComputations += erdComputation
+      structs.top.initStatements += initStatement
+      structs.top.initChoiceStatements += initChoiceStatement
+      structs.top.parserStatements += parseStatement
+      structs.top.unparserStatements += unparseStatement
+    }
+  }
+
+  def addAfterSwitchStatements(): Unit = {
+    if (structs.top.initChoiceStatements.nonEmpty) {
+      val declaration = s"    };"
+      val initChoiceStatement =
+        s"""    default:
+           |        instance->_choice = NO_CHOICE;
+           |        break;
+           |    }
+           |
+           |    if (instance->_choice != NO_CHOICE)
+           |    {
+           |        const size_t choice = instance->_choice + 1; // skip the 
_choice field
+           |        const size_t offset = instance->_base.erd->offsets[choice];
+           |        const ERD *  childERD = 
instance->_base.erd->childrenERDs[choice];
+           |        InfosetBase *childNode = (InfosetBase *)((const char 
*)instance + offset);
+           |        childNode->erd = childERD;
+           |        return true;
+           |    }
+           |    else
+           |    {
+           |        return false;
+           |    }""".stripMargin
+      val parseStatement =
+        s"""    default:
+           |        pstate->error_msg = "node's _choice field was not 
initialized during parsing";

Review comment:
       I did the following tests:
   
   ```shell
   $ cd daffodil-runtime2/src/test/resources/org/apache/daffodil/runtime2
   $ daffodil generate c -s nested.dfdl.xsd -r NestedUnion
   $ cp nested.dfdl.xsd c/
   $ cp nested_union_parse_4.dat c/parse.dat
   $ cp nested_union_unparse_4.xml c/unparse.xml
   $ cd c
   $ make tests
   ./daffodil parse parse.dat -o test_unparse.xml
   xmldiff unparse.xml test_unparse.xml
   
   ./daffodil unparse unparse.xml -o test_parse.dat
   diff parse.dat test_parse.dat
   $ : Also check Daffodil runtime1...
   $ daffodil parse -s nested.dfdl.xsd -r NestedUnion -o test_unparse.xml 
parse.dat # No errors
   $ xmldiff unparse.xml test_unparse.xml # No errors
   $ daffodil unparse -s nested.dfdl.xsd -r NestedUnion -o test_parse.dat 
unparse.xml # No errors
   $ diff parse.dat test_parse.dat # No errors
   $ : Now change the choice dispatch key in parse.dat...
   $ emacs parse.dat # change 4th byte from ^D to ^@
   $ make parse-test
   ./daffodil parse parse.dat -o test_unparse.xml
   ./daffodil: Parse error: no match between choice dispatch key and any branch 
key
   make: *** [Makefile:42: parse-test] Error 1
   $ daffodil parse -s nested.dfdl.xsd -r NestedUnion -o test_unparse.xml 
parse.dat
   [error] Parse Error: Choice dispatch key (0) failed to match any of the 
branch keys.
   Schema context: choice[1] Location line 71 column 12 in 
file:/home/interran/apache/incubator-daffodil-runtime2/daffodil-runtime2/src/test/resources/org/apache/daffodil/runtime2/c/nested.dfdl.xsd
   Data location was preceding byte 4
   $ : Now change the choice dispatch key in unparse.xml as well....
   $ emacs unparse.xml # change <tag>4</tag> to <tag>0</tag>
   $ make unparse-test
   ./daffodil unparse unparse.xml -o test_parse.dat
   ./daffodil: Walk error: no match between choice dispatch key and any branch 
key
   make: *** [Makefile:46: unparse-test] Error 1
   $ daffodil unparse -s nested.dfdl.xsd -r NestedUnion -o test_parse.dat 
unparse.xml # No errors
   $ diff parse.dat test_parse.dat # No errors, 4th byte is zero in both files
   ```
   
   My point is that this line in CodeGeneratorState which we are talking about 
makes sure c-daffodil behaves the same as scala-daffodil.  Both report a 
processing error saying there is no match between the choice dispatch key and 
any branch key when parsing the parse.dat file.  You can say that c-daffodil 
shouldn't be more strict than scala-daffodil when unparsing (it reports a no 
match processing error on the modifed unparse.xml while scala-daffodil silently 
writes out the same modified parse.dat) but that's a different part of code 
than this one.




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

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


Reply via email to