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]