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]