This is an automated email from the ASF dual-hosted git repository.
mbeckerle pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-daffodil.git
The following commit(s) were added to refs/heads/master by this push:
new 7e8e1b3 Introduce sharing into schema compiler to improve speed and
space.
7e8e1b3 is described below
commit 7e8e1b3ed110efe939908cdf1e12df50d1f86527
Author: Michael Beckerle <[email protected]>
AuthorDate: Thu Feb 13 18:46:08 2020 -0500
Introduce sharing into schema compiler to improve speed and space.
This eliminates the combinatorial explosion of copying and makes
schema compilation roughly linear in the size of the schema.
Schema compilation time for some large schemas like EDIFACT has dropped
from minutes to seconds.
Primary mechanism is SharedFactory, PropEnv and SharedTerm
cache/Memoization.
We depend on being smart about sharing things that can be shared and
which
don't reference their point-of-use context. I.e., we only share what can
be
shared properly. Nothing in the type system enforces this however.
Instrumentation counts the number of elements compiled and the number of
shared terms memoized currently. These are checked to insure
we don't re-introduce sharing. See tests in TestRefMap class.
Same output instrumentation for EDIFACT:
Without new sharing:
[Schema had 18820 elements.]
[Schema memoized 690 shared terms.]
With new sharing:
[Schema had 1992 elements.]
[Schema memoized 690 shared terms.]
DAFFODIL-1444
---
.../org/apache/daffodil/compiler/Compiler.scala | 55 ++---
.../org/apache/daffodil/dpath/Expression.scala | 9 +-
.../daffodil/dsom/AnnotatedSchemaComponent.scala | 106 +++++++-
.../org/apache/daffodil/dsom/ElementBase.scala | 15 +-
.../scala/org/apache/daffodil/dsom/GroupDef.scala | 15 +-
.../scala/org/apache/daffodil/dsom/GroupRef.scala | 4 +-
.../org/apache/daffodil/dsom/ModelGroup.scala | 35 ++-
.../org/apache/daffodil/dsom/PropProviders.scala | 18 ++
.../org/apache/daffodil/dsom/SchemaComponent.scala | 2 +-
.../scala/org/apache/daffodil/dsom/SchemaSet.scala | 13 +
.../org/apache/daffodil/dsom/SequenceGroup.scala | 4 +-
.../main/scala/org/apache/daffodil/dsom/Term.scala | 1 +
.../daffodil/grammar/ChoiceGrammarMixin.scala | 2 +-
.../daffodil/grammar/ElementBaseGrammarMixin.scala | 67 ++++-
.../daffodil/grammar/ModelGroupGrammarMixin.scala | 31 ++-
.../daffodil/grammar/SequenceGrammarMixin.scala | 2 +-
.../runtime1/ElementBaseRuntime1Mixin.scala | 4 +-
.../daffodil/runtime1/TermRuntime1Mixin.scala | 2 +-
.../apache/daffodil/dsom/TestDsomCompiler.scala | 2 +-
.../dsom/TestInteriorAlignmentElimination.scala | 118 +++++++++
.../TestPolymorphicUpwardRelativeExpressions.scala | 273 +++++++++++++++++++++
.../org/apache/daffodil/dsom/TestRefMap.scala | 28 +--
.../scala/org/apache/daffodil/util/Memoize1.scala | 63 -----
.../org/apache/daffodil/util/TestMemoize1.scala | 138 -----------
.../apache/daffodil/dsom/CompiledExpression1.scala | 6 +-
25 files changed, 712 insertions(+), 301 deletions(-)
diff --git
a/daffodil-core/src/main/scala/org/apache/daffodil/compiler/Compiler.scala
b/daffodil-core/src/main/scala/org/apache/daffodil/compiler/Compiler.scala
index 979b3d4..af270ec 100644
--- a/daffodil-core/src/main/scala/org/apache/daffodil/compiler/Compiler.scala
+++ b/daffodil-core/src/main/scala/org/apache/daffodil/compiler/Compiler.scala
@@ -336,37 +336,36 @@ class Compiler(var validateDFDLSchemas: Boolean = true)
* This is to avoid issues when TDML tests are running in parallel
* and compiling schemas that are not in files, but just embedded in the
tests.
*/
- def compileSource(schemaSource: DaffodilSchemaSource): ProcessorFactory =
- Compiler.synchronized {
- //
- // The SchemaSet object and the ProcessorFactory mutually refer to each
other because
- // the API allows the root to be specified by API calls on the processor
factory, the compiler,
- // or it can be inferred from the contents of the schema documents.
- //
- // The logic for assembling all those sources together is in the
SchemaSet.
- //
- // To make these two objects refer to each other, we we create them
lazily, and
- // pass by name to SchemaSet constructor. This is the typical scala idiom
- // for creating things that point mutually without using a side-effect
which may or may not
- // have happened a the time the information is demanded.
- //
- lazy val sset: SchemaSet = new SchemaSet(Some(pf), rootSpec,
externalDFDLVariables, Seq(schemaSource), validateDFDLSchemas,
checkAllTopLevel, tunablesObj)
- lazy val pf: ProcessorFactory = new ProcessorFactory(sset)
- val err = pf.isError
- val diags = pf.getDiagnostics // might be warnings even if not isError
- if (err) {
- Assert.invariant(diags.length > 0)
- log(LogLevel.Compile, "Compilation (ProcessorFactory) produced %d
errors/warnings.", diags.length)
+ def compileSource(schemaSource: DaffodilSchemaSource): ProcessorFactory = {
+ //
+ // The SchemaSet object and the ProcessorFactory mutually refer to each
other because
+ // the API allows the root to be specified by API calls on the processor
factory, the compiler,
+ // or it can be inferred from the contents of the schema documents.
+ //
+ // The logic for assembling all those sources together is in the SchemaSet.
+ //
+ // To make these two objects refer to each other, we we create them
lazily, and
+ // pass by name to SchemaSet constructor. This is the typical scala idiom
+ // for creating things that point mutually without using a side-effect
which may or may not
+ // have happened a the time the information is demanded.
+ //
+ lazy val sset: SchemaSet = new SchemaSet(Some(pf), rootSpec,
externalDFDLVariables, Seq(schemaSource), validateDFDLSchemas,
checkAllTopLevel, tunablesObj)
+ lazy val pf: ProcessorFactory = new ProcessorFactory(sset)
+ val err = pf.isError
+ val diags = pf.getDiagnostics // might be warnings even if not isError
+ if (err) {
+ Assert.invariant(diags.length > 0)
+ log(LogLevel.Compile, "Compilation (ProcessorFactory) produced %d
errors/warnings.", diags.length)
+ } else {
+ if (diags.length > 0) {
+ log(LogLevel.Compile, "Compilation (ProcessorFactory) produced %d
warnings.", diags.length)
} else {
- if (diags.length > 0) {
- log(LogLevel.Compile, "Compilation (ProcessorFactory) produced %d
warnings.", diags.length)
- } else {
- log(LogLevel.Compile, "ProcessorFactory completed with no errors.")
- }
+ log(LogLevel.Compile, "ProcessorFactory completed with no errors.")
}
- log(LogLevel.Compile, "Schema had %s elements.", ElementBase.count)
- pf
}
+ log(LogLevel.Compile, "Schema had %s elements.",
sset.elementBaseInstanceCount)
+ pf
+ }
/**
* For convenient unit testing allow a literal XML node.
diff --git
a/daffodil-core/src/main/scala/org/apache/daffodil/dpath/Expression.scala
b/daffodil-core/src/main/scala/org/apache/daffodil/dpath/Expression.scala
index 879c57b..ad5c0b6 100644
--- a/daffodil-core/src/main/scala/org/apache/daffodil/dpath/Expression.scala
+++ b/daffodil-core/src/main/scala/org/apache/daffodil/dpath/Expression.scala
@@ -26,7 +26,7 @@ import org.apache.daffodil.xml.RefQName
import scala.util.{ Success, Failure }
import org.apache.daffodil.dsom.RelativePathPastRootError
import org.apache.daffodil.equality._
-import java.math.{ BigDecimal => JBigDecimal, BigInteger => JBigInt}
+import java.math.{ BigDecimal => JBigDecimal, BigInteger => JBigInt }
import java.lang.{ Long => JLong, Integer => JInt, Boolean => JBoolean, Double
=> JDouble }
import org.apache.daffodil.util.Numbers
import org.apache.daffodil.api.WarnID
@@ -579,8 +579,7 @@ case class WholeExpression(
extends Expression {
final override lazy val tunable: DaffodilTunables = host.tunable
- final override lazy val unqualifiedPathStepPolicy : UnqualifiedPathStepPolicy
- = host.unqualifiedPathStepPolicy
+ final override lazy val unqualifiedPathStepPolicy: UnqualifiedPathStepPolicy
= host.unqualifiedPathStepPolicy
def init() {
this.setOOLAGContext(host) // we are the root of expression, but we
propagate diagnostics further.
@@ -1017,7 +1016,7 @@ sealed abstract class DownStepExpression(s: String,
predArg: Option[PredicateExp
Assert.invariant(allTypes.length > 0)
if (allTypes.length > 1) {
val (relevantExpr, details) = polymorphicExpressionErrorDetails(this)
- notYetImplemented(
+ subsetError(
"Expression %s has different types at different points of usage.%s",
relevantExpr.text, details)
} else {
@@ -1335,7 +1334,7 @@ abstract class LiteralExpressionBase(value: Any)
* Convert to regular types from the pessimistic BigInt
* and BigDecimal that come in from the parser.
*/
- lazy val litValue:DataValuePrimitive = value match {
+ lazy val litValue: DataValuePrimitive = value match {
case s: String => s
case i: Int => i
case i: JBigInt => {
diff --git
a/daffodil-core/src/main/scala/org/apache/daffodil/dsom/AnnotatedSchemaComponent.scala
b/daffodil-core/src/main/scala/org/apache/daffodil/dsom/AnnotatedSchemaComponent.scala
index 97412e0..1214006 100644
---
a/daffodil-core/src/main/scala/org/apache/daffodil/dsom/AnnotatedSchemaComponent.scala
+++
b/daffodil-core/src/main/scala/org/apache/daffodil/dsom/AnnotatedSchemaComponent.scala
@@ -28,6 +28,8 @@ import org.apache.daffodil.schema.annotation.props.NotFound
import org.apache.daffodil.schema.annotation.props.Found
import org.apache.daffodil.schema.annotation.props.FindPropertyMixin
import org.apache.daffodil.api.WarnID
+import org.apache.daffodil.schema.annotation.props.PropTypes
+import scala.collection.mutable
/**
* Only objects from which we generate processors (parsers/unparsers)
@@ -201,6 +203,75 @@ abstract class AnnotatedSchemaComponentImpl(
}
/**
+ * Identifies the property environment of a term.
+ *
+ * If two terms have the same propEnv and same def/decl, then some things can
be shared
+ * about their implementation.
+ *
+ * If they have different propEnv, then all bets are off.
+ *
+ * Can be used as a key for whether to create new instance in a factory, or
whether one
+ * can be re-used because the property environment is the same.
+ *
+ * The trick is that the regular objects which carry properties have location
information
+ * unique to where they originate (for diagnostic messaging purposes).
+ *
+ * What we want is pure value based - depends on the properties and their
values only.
+ *
+ * The components are of type Seq[Set[String, String]] because the machinery
below has
+ * alredy converted dfdl:ref="q:name" format references into references to
objects having
+ * property resolvers. It is easiest just to extract the string-to-string
information rather than
+ * trying to reuse the named format objects directly.
+ *
+ * These Seq[Set[String, String]] are conceptually equivalent to just property
sets,
+ * but keeping them ordered like this preserves some information for us which
+ * might be useful in debugging/maintenance of the code. the first set in
+ * the sequence are the local properties excluding dfdl:ref, the next set is
those from
+ * the dfdl:ref, and the next from the next dfdl:ref, and so on. For purposes
of
+ * a hash key we don't care that they are flattened. that happens elsewhere.
It will
+ * be much easier to see why two PropEnv objects are not equal looking at this
than an
+ * entirely flattened set of properties, most of which aren't relevant.
+ *
+ * optNext is the optional next schema component. So for an element reference,
it is the
+ * global element declaration. For a group reference, it is the group
definition. It is the
+ * next place we would get properties from (and defaults from) for property
resolution.
+ */
+case class PropEnv(
+ localProps: Seq[Set[(String, String)]],
+ defaultPropSource: Seq[Set[(String, String)]],
+ optNext: Option[scala.xml.Node])
+
+/**
+ * Shared object factory/cache.
+ *
+ * Creates objects for new keys, shares previously computed ones (and avoids
+ * recomputing them) for existing keys.
+ */
+final class SharedFactory[SharedType] {
+
+ private type KeyType = (scala.xml.Node, PropEnv)
+
+ private val vals = new mutable.HashMap[KeyType, SharedType]
+
+ /**
+ * The passing of the value argument by name is critical here, as
+ * we want to avoid evaluating that at all when the key is one
+ * we have already seen.
+ */
+ final def getShared(key: KeyType, value: => SharedType): SharedType = {
+ val opt = vals.get(key)
+ opt match {
+ case Some(y) => y
+ case None => {
+ val shared = value
+ vals.put(key, shared)
+ shared
+ }
+ }
+ }
+}
+
+/**
* Shared characteristics of any annotated schema component.
*
* Not all components can carry DFDL annotations.
@@ -211,18 +282,31 @@ trait AnnotatedSchemaComponent
with OverlapCheckMixin {
/**
- * Since validation of extra attributes on XML Schema elements is
- * normally lax validation, we can't count on validation of DFDL schemas
- * to tell us whether short-form annotations are correct or not.
- *
- * So, we have to do this check ourselves.
- *
- * TBD: change properties code generator to output the various lists of
- * properties that we have to check against. (Might already be there...?)
- *
+ * If two terms have the same propEnv, they have identical properties
+ * in scope.
+ */
+ private lazy val propEnv: PropEnv = {
+ val localPropsSets = this.nonDefaultFormatChain.propertyPairsSets
+ val defaultPropObj = this.defaultFormatChain.propertyPairsSets
+ val next = refersToForPropertyCombining
+ PropEnv(localPropsSets, defaultPropObj, next.map { _.xml })
+ }
+
+ /**
+ * The thing that provides the actual definition. If this is a local
+ * element decl or local sequence/choice, then this is the thing,
+ * if this is some sort of a ref, then the referenced definition is the
thing.
*/
- // TODO: Implement this - DFDL-598, DFDL-1512
- // private def areShortFormAnnotationsValid: Boolean = true
+ private lazy val actualDef: AnnotatedSchemaComponent = {
+ this match {
+ case gr: GroupRef => gr.groupDef
+ case aer: AbstractElementRef => aer.referencedElement
+ case eb: ElementBase => eb
+ case grl: GroupDefLike => grl
+ }
+ }
+
+ protected final lazy val shareKey = (actualDef.xml, propEnv)
/**
* For property combining only. E.g., doesn't refer from an element
diff --git
a/daffodil-core/src/main/scala/org/apache/daffodil/dsom/ElementBase.scala
b/daffodil-core/src/main/scala/org/apache/daffodil/dsom/ElementBase.scala
index 7e07712..c960c02 100644
--- a/daffodil-core/src/main/scala/org/apache/daffodil/dsom/ElementBase.scala
+++ b/daffodil-core/src/main/scala/org/apache/daffodil/dsom/ElementBase.scala
@@ -46,14 +46,8 @@ import
org.apache.daffodil.infoset.DataValue.DataValuePrimitiveOrUseNilForDefaul
*
* Our approach is to provide common behaviors on base classes or
traits/mixins, and to have distinct
* classes for each instance type.
- */
-
-object ElementBase {
- var count = 0
-}
-
-/**
- * Shared by all forms of elements, local or global or element reference.
+ *
+ * This base is shared by all forms of elements, local or global or element
reference.
*/
trait ElementBase
extends Term
@@ -74,8 +68,11 @@ trait ElementBase
override final def eBase = this
- ElementBase.count += 1 // how many elements in this schema.
+ lazy val init: Unit = {
+ schemaSet.elementBaseInstanceCount += 1
+ }
+ requiredEvaluations(init)
requiredEvaluations(typeDef)
requiredEvaluations(isSimpleType)
requiredEvaluations(if (hasPattern) patternValues)
diff --git
a/daffodil-core/src/main/scala/org/apache/daffodil/dsom/GroupDef.scala
b/daffodil-core/src/main/scala/org/apache/daffodil/dsom/GroupDef.scala
index 5a9d445..4c9475c 100644
--- a/daffodil-core/src/main/scala/org/apache/daffodil/dsom/GroupDef.scala
+++ b/daffodil-core/src/main/scala/org/apache/daffodil/dsom/GroupDef.scala
@@ -64,19 +64,29 @@ trait GroupDefLike
extends AnnotatedSchemaComponent
with ProvidesDFDLStatementMixin {
+ /**
+ * Not named groupMembers to avoid confusion with ModelGroup.groupMembers.
+ *
+ * This is not necessarily a Term subclass, it is shared by global group
definitions
+ * which are not terms. Non-terms don't deal with sharing the members.
+ * So this just provides them for sharing (as appropriate) by shared Terms.
+ */
+ final def groupMembersNotShared = groupMembersDef
+
def xmlChildren: Seq[Node]
private lazy val goodXmlChildren = LV('goodXMLChildren) {
xmlChildren.flatMap { removeNonInteresting(_) } }.value
/** Returns the group members that are elements or model groups. */
- lazy val groupMembers: Seq[Term] = LV('groupMembers) {
+ protected lazy val groupMembersDef: Seq[Term] = LV('groupMembers) {
val positions = List.range(1, goodXmlChildren.length + 1) // range is
exclusive on 2nd arg. So +1.
val pairs = goodXmlChildren zip positions
- pairs.map {
+ val members = pairs.map {
case (n, i) =>
val t = TermFactory(n, this, i)
t
}
+ members
}.value
/**
@@ -114,7 +124,6 @@ sealed abstract class GlobalGroupDef(
with ResolvesLocalProperties // for dfdl:choiceBranchKey
{
- requiredEvaluations(groupMembers)
requiredEvaluations(validateChoiceBranchKey)
lazy val groupRef = grefArg // once only
diff --git
a/daffodil-core/src/main/scala/org/apache/daffodil/dsom/GroupRef.scala
b/daffodil-core/src/main/scala/org/apache/daffodil/dsom/GroupRef.scala
index 1b15ea4..fbf27c3 100644
--- a/daffodil-core/src/main/scala/org/apache/daffodil/dsom/GroupRef.scala
+++ b/daffodil-core/src/main/scala/org/apache/daffodil/dsom/GroupRef.scala
@@ -32,8 +32,8 @@ trait GroupRef { self: ModelGroup =>
final override lazy val optReferredToComponent = Some(referredToComponent)
- final override lazy val groupMembers = LV('groupMembers) {
- groupDef.groupMembers
+ final override protected lazy val groupMembersDef = LV('groupMembers) {
+ groupDef.groupMembersNotShared
}.value
override protected def annotationFactory(node: Node): Option[DFDLAnnotation]
= {
diff --git
a/daffodil-core/src/main/scala/org/apache/daffodil/dsom/ModelGroup.scala
b/daffodil-core/src/main/scala/org/apache/daffodil/dsom/ModelGroup.scala
index 8982b94..a5a7945 100644
--- a/daffodil-core/src/main/scala/org/apache/daffodil/dsom/ModelGroup.scala
+++ b/daffodil-core/src/main/scala/org/apache/daffodil/dsom/ModelGroup.scala
@@ -160,7 +160,40 @@ abstract class ModelGroup(index: Int)
groupMembers.forall {
_.hasStaticallyRequiredOccurrencesInDataRepresentation }
}
- def groupMembers: Seq[Term]
+ /**
+ * The group members are shared across all instances of the group, when they
+ * are for groups with the same shareKey.
+ *
+ * This sharing is critical, because without it, we would be creating a new
+ * instance of all members recursively for every reference to a group.
+ * Recursively that would lead to a combinatorial explosion of copies.
+ *
+ * The public member to access group members, which insures sharing is
preserved
+ * is this member.
+ *
+ * The various subclasses of this class define the group members by
+ * overrides of the protected groupMembersDef member.
+ *
+ * Note that the sharing depends on some scala subtleties here. Like that
+ * this is a lazy val, so evaluated exactly once, and that the 2nd argument
+ * to getShared is passed by name, so it is not necessarily evaluated at
+ * all. E.g., if the share-able item already exists, then it is returned and
+ * the 2nd arg isn't evaluated for that call.
+ *
+ * This also depends on groupMembersDef overrides all being lazy val.
+ */
+ final lazy val groupMembers: Seq[Term] =
+ schemaSet.sharedGroupMembersFactory.getShared(shareKey, groupMembersDef)
+
+ /**
+ * Override to define the group members. They are accessed by way of the
public
+ * member groupMembers which insures these are properly shared across all
+ * references to the group.
+ *
+ * These *must* be overridden as lazy val to insure that we compute them only
+ * once and share them appropriately.
+ */
+ protected def groupMembersDef: Seq[Term]
final lazy val representedMembers = groupMembers.filter { _.isRepresented }
diff --git
a/daffodil-core/src/main/scala/org/apache/daffodil/dsom/PropProviders.scala
b/daffodil-core/src/main/scala/org/apache/daffodil/dsom/PropProviders.scala
index b746f16..5919b5a 100644
--- a/daffodil-core/src/main/scala/org/apache/daffodil/dsom/PropProviders.scala
+++ b/daffodil-core/src/main/scala/org/apache/daffodil/dsom/PropProviders.scala
@@ -69,6 +69,14 @@ trait LeafPropProvider
*/
def justThisOneProperties: PropMap
+ /**
+ * Just the properties as string pairs, without any location information.
+ *
+ * Intended for use comparing to see if two prop providers are equivalent
+ * in terms of providing the same property values.
+ */
+ final lazy val justThisOnePropertyPairsSet = justThisOneProperties.map {
case (s1, (s2, _)) => (s1, s2) }.toSet
+
final def leafFindProperty(pname: String): PropertyLookupResult = {
log(LogLevel.Debug, "%s leafFindProperty %s on %s", diagnosticDebugName,
pname, this)
val mine = justThisOneProperties.get(pname)
@@ -108,6 +116,16 @@ final class ChainPropProvider(leafProvidersArg:
Seq[LeafPropProvider], forAnnota
override def toString() = Misc.getNameFromClass(this) + "(" + forAnnotation
+ ")"
/**
+ * This is a sequence of sets basically for debug/maintenance reasons.
+ * Conceptually it could be flattened to just a set, but this way the
contents of each set
+ * may be visibly meaningful when debugging, as the first set comes from the
+ * first leaf provider, second from the second (which comes from first
dfdl:ref
+ * reference), etc.
+ */
+ final lazy val propertyPairsSets: Seq[Set[(String, String)]] =
+ leafProviders.map { _.justThisOnePropertyPairsSet }
+
+ /**
* for debug/test only
*/
final lazy val properties: PropMap = leafProviders.flatMap {
_.properties.toSeq }.toMap
diff --git
a/daffodil-core/src/main/scala/org/apache/daffodil/dsom/SchemaComponent.scala
b/daffodil-core/src/main/scala/org/apache/daffodil/dsom/SchemaComponent.scala
index 136b16a..4633333 100644
---
a/daffodil-core/src/main/scala/org/apache/daffodil/dsom/SchemaComponent.scala
+++
b/daffodil-core/src/main/scala/org/apache/daffodil/dsom/SchemaComponent.scala
@@ -68,7 +68,7 @@ trait SchemaComponent
final override lazy val unqualifiedPathStepPolicy =
tunable.unqualifiedPathStepPolicy
lazy val dpathCompileInfo: DPathCompileInfo = {
- lazy val parents = enclosingComponent.map { _.dpathCompileInfo }.toSeq
+ lazy val parents = enclosingComponents.map { _.encloser.dpathCompileInfo
}.toSeq
new DPathCompileInfo(
parents,
variableMap,
diff --git
a/daffodil-core/src/main/scala/org/apache/daffodil/dsom/SchemaSet.scala
b/daffodil-core/src/main/scala/org/apache/daffodil/dsom/SchemaSet.scala
index 9c1292a..f16e377 100644
--- a/daffodil-core/src/main/scala/org/apache/daffodil/dsom/SchemaSet.scala
+++ b/daffodil-core/src/main/scala/org/apache/daffodil/dsom/SchemaSet.scala
@@ -41,6 +41,7 @@ import scala.collection.immutable.Map
import scala.collection.immutable.HashMap
import org.apache.daffodil.compiler.ProcessorFactory
import org.apache.daffodil.processors.TypeCalculatorCompiler.TypeCalcMap
+import org.apache.daffodil.grammar.Gram
/**
* A schema set is exactly that, a set of schemas. Each schema has
@@ -74,6 +75,18 @@ final class SchemaSet(
extends SchemaComponentImpl(<schemaSet/>, None)
with SchemaSetIncludesAndImportsMixin {
+ /**
+ * Used to count instances of element base objects created by
+ * compiler - to be sure we're not duplicating them unnecessarily.
+ */
+ var elementBaseInstanceCount = 0L
+
+ lazy val sharedComplexContentFactory = new SharedFactory[Gram]
+ lazy val sharedNilLitFactory = new SharedFactory[Gram]
+ lazy val sharedSimpleValueFactory = new SharedFactory[Gram]
+ lazy val sharedGroupContentsFactory = new SharedFactory[Gram]
+ lazy val sharedGroupMembersFactory = new SharedFactory[Seq[Term]]
+
private lazy val processorFactory = pfArg // insure this by name arg is
evaluated exactly once.
lazy val root = rootElement(processorFactory.flatMap { _.rootSpec })
diff --git
a/daffodil-core/src/main/scala/org/apache/daffodil/dsom/SequenceGroup.scala
b/daffodil-core/src/main/scala/org/apache/daffodil/dsom/SequenceGroup.scala
index 9787135..e9a226c 100644
--- a/daffodil-core/src/main/scala/org/apache/daffodil/dsom/SequenceGroup.scala
+++ b/daffodil-core/src/main/scala/org/apache/daffodil/dsom/SequenceGroup.scala
@@ -263,6 +263,8 @@ trait SequenceDefMixin
with GroupDefLike
with FindPropertyMixin {
+ def groupMembersNotShared: Seq[Term]
+
protected final def isMyFormatAnnotation(a: DFDLAnnotation) =
a.isInstanceOf[DFDLSequence]
protected final def annotationFactory(node: Node): Option[DFDLAnnotation] = {
@@ -333,7 +335,7 @@ final class ChoiceBranchImpliedSequence(rawGM: Term)
with SequenceDefMixin
with ChoiceBranchImpliedSequenceRuntime1Mixin {
- override final lazy val groupMembers: Seq[Term] = Seq(rawGM)
+ override final protected lazy val groupMembersDef: Seq[Term] = Seq(rawGM)
override def separatorSuppressionPolicy: SeparatorSuppressionPolicy =
SeparatorSuppressionPolicy.TrailingEmptyStrict
diff --git a/daffodil-core/src/main/scala/org/apache/daffodil/dsom/Term.scala
b/daffodil-core/src/main/scala/org/apache/daffodil/dsom/Term.scala
index 0c43db8..a550e3f 100644
--- a/daffodil-core/src/main/scala/org/apache/daffodil/dsom/Term.scala
+++ b/daffodil-core/src/main/scala/org/apache/daffodil/dsom/Term.scala
@@ -43,6 +43,7 @@ import
org.apache.daffodil.schema.annotation.props.gen.OccursCountKind
import org.apache.daffodil.schema.annotation.props.SeparatorSuppressionPolicy
import org.apache.daffodil.api.WarnID
import org.apache.daffodil.infoset.PartialNextElementResolver
+import scala.collection.mutable
/**
* Mixin for objects that are shared, but have consistency checks to be run
diff --git
a/daffodil-core/src/main/scala/org/apache/daffodil/grammar/ChoiceGrammarMixin.scala
b/daffodil-core/src/main/scala/org/apache/daffodil/grammar/ChoiceGrammarMixin.scala
index 2c20972..ac143d3 100644
---
a/daffodil-core/src/main/scala/org/apache/daffodil/grammar/ChoiceGrammarMixin.scala
+++
b/daffodil-core/src/main/scala/org/apache/daffodil/grammar/ChoiceGrammarMixin.scala
@@ -30,7 +30,7 @@ trait ChoiceGrammarMixin
extends GrammarMixin
with ChoiceTermRuntime1Mixin { self: ChoiceTermBase =>
- override lazy val groupContent = prod("choiceContent") {
+ override lazy val groupContentDef = prod("choiceContent") {
ChoiceCombinator(this, alternatives)
}
diff --git
a/daffodil-core/src/main/scala/org/apache/daffodil/grammar/ElementBaseGrammarMixin.scala
b/daffodil-core/src/main/scala/org/apache/daffodil/grammar/ElementBaseGrammarMixin.scala
index 65be2eb..c80f26d 100644
---
a/daffodil-core/src/main/scala/org/apache/daffodil/grammar/ElementBaseGrammarMixin.scala
+++
b/daffodil-core/src/main/scala/org/apache/daffodil/grammar/ElementBaseGrammarMixin.scala
@@ -40,6 +40,12 @@ import org.apache.daffodil.xml.QName
import org.apache.daffodil.xml.XMLUtils
import org.apache.daffodil.dsom.ExpressionCompilers
import org.apache.daffodil.runtime1.ElementBaseRuntime1Mixin
+import org.apache.daffodil.dsom.SchemaSet
+import org.apache.daffodil.dsom.SharedFactory
+import org.apache.daffodil.grammar.primitives.ConvertTextBooleanPrim
+import org.apache.daffodil.grammar.primitives.LeadingSkipRegion
+import org.apache.daffodil.grammar.primitives.SimpleNilOrValue
+import org.apache.daffodil.grammar.primitives.LiteralValueNilOfSpecifiedLength
/////////////////////////////////////////////////////////////////
// Elements System
@@ -443,11 +449,25 @@ trait ElementBaseGrammarMixin
lazy val parsedValue = prod("parsedValue", isSimpleType) {
initiatorRegion ~
valueMTA ~
- captureLengthRegions(leftPadding, retrySimpleType(value), rightPadding ~
rightFill) ~
- terminatorRegion
+ sharedSimpleParsedValue
}
/**
+ * We share the schema compilation objects for the sharable part of simple
values
+ * which is the part inside the framing.
+ *
+ * This eliminates redundant computation when element references reuse
elements.
+ *
+ * It is critical that the 2nd argument to getShared is passed by name, so
not
+ * evaluated a second time when sharing opportunities are discovered (same
shareKey).
+ */
+ lazy val sharedSimpleParsedValue =
+ schemaSet.sharedSimpleValueFactory.getShared(
+ shareKey,
+ captureLengthRegions(leftPadding, retrySimpleType(value), rightPadding ~
rightFill) ~
+ terminatorRegion)
+
+ /**
* Wrapped around the simple value unparsers where the simple type value is
* dynamic (e.g. prefixed length, dfdl:outputValueCalc). The correct parser
* will be inserted to retry until the simple type value is available.
@@ -597,7 +617,6 @@ trait ElementBaseGrammarMixin
notYetImplemented("objectKind='chars'")
}
-
private lazy val textNumber = textStandardNumber || textZonedNumber
private lazy val textNonNumber = {
@@ -607,7 +626,7 @@ trait ElementBaseGrammarMixin
private lazy val textStandardNumber = prod("textStandardNumber",
textNumberRep == TextNumberRep.Standard) {
val converter = textStandardBaseDefaulted match {
case 10 => textConverter
- case 2 | 8 | 16 => textStandardNonBaseTenConverter
+ case 2 | 8 | 16 => textStandardNonBaseTenConverter
case _ => Assert.impossible()
}
ConvertTextCombinator(this, stringValue, converter)
@@ -657,7 +676,6 @@ trait ElementBaseGrammarMixin
ConvertZonedCombinator(this, new BCDIntegerPrefixedLength(this),
textConverter)
}
-
private lazy val ibm4690PackedKnownLengthCalendar =
prod("ibm4690PackedKnownLengthCalendar", binaryCalendarRep ==
BinaryCalendarRep.Ibm4690Packed) {
ConvertZonedCombinator(this, new IBM4690PackedIntegerKnownLength(this,
false, binaryNumberKnownLengthInBits), textConverter)
}
@@ -671,7 +689,6 @@ trait ElementBaseGrammarMixin
ConvertZonedCombinator(this, new IBM4690PackedIntegerPrefixedLength(this,
false), textConverter)
}
-
private lazy val packedKnownLengthCalendar =
prod("packedKnownLengthCalendar", binaryCalendarRep ==
BinaryCalendarRep.Packed) {
ConvertZonedCombinator(this, new PackedIntegerKnownLength(this, false,
packedSignCodes, binaryNumberKnownLengthInBits), textConverter)
}
@@ -685,7 +702,6 @@ trait ElementBaseGrammarMixin
ConvertZonedCombinator(this, new PackedIntegerPrefixedLength(this, false,
packedSignCodes), textConverter)
}
-
def primType: PrimType
protected final lazy val value = prod("value", isSimpleType) {
@@ -925,10 +941,24 @@ trait ElementBaseGrammarMixin
private lazy val nilLit = prod("nilLit", isNilLit) {
nilElementInitiator ~
nilLitMTA ~
- nilLitSimpleOrComplex ~
- nilElementTerminator
+ sharedNilLit
}
+ /**
+ * We share the schema compilation objects for the sharable part of nil
literals
+ * which is the part inside the framing.
+ *
+ * This eliminates redundant computation when element references reuse
elements.
+ *
+ * It is critical that the 2nd argument to getShared is passed by name, so
not
+ * evaluated a second time when sharing opportunities are discovered (same
shareKey).
+ */
+ private lazy val sharedNilLit: Gram =
+ schemaSet.sharedNilLitFactory.getShared(
+ shareKey,
+ nilLitSimpleOrComplex ~
+ nilElementTerminator)
+
private lazy val nilLitSimpleOrComplex = prod("nilLitSimpleOrComplex") {
nilLitSimple || nilLitComplex }
private lazy val nilLitSimple = prod("nilLitSimple", isSimpleType) {
@@ -1072,13 +1102,26 @@ trait ElementBaseGrammarMixin
}
private lazy val complexContentSpecifiedLength =
prod("complexContentSpecifiedLength", isComplexType) {
- initiatorRegion ~
+ initiatorRegion ~ sharedComplexContentRegion
+ }
+
+ /**
+ * We share the schema compilation objects for the sharable part of complex
elements
+ * which is the part inside the start framing.
+ *
+ * This eliminates redundant computation when element references reuse
elements.
+ *
+ * It is critical that the 2nd argument to getShared is passed by name, so
not
+ * evaluated a second time when sharing opportunities are discovered (same
shareKey).
+ */
+ private lazy val sharedComplexContentRegion: Gram =
+ schemaSet.sharedComplexContentFactory.getShared(
+ shareKey,
captureLengthRegions(
EmptyGram,
specifiedLength(complexContent),
elementUnused) ~
- terminatorRegion
- }
+ terminatorRegion)
private lazy val scalarComplexContent = prod("scalarComplexContent",
isComplexType) {
if (!nilLit.isEmpty) {
diff --git
a/daffodil-core/src/main/scala/org/apache/daffodil/grammar/ModelGroupGrammarMixin.scala
b/daffodil-core/src/main/scala/org/apache/daffodil/grammar/ModelGroupGrammarMixin.scala
index 739b8c7..b1540e1 100644
---
a/daffodil-core/src/main/scala/org/apache/daffodil/grammar/ModelGroupGrammarMixin.scala
+++
b/daffodil-core/src/main/scala/org/apache/daffodil/grammar/ModelGroupGrammarMixin.scala
@@ -20,6 +20,7 @@ package org.apache.daffodil.grammar
import org.apache.daffodil.schema.annotation.props.gen._
import org.apache.daffodil.dsom.InitiatedTerminatedMixin
import org.apache.daffodil.dsom.ModelGroup
+import org.apache.daffodil.dsom.SharedFactory
import org.apache.daffodil.grammar.primitives.TrailingSkipRegion
import org.apache.daffodil.grammar.primitives.LeadingSkipRegion
import org.apache.daffodil.grammar.primitives.AlignmentFill
@@ -30,6 +31,7 @@ import org.apache.daffodil.dsom.ChoiceTermBase
import org.apache.daffodil.grammar.primitives.DelimiterStackCombinatorChoice
import org.apache.daffodil.runtime1.ModelGroupRuntime1Mixin
import org.apache.daffodil.grammar.primitives.LeadingSkipRegion
+import org.apache.daffodil.dsom.SchemaSet
trait ModelGroupGrammarMixin
extends InitiatedTerminatedMixin
@@ -45,10 +47,10 @@ trait ModelGroupGrammarMixin
private lazy val groupRightFraming = prod("groupRightFraming") {
TrailingSkipRegion(this) }
final override lazy val termContentBody = prod("termContentBody") {
- dfdlStatementEvaluations ~ groupLeftFraming ~ _content ~ groupRightFraming
+ dfdlStatementEvaluations ~ groupLeftFraming ~
groupContentWithInitiatorTerminator ~ groupRightFraming
}
- private lazy val _content = prod("_content") {
+ private lazy val groupContentWithInitiatorTerminator =
prod("groupContentWithInitiatorTerminator") {
val finalContent =
if (hasDelimiters ||
enclosingTerm.map(_.hasDelimiters).getOrElse(false) //
@@ -66,10 +68,31 @@ trait ModelGroupGrammarMixin
case c: ChoiceTermBase => DelimiterStackCombinatorChoice(c, content)
case s: SequenceTermBase => DelimiterStackCombinatorSequence(s,
content)
}
- } else { groupContent }
+ } else { groupContentDef }
finalContent
}
- protected def groupContent: Gram
+ /**
+ * groupContent is shared for all groups across all uses of the group
+ * that have the same shareKey. (e.g., by
+ * multiple group references if they have common properties).
+ * This eliminates redundant computation of the grammar
+ * structures and any runtime objects subsequently created.
+ *
+ * The framing and initiator/terminator are not shared, they surround the
shared part.
+ *
+ * Subclasses define the group content by way of the protected
groupContentDef override.
+ *
+ * It is crucial to efficiency (avoiding redundant computation) that the 2nd
argument
+ * to getShared is passed by name, not evaluated unless necessary.
+ */
+ private lazy val groupContent =
schemaSet.sharedGroupContentsFactory.getShared(shareKey, groupContentDef)
+
+ /**
+ * Override to define the actual group content.
+ *
+ * Must be overridden as a lazy val to avoid redundant computation.
+ */
+ protected def groupContentDef: Gram
}
diff --git
a/daffodil-core/src/main/scala/org/apache/daffodil/grammar/SequenceGrammarMixin.scala
b/daffodil-core/src/main/scala/org/apache/daffodil/grammar/SequenceGrammarMixin.scala
index 330564d..e30a12d 100644
---
a/daffodil-core/src/main/scala/org/apache/daffodil/grammar/SequenceGrammarMixin.scala
+++
b/daffodil-core/src/main/scala/org/apache/daffodil/grammar/SequenceGrammarMixin.scala
@@ -29,7 +29,7 @@ trait SequenceGrammarMixin
extends GrammarMixin
with SequenceTermRuntime1Mixin { self: SequenceTermBase =>
- final override lazy val groupContent = prod("groupContent") {
+ final override lazy val groupContentDef = prod("groupContentDef") {
if (isLayered) layerContent
else sequenceContent
}
diff --git
a/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/ElementBaseRuntime1Mixin.scala
b/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/ElementBaseRuntime1Mixin.scala
index b174ef5..caccc4b 100644
---
a/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/ElementBaseRuntime1Mixin.scala
+++
b/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/ElementBaseRuntime1Mixin.scala
@@ -120,7 +120,7 @@ trait ElementBaseRuntime1Mixin { self: ElementBase =>
}
final override lazy val dpathCompileInfo = dpathElementCompileInfo
-
+
/**
* Just an abbrev. analogous to erd, trd, etc.
*/
@@ -130,7 +130,7 @@ trait ElementBaseRuntime1Mixin { self: ElementBase =>
* This is the compile info for this element term.
*/
lazy val dpathElementCompileInfo: DPathElementCompileInfo = {
- val ee = enclosingElement.toSeq
+ val ee = enclosingElements
lazy val parents = ee.map {
_.dpathElementCompileInfo
}
diff --git
a/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/TermRuntime1Mixin.scala
b/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/TermRuntime1Mixin.scala
index 2e65f14..b328337 100644
---
a/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/TermRuntime1Mixin.scala
+++
b/daffodil-core/src/main/scala/org/apache/daffodil/runtime1/TermRuntime1Mixin.scala
@@ -241,7 +241,7 @@ trait TermRuntime1Mixin { self: Term =>
//
// start after this term in the lexically enclosing sequence siblings
//
- val sibTerms = parentSeqDef.groupMembers.drop(position)
+ val sibTerms = parentSeqDef.groupMembersNotShared.drop(position)
//
// compute what is possible for each of them.
//
diff --git
a/daffodil-core/src/test/scala/org/apache/daffodil/dsom/TestDsomCompiler.scala
b/daffodil-core/src/test/scala/org/apache/daffodil/dsom/TestDsomCompiler.scala
index a57741f..dc76e7a 100644
---
a/daffodil-core/src/test/scala/org/apache/daffodil/dsom/TestDsomCompiler.scala
+++
b/daffodil-core/src/test/scala/org/apache/daffodil/dsom/TestDsomCompiler.scala
@@ -309,7 +309,7 @@ class TestDsomCompiler extends Logging {
val Seq(_: ElementRef, gr: GroupRef) = seq1.groupMembers
val cgd = gr.groupDef.asInstanceOf[GlobalChoiceGroupDef]
val cgr = cgd.groupRef.asInstanceOf[ChoiceGroupRef]
- val Seq(_, cd2: LocalElementDecl, rest @ _*) = cgd.groupMembers //
Children nodes of Choice-node, there are 3
+ val Seq(_, cd2: LocalElementDecl, rest @ _*) = cgd.groupMembersNotShared
// Children nodes of Choice-node, there are 3
// val Seq(a1: DFDLChoice) = ch1.annotationObjs // Obtain the annotation
object that is a child
// of the group node.
diff --git
a/daffodil-core/src/test/scala/org/apache/daffodil/dsom/TestInteriorAlignmentElimination.scala
b/daffodil-core/src/test/scala/org/apache/daffodil/dsom/TestInteriorAlignmentElimination.scala
new file mode 100644
index 0000000..efd0790
--- /dev/null
+++
b/daffodil-core/src/test/scala/org/apache/daffodil/dsom/TestInteriorAlignmentElimination.scala
@@ -0,0 +1,118 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.daffodil.dsom
+
+import scala.xml.{ XML, Utility, Node }
+import org.junit.Test
+import org.apache.daffodil.compiler._
+import org.apache.daffodil.Implicits._;
+import org.apache.daffodil.schema.annotation.props.gen.{ YesNo, TextNumberRep,
SeparatorPosition, Representation, OccursCountKind, NilKind, LengthKind,
ChoiceLengthKind, ByteOrder, BinaryNumberRep, AlignmentUnits }
+import org.apache.daffodil.schema.annotation.props.AlignmentType
+import org.apache.daffodil.util.{ Misc, Logging }
+import org.apache.daffodil.xml.XMLUtils
+import junit.framework.Assert._
+import org.apache.daffodil.api.Diagnostic
+import org.apache.daffodil.util._
+import org.junit.Test
+import org.apache.daffodil.schema.annotation.props.Found
+
+class TestInteriorAlignmentElimination extends Logging {
+
+ val xsd = XMLUtils.XSD_NAMESPACE
+ val dfdl = XMLUtils.dfdlAppinfoSource // XMLUtils.DFDL_NAMESPACE
+ val xsi = XMLUtils.XSI_NAMESPACE
+ val example = XMLUtils.EXAMPLE_NAMESPACE
+
+ /**
+ * This test illustrates a complex situation involving where
+ * interior alignment can be optimized out inappropriately by
+ * the schema compiler.
+ */
+ @Test def testInteriorAlign1(): Unit = {
+ val testSchema = SchemaUtils.dfdlTestSchema(
+ <xs:include
schemaLocation="org/apache/daffodil/xsd/DFDLGeneralFormat.dfdl.xsd"/>,
+ <dfdl:format ref="tns:GeneralFormat" representation="binary"
lengthKind="implicit" lengthUnits="bytes" alignmentUnits="bytes"/>,
+ <xs:element name="r" dfdl:lengthKind="implicit">
+ <xs:complexType>
+ <xs:sequence>
+ <xs:element name="int3" type="xs:int" dfdl:lengthKind="explicit"
dfdl:length="3"/>
+ <xs:element ref="ex:e1"/>
+ <xs:element name="int1" type="xs:int" dfdl:lengthKind="explicit"
dfdl:length="1"/>
+ <xs:element ref="ex:e2"/>
+ </xs:sequence>
+ </xs:complexType>
+ </xs:element>
+ <xs:element name="e1" dfdl:lengthKind="implicit">
+ <xs:complexType>
+ <xs:sequence>
+ <xs:element ref="ex:eShared"/>
+ </xs:sequence>
+ </xs:complexType>
+ </xs:element>
+ <xs:element name="e2" dfdl:lengthKind="implicit">
+ <xs:complexType>
+ <xs:sequence>
+ >
+ <xs:element ref="ex:eShared"/>
+ </xs:sequence>
+ </xs:complexType>
+ </xs:element>
+ <xs:element name="eShared" dfdl:lengthKind="implicit">
+ <xs:complexType>
+ <xs:sequence>
+ <xs:element name="int1" type="xs:int" dfdl:lengthKind="explicit"
dfdl:length="1"/>
+ <!--
+ Schema compilation will compile eShared once.
+
+ Depending on the amount of knowledge the schema compiler keeps
about alignment, it
+ may know that eShared starts at bit 24 the first time, and it
will then know that after the first
+ int1, the alignment is now 4-byte, meeting the requirements for
the 'aligned' field.
+
+ Hence, it will optimize out the alignmentFill parser that would
skip some bytes to achieve
+ the 4 byte alignment.
+
+ The second occurrence of eShared is on a 9 byte boundary. After
int1 it is on a 10-byte boundary
+ and needs 2-bytes of alignmentFill region in order to parse the
aligned element.
+
+ But compilation happens only once, and so if the alignment
region is optimized out; so we
+ won't get the 2-bytes of alignment, and the parser will fail to
parse properly.
+
+ Correct compilation will not assume it knows the 32 bits are
satisfied the first time
+ around, and will assume it only knows of 8-bit alignment, so it
will keep AlignmentFill,
+ and this test will pass.
+ -->
+ <xs:element name="aligned" type="xs:int" dfdl:alignment="4"
dfdl:lengthKind="explicit" dfdl:length="4"/>
+ </xs:sequence>
+ </xs:complexType>
+ </xs:element>,
+ elementFormDefault = "unqualified")
+ //
+ // in the below hex string, it is the 6666 that is the alignment fill.
+ //
+ val (_, actual) = TestUtils.testBinary(testSchema,
"00000102000000030405666600000007", areTracing = false)
+ val expected =
+ <ex:r>
+ <int3>1</int3>
+
<ex:e1><ex:eShared><int1>2</int1><aligned>3</aligned></ex:eShared></ex:e1>
+ <int1>4</int1>
+
<ex:e2><ex:eShared><int1>5</int1><aligned>7</aligned></ex:eShared></ex:e2>
+ </ex:r>
+ TestUtils.assertEqualsXMLElements(expected, actual)
+ }
+
+}
diff --git
a/daffodil-core/src/test/scala/org/apache/daffodil/dsom/TestPolymorphicUpwardRelativeExpressions.scala
b/daffodil-core/src/test/scala/org/apache/daffodil/dsom/TestPolymorphicUpwardRelativeExpressions.scala
new file mode 100644
index 0000000..69a1134
--- /dev/null
+++
b/daffodil-core/src/test/scala/org/apache/daffodil/dsom/TestPolymorphicUpwardRelativeExpressions.scala
@@ -0,0 +1,273 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.daffodil.dsom
+
+import scala.xml.{ XML, Utility, Node }
+import org.junit.Test
+import org.apache.daffodil.compiler._
+import org.apache.daffodil.Implicits._;
+import org.apache.daffodil.schema.annotation.props.gen.{ YesNo, TextNumberRep,
SeparatorPosition, Representation, OccursCountKind, NilKind, LengthKind,
ChoiceLengthKind, ByteOrder, BinaryNumberRep, AlignmentUnits }
+import org.apache.daffodil.schema.annotation.props.AlignmentType
+import org.apache.daffodil.util.{ Misc, Logging }
+import org.apache.daffodil.xml.XMLUtils
+import junit.framework.Assert._
+import org.apache.daffodil.api.Diagnostic
+import org.apache.daffodil.util._
+import org.junit.Test
+import org.apache.daffodil.schema.annotation.props.Found
+
+class TestPolymorphicUpwardRelativeExpressions extends Logging {
+
+ val xsd = XMLUtils.XSD_NAMESPACE
+ val dfdl = XMLUtils.dfdlAppinfoSource // XMLUtils.DFDL_NAMESPACE
+ val xsi = XMLUtils.XSI_NAMESPACE
+ val example = XMLUtils.EXAMPLE_NAMESPACE
+
+ /**
+ * This test illustrates that even in the presence of compiling an expression
+ * only once, we detect all places that polymorphic references look upward
to, and
+ * check that all such are consistently typed.
+ */
+ @Test def testPoly1(): Unit = {
+ val testSchema = SchemaUtils.dfdlTestSchema(
+ <xs:include
schemaLocation="org/apache/daffodil/xsd/DFDLGeneralFormat.dfdl.xsd"/>,
+ <dfdl:format ref="tns:GeneralFormat" lengthKind="delimited"/>,
+ <xs:element name="r" dfdl:lengthKind="implicit">
+ <xs:complexType>
+ <xs:sequence>
+ <xs:element name="selector" type="xs:string" dfdl:terminator=";"/>
+ <xs:choice>
+ <xs:element ref="ex:e1"/>
+ <xs:element ref="ex:e2"/>
+ </xs:choice>
+ </xs:sequence>
+ </xs:complexType>
+ </xs:element>
+ <xs:element name="e1" dfdl:lengthKind="implicit">
+ <xs:complexType>
+ <xs:sequence>
+ <xs:sequence>
+ <xs:annotation>
+ <xs:appinfo source="http://www.ogf.org/dfdl/">
+ <dfdl:discriminator test="{ ../selector eq 'e1' }"/>
+ </xs:appinfo>
+ </xs:annotation>
+ </xs:sequence>
+ <xs:element name="poly" type="xs:int" dfdl:terminator=";">
+ <!--
+ This element ex:r/ex:e1/poly is instance 1. An int.
+ -->
+ </xs:element>
+ <xs:element ref="ex:eShared"/>
+ </xs:sequence>
+ </xs:complexType>
+ </xs:element>
+ <xs:element name="e2" dfdl:lengthKind="implicit">
+ <xs:complexType>
+ <xs:sequence>
+ <xs:sequence>
+ <xs:annotation>
+ <xs:appinfo source="http://www.ogf.org/dfdl/">
+ <dfdl:discriminator test="{ ../selector eq 'e2' }"/>
+ </xs:appinfo>
+ </xs:annotation>
+ </xs:sequence>
+ <xs:element name="poly" type="xs:date" dfdl:terminator=";">
+ <!--
+ This element ex:r/ex:e2/poly is instance 2. A date.
+
+ It will have a separate ERD and DPathElementCompileInfo object
+ from the prior one.
+ -->
+ </xs:element>
+ <xs:element ref="ex:eShared"/>
+ </xs:sequence>
+ </xs:complexType>
+ </xs:element>
+ <xs:element name="eShared" dfdl:lengthKind="implicit">
+ <xs:complexType>
+ <xs:sequence>
+ <!--
+ This sequence's groupMembers will be compiled
+ once and shared.
+
+ So the expression inside eInner is compiled only once.
+ -->
+ <xs:element ref="ex:eInner"/>
+ </xs:sequence>
+ </xs:complexType>
+ </xs:element>
+ <xs:element name="eInner" dfdl:lengthKind="implicit">
+ <!--
+ So since eInner appears only inside eShared's sequence, it will
+ be compiled once.
+
+ That means the assert and its expression is compiled once.
+
+ When that expression is compiled, both e1/poly and e2/poly must be
checked.
+ -->
+ <xs:complexType>
+ <xs:sequence>
+ <xs:sequence>
+ <xs:annotation>
+ <xs:appinfo source="http://www.ogf.org/dfdl/">
+ <dfdl:assert test="{ ../../poly eq 5 }">
+ <!--
+ This expression referring to poly could be referring
+ to either ex:r/ex:e1/poly or to ex:r/ex:e2/poly
+
+ Since e2/poly is an xs:date, the expression ../../poly
eq 5
+ isn't even type-checked properly.
+
+ This should get a schema-compile-time type-error.
+ -->
+ </dfdl:assert>
+ </xs:appinfo>
+ </xs:annotation>
+ </xs:sequence>
+ <xs:element name="foo" type="xs:int" dfdl:terminator=";"/>
+ </xs:sequence>
+ </xs:complexType>
+ </xs:element>,
+ elementFormDefault = "unqualified")
+ val e = intercept[Exception] {
+ TestUtils.testString(testSchema, "e2;1961-02-01;6;", areTracing = false)
+ }
+ val msg = e.getMessage()
+ val hasSDE = msg.contains("Schema Definition Error")
+ val hasPolyInt = msg.contains("../../poly with xs:int")
+ val hasPolyDate = msg.contains("../../poly with xs:date")
+ val hasDifferentPhrase = msg.contains("different types at different
points")
+ assertTrue(hasSDE)
+ assertTrue(hasPolyInt)
+ assertTrue(hasPolyDate)
+ assertTrue(hasDifferentPhrase)
+ }
+
+ @Test def testPoly2(): Unit = {
+ val testSchema = SchemaUtils.dfdlTestSchema(
+ <xs:include
schemaLocation="org/apache/daffodil/xsd/DFDLGeneralFormat.dfdl.xsd"/>,
+ <dfdl:format ref="tns:GeneralFormat" lengthKind="delimited"/>,
+ <xs:element name="r" dfdl:lengthKind="implicit">
+ <xs:complexType>
+ <xs:sequence>
+ <xs:element name="selector" type="xs:string" dfdl:terminator=";"/>
+ <xs:choice>
+ <xs:element ref="ex:e1"/>
+ <xs:element ref="ex:e2"/>
+ </xs:choice>
+ </xs:sequence>
+ </xs:complexType>
+ </xs:element>
+ <xs:element name="e1" dfdl:lengthKind="implicit">
+ <xs:complexType>
+ <xs:sequence>
+ <xs:sequence>
+ <xs:annotation>
+ <xs:appinfo source="http://www.ogf.org/dfdl/">
+ <dfdl:discriminator test="{ ../selector eq 'e1' }"/>
+ </xs:appinfo>
+ </xs:annotation>
+ </xs:sequence>
+ <xs:element name="poly" type="xs:int" dfdl:terminator=";">
+ <!--
+ This element ex:r/ex:e1/poly is instance 1
+ -->
+ </xs:element>
+ <xs:element ref="ex:eShared"/>
+ </xs:sequence>
+ </xs:complexType>
+ </xs:element>
+ <xs:element name="e2" dfdl:lengthKind="implicit">
+ <xs:complexType>
+ <xs:sequence>
+ <xs:sequence>
+ <xs:annotation>
+ <xs:appinfo source="http://www.ogf.org/dfdl/">
+ <dfdl:discriminator test="{ ../selector eq 'e2' }"/>
+ </xs:appinfo>
+ </xs:annotation>
+ </xs:sequence>
+ <xs:element name="poly" type="xs:int" dfdl:terminator=";">
+ <!--
+ This element ex:r/ex:e2/poly is instance 2
+ It will have a separate ERD and DPathElementCompileInfo object
+ from the prior one.
+ -->
+ </xs:element>
+ <xs:element ref="ex:eShared"/>
+ </xs:sequence>
+ </xs:complexType>
+ </xs:element>
+ <xs:element name="eShared" dfdl:lengthKind="implicit">
+ <xs:complexType>
+ <xs:sequence>
+ <!--
+ This sequence's groupMembers will be compiled
+ once and shared.
+ -->
+ <xs:element ref="ex:eInner"/>
+ </xs:sequence>
+ </xs:complexType>
+ </xs:element>
+ <xs:element name="eInner" dfdl:lengthKind="implicit">
+ <!--
+ So since eInner appears only inside eShared's sequence, it will
+ be compiled once.
+
+ That means the assert and its expression is compiled once.
+
+ However, both e1/poly and e2/poly which are separate elements with
+ separate ERDs and separate DPathElementCompileInfo objects, are both
+ being marked as referenced from expressions.
+ -->
+ <xs:complexType>
+ <xs:sequence>
+ <xs:sequence>
+ <xs:annotation>
+ <xs:appinfo source="http://www.ogf.org/dfdl/">
+ <dfdl:assert test="{ ../../poly eq 5 }">
+ <!--
+ This expression referring to poly could be referring
+ to either ex:r/ex:e1/poly or to ex:r/ex:e2/poly
+ -->
+ </dfdl:assert>
+ </xs:appinfo>
+ </xs:annotation>
+ </xs:sequence>
+ <xs:element name="foo" type="xs:int" dfdl:terminator=";"/>
+ </xs:sequence>
+ </xs:complexType>
+ </xs:element>,
+ elementFormDefault = "unqualified")
+ val (_, actual) = TestUtils.testString(testSchema, "e2;5;6;", areTracing =
false)
+ val expected =
+ <ex:r>
+ <selector>e2</selector>
+ <ex:e2>
+ <poly>5</poly>
+ <ex:eShared>
+ <ex:eInner>
+ <foo>6</foo>
+ </ex:eInner>
+ </ex:eShared>
+ </ex:e2>
+ </ex:r>
+ TestUtils.assertEqualsXMLElements(expected, actual)
+ }
+}
diff --git
a/daffodil-core/src/test/scala/org/apache/daffodil/dsom/TestRefMap.scala
b/daffodil-core/src/test/scala/org/apache/daffodil/dsom/TestRefMap.scala
index d003fcf..62db3c0 100644
--- a/daffodil-core/src/test/scala/org/apache/daffodil/dsom/TestRefMap.scala
+++ b/daffodil-core/src/test/scala/org/apache/daffodil/dsom/TestRefMap.scala
@@ -249,7 +249,7 @@ class TestRefMap extends Logging {
val sset = compiler.compileNode(testSchema).sset
val root = sset.root
val comps = root.allComponents
- assertEquals(25, comps.length)
+ assertEquals(13, comps.length)
val refMap = root.refMap
val numEntries = refMap.size
assertEquals(4, numEntries)
@@ -262,7 +262,7 @@ class TestRefMap extends Logging {
assertEquals(2, allERefsCount)
val allCTRefsCount = root.allCTRefs.size
assertEquals(1, allCTRefsCount)
- assertEquals(25, root.numComponents)
+ assertEquals(13, root.numComponents)
assertEquals(11, root.numUniqueComponents)
}
@@ -303,14 +303,14 @@ class TestRefMap extends Logging {
val sset = compiler.compileNode(testSchema).sset
val root = sset.root
val comps = root.allComponents
- assertEquals(57, comps.length)
+ assertEquals(19, comps.length)
val refMap = root.refMap
val numEntries = refMap.size
assertEquals(5, numEntries)
val refPairsMap = root.refPairsMap.toSeq
val numRefPairs = refPairsMap.map { _._2.length }.sum
assertEquals(8, numRefPairs)
- assertEquals(57, root.numComponents)
+ assertEquals(19, root.numComponents)
assertEquals(15, root.numUniqueComponents)
}
@@ -354,14 +354,14 @@ class TestRefMap extends Logging {
val sset = compiler.compileNode(testSchema).sset
val root = sset.root
val comps = root.allComponents
- assertEquals(61, comps.length)
+ assertEquals(23, comps.length)
val refMap = root.refMap
val numEntries = refMap.size
assertEquals(4, numEntries)
val refPairsMap = root.refPairsMap.toSeq
val numRefPairs = refPairsMap.map { _._2.length }.sum
assertEquals(7, numRefPairs)
- assertEquals(61, root.numComponents)
+ assertEquals(23, root.numComponents)
assertEquals(17, root.numUniqueComponents)
}
@@ -413,14 +413,14 @@ class TestRefMap extends Logging {
val sset = compiler.compileNode(testSchema).sset
val root = sset.root
val comps = root.allComponents
- assertEquals(125, comps.length)
+ assertEquals(29, comps.length)
val refMap = root.refMap
val numEntries = refMap.size
assertEquals(5, numEntries)
val refPairsMap = root.refPairsMap.toSeq
val numRefPairs = refPairsMap.map { _._2.length }.sum
assertEquals(9, numRefPairs)
- assertEquals(125, root.numComponents)
+ assertEquals(29, root.numComponents)
assertEquals(21, root.numUniqueComponents)
}
@@ -480,14 +480,14 @@ class TestRefMap extends Logging {
val sset = compiler.compileNode(testSchema).sset
val root = sset.root
val comps = root.allComponents
- assertEquals(253, comps.length)
+ assertEquals(35, comps.length)
val refMap = root.refMap
val numEntries = refMap.size
assertEquals(6, numEntries)
val refPairsMap = root.refPairsMap.toSeq
val numRefPairs = refPairsMap.map { _._2.length }.sum
assertEquals(11, numRefPairs)
- assertEquals(253, root.numComponents)
+ assertEquals(35, root.numComponents)
assertEquals(25, root.numUniqueComponents)
}
@@ -555,14 +555,14 @@ class TestRefMap extends Logging {
val sset = compiler.compileNode(testSchema).sset
val root = sset.root
val comps = root.allComponents
- assertEquals(509, comps.length)
+ assertEquals(41, comps.length)
val refMap = root.refMap
val numEntries = refMap.size
assertEquals(7, numEntries)
val refPairsMap = root.refPairsMap.toSeq
val numRefPairs = refPairsMap.map { _._2.length }.sum
assertEquals(13, numRefPairs)
- assertEquals(509, root.numComponents)
+ assertEquals(41, root.numComponents)
assertEquals(29, root.numUniqueComponents)
}
@@ -638,14 +638,14 @@ class TestRefMap extends Logging {
val sset = compiler.compileNode(testSchema).sset
val root = sset.root
val comps = root.allComponents
- assertEquals(1021, comps.length)
+ assertEquals(47, comps.length)
val refMap = root.refMap
val numEntries = refMap.size
assertEquals(8, numEntries)
val refPairsMap = root.refPairsMap.toSeq
val numRefPairs = refPairsMap.map { _._2.length }.sum
assertEquals(15, numRefPairs)
- assertEquals(1021, root.numComponents)
+ assertEquals(47, root.numComponents)
assertEquals(33, root.numUniqueComponents)
}
}
diff --git
a/daffodil-lib/src/main/scala/org/apache/daffodil/util/Memoize1.scala
b/daffodil-lib/src/main/scala/org/apache/daffodil/util/Memoize1.scala
deleted file mode 100644
index d0dc5d9..0000000
--- a/daffodil-lib/src/main/scala/org/apache/daffodil/util/Memoize1.scala
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.daffodil.util
-
-import scala.collection.mutable.Map
-
-/**
- * A memoizing funtion is one that keeps a cache of what arguments it
- * has already seen, and if it sees the argument again, it does not recompute
- * it just takes the answer from the cache. This saves computation time
- * if finding the object in the cache is substantially cheaper than computing
- * the value, and it is expected that the function will be called with the
- * same arguments repeatedly.
- *
- * This just adds overhead if the argument is unique every time.
- *
- * The other reason to memoize is if you want the result to be EQ equal for
equal or
- * equivalent arguments, so as to allow fast EQ comparisons elsewhere in your
- * system. Memoized functions will return the exact same object from the cache
- * every time.
- *
- * This 1-arg version of memoize can be composed using curried functions to
obtain
- * 2, 3, or more argument versions, i.e., that memorize all their arguments.
- *
- * See the unit tests for examples of how to do that.
- *
- * Note that the argument must be an AnyRef. This is to force avoidance of
- * passing primitive number types (which are AnyVal) that get boxed, causing
inefficient allocation.
- */
-class Memoize1[-T <: AnyRef, +R] private (f: T => R) extends (T => R) {
- private[this] var vals = Map.empty[T, R]
-
- override def apply(x: T): R = {
- val opt = vals.get(x)
- opt match {
- case Some(y) => y
- case None => {
- val y = f(x)
- vals.put(x, y)
- y
- }
- }
- }
-}
-
-object Memoize1 {
- def apply[T <: AnyRef, R](f: T => R) = new Memoize1(f).apply _
-}
diff --git
a/daffodil-lib/src/test/scala/org/apache/daffodil/util/TestMemoize1.scala
b/daffodil-lib/src/test/scala/org/apache/daffodil/util/TestMemoize1.scala
deleted file mode 100644
index 2dc3c01..0000000
--- a/daffodil-lib/src/test/scala/org/apache/daffodil/util/TestMemoize1.scala
+++ /dev/null
@@ -1,138 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.daffodil.util
-
-import org.junit.Test
-import org.junit.Assert._
-
-class TestMemoize1 {
-
- @Test def testMemoize1() {
- var counter = 0
-
- def dontCallMeTwice(x: String): String = {
- counter = counter + 1
- x.map { x => x.toUpper }
- }
-
- val memoDontCallMeTwice = Memoize1(dontCallMeTwice)
-
- assertEquals("FOO", memoDontCallMeTwice("foo"))
- assertEquals("FOO", memoDontCallMeTwice("foo"))
- assertEquals("BAR", memoDontCallMeTwice("bar"))
- assertEquals("BAR", memoDontCallMeTwice("bar"))
- assertEquals("QUUX", memoDontCallMeTwice("quux"))
- assertEquals(3, counter)
- }
-
- /*
- * This next test illustrates that you can compose multiple uses of Memoize1
to get a
- * multi-arg version of it.
- */
-
- /** So we can see how many times our function gets called. */
- var counter = 0
-
- /**
- * This is the function we wish to use Memoizing with.
- *
- * We rename it to put suffix "_notMemoized" and make private
- */
- private def threeArgFunctionExpensiveToCompute_notMemoized(x: String, y:
String, z: String) = {
- counter = counter + 1
- ((x + y + z).toString, ()) // imagine this is expensive to compute, or we
need exact eq object back each time
- }
-
- /**
- * Memoized version of the function. This is val because we want to
- * construct this thing exactly once.
- *
- * When this is applied, because of the way this is nested, no tuples should
- * have to be allocated per call. The allocating happens as the cache is
- * built.
- */
- private val memoDontCallMeTwiceSameArgs = {
- Memoize1((x: String) =>
- Memoize1((y: String) =>
- Memoize1((z: String) =>
- threeArgFunctionExpensiveToCompute_notMemoized(x, y, z))))
- }
-
- /**
- * Here's our function that we want people to call/use, ignoring that
- * memoizing is happening under the hood.
- */
- def threeArgFunction(x: String, y: String, z: String) =
memoDontCallMeTwiceSameArgs(x)(y)(z)
-
- @Test def testMemoize3Arg(): Unit = {
- val res1 = threeArgFunction("foo", "bar", "baz")
- val res2 = threeArgFunction("quux", "flux", "pux")
- assertEquals(2, counter)
- assertEquals(("foobarbaz", ()), res1)
- assertEquals(("quuxfluxpux", ()), res2)
- val res3 = threeArgFunction("foo", "bar", "baz")
- val res4 = threeArgFunction("quux", "flux", "pux")
- assertEquals(2, counter) // no more calls happened.
- assertEquals(("foobarbaz", ()), res3)
- assertEquals(("quuxfluxpux", ()), res4)
- assertTrue(res1 eq res3) // results are EQ, not just equal.
- assertTrue(res2 eq res4)
- }
-
- /**
- * This test shows that memoized nests are efficient, i.e., calling them
- * isn't going to allocate tuples or closures or anything like that, because
- * the objects being returned are EQ - including the intermediate function
- * objects.
- *
- * These tests use Strings as the argument types, because if you used Int or
- * other primitive number types, then Boxed numbers are going to get
allocated
- * for looking things up in the hash tables that Memoize1 is based on.
- *
- * So if you converted this into something for profiling, you'd see lots of
allocating.
- * To avoid the allocation, always pass an AnyRef to this.
- */
- @Test def testMemoize3ArgNoAllocations(): Unit = {
- val m1 = memoDontCallMeTwiceSameArgs("first")
- val m2 = memoDontCallMeTwiceSameArgs("first")
- // m1 is a function object that is closed over the value "first" for x.
- assertTrue(m1 eq m2) // exact same object
- val m3 = m1("second")
- val m4 = m1("second")
- // m2 is a function object that is closed over the value "first" for x,
and "second" for y.
- assertTrue(m3 eq m4)
- val m5 = m3("third")
- val m6 = m3("third")
- assertTrue(m5 eq m6)
- assertEquals(("firstsecondthird", ()), m5)
- }
-
- /**
- * This test (if you uncomment it), shows that passing a
- * primitive number type causes a scala compilation error.
- */
- // @Test def testUsePrimitiveNumberArg(): Unit = {
- //
- // val mf = Memoize1((x: Int) => x + x)
- // val mf2 = Memoize1[Int, String]((x: Int) => x.toString)
- //
- // mf(5)
- // mf2(10)
- // }
-
-}
diff --git
a/daffodil-runtime1/src/main/scala/org/apache/daffodil/dsom/CompiledExpression1.scala
b/daffodil-runtime1/src/main/scala/org/apache/daffodil/dsom/CompiledExpression1.scala
index 173e1cc..da9c5a4 100644
---
a/daffodil-runtime1/src/main/scala/org/apache/daffodil/dsom/CompiledExpression1.scala
+++
b/daffodil-runtime1/src/main/scala/org/apache/daffodil/dsom/CompiledExpression1.scala
@@ -72,7 +72,7 @@ abstract class CompiledExpression[+T <: AnyRef](
extends ContentValueReferencedElementInfoMixin with Serializable {
DataValue.assertValueIsNotDataValue(valueForDebugPrinting)
-
+
final def toBriefXML(depth: Int = -1) = {
"'" + prettyExpr + "'"
}
@@ -201,7 +201,7 @@ final case class ConstantExpression[+T <: AnyRef](
* into "passes".
*/
class DPathCompileInfo(
- @TransientParam parentsArg: => Seq[DPathCompileInfo],
+ @TransientParam parentsArg: Seq[DPathCompileInfo],
@TransientParam variableMapArg: => VariableMap,
val namespaces: scala.xml.NamespaceBinding,
val path: String,
@@ -282,7 +282,7 @@ class DPathCompileInfo(
* structures are created which reference these.
*/
class DPathElementCompileInfo(
- @TransientParam parentsArg: => Seq[DPathElementCompileInfo],
+ @TransientParam parentsArg: Seq[DPathElementCompileInfo],
@TransientParam variableMap: => VariableMap,
@TransientParam elementChildrenCompileInfoArg: =>
Seq[DPathElementCompileInfo],
namespaces: scala.xml.NamespaceBinding,