tuxji commented on a change in pull request #681:
URL: https://github.com/apache/daffodil/pull/681#discussion_r754556876
##########
File path:
daffodil-runtime2/src/main/scala/org/apache/daffodil/runtime2/generators/CodeGeneratorState.scala
##########
@@ -36,11 +37,32 @@ import scala.collection.mutable
* Builds up the state of generated code.
*/
class CodeGeneratorState {
- 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]()
+ private val elementsAlreadySeen = mutable.Map.empty[String, ElementBase]
Review comment:
The definition of mutable.Map.empty is:
```scala
def empty[K, V]: Map[K, V] = new HashMap[K, V]
```
The other mutable.\<C\>.empty functions also return new instances. There is
no semantic (only syntactic) difference between empty calls (parentheses are
optional), apply calls (parentheses are mandatory), and constructors calls
(`new` is mandatory). I checked and found all three types of mutable
container calls used in Daffodil source code. Nevertheless, I'll switch back
to apply since that idiom is more common and some lines at the end of this same
file use apply as well.
##########
File path:
daffodil-runtime2/src/test/scala/org/apache/daffodil/runtime2/TestCodeGenerator.scala
##########
@@ -168,4 +174,40 @@ class TestCodeGenerator {
assert(ur.isError, "expected ur.isError to be true")
assert(ur.getDiagnostics.nonEmpty, "expected ur.getDiagnostics to be
non-empty")
}
+
+ 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 codeDir = cg.generateCode(rootNS, tempDir.toString)
+ assert(!cg.isError, cg.getDiagnostics.map(_.getMessage()).mkString("\n"))
+ val generatedCodeHeader = codeDir/"libruntime"/"generated_code.h"
+ val generatedCodeFile = codeDir/"libruntime"/"generated_code.c"
+
+ // Replace the example generated code files
+ os.copy(generatedCodeHeader, exampleCodeHeader, replaceExisting = true)
+ os.copy(generatedCodeFile, exampleCodeFile, replaceExisting = true)
+ }
Review comment:
Okay, then I want to remove this test and make "sbt compile" update the
example code files instead. We should automatically generate (a.k.a. update)
the example code files whenever we build Daffodil, just like the build
automatically generates those other files and keeps them up to date after any
changes to the source code:
```scala
lazy val genManaged = taskKey[Unit]("Generate managed sources and resources")
lazy val genProps = taskKey[Seq[File]]("Generate properties scala source")
lazy val genSchemas = taskKey[Seq[File]]("Generated DFDL schemas")
```
All I want is to have pull requests include any changes to the example code
files automatically. I don't want any extra manual step. We have parse and
unparse tests which compile and run the new generated files. These tests will
warn us if any code generator backend changes break the generated files, so
there is no reason why we need to compare the new generated files to the old
files and fail the build even if only one byte differs. It just would be nice
(and helpful for reviewers) to show how code generator backend changes affect
the example code files.
Steve (or Mike), what's the easiest way to make "sbt compile" call
`updateGeneratedCodeExample` without needing to stage the daffodil CLI?
##########
File path:
daffodil-runtime2/src/main/scala/org/apache/daffodil/runtime2/generators/CodeGeneratorState.scala
##########
@@ -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
Review comment:
The tool is include-what-you-use, iwyu for short. It's telling us that
we're dereferencing an anonymous union inside the Error struct, which has no
union name but does have a member name:
```c
typedef struct Error
{
uint8_t code;
union
{
int c; // for %c
int64_t d64; // for %d64
const char *s; // for %s
} arg;
} Error;
```
##########
File path:
daffodil-runtime2/src/main/scala/org/apache/daffodil/runtime2/generators/CodeGeneratorState.scala
##########
@@ -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)
- case _ => ""
+
+ // 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
+ case _ =>
+ ""
Review comment:
Method addBeforeSwitchStatements in this same file calls
choiceDispatchField on every complexType (group) element and generates some
additional C code only if choiceDispatchField returns a non-blank string:
```scala
val dispatchField = choiceDispatchField(context)
if (dispatchField.nonEmpty) {
```
I've moved the empty string to the same line as the case and added a comment
explaining why returning "" is OK here.
--
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]