You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@daffodil.apache.org by ja...@apache.org on 2021/03/15 14:22:29 UTC
[daffodil] branch master updated: Implement variable direction
property
This is an automated email from the ASF dual-hosted git repository.
jadams pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/daffodil.git
The following commit(s) were added to refs/heads/master by this push:
new f1603a0 Implement variable direction property
f1603a0 is described below
commit f1603a0da8c9610f7ade79250315a2b5db333db2
Author: Josh Adams <ja...@tresys.com>
AuthorDate: Wed Dec 9 10:47:29 2020 -0500
Implement variable direction property
This adds a 'direction' property to defineVariable statements with the
following options:
- both (default)
- parseOnly
- unparseOnly
Current behavior is to create NadaParser/Unparser's for setVariable and
newVariableInstance statements when we are processing in the opposite
direction of the direction property while attempting to read a variable
while going the opposite direction will trigger a warning.
This commit also addresses several issues involving how
newVariableInstances are handled during unparsing suspensions.
DAFFODIL-2429
---
.../apache/daffodil/dsom/DFDLDefineVariable.scala | 10 +-
.../grammar/primitives/ElementCombinator.scala | 4 +-
.../grammar/primitives/PrimitivesExpressions.scala | 48 +-
.../externalvars/TestExternalVariablesLoader.scala | 4 +-
.../org/apache/daffodil/xsd/DFDL_part3_model.xsd | 1 +
.../resources/org/apache/daffodil/xsd/dfdlx.xsd | 9 +
.../processors/unparsers/ElementUnparser.scala | 4 +-
.../unparsers/ExpressionEvaluatingUnparsers.scala | 36 +-
.../daffodil/debugger/InteractiveDebugger.scala | 1 +
.../org/apache/daffodil/dpath/DPathRuntime.scala | 3 +-
.../apache/daffodil/processors/RuntimeData.scala | 6 +-
.../daffodil/processors/SuspendableOperation.scala | 2 +-
.../apache/daffodil/processors/VariableMap1.scala | 132 ++++--
.../parsers/ExpressionEvaluatingParsers.scala | 5 +
.../daffodil/processors/parsers/PState.scala | 2 +-
.../daffodil/processors/unparsers/UState.scala | 12 +-
.../daffodil/section07/variables/variables.tdml | 524 ++++++++++++++++++++-
.../section07/variables/TestVariables.scala | 12 +
18 files changed, 718 insertions(+), 97 deletions(-)
diff --git a/daffodil-core/src/main/scala/org/apache/daffodil/dsom/DFDLDefineVariable.scala b/daffodil-core/src/main/scala/org/apache/daffodil/dsom/DFDLDefineVariable.scala
index 41d7b97..e51bde1 100644
--- a/daffodil-core/src/main/scala/org/apache/daffodil/dsom/DFDLDefineVariable.scala
+++ b/daffodil-core/src/main/scala/org/apache/daffodil/dsom/DFDLDefineVariable.scala
@@ -28,6 +28,7 @@ import org.apache.daffodil.dpath.NodeInfo.PrimType
import org.apache.daffodil.util.Maybe
import org.apache.daffodil.grammar.primitives.{ SetVariable, NewVariableInstanceStart, NewVariableInstanceEnd }
import org.apache.daffodil.schema.annotation.props.Found
+import org.apache.daffodil.schema.annotation.props.gen.VariableDirection
class DFDLDefineVariable(node: Node, doc: SchemaDocument)
extends DFDLDefiningAnnotation(node, doc) {
@@ -40,6 +41,11 @@ class DFDLDefineVariable(node: Node, doc: SchemaDocument)
final lazy val external = getAttributeOption("external").map { _.toBoolean }.getOrElse(false)
+ final lazy val direction = {
+ val directionStr = getAttributeOption(XMLUtils.DFDLX_NAMESPACE, "direction").getOrElse("both")
+ VariableDirection(directionStr, this)
+ }
+
private lazy val defaultValueAsAttribute = getAttributeOption("defaultValue")
private lazy val defaultValueAsElement = node.child.text.trim
@@ -83,6 +89,7 @@ class DFDLDefineVariable(node: Node, doc: SchemaDocument)
this.path,
this.namespaces,
this.external,
+ this.direction,
maybeDefaultValueExpr,
this.typeQName,
this.namedQName.asInstanceOf[GlobalQName],
@@ -116,7 +123,7 @@ final class DFDLNewVariableInstance(node: Node, decl: AnnotatedSchemaComponent)
private lazy val attrValue = getAttributeOption("value")
- private lazy val <dfdl:setVariable>{ eltChildren @ _* }</dfdl:setVariable> = node
+ private lazy val <dfdl:newVariableInstance>{ eltChildren @ _* }</dfdl:newVariableInstance> = node
private lazy val eltValue = eltChildren.text.trim
@@ -159,6 +166,7 @@ final class DFDLNewVariableInstance(node: Node, decl: AnnotatedSchemaComponent)
this.path,
this.namespaces,
defv.external,
+ defv.direction,
maybeDefaultValueExpr,
defv.typeQName,
defv.namedQName.asInstanceOf[GlobalQName],
diff --git a/daffodil-core/src/main/scala/org/apache/daffodil/grammar/primitives/ElementCombinator.scala b/daffodil-core/src/main/scala/org/apache/daffodil/grammar/primitives/ElementCombinator.scala
index fd50d50..3918f7f 100644
--- a/daffodil-core/src/main/scala/org/apache/daffodil/grammar/primitives/ElementCombinator.scala
+++ b/daffodil-core/src/main/scala/org/apache/daffodil/grammar/primitives/ElementCombinator.scala
@@ -90,7 +90,7 @@ class ElementCombinator(
subComb.parser
}
- private lazy val uSetVars = context.setVariableStatements.map(_.gram(context).unparser).toArray
+ private lazy val uSetVars = context.setVariableStatements.map(_.gram(context).unparser).toArray.filterNot { _.isEmpty }
private lazy val eBeforeUnparser: Maybe[Unparser] =
if (eBeforeContent.isEmpty) Maybe.Nope
@@ -359,7 +359,7 @@ abstract class ElementCombinatorBase(context: ElementBase, eGramBefore: Gram, eG
}
}
lazy val patAssert = context.assertStatements.filter(_.testKind == TestKind.Pattern).map(_.gram(context).parser).toArray
- lazy val pSetVar = context.setVariableStatements.map(_.gram(context).parser).toArray
+ lazy val pSetVar = context.setVariableStatements.map(_.gram(context).parser).toArray.filterNot { _.isEmpty }
lazy val testDiscrim = {
val td = context.discriminatorStatements.filter(_.testKind == TestKind.Expression)
Assert.invariant(td.size <= 1)
diff --git a/daffodil-core/src/main/scala/org/apache/daffodil/grammar/primitives/PrimitivesExpressions.scala b/daffodil-core/src/main/scala/org/apache/daffodil/grammar/primitives/PrimitivesExpressions.scala
index fe5ec20..4c3075b 100644
--- a/daffodil-core/src/main/scala/org/apache/daffodil/grammar/primitives/PrimitivesExpressions.scala
+++ b/daffodil-core/src/main/scala/org/apache/daffodil/grammar/primitives/PrimitivesExpressions.scala
@@ -31,13 +31,16 @@ import org.apache.daffodil.dsom.ElementBase
import org.apache.daffodil.processors.parsers.NewVariableInstanceEndParser
import org.apache.daffodil.processors.parsers.SetVariableParser
import org.apache.daffodil.processors.parsers.IVCParser
+import org.apache.daffodil.processors.parsers.NadaParser
import org.apache.daffodil.processors.unparsers.SetVariableUnparser
import org.apache.daffodil.processors.unparsers.NewVariableInstanceEndUnparser
import org.apache.daffodil.processors.unparsers.NewVariableInstanceStartUnparser
+import org.apache.daffodil.processors.unparsers.NadaUnparser
import org.apache.daffodil.compiler.ForParser
import org.apache.daffodil.schema.annotation.props.PropertyLookupResult
import org.apache.daffodil.schema.annotation.props.Found
import org.apache.daffodil.schema.annotation.props.gen.FailureType
+import org.apache.daffodil.schema.annotation.props.gen.VariableDirection
import org.apache.daffodil.dsom.ExpressionCompilers
import org.apache.daffodil.dsom.DFDLSetVariable
import org.apache.daffodil.dsom.DFDLNewVariableInstance
@@ -179,8 +182,19 @@ case class SetVariable(stmt: DFDLSetVariable)
override lazy val nodeKind = stmt.defv.primType
- lazy val parser: DaffodilParser = new SetVariableParser(expr, stmt.defv.runtimeData)
- override lazy val unparser: DaffodilUnparser = new SetVariableUnparser(expr, stmt.defv.runtimeData, stmt.nonTermRuntimeData)
+ lazy val parser: DaffodilParser = {
+ if (stmt.defv.runtimeData.direction == VariableDirection.UnparseOnly)
+ new NadaParser(stmt.defv.runtimeData)
+ else
+ new SetVariableParser(expr, stmt.defv.runtimeData)
+ }
+
+ override lazy val unparser: DaffodilUnparser = {
+ if (stmt.defv.runtimeData.direction == VariableDirection.ParseOnly)
+ new NadaUnparser(stmt.defv.runtimeData)
+ else
+ new SetVariableUnparser(expr, stmt.defv.runtimeData, stmt.nonTermRuntimeData)
+ }
}
abstract class NewVariableInstanceBase(decl: AnnotatedSchemaComponent, stmt: DFDLNewVariableInstance)
@@ -190,15 +204,37 @@ abstract class NewVariableInstanceBase(decl: AnnotatedSchemaComponent, stmt: DFD
case class NewVariableInstanceStart(decl: AnnotatedSchemaComponent, stmt: DFDLNewVariableInstance)
extends NewVariableInstanceBase(decl, stmt) {
- lazy val parser: DaffodilParser = new NewVariableInstanceStartParser(stmt.variableRuntimeData)
- override lazy val unparser: DaffodilUnparser = new NewVariableInstanceStartUnparser(stmt.variableRuntimeData)
+ lazy val parser: DaffodilParser = {
+ if (stmt.defv.runtimeData.direction == VariableDirection.UnparseOnly)
+ new NadaParser(stmt.variableRuntimeData)
+ else
+ new NewVariableInstanceStartParser(stmt.variableRuntimeData)
+ }
+
+ override lazy val unparser: DaffodilUnparser = {
+ if (stmt.defv.runtimeData.direction == VariableDirection.ParseOnly)
+ new NadaUnparser(stmt.variableRuntimeData)
+ else
+ new NewVariableInstanceStartUnparser(stmt.variableRuntimeData)
+ }
}
case class NewVariableInstanceEnd(decl: AnnotatedSchemaComponent, stmt: DFDLNewVariableInstance)
extends NewVariableInstanceBase(decl, stmt) {
- lazy val parser: DaffodilParser = new NewVariableInstanceEndParser(stmt.variableRuntimeData)
- override lazy val unparser: DaffodilUnparser = new NewVariableInstanceEndUnparser(stmt.variableRuntimeData)
+ lazy val parser: DaffodilParser = {
+ if (stmt.defv.runtimeData.direction == VariableDirection.UnparseOnly)
+ new NadaParser(stmt.variableRuntimeData)
+ else
+ new NewVariableInstanceEndParser(stmt.variableRuntimeData)
+ }
+
+ override lazy val unparser: DaffodilUnparser = {
+ if (stmt.defv.runtimeData.direction == VariableDirection.ParseOnly)
+ new NadaUnparser(stmt.variableRuntimeData)
+ else
+ new NewVariableInstanceEndUnparser(stmt.variableRuntimeData)
+ }
}
/**
diff --git a/daffodil-core/src/test/scala/org/apache/daffodil/externalvars/TestExternalVariablesLoader.scala b/daffodil-core/src/test/scala/org/apache/daffodil/externalvars/TestExternalVariablesLoader.scala
index 60e68d9..9d0e3bb 100644
--- a/daffodil-core/src/test/scala/org/apache/daffodil/externalvars/TestExternalVariablesLoader.scala
+++ b/daffodil-core/src/test/scala/org/apache/daffodil/externalvars/TestExternalVariablesLoader.scala
@@ -110,9 +110,9 @@ class TestExternalVariablesLoader extends Logging {
// Verify that the external variables override the previous values
// in the VariableMap
- val value1 = vmap.readVariable(v_no_default_vrd, Fakes.fakeElem, Maybe.Nope)
+ val value1 = vmap.find(v_no_default_vrd.globalQName).get.value
assertEquals(1, value1.getAnyRef)
- val value2 = vmap.readVariable(v_with_default_vrd, Fakes.fakeElem, Maybe.Nope)
+ val value2 = vmap.find(v_with_default_vrd.globalQName).get.value
assertEquals(2, value2.getAnyRef)
}
diff --git a/daffodil-propgen/src/main/resources/org/apache/daffodil/xsd/DFDL_part3_model.xsd b/daffodil-propgen/src/main/resources/org/apache/daffodil/xsd/DFDL_part3_model.xsd
index 7b6b18d..f3c15b6 100644
--- a/daffodil-propgen/src/main/resources/org/apache/daffodil/xsd/DFDL_part3_model.xsd
+++ b/daffodil-propgen/src/main/resources/org/apache/daffodil/xsd/DFDL_part3_model.xsd
@@ -136,6 +136,7 @@
<xsd:attribute name="external" type="xsd:boolean" />
<xsd:attribute name="defaultValue"
type="dfdl:DFDLStringLiteral_Or_DFDLExpression" />
+ <xsd:attribute ref="dfdlx:direction" />
</xsd:attributeGroup>
<xsd:attributeGroup name="SetVariableAG">
diff --git a/daffodil-propgen/src/main/resources/org/apache/daffodil/xsd/dfdlx.xsd b/daffodil-propgen/src/main/resources/org/apache/daffodil/xsd/dfdlx.xsd
index e463645..d253767 100644
--- a/daffodil-propgen/src/main/resources/org/apache/daffodil/xsd/dfdlx.xsd
+++ b/daffodil-propgen/src/main/resources/org/apache/daffodil/xsd/dfdlx.xsd
@@ -57,6 +57,15 @@
</xs:restriction>
</xs:simpleType>
+ <xs:attribute name="direction" type="dfdlx:VariableDirectionEnum"/>
+ <xs:simpleType name="VariableDirectionEnum">
+ <xs:restriction base="xs:string">
+ <xs:enumeration value="both" />
+ <xs:enumeration value="parseOnly" />
+ <xs:enumeration value="unparseOnly" />
+ </xs:restriction>
+ </xs:simpleType>
+
<xs:attribute name="emptyElementParsePolicy" type="dfdlx:EmptyElementParsePolicyEnum"/>
<xs:simpleType name="EmptyElementParsePolicyEnum">
<xs:restriction base="xs:string">
diff --git a/daffodil-runtime1-unparser/src/main/scala/org/apache/daffodil/processors/unparsers/ElementUnparser.scala b/daffodil-runtime1-unparser/src/main/scala/org/apache/daffodil/processors/unparsers/ElementUnparser.scala
index e224c10..3b8c648 100644
--- a/daffodil-runtime1-unparser/src/main/scala/org/apache/daffodil/processors/unparsers/ElementUnparser.scala
+++ b/daffodil-runtime1-unparser/src/main/scala/org/apache/daffodil/processors/unparsers/ElementUnparser.scala
@@ -311,7 +311,7 @@ class ElementSpecifiedLengthUnparser(
/**
* For dfdl:outputValueCalc elements.
*/
-class ElementOVCSpecifiedLengthUnparserSuspendableExpresion(
+class ElementOVCSpecifiedLengthUnparserSuspendableExpression(
callingUnparser: ElementOVCSpecifiedLengthUnparser)
extends SuspendableExpression {
@@ -355,7 +355,7 @@ class ElementOVCSpecifiedLengthUnparser(
override lazy val runtimeDependencies = maybeTargetLengthEv.toList.toVector
private def suspendableExpression =
- new ElementOVCSpecifiedLengthUnparserSuspendableExpresion(this)
+ new ElementOVCSpecifiedLengthUnparserSuspendableExpression(this)
Assert.invariant(context.outputValueCalcExpr.isDefined)
diff --git a/daffodil-runtime1-unparser/src/main/scala/org/apache/daffodil/processors/unparsers/ExpressionEvaluatingUnparsers.scala b/daffodil-runtime1-unparser/src/main/scala/org/apache/daffodil/processors/unparsers/ExpressionEvaluatingUnparsers.scala
index d0b7e0f..c237eeb 100644
--- a/daffodil-runtime1-unparser/src/main/scala/org/apache/daffodil/processors/unparsers/ExpressionEvaluatingUnparsers.scala
+++ b/daffodil-runtime1-unparser/src/main/scala/org/apache/daffodil/processors/unparsers/ExpressionEvaluatingUnparsers.scala
@@ -27,10 +27,11 @@ import org.apache.daffodil.infoset.Infoset
import org.apache.daffodil.processors.ElementRuntimeData
import org.apache.daffodil.processors.Evaluatable
import org.apache.daffodil.processors.NonTermRuntimeData
-import org.apache.daffodil.processors.RuntimeData
import org.apache.daffodil.processors.Success
import org.apache.daffodil.processors.TypeCalculator
import org.apache.daffodil.processors.VariableRuntimeData
+import org.apache.daffodil.processors.{ VariableInProcess, VariableDefined }
+//import org.apache.daffodil.processors.SuspendableOperation
import org.apache.daffodil.util.Maybe
import org.apache.daffodil.util.MaybeULong
@@ -77,9 +78,24 @@ final class SetVariableUnparser(
}
+final class NewVariableInstanceSuspendableExpression(
+ override val expr: CompiledExpression[AnyRef],
+ override val rd: VariableRuntimeData)
+ extends SuspendableExpression {
+
+ override protected def processExpressionResult(ustate: UState, v: DataValuePrimitive): Unit = {
+ val vi = ustate.variableMap.find(rd.globalQName)
+ Assert.invariant(vi.isDefined)
+ vi.get.setState(VariableDefined)
+ vi.get.setValue(v)
+ }
+
+ override protected def maybeKnownLengthInBits(ustate: UState) = MaybeULong(0)
+}
+
// When implemented this almost certainly wants to be a combinator
// Not two separate unparsers.
-class NewVariableInstanceStartUnparser(override val context: RuntimeData)
+class NewVariableInstanceStartUnparser(override val context: VariableRuntimeData)
extends PrimUnparserNoData {
override lazy val runtimeDependencies = Vector()
@@ -87,21 +103,25 @@ class NewVariableInstanceStartUnparser(override val context: RuntimeData)
override lazy val childProcessors = Vector()
override def unparse(state: UState) = {
- val vrd = context.asInstanceOf[VariableRuntimeData]
- state.newVariableInstance(vrd)
+ state.variableMap.newVariableInstance(context)
+ if (context.maybeDefaultValueExpr.isDefined) {
+ val maybeVar = state.variableMap.find(context.globalQName)
+ Assert.invariant(maybeVar.isDefined)
+ maybeVar.get.setState(VariableInProcess)
+ val suspendableExpression = new NewVariableInstanceSuspendableExpression(context.maybeDefaultValueExpr.get, context)
+ suspendableExpression.run(state)
+ }
}
}
-class NewVariableInstanceEndUnparser(override val context: RuntimeData)
+class NewVariableInstanceEndUnparser(override val context: VariableRuntimeData)
extends PrimUnparserNoData {
override lazy val runtimeDependencies = Vector()
override lazy val childProcessors = Vector()
- override def unparse(state: UState) = {
- state.removeVariableInstance(context.asInstanceOf[VariableRuntimeData])
- }
+ override def unparse(state: UState) = state.variableMap.removeVariableInstance(context)
}
class TypeValueCalcUnparser(typeCalculator: TypeCalculator, repTypeUnparser: Unparser, e: ElementRuntimeData, repTypeRuntimeData: ElementRuntimeData)
diff --git a/daffodil-runtime1/src/main/scala/org/apache/daffodil/debugger/InteractiveDebugger.scala b/daffodil-runtime1/src/main/scala/org/apache/daffodil/debugger/InteractiveDebugger.scala
index 7ba02d4..42dfb0c 100644
--- a/daffodil-runtime1/src/main/scala/org/apache/daffodil/debugger/InteractiveDebugger.scala
+++ b/daffodil-runtime1/src/main/scala/org/apache/daffodil/debugger/InteractiveDebugger.scala
@@ -1745,6 +1745,7 @@ class InteractiveDebugger(runner: InteractiveDebuggerRunner, eCompilers: Express
case VariableRead => "read"
case VariableSet => "set"
case VariableUndefined => "undefined"
+ case VariableBeingDefined => "being defined"
case VariableInProcess => "in process"
}
diff --git a/daffodil-runtime1/src/main/scala/org/apache/daffodil/dpath/DPathRuntime.scala b/daffodil-runtime1/src/main/scala/org/apache/daffodil/dpath/DPathRuntime.scala
index 9a59566..edd51e3 100644
--- a/daffodil-runtime1/src/main/scala/org/apache/daffodil/dpath/DPathRuntime.scala
+++ b/daffodil-runtime1/src/main/scala/org/apache/daffodil/dpath/DPathRuntime.scala
@@ -179,7 +179,8 @@ case class VRef(vrd: VariableRuntimeData, context: ThrowsSDE)
override def run(dstate: DState): Unit = {
Assert.invariant(dstate.vmap != null)
- dstate.setCurrentValue(dstate.vmap.readVariable(vrd, context, dstate.parseOrUnparseState))
+ Assert.invariant(dstate.parseOrUnparseState.isDefined)
+ dstate.setCurrentValue(dstate.vmap.readVariable(vrd, context, dstate.parseOrUnparseState.get))
}
override def toXML = toXML("$" + vrd.globalQName.toPrettyString)
diff --git a/daffodil-runtime1/src/main/scala/org/apache/daffodil/processors/RuntimeData.scala b/daffodil-runtime1/src/main/scala/org/apache/daffodil/processors/RuntimeData.scala
index 6519e73..00ef060 100644
--- a/daffodil-runtime1/src/main/scala/org/apache/daffodil/processors/RuntimeData.scala
+++ b/daffodil-runtime1/src/main/scala/org/apache/daffodil/processors/RuntimeData.scala
@@ -35,6 +35,7 @@ import org.apache.daffodil.infoset.PartialNextElementResolver
import org.apache.daffodil.schema.annotation.props.gen.BitOrder
import org.apache.daffodil.schema.annotation.props.gen.Representation
import org.apache.daffodil.schema.annotation.props.gen.YesNo
+import org.apache.daffodil.schema.annotation.props.gen.VariableDirection
import org.apache.daffodil.util.Maybe
import org.apache.daffodil.util.Maybe.Nope
import org.apache.daffodil.util.PreSerialization
@@ -952,6 +953,7 @@ final class VariableRuntimeData(
pathArg: String,
namespacesArg: NamespaceBinding,
val external: Boolean,
+ val direction: VariableDirection,
@TransientParam maybeDefaultValueExprArg: => Maybe[CompiledExpression[AnyRef]],
val typeRef: RefQName,
val globalQName: GlobalQName,
@@ -999,8 +1001,6 @@ final class VariableRuntimeData(
VariableInstance(state, defaultValue, this, maybeDefaultValueExpr)
}
- def createVariableInstance(): VariableInstance = {
- VariableInstance(state, value, this, maybeDefaultValueExpr)
- }
+ def createVariableInstance(): VariableInstance = VariableInstance(state, value, this, maybeDefaultValueExpr)
}
diff --git a/daffodil-runtime1/src/main/scala/org/apache/daffodil/processors/SuspendableOperation.scala b/daffodil-runtime1/src/main/scala/org/apache/daffodil/processors/SuspendableOperation.scala
index ae9d91a..95a3ed5 100644
--- a/daffodil-runtime1/src/main/scala/org/apache/daffodil/processors/SuspendableOperation.scala
+++ b/daffodil-runtime1/src/main/scala/org/apache/daffodil/processors/SuspendableOperation.scala
@@ -37,7 +37,7 @@ import org.apache.daffodil.infoset.RetryableException
trait SuspendableOperation
extends Suspension {
- override def rd: TermRuntimeData
+ override def rd: RuntimeData
override def toString = "%s for %s".format(Misc.getNameFromClass(this), rd.diagnosticDebugName)
diff --git a/daffodil-runtime1/src/main/scala/org/apache/daffodil/processors/VariableMap1.scala b/daffodil-runtime1/src/main/scala/org/apache/daffodil/processors/VariableMap1.scala
index 0674cf5..958ba3d 100644
--- a/daffodil-runtime1/src/main/scala/org/apache/daffodil/processors/VariableMap1.scala
+++ b/daffodil-runtime1/src/main/scala/org/apache/daffodil/processors/VariableMap1.scala
@@ -33,6 +33,8 @@ import org.apache.daffodil.util.PreSerialization
import org.apache.daffodil.util.TransientParam
import org.apache.daffodil.xml.{GlobalQName, UnspecifiedNamespace, NamedQName, RefQName}
import org.apache.daffodil.processors.parsers.PState
+import org.apache.daffodil.processors.unparsers.UState
+import org.apache.daffodil.schema.annotation.props.gen.VariableDirection
import scala.collection.mutable.{Map, ArrayBuffer}
@@ -42,6 +44,11 @@ case object VariableUndefined extends VariableState
case object VariableDefined extends VariableState
+/**
+ * Used to detect circular definitions of variables
+ */
+case object VariableBeingDefined extends VariableState
+
case object VariableSet extends VariableState
case object VariableRead extends VariableState
@@ -141,19 +148,22 @@ class VariableInstance private (
this._value = Some(v)
}
+ def setDefaultValue(v: DataValuePrimitiveNullable) = this._value = Some(v)
+
override def preSerialization: Unit = {
defaultValueExpr
value
state
}
- override def toString: String = "VariableInstance(%s,%s,%s,%s,%s,%s".format(
+ override def toString: String = "VariableInstance(%s,%s,%s,%s,%s,%s,%s)".format(
state,
value,
rd,
defaultValueExpr,
priorState,
- priorValue)
+ priorValue,
+ this.hashCode)
def copy(
state: VariableState = state,
@@ -211,6 +221,10 @@ class VariableHasNoValue(qname: NamedQName, context: VariableRuntimeData) extend
"Variable map (runtime): variable %s has no value. It was not set, and has no default value.".format(qname))
with RetryableException
+class VariableSuspended(qname: NamedQName, context: VariableRuntimeData) extends VariableException(qname, context,
+ "Variable map (runtime): variable %s is currently suspended".format(qname))
+ with RetryableException
+
/**
* This expression can be thrown either at the start of parsing is the
* expressions in a defineVariable are circular, or later during parsing if
@@ -244,6 +258,8 @@ final class VariableBox(initialVMap: VariableMap) {
def setVMap(newMap: VariableMap): Unit = {
vmap_ = newMap
}
+
+ def cloneForSuspension(): VariableBox = new VariableBox(vmap.topLevelInstances)
}
/**
@@ -269,9 +285,9 @@ class VariableMap private(vTable: Map[GlobalQName, ArrayBuffer[VariableInstance]
this(Map(topLevelVRDs.map {
vrd =>
val variab = vrd.createVariableInstance()
- val abuf = new ArrayBuffer[VariableInstance]
- abuf += variab
- (vrd.globalQName, abuf)
+ val variableInstances = new ArrayBuffer[VariableInstance]
+ variableInstances += variab
+ (vrd.globalQName, variableInstances)
}: _*))
override def toString(): String = {
@@ -283,8 +299,18 @@ class VariableMap private(vTable: Map[GlobalQName, ArrayBuffer[VariableInstance]
* VariableInstances are mutable and cannot safely be shared across threads
*/
def copy(): VariableMap = {
- val table = vTable.map { case (k: GlobalQName, abuf: ArrayBuffer[VariableInstance]) => {
- val newBuf = abuf.map { _.copy() }
+ val table = vTable.map { case (k: GlobalQName, variableInstances: ArrayBuffer[VariableInstance]) => {
+ val newBuf = variableInstances.map { _.copy() }
+ (k, newBuf)
+ }}
+
+ new VariableMap(table)
+ }
+
+ def topLevelInstances(): VariableMap = {
+ val table = vTable.map { case (k: GlobalQName, variableInstances: ArrayBuffer[VariableInstance]) => {
+ val newBuf = new ArrayBuffer[VariableInstance]()
+ newBuf.append(variableInstances.last)
(k, newBuf)
}}
@@ -297,7 +323,7 @@ class VariableMap private(vTable: Map[GlobalQName, ArrayBuffer[VariableInstance]
// must also ensure that the expressions only reference other variables that
// have default value expressions or are defined externally.
def forceExpressionEvaluations(state: ParseOrUnparseState): Unit = {
- vTable.foreach { case (_, abuf) => { abuf.foreach { inst => {
+ vTable.foreach { case (_, variableInstances) => { variableInstances.foreach { inst => {
(inst.state, inst.value, inst.defaultValueExpr.isDefined) match {
// Evaluate defineVariable statements with non-constant default value expressions
case (VariableDefined, DataValue.NoValue, true) => {
@@ -342,16 +368,24 @@ class VariableMap private(vTable: Map[GlobalQName, ArrayBuffer[VariableInstance]
* Returns the value of a variable and sets the state of the variable to be
* VariableRead.
*/
- def readVariable(vrd: VariableRuntimeData, referringContext: ThrowsSDE, maybeState: Maybe[ParseOrUnparseState]): DataValuePrimitive = {
+ def readVariable(vrd: VariableRuntimeData, referringContext: ThrowsSDE, pstate: ParseOrUnparseState): DataValuePrimitive = {
val varQName = vrd.globalQName
- val abuf = vTable.get(varQName)
- if (abuf.isDefined) {
- val variable = abuf.get.last
+ vrd.direction match {
+ case VariableDirection.ParseOnly if (!pstate.isInstanceOf[PState]) =>
+ pstate.SDE("Attempting to read variable %s which is marked as parseOnly during unparsing".format(varQName))
+ case VariableDirection.UnparseOnly if (!pstate.isInstanceOf[UState]) =>
+ pstate.SDE("Attempting to read variable %s which is marked as unparseOnly during parsing".format(varQName))
+ case _ => // Do nothing
+ }
+
+ val variableInstances = vTable.get(varQName)
+ if (variableInstances.isDefined) {
+ val variable = variableInstances.get.last
variable.state match {
case VariableRead if (variable.value.isDefined) => variable.value.getNonNullable
case VariableDefined | VariableSet if (variable.value.isDefined) => {
- if (maybeState.isDefined && maybeState.get.isInstanceOf[PState])
- maybeState.get.asInstanceOf[PState].markVariableRead(vrd)
+ if (pstate.isInstanceOf[PState])
+ pstate.asInstanceOf[PState].markVariableRead(vrd)
variable.setState(VariableRead)
variable.value.getNonNullable
@@ -362,10 +396,8 @@ class VariableMap private(vTable: Map[GlobalQName, ArrayBuffer[VariableInstance]
// forceExpressionEvaluations function after which all variables should
// have a defined value
case VariableDefined if (!variable.value.isDefined && variable.defaultValueExpr.isDefined) => {
- Assert.invariant(maybeState.isDefined)
- variable.setState(VariableInProcess)
- val state = maybeState.get
- val res = DataValue.unsafeFromAnyRef(variable.defaultValueExpr.get.evaluate(state))
+ variable.setState(VariableBeingDefined)
+ val res = DataValue.unsafeFromAnyRef(variable.defaultValueExpr.get.evaluate(pstate))
// Need to update the variable's value with the result of the
// expression
@@ -374,7 +406,8 @@ class VariableMap private(vTable: Map[GlobalQName, ArrayBuffer[VariableInstance]
res
}
- case VariableInProcess => throw new VariableCircularDefinition(varQName, vrd)
+ case VariableBeingDefined => throw new VariableCircularDefinition(varQName, vrd)
+ case VariableInProcess => throw new VariableSuspended(varQName, vrd)
case _ => throw new VariableHasNoValue(varQName, vrd)
}
} else
@@ -386,9 +419,9 @@ class VariableMap private(vTable: Map[GlobalQName, ArrayBuffer[VariableInstance]
*/
def setVariable(vrd: VariableRuntimeData, newValue: DataValuePrimitive, referringContext: ThrowsSDE, pstate: ParseOrUnparseState) = {
val varQName = vrd.globalQName
- val abuf = vTable.get(varQName)
- if (abuf.isDefined) {
- val variable = abuf.get.last
+ val variableInstances = vTable.get(varQName)
+ if (variableInstances.isDefined) {
+ val variable = variableInstances.get.last
variable.state match {
case VariableSet => {
referringContext.SDE("Cannot set variable %s twice. State was: %s. Existing value: %s",
@@ -407,6 +440,21 @@ class VariableMap private(vTable: Map[GlobalQName, ArrayBuffer[VariableInstance]
}
case _ => {
+ vrd.direction match {
+ /**
+ * Due to potential race conditions regarding the setting of
+ * variables via setVariable and default values in cominbation with
+ * suspesions during unparsing, we only allow the use of either
+ * setVariable statements or a default value when unparsing a
+ * variable.
+ */
+ case VariableDirection.UnparseOnly | VariableDirection.Both if (vrd.maybeDefaultValueExpr.isDefined && variableInstances.get.size > 1) => {
+ // Variable has an unparse direction, a default value, and a
+ // newVariableInstance
+ pstate.SDE("Variable %s has an unparse direction and a default value, setting the variable may cause race conditions when combined with a forward referencing expression.", varQName)
+ }
+ case _ => // Do nothing
+ }
variable.setValue(VariableUtils.convert(newValue.getAnyRef.toString, variable.rd, referringContext))
variable.setState(VariableSet)
}
@@ -415,30 +463,38 @@ class VariableMap private(vTable: Map[GlobalQName, ArrayBuffer[VariableInstance]
}
/**
- * Creates a new instance of a variable
+ * Creates a new instance of a variable with default value
*/
- def newVariableInstance(vrd: VariableRuntimeData, state: ParseOrUnparseState) = {
+ def newVariableInstance(vrd: VariableRuntimeData, defaultValue: DataValuePrimitive) = {
val varQName = vrd.globalQName
- val abuf = vTable.get(varQName)
- Assert.invariant(abuf.isDefined)
+ val variableInstances = vTable.get(varQName)
+ Assert.invariant(variableInstances.isDefined)
- if (vrd.maybeDefaultValueExpr.isDefined) {
- val defaultValue = DataValue.unsafeFromAnyRef(vrd.maybeDefaultValueExpr.get.evaluate(state))
- abuf.get += vrd.createVariableInstance(VariableUtils.convert(defaultValue.getAnyRef.toString, vrd, vrd))
- } else
- abuf.get += vrd.createVariableInstance()
+ variableInstances.get += vrd.createVariableInstance(VariableUtils.convert(defaultValue.getAnyRef.toString, vrd, vrd))
+ }
+
+ /**
+ * Creates a new instance of a variable without default value
+ */
+ def newVariableInstance(vrd: VariableRuntimeData) = {
+ val varQName = vrd.globalQName
+ val variableInstances = vTable.get(varQName)
+ Assert.invariant(variableInstances.isDefined)
+
+ val v = vrd.createVariableInstance()
+ variableInstances.get += v
}
def removeVariableInstance(vrd: VariableRuntimeData): Unit = {
val varQName = vrd.globalQName
- val abuf = vTable.get(varQName)
- Assert.invariant(abuf.isDefined)
- abuf.get.trimEnd(1)
+ val variableInstances = vTable.get(varQName)
+ Assert.invariant(variableInstances.isDefined && variableInstances.get.nonEmpty)
+ variableInstances.get.trimEnd(1)
}
private lazy val externalVarGlobalQNames: Seq[GlobalQName] =
- vTable.map { case (_, abuf) if (abuf.last.rd.external) => abuf.last.rd.globalQName }.toSeq
+ vTable.map { case (_, variableInstances) if (variableInstances.last.rd.external) => variableInstances.last.rd.globalQName }.toSeq
/**
* Assigns an external variable and sets the variables state to VariableSet
@@ -465,11 +521,11 @@ class VariableMap private(vTable: Map[GlobalQName, ArrayBuffer[VariableInstance]
bindingQName
}
- val abuf = vTable.get(varQName.toGlobalQName)
- if (!abuf.isDefined)
+ val variableInstances = vTable.get(varQName.toGlobalQName)
+ if (!variableInstances.isDefined)
referringContext.schemaDefinitionError("unknown variable %s", varQName)
else {
- val variable = abuf.get.last
+ val variable = variableInstances.get.last
variable.state match {
case VariableDefined if (!variable.rd.external) => {
referringContext.SDE("Cannot set variable %s externally. State was: %s. Existing value: %s.",
diff --git a/daffodil-runtime1/src/main/scala/org/apache/daffodil/processors/parsers/ExpressionEvaluatingParsers.scala b/daffodil-runtime1/src/main/scala/org/apache/daffodil/processors/parsers/ExpressionEvaluatingParsers.scala
index c187005..14cc553 100644
--- a/daffodil-runtime1/src/main/scala/org/apache/daffodil/processors/parsers/ExpressionEvaluatingParsers.scala
+++ b/daffodil-runtime1/src/main/scala/org/apache/daffodil/processors/parsers/ExpressionEvaluatingParsers.scala
@@ -166,6 +166,11 @@ class NewVariableInstanceStartParser(override val context: VariableRuntimeData)
def parse(start: PState): Unit = {
start.newVariableInstance(context)
+ if (context.maybeDefaultValueExpr.isDefined) {
+ val v = start.variableMap.find(context.globalQName).get
+ val res = DataValue.unsafeFromAnyRef(context.maybeDefaultValueExpr.get.evaluate(start))
+ v.setDefaultValue(res)
+ }
}
}
diff --git a/daffodil-runtime1/src/main/scala/org/apache/daffodil/processors/parsers/PState.scala b/daffodil-runtime1/src/main/scala/org/apache/daffodil/processors/parsers/PState.scala
index 0a25927..9dbc258 100644
--- a/daffodil-runtime1/src/main/scala/org/apache/daffodil/processors/parsers/PState.scala
+++ b/daffodil-runtime1/src/main/scala/org/apache/daffodil/processors/parsers/PState.scala
@@ -344,7 +344,7 @@ final class PState private (
}
def newVariableInstance(vrd: VariableRuntimeData): Unit = {
- variableMap.newVariableInstance(vrd, this)
+ variableMap.newVariableInstance(vrd)
changedVariablesStack.top += vrd.globalQName
}
diff --git a/daffodil-runtime1/src/main/scala/org/apache/daffodil/processors/unparsers/UState.scala b/daffodil-runtime1/src/main/scala/org/apache/daffodil/processors/unparsers/UState.scala
index b450514..fa21f25 100644
--- a/daffodil-runtime1/src/main/scala/org/apache/daffodil/processors/unparsers/UState.scala
+++ b/daffodil-runtime1/src/main/scala/org/apache/daffodil/processors/unparsers/UState.scala
@@ -53,7 +53,6 @@ import org.apache.daffodil.processors.TermRuntimeData
import org.apache.daffodil.processors.UnparseResult
import org.apache.daffodil.processors.VariableBox
import org.apache.daffodil.processors.VariableMap
-import org.apache.daffodil.processors.VariableRuntimeData
import org.apache.daffodil.processors.charset.BitsCharset
import org.apache.daffodil.processors.charset.BitsCharsetDecoder
import org.apache.daffodil.processors.charset.BitsCharsetEncoder
@@ -341,14 +340,6 @@ abstract class UState(
def documentElement: DIDocument
- def newVariableInstance(vrd: VariableRuntimeData): Unit = {
- variableMap.newVariableInstance(vrd, this)
- }
-
- def removeVariableInstance(vrd: VariableRuntimeData): Unit = {
- variableMap.removeVariableInstance(vrd)
- }
-
final val releaseUnneededInfoset: Boolean = !areDebugging && tunable.releaseUnneededInfoset
def delimitedParseResult = Nope
@@ -439,7 +430,6 @@ final class UStateForSuspension(
override def incrementHiddenDef = Assert.usageError("Unparser suspended UStates need not be aware of hidden contexts")
override def decrementHiddenDef = Assert.usageError("Unparser suspended UStates need not be aware of hidden contexts")
override def withinHiddenNest = Assert.usageError("Unparser suspended UStates need not be aware of hidden contexts")
-
}
final class UStateMain private (
@@ -504,7 +494,7 @@ final class UStateMain private (
val clone = new UStateForSuspension(
this,
suspendedDOS,
- variableBox,
+ variableBox.cloneForSuspension,
currentInfosetNodeStack.top.get, // only need the to of the stack, not the whole thing
arrayIndexStack.top, // only need the top of the stack, not the whole thing
es,
diff --git a/daffodil-test/src/test/resources/org/apache/daffodil/section07/variables/variables.tdml b/daffodil-test/src/test/resources/org/apache/daffodil/section07/variables/variables.tdml
index 4c79505..589e658 100644
--- a/daffodil-test/src/test/resources/org/apache/daffodil/section07/variables/variables.tdml
+++ b/daffodil-test/src/test/resources/org/apache/daffodil/section07/variables/variables.tdml
@@ -19,7 +19,9 @@
<tdml:testSuite xmlns="http://example.com"
xmlns:tdml="http://www.ibm.com/xmlns/dfdl/testData" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:dfdl="http://www.ogf.org/dfdl/dfdl-1.0/" xmlns:xs="http://www.w3.org/2001/XMLSchema"
+ xmlns:dfdlx="http://www.ogf.org/dfdl/dfdl-1.0/extensions"
xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:ct="http://w3.ibm.com/xmlns/dfdl/ctInfoset"
+ xmlns:fn="http://www.w3.org/2005/xpath-functions"
xmlns:ex="http://example.com"
defaultRoundTrip="true">
@@ -437,6 +439,88 @@
</xs:complexType>
</xs:element>
+ <dfdl:defineVariable name="v1" type="xs:string" defaultValue="v1_global_default" />
+ <dfdl:defineVariable name="v2" type="xs:string" />
+ <xs:element name="nvi14">
+ <xs:complexType>
+ <xs:sequence>
+ <xs:annotation>
+ <xs:appinfo source="http://www.ogf.org/dfdl/">
+ <dfdl:setVariable ref="ex:v1">{ "v1_global_set" }</dfdl:setVariable>
+ </xs:appinfo>
+ </xs:annotation>
+ <xs:element name="nest1">
+ <xs:complexType>
+ <xs:sequence>
+ <xs:annotation>
+ <xs:appinfo source="http://www.ogf.org/dfdl/">
+ <dfdl:newVariableInstance ref="ex:v1"
+ defaultValue="v1_nest1_nvi1_default" />
+ <dfdl:newVariableInstance ref="ex:v2" />
+ <dfdl:setVariable ref="ex:v2">{ "v2_nest1_nvi1_set" }</dfdl:setVariable>
+ </xs:appinfo>
+ </xs:annotation>
+ <xs:element name="nest2">
+ <xs:complexType>
+ <xs:sequence>
+ <xs:annotation>
+ <xs:appinfo source="http://www.ogf.org/dfdl/">
+ <dfdl:newVariableInstance ref="ex:v1"
+ defaultValue="v1_nest2_nvi2_default" />
+ <dfdl:newVariableInstance ref="ex:v2" />
+ </xs:appinfo>
+ </xs:annotation>
+ <xs:element name="choice">
+ <xs:complexType>
+ <xs:choice>
+ <xs:element name="c1_v2" type="xs:string" dfdl:lengthKind="delimited">
+ <xs:annotation>
+ <xs:appinfo source="http://www.ogf.org/dfdl/">
+ <dfdl:setVariable ref="ex:v2">{ "v2_c1_set" }</dfdl:setVariable>
+ <dfdl:assert><![CDATA[{ xs:string(.) eq "fail" }]]></dfdl:assert>
+ </xs:appinfo>
+ </xs:annotation>
+ </xs:element>
+ <xs:element name="c2_v2" type="xs:string" dfdl:lengthKind="delimited">
+ <xs:annotation>
+ <xs:appinfo source="http://www.ogf.org/dfdl/">
+ <dfdl:setVariable ref="ex:v2">{ "v2_c2_set" }</dfdl:setVariable>
+ <dfdl:assert><![CDATA[{ xs:string(.) eq "fail" }]]></dfdl:assert>
+ </xs:appinfo>
+ </xs:annotation>
+ </xs:element>
+ <xs:element name="c3_v2" type="xs:string" dfdl:lengthKind="delimited">
+ <xs:annotation>
+ <xs:appinfo source="http://www.ogf.org/dfdl/">
+ <dfdl:setVariable ref="ex:v2">{ "v2_c3_set" }</dfdl:setVariable>
+ <dfdl:assert><![CDATA[{ xs:string(.) eq "nest2_nvi2_default2" }]]></dfdl:assert>
+ </xs:appinfo>
+ </xs:annotation>
+ </xs:element>
+ </xs:choice>
+ </xs:complexType>
+ </xs:element>
+ <xs:element name="d" type="xs:string"
+ dfdl:inputValueCalc="{ $ex:v1 }" />
+ <xs:element name="e" type="xs:string"
+ dfdl:inputValueCalc="{ $ex:v2 }" />
+ </xs:sequence>
+ </xs:complexType>
+ </xs:element>
+ <xs:element name="f" type="xs:string"
+ dfdl:inputValueCalc="{ $ex:v1 }">
+ </xs:element>
+ <xs:element name="g" type="xs:string"
+ dfdl:inputValueCalc="{ $ex:v2 }" />
+ </xs:sequence>
+ </xs:complexType>
+ </xs:element>
+ <xs:element name="h" type="xs:string"
+ dfdl:inputValueCalc="{ $ex:v1 }" />
+ </xs:sequence>
+ </xs:complexType>
+ </xs:element>
+
<xs:element name="defNonConst">
<xs:complexType>
<xs:sequence>
@@ -764,6 +848,20 @@
</tdml:parserTestCase>
+ <tdml:parserTestCase name="setAfterDefaultUnparseErr" root="r2"
+ model="v"
+ description="Error to set the value of a variable instance that has a default value when unparsing">
+
+ <tdml:document />
+
+ <tdml:errors>
+ <tdml:error>variable</tdml:error>
+ <tdml:error>after</tdml:error>
+ <tdml:error>v_with_default</tdml:error>
+ </tdml:errors>
+
+ </tdml:parserTestCase>
+
<tdml:parserTestCase name="varInstance_01" root="nvi1"
model="v"
description="Simple demonstration of newVariableInstance">
@@ -876,29 +974,16 @@
<tdml:parserTestCase name="varInstance_08" root="nvi8"
model="v"
- description="Demonstrates that newVariableInstance correctly goes into and out of scope with backtracking">
+ description="Demonstrates that newVariableInstance default values cannot be used in combination with setVariable when unparsing">
<tdml:document><![CDATA[nest2_nvi2_default2]]></tdml:document>
- <tdml:infoset>
- <tdml:dfdlInfoset>
- <nvi8 xmlns="http://example.com">
- <nest1>
- <nest2>
- <choice>
- <c3_s2>nest2_nvi2_default2</c3_s2>
- </choice>
- <d>c3_s1_set1</d>
- <e>nest2_nvi2_default2</e>
- </nest2>
- <f>nest1_nvi1_set1</f>
- <g>nest1_nvi1_default2</g>
- </nest1>
- <h>global_set1</h>
- <i>global_default2</i>
- </nvi8>
- </tdml:dfdlInfoset>
- </tdml:infoset>
+ <tdml:errors>
+ <tdml:error>unparse direction</tdml:error>
+ <tdml:error>default value</tdml:error>
+ <tdml:error>race condition</tdml:error>
+ <tdml:error>forward referencing expression</tdml:error>
+ </tdml:errors>
</tdml:parserTestCase>
<tdml:parserTestCase name="varInstance_09" root="nvi9"
@@ -952,7 +1037,7 @@
</tdml:parserTestCase>
<tdml:parserTestCase name="varInstance_12" root="nvi12"
- model="v"
+ model="v" roundTrip="false"
description="newVariablesInstance with a non-constant expression as defaultValue">
<tdml:document><![CDATA[7]]></tdml:document>
@@ -970,6 +1055,32 @@
</tdml:infoset>
</tdml:parserTestCase>
+ <tdml:parserTestCase name="varInstance_14" root="nvi14"
+ model="v"
+ description="Demonstrates that newVariableInstance correctly goes into and out of scope with backtracking">
+
+ <tdml:document><![CDATA[nest2_nvi2_default2]]></tdml:document>
+
+ <tdml:infoset>
+ <tdml:dfdlInfoset>
+ <nvi14 xmlns="http://example.com">
+ <nest1>
+ <nest2>
+ <choice>
+ <c3_v2>nest2_nvi2_default2</c3_v2>
+ </choice>
+ <d>v1_nest2_nvi2_default</d>
+ <e>v2_c3_set</e>
+ </nest2>
+ <f>v1_nest1_nvi1_default</f>
+ <g>v2_nest1_nvi1_set</g>
+ </nest1>
+ <h>v1_global_set</h>
+ </nvi14>
+ </tdml:dfdlInfoset>
+ </tdml:infoset>
+ </tdml:parserTestCase>
+
<tdml:parserTestCase name="defineVariable_nonConstantExpression" root="defNonConst"
model="v"
description="defineVariable with a non-constant expression as defaultValue">
@@ -2078,4 +2189,375 @@
</tdml:infoset>
</tdml:parserTestCase>
+
+ <tdml:defineSchema name="variable_direction_schema">
+ <xs:include schemaLocation="org/apache/daffodil/xsd/DFDLGeneralFormat.dfdl.xsd"/>
+ <dfdl:format ref="ex:GeneralFormat"
+ representation="binary"/>
+
+ <dfdl:defineVariable name="remainingDottedAddr" type="xs:string" dfdlx:direction="unparseOnly" />
+ <dfdl:defineVariable name="priorRemainingDottedAddr" type="xs:string" dfdlx:direction="unparseOnly" />
+ <dfdl:defineVariable name="ipAddr" type="xs:string" dfdlx:direction="unparseOnly" />
+
+ <xs:group name="IPSrcGrp">
+ <xs:sequence>
+ <xs:annotation><xs:appinfo source="http://www.ogf.org/dfdl/">
+ <dfdl:newVariableInstance ref="ex:ipAddr" defaultValue='{ ex:IPSrc }'/><!-- example 1.2.3.4 -->
+ </xs:appinfo></xs:annotation>
+ <xs:element name="IPSrcString">
+ <xs:complexType>
+ <xs:group ref="ex:IPAddressGroup"/>
+ </xs:complexType>
+ </xs:element>
+ </xs:sequence>
+ </xs:group>
+
+ <xs:group name="IPAddressGroup">
+ <xs:sequence>
+ <xs:sequence>
+ <xs:annotation><xs:appinfo source="http://www.ogf.org/dfdl/">
+ <dfdl:newVariableInstance ref="ex:priorRemainingDottedAddr" defaultValue='{ $ex:ipAddr }'/><!-- example 1.2.3.4 -->
+ <dfdl:newVariableInstance ref="ex:remainingDottedAddr" defaultValue='{ $ex:priorRemainingDottedAddr }'/><!-- example 1.2.3.4 -->
+ </xs:appinfo></xs:annotation>
+ <xs:element name="Byte1" type="xs:unsignedByte" dfdl:outputValueCalc="{
+ xs:unsignedByte(fn:substring-before($ex:remainingDottedAddr, '.'))
+ }"/>
+ <xs:sequence>
+ <xs:annotation><xs:appinfo source="http://www.ogf.org/dfdl/">
+ <dfdl:newVariableInstance ref="ex:priorRemainingDottedAddr" defaultValue='{ $ex:remainingDottedAddr }'/><!-- example 1.2.3.4 -->
+ <dfdl:newVariableInstance ref="ex:remainingDottedAddr" defaultValue='{ fn:substring-after($ex:priorRemainingDottedAddr, ".") }'/><!-- example 2.3.4 -->
+ </xs:appinfo></xs:annotation>
+ <xs:element name="Byte2" type="xs:unsignedByte" dfdl:outputValueCalc="{
+ xs:unsignedByte(fn:substring-before($ex:remainingDottedAddr, '.'))
+ }"/>
+ <xs:sequence>
+ <xs:annotation><xs:appinfo source="http://www.ogf.org/dfdl/">
+ <dfdl:newVariableInstance ref="ex:priorRemainingDottedAddr" defaultValue='{ $ex:remainingDottedAddr }'/><!-- example 2.3.4 -->
+ <dfdl:newVariableInstance ref="ex:remainingDottedAddr" defaultValue='{ fn:substring-after($ex:priorRemainingDottedAddr, ".") }'/><!-- example 3.4 -->
+ </xs:appinfo></xs:annotation>
+ <xs:element name="Byte3" type="xs:unsignedByte" dfdl:outputValueCalc="{
+ xs:unsignedByte(fn:substring-before($ex:remainingDottedAddr, '.'))
+ }"/>
+ <xs:sequence>
+ <xs:annotation><xs:appinfo source="http://www.ogf.org/dfdl/">
+ <dfdl:newVariableInstance ref="ex:priorRemainingDottedAddr" defaultValue='{ $ex:remainingDottedAddr }'/><!-- example 3.4 -->
+ <dfdl:newVariableInstance ref="ex:remainingDottedAddr" defaultValue='{ fn:substring-after($ex:priorRemainingDottedAddr, ".") }'/><!-- example 4 -->
+ </xs:appinfo></xs:annotation>
+ <xs:element name="Byte4" type="xs:unsignedByte" dfdl:outputValueCalc="{
+ xs:unsignedByte($ex:remainingDottedAddr)
+ }"/>
+ </xs:sequence>
+ </xs:sequence>
+ </xs:sequence>
+ </xs:sequence>
+ </xs:sequence>
+ </xs:group>
+
+ <xs:element name="root">
+ <xs:complexType>
+ <xs:sequence>
+ <xs:sequence dfdl:hiddenGroupRef="ex:IPSrcGrp" />
+ <xs:element name="IPSrc" type="xs:string" dfdl:inputValueCalc="{ fn:concat(../ex:IPSrcString/ex:Byte1, '.', ../ex:IPSrcString/ex:Byte2, '.', ../ex:IPSrcString/ex:Byte3, '.', ../ex:IPSrcString/ex:Byte4) }" />
+ </xs:sequence>
+ </xs:complexType>
+ </xs:element>
+
+ </tdml:defineSchema>
+
+ <tdml:parserTestCase name="varDirection_1" root="root"
+ model="variable_direction_schema" description="Section 7 - ">
+ <tdml:document>
+ <tdml:documentPart type="bits">00000001 00000010 00000011 00000100</tdml:documentPart>
+ </tdml:document>
+ <tdml:infoset>
+ <tdml:dfdlInfoset>
+ <root>
+ <IPSrc>1.2.3.4</IPSrc>
+ </root>
+ </tdml:dfdlInfoset>
+ </tdml:infoset>
+ </tdml:parserTestCase>
+
+ <tdml:defineSchema name="variable_direction_simple">
+ <xs:include schemaLocation="org/apache/daffodil/xsd/DFDLGeneralFormat.dfdl.xsd"/>
+ <dfdl:format ref="ex:GeneralFormat"
+ representation="text" textNumberRep="standard" alignment="8" alignmentUnits="bits"
+ lengthUnits="bytes" lengthKind="delimited" />
+
+ <dfdl:defineVariable name="input" type="xs:int" dfdlx:direction="parseOnly" defaultValue="{ 1 }" />
+ <dfdl:defineVariable name="output" type="xs:int" dfdlx:direction="unparseOnly" defaultValue="{ 2 }" />
+
+ <xs:element name="e1" type="xs:int" dfdl:inputValueCalc="{ $ex:input }" />
+ <xs:element name="e2" type="xs:int" dfdl:inputValueCalc="{ $ex:output }" />
+
+ <xs:element name="refSimpleElementWithOvcVar">
+ <xs:complexType>
+ <xs:sequence>
+ <xs:element ref="st1" dfdl:outputValueCalc="{ $ex:output }" />
+ </xs:sequence>
+ </xs:complexType>
+ </xs:element>
+ <xs:element name="st1" type="xs:int" />
+
+ <xs:element name="refSimpleElementWithOvcVar2">
+ <xs:complexType>
+ <xs:sequence>
+ <xs:element ref="st1" dfdl:outputValueCalc="{ $ex:input }" />
+ </xs:sequence>
+ </xs:complexType>
+ </xs:element>
+
+ <xs:element name="direction_setVar1">
+ <xs:complexType>
+ <xs:sequence>
+ <xs:annotation>
+ <xs:appinfo source="http://www.ogf.org/dfdl/">
+ <dfdl:setVariable ref="ex:output">{ 4 }</dfdl:setVariable>
+ </xs:appinfo>
+ </xs:annotation>
+ <xs:element name="st1" type="xs:int" dfdl:inputValueCalc="{ $ex:output }" />
+ </xs:sequence>
+ </xs:complexType>
+ </xs:element>
+
+ <xs:element name="direction_setVar2">
+ <xs:complexType>
+ <xs:sequence>
+ <xs:annotation>
+ <xs:appinfo source="http://www.ogf.org/dfdl/">
+ <dfdl:setVariable ref="ex:output">{ 4 }</dfdl:setVariable>
+ </xs:appinfo>
+ </xs:annotation>
+ <xs:element name="st1" type="xs:int" dfdl:outputValueCalc="{ $ex:output }" />
+ </xs:sequence>
+ </xs:complexType>
+ </xs:element>
+
+ <xs:element name="direction_nvi1">
+ <xs:complexType>
+ <xs:sequence>
+ <xs:annotation>
+ <xs:appinfo source="http://www.ogf.org/dfdl/">
+ <dfdl:newVariableInstance ref="ex:output" defaultValue="4" />
+ </xs:appinfo>
+ </xs:annotation>
+ <xs:element name="st1" type="xs:int" dfdl:inputValueCalc="{ $ex:output }" />
+ </xs:sequence>
+ </xs:complexType>
+ </xs:element>
+
+ <xs:element name="direction_nvi2">
+ <xs:complexType>
+ <xs:sequence>
+ <xs:annotation>
+ <xs:appinfo source="http://www.ogf.org/dfdl/">
+ <dfdl:newVariableInstance ref="ex:output" defaultValue="4" />
+ </xs:appinfo>
+ </xs:annotation>
+ <xs:element name="st1" type="xs:int" dfdl:outputValueCalc="{ $ex:output }" />
+ </xs:sequence>
+ </xs:complexType>
+ </xs:element>
+
+ </tdml:defineSchema>
+
+ <tdml:parserTestCase name="varDirection_2" root="e1"
+ model="variable_direction_simple" description="Simple test of parseOnly variable" >
+ <tdml:document />
+ <tdml:infoset>
+ <tdml:dfdlInfoset>
+ <e1>1</e1>
+ </tdml:dfdlInfoset>
+ </tdml:infoset>
+ </tdml:parserTestCase>
+
+ <tdml:unparserTestCase name="varDirection_3" root="refSimpleElementWithOvcVar"
+ model="variable_direction_simple" description="Simple test of unparseOnly variable" roundTrip="false">
+ <tdml:infoset>
+ <tdml:dfdlInfoset>
+ <refSimpleElementWithOvcVar xmlns="http://example.com">
+ <st1>9</st1>
+ </refSimpleElementWithOvcVar>
+ </tdml:dfdlInfoset>
+ </tdml:infoset>
+ <tdml:document>
+ <tdml:documentPart type="text">2</tdml:documentPart>
+ </tdml:document>
+ </tdml:unparserTestCase>
+
+ <tdml:parserTestCase name="varDirection_err1" root="e2"
+ model="variable_direction_simple" description="Attempt to read variable marked unparseOnly during parsing" >
+ <tdml:document />
+ <tdml:errors>
+ <tdml:error>Attempting to read variable</tdml:error>
+ <tdml:error>marked as unparseOnly</tdml:error>
+ <tdml:error>during parsing</tdml:error>
+ </tdml:errors>
+ </tdml:parserTestCase>
+
+ <tdml:unparserTestCase name="varDirection_err2" root="refSimpleElementWithOvcVar2"
+ model="variable_direction_simple" description="Attempt to read a variable marked parseOnly during unparsing" roundTrip="false">
+ <tdml:infoset>
+ <tdml:dfdlInfoset>
+ <refSimpleElementWithOvcVar2 xmlns="http://example.com">
+ <st1>9</st1>
+ </refSimpleElementWithOvcVar2>
+ </tdml:dfdlInfoset>
+ </tdml:infoset>
+ <tdml:document>
+ <tdml:documentPart type="text">1</tdml:documentPart>
+ </tdml:document>
+
+ <tdml:errors>
+ <tdml:error>Attempting to read variable</tdml:error>
+ <tdml:error>marked as parseOnly</tdml:error>
+ <tdml:error>during unparsing</tdml:error>
+ </tdml:errors>
+ </tdml:unparserTestCase>
+
+ <tdml:parserTestCase name="varDirection_setVar1" root="direction_setVar1"
+ model="variable_direction_simple" description="Attempt to set a variable marked unparseOnly during parsing. SetVar should be ignored" roundTrip="false">
+ <tdml:document />
+ <tdml:errors>
+ <tdml:error>Attempting to read variable</tdml:error>
+ <tdml:error>marked as unparseOnly</tdml:error>
+ <tdml:error>during parsing</tdml:error>
+ </tdml:errors>
+ </tdml:parserTestCase>
+
+ <tdml:unparserTestCase name="varDirection_setVar2" root="direction_setVar2"
+ model="variable_direction_simple" description="Attempt to set a variable marked unparseOnly during parsing" roundTrip="false">
+ <tdml:document>
+ <tdml:documentPart type="text">4</tdml:documentPart>
+ </tdml:document>
+ <tdml:infoset>
+ <tdml:dfdlInfoset>
+ <direction_setVar2 xmlns="http://example.com">
+ <st1>2</st1>
+ </direction_setVar2>
+ </tdml:dfdlInfoset>
+ </tdml:infoset>
+
+ </tdml:unparserTestCase>
+
+ <tdml:parserTestCase name="varDirection_nvi1" root="direction_nvi1"
+ model="variable_direction_simple" description="Attempt to set a variable marked unparseOnly during parsing" roundTrip="false">
+ <tdml:document />
+ <tdml:errors>
+ <tdml:error>Attempting to read variable</tdml:error>
+ <tdml:error>marked as unparseOnly</tdml:error>
+ <tdml:error>during parsing</tdml:error>
+ </tdml:errors>
+ </tdml:parserTestCase>
+
+ <tdml:unparserTestCase name="varDirection_nvi2" root="direction_nvi2"
+ model="variable_direction_simple" description="Attempt to set a variable marked unparseOnly during parsing" roundTrip="false">
+ <tdml:document>
+ <tdml:documentPart type="text">4</tdml:documentPart>
+ </tdml:document>
+ <tdml:infoset>
+ <tdml:dfdlInfoset>
+ <direction_nvi2 xmlns="http://example.com">
+ <st1>2</st1>
+ </direction_nvi2>
+ </tdml:dfdlInfoset>
+ </tdml:infoset>
+
+ </tdml:unparserTestCase>
+
+ <tdml:defineSchema name="variable_suspension_scope">
+ <xs:include schemaLocation="org/apache/daffodil/xsd/DFDLGeneralFormat.dfdl.xsd"/>
+ <dfdl:format ref="ex:GeneralFormat"
+ representation="text" lengthKind="delimited"/>
+
+ <dfdl:defineVariable name="remainingDottedAddr" type="xs:string" dfdlx:direction="unparseOnly" />
+ <dfdl:defineVariable name="priorRemainingDottedAddr" type="xs:string" dfdlx:direction="unparseOnly" />
+ <dfdl:defineVariable name="ipAddr" type="xs:string" dfdlx:direction="unparseOnly" />
+
+ <xs:group name="IPSrcGrp">
+ <xs:sequence>
+ <xs:annotation><xs:appinfo source="http://www.ogf.org/dfdl/">
+ <dfdl:newVariableInstance ref="ex:ipAddr" defaultValue='{ ex:IPSrc }'/><!-- example 1.2.3.4 -->
+ </xs:appinfo></xs:annotation>
+ <xs:element name="IPSrcString">
+ <xs:complexType>
+ <xs:group ref="ex:IPAddressGroup"/>
+ </xs:complexType>
+ </xs:element>
+ </xs:sequence>
+ </xs:group>
+
+ <xs:group name="IPAddressGroup">
+ <xs:sequence dfdl:separator=",">
+ <xs:sequence dfdl:separator=",">
+ <xs:annotation><xs:appinfo source="http://www.ogf.org/dfdl/">
+ <dfdl:newVariableInstance ref="ex:priorRemainingDottedAddr" defaultValue='{ $ex:ipAddr }'/><!-- example 1.2.3.4 -->
+ <dfdl:newVariableInstance ref="ex:remainingDottedAddr" defaultValue='{ $ex:priorRemainingDottedAddr }'/><!-- example 1.2.3.4 -->
+ </xs:appinfo></xs:annotation>
+ <xs:element name="Byte1" type="xs:unsignedByte" dfdl:outputValueCalc="{
+ xs:unsignedByte(fn:substring-before($ex:remainingDottedAddr, '.'))
+ }"/>
+ <xs:sequence dfdl:separator=",">
+ <xs:annotation><xs:appinfo source="http://www.ogf.org/dfdl/">
+ <dfdl:newVariableInstance ref="ex:priorRemainingDottedAddr" defaultValue='{ $ex:remainingDottedAddr }'/><!-- example 1.2.3.4 -->
+ <dfdl:newVariableInstance ref="ex:remainingDottedAddr" defaultValue='{ fn:substring-after($ex:priorRemainingDottedAddr, ".") }'/><!-- example 2.3.4 -->
+ </xs:appinfo></xs:annotation>
+ <xs:element name="Byte2" type="xs:unsignedByte" dfdl:outputValueCalc="{
+ xs:unsignedByte(fn:substring-before($ex:remainingDottedAddr, '.'))
+ }"/>
+ <xs:sequence dfdl:separator=",">
+ <xs:annotation><xs:appinfo source="http://www.ogf.org/dfdl/">
+ <dfdl:newVariableInstance ref="ex:priorRemainingDottedAddr" defaultValue='{ $ex:remainingDottedAddr }'/><!-- example 2.3.4 -->
+ <dfdl:newVariableInstance ref="ex:remainingDottedAddr" defaultValue='{ fn:substring-after($ex:priorRemainingDottedAddr, ".") }'/><!-- example 3.4 -->
+ </xs:appinfo></xs:annotation>
+ <xs:element name="Byte3" type="xs:unsignedByte" dfdl:outputValueCalc="{
+ xs:unsignedByte(fn:substring-before($ex:remainingDottedAddr, '.'))
+ }"/>
+ <xs:sequence dfdl:separator=",">
+ <xs:annotation><xs:appinfo source="http://www.ogf.org/dfdl/">
+ <dfdl:newVariableInstance ref="ex:priorRemainingDottedAddr" defaultValue='{ $ex:remainingDottedAddr }'/><!-- example 3.4 -->
+ <dfdl:newVariableInstance ref="ex:remainingDottedAddr" defaultValue='{ fn:substring-after($ex:priorRemainingDottedAddr, ".") }'/><!-- example 4 -->
+ </xs:appinfo></xs:annotation>
+ <xs:element name="Byte4" type="xs:unsignedByte" dfdl:outputValueCalc="{
+ xs:unsignedByte($ex:remainingDottedAddr)
+ }"/>
+ </xs:sequence>
+ <xs:element name="checkPrior3" type="xs:string" dfdl:outputValueCalc="{ $ex:priorRemainingDottedAddr }" />
+ <xs:element name="checkRemaining3" type="xs:string" dfdl:outputValueCalc="{ $ex:remainingDottedAddr }" />
+ </xs:sequence>
+ <xs:element name="checkPrior2" type="xs:string" dfdl:outputValueCalc="{ $ex:priorRemainingDottedAddr }" />
+ <xs:element name="checkRemaining2" type="xs:string" dfdl:outputValueCalc="{ $ex:remainingDottedAddr }" />
+ </xs:sequence>
+ <xs:element name="checkPrior1" type="xs:string" dfdl:outputValueCalc="{ $ex:priorRemainingDottedAddr }" />
+ <xs:element name="checkRemaining1" type="xs:string" dfdl:outputValueCalc="{ $ex:remainingDottedAddr }" />
+ </xs:sequence>
+ </xs:sequence>
+ </xs:group>
+
+ <xs:element name="root">
+ <xs:complexType>
+ <xs:sequence>
+ <xs:sequence dfdl:hiddenGroupRef="ex:IPSrcGrp" />
+ <xs:element name="IPSrc" type="xs:string" dfdl:inputValueCalc="{ fn:concat(../ex:IPSrcString/ex:Byte1, '.', ../ex:IPSrcString/ex:Byte2, '.', ../ex:IPSrcString/ex:Byte3, '.', ../ex:IPSrcString/ex:Byte4) }" />
+
+ </xs:sequence>
+ </xs:complexType>
+ </xs:element>
+ </tdml:defineSchema>
+
+ <tdml:unparserTestCase name="varInstance_13" root="root"
+ model="variable_suspension_scope" roundTrip="false"
+ description="Demonstrate suspensions work correctly as NVI goes out of scope">
+ <tdml:infoset>
+ <tdml:dfdlInfoset>
+ <root>
+ <IPSrc>1.2.3.4</IPSrc>
+ </root>
+ </tdml:dfdlInfoset>
+ </tdml:infoset>
+ <tdml:document>
+ <tdml:documentPart type="text">1,2,3,4,2.3.4,3.4,1.2.3.4,2.3.4,1.2.3.4,1.2.3.4</tdml:documentPart>
+ </tdml:document>
+ </tdml:unparserTestCase>
+
</tdml:testSuite>
diff --git a/daffodil-test/src/test/scala/org/apache/daffodil/section07/variables/TestVariables.scala b/daffodil-test/src/test/scala/org/apache/daffodil/section07/variables/TestVariables.scala
index 3d448df..cc6716d 100644
--- a/daffodil-test/src/test/scala/org/apache/daffodil/section07/variables/TestVariables.scala
+++ b/daffodil-test/src/test/scala/org/apache/daffodil/section07/variables/TestVariables.scala
@@ -60,6 +60,18 @@ class TestVariables {
@Test def test_varInstance_10(): Unit = { runner.runOneTest("varInstance_10") }
@Test def test_varInstance_11(): Unit = { runner.runOneTest("varInstance_11") }
@Test def test_varInstance_12(): Unit = { runner.runOneTest("varInstance_12") }
+ @Test def test_varInstance_13(): Unit = { runner.runOneTest("varInstance_13") }
+ @Test def test_varInstance_14(): Unit = { runner.runOneTest("varInstance_14") }
+
+ @Test def test_varDirection_1(): Unit = { runner.runOneTest("varDirection_1") }
+ @Test def test_varDirection_2(): Unit = { runner.runOneTest("varDirection_2") }
+ @Test def test_varDirection_3(): Unit = { runner.runOneTest("varDirection_3") }
+ @Test def test_varDirection_err1(): Unit = { runner.runOneTest("varDirection_err1") }
+ @Test def test_varDirection_err2(): Unit = { runner.runOneTest("varDirection_err2") }
+ @Test def test_varDirection_setVar1(): Unit = { runner.runOneTest("varDirection_setVar1") }
+ @Test def test_varDirection_setVar2(): Unit = { runner.runOneTest("varDirection_setVar2") }
+ @Test def test_varDirection_nvi1(): Unit = { runner.runOneTest("varDirection_nvi1") }
+ @Test def test_varDirection_nvi2(): Unit = { runner.runOneTest("varDirection_nvi2") }
@Test def test_defineVariable_nonConstantExpression(): Unit = { runner.runOneTest("defineVariable_nonConstantExpression") }
@Test def test_defineVariable_nonConstantExpression_unp(): Unit = { runner.runOneTest("defineVariable_nonConstantExpression_unp") }