| /* |
| * 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.grammar |
| |
| import java.lang.{ Long => JLong } |
| import org.apache.daffodil.api.WarnID |
| import org.apache.daffodil.dpath.NodeInfo |
| import org.apache.daffodil.dpath.NodeInfo.PrimType |
| import org.apache.daffodil.dsom.PrefixLengthQuasiElementDecl |
| import org.apache.daffodil.dsom.ElementBase |
| import org.apache.daffodil.dsom.InitiatedTerminatedMixin |
| import org.apache.daffodil.dsom.TunableLimitExceededError |
| import org.apache.daffodil.exceptions.Assert |
| import org.apache.daffodil.grammar.primitives._ // there are too many to show individually |
| import org.apache.daffodil.processors.TextJustificationType |
| import org.apache.daffodil.schema.annotation.props.Found |
| import org.apache.daffodil.schema.annotation.props.NotFound |
| import org.apache.daffodil.schema.annotation.props.gen._ // there are too many to show individually |
| import org.apache.daffodil.util.PackedSignCodes |
| import org.apache.daffodil.xml.GlobalQName |
| import org.apache.daffodil.xml.XMLUtils |
| import org.apache.daffodil.dsom.ExpressionCompilers |
| import org.apache.daffodil.runtime1.ElementBaseRuntime1Mixin |
| 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 |
| ///////////////////////////////////////////////////////////////// |
| |
| trait ElementBaseGrammarMixin |
| extends InitiatedTerminatedMixin |
| with AlignedMixin |
| with HasStatementsGrammarMixin |
| with PaddingInfoMixin |
| with ElementBaseRuntime1Mixin { self: ElementBase => |
| |
| requiredEvaluationsIfActivated(checkPrefixedLengthElementDecl) |
| |
| private val context = this |
| |
| private lazy val (leftPadding, rightPadding) = { |
| if (unparsingPadChar.isEmpty) |
| (EmptyGram, EmptyGram) |
| else { |
| import TextJustificationType._ |
| this.justificationPad match { |
| case None => (EmptyGram, EmptyGram) |
| case Left => (EmptyGram, OnlyPadding(context)) |
| case Right => (OnlyPadding(context), EmptyGram) |
| case Center => (LeftCenteredPadding(context), RightCenteredPadding(context)) |
| } |
| } |
| } |
| |
| protected lazy val isDelimitedPrefixedPattern = { |
| import LengthKind._ |
| lengthKind match { |
| case Delimited => true // don't test for hasDelimiters because it might not be our delimiter, but a surrounding group's separator, or it's terminator, etc. |
| case Pattern => true |
| case Prefixed => true |
| case _ => false |
| } |
| } |
| |
| /** |
| * true if padding will be inserted for this delimited element when unparsing. |
| */ |
| protected lazy val isDelimitedPrefixedPatternWithPadding = { |
| (isDelimitedPrefixedPattern && |
| (impliedRepresentation eq Representation.Text) && |
| (justificationPad ne TextJustificationType.None) && |
| minLen > 0) |
| } |
| |
| |
| private lazy val prefixLengthTypeGSTD = LV('prefixLengthTypeGSTD){ |
| // We need to resolve the global simple type of the prefix length type |
| // because we need to create a detached element with the same schema |
| // document/parent of the GSTD. |
| schemaSet.getGlobalSimpleTypeDef(prefixLengthType).getOrElse( |
| schemaDefinitionError( |
| "Failed to resolve dfdl:prefixLengthType=\"%s\" to a simpleType", |
| prefixLengthType.toQNameString)) |
| }.value |
| |
| lazy val prefixedLengthElementDecl: PrefixLengthQuasiElementDecl = LV('prefixedLengthElementDecl){ |
| Assert.invariant(lengthKind == LengthKind.Prefixed) |
| val detachedNode = |
| <element name={name + " (prefixLength)"} type={prefixLengthType.toQNameString}/> |
| .copy(scope = prefixLengthTypeGSTD.xml.scope) |
| val detachedElementDecl = { |
| PrefixLengthQuasiElementDecl(detachedNode, prefixLengthTypeGSTD) |
| } |
| detachedElementDecl |
| }.value |
| |
| final lazy val optPrefixLengthElementDecl: Option[PrefixLengthQuasiElementDecl] = |
| if (lengthKind == LengthKind.Prefixed) |
| Some(prefixedLengthElementDecl) |
| else |
| None |
| |
| final lazy val checkPrefixedLengthElementDecl: Unit = { |
| if (lengthKind != LengthKind.Prefixed) () |
| else { |
| val detachedElementDecl = prefixedLengthElementDecl |
| val prefixedLengthKind = detachedElementDecl.lengthKind |
| prefixedLengthKind match { |
| case LengthKind.Delimited | LengthKind.EndOfParent | LengthKind.Pattern => |
| schemaDefinitionError( |
| "%s is specified as a dfdl:prefixLengthType, but has a dfdl:lengthKind of %s", |
| prefixLengthType, |
| prefixedLengthKind) |
| case LengthKind.Explicit if detachedElementDecl.optLengthConstant.isEmpty => |
| schemaDefinitionError( |
| "%s is specified as a dfdl:prefixLengthType, but has an expression for dfdl:length", |
| prefixLengthType) |
| case LengthKind.Implicit | LengthKind.Explicit if prefixIncludesPrefixLength == YesNo.Yes && |
| lengthUnits != detachedElementDecl.lengthUnits => |
| schemaDefinitionError( |
| "%s is specified as a dfdl:prefixLengthType where dfdl:prefixIncludesPrefixLength=\"yes\" " + |
| "with dfdl:lengthKind %s, but has different dfdl:lengthUnits than the element", |
| prefixLengthType, |
| prefixedLengthKind) |
| case _ => // ok |
| } |
| |
| schemaDefinitionUnless( |
| detachedElementDecl.primType.isSubtypeOf(NodeInfo.Integer), |
| "%s is specified as a dfdl:prefixLengthType, but its type xs:%s is not a subtype of xs:integer", |
| prefixLengthType, |
| detachedElementDecl.primType.toString.toLowerCase) |
| |
| schemaDefinitionWhen( |
| detachedElementDecl.isOutputValueCalc, |
| "%s is specified as a dfdl:prefixLengthType, but specifies dfdl:outputValueCalc", |
| prefixLengthType) |
| schemaDefinitionWhen( |
| detachedElementDecl.hasInitiator, |
| "%s is specified as a dfdl:prefixLengthType, but specifies a dfdl:initiator", |
| prefixLengthType) |
| schemaDefinitionWhen( |
| detachedElementDecl.hasTerminator, |
| "%s is specified as a dfdl:prefixLengthType, but specifies a dfdl:terminator", |
| prefixLengthType) |
| schemaDefinitionWhen( |
| detachedElementDecl.alignment != 1, |
| "%s is specified as a dfdl:prefixLengthType, but specifies a dfdl:alignment other than 1", |
| prefixLengthType) |
| schemaDefinitionWhen( |
| detachedElementDecl.leadingSkip != 0, |
| "%s is specified as a dfdl:prefixLengthType, but specifies a dfdl:leadingSkip other than 0", |
| prefixLengthType) |
| schemaDefinitionWhen( |
| detachedElementDecl.trailingSkip != 0, |
| "%s is specified as a dfdl:prefixLengthType, but specifies a dfdl:trailingSkip other than 0", |
| prefixLengthType) |
| |
| if (detachedElementDecl.lengthKind == LengthKind.Prefixed && |
| detachedElementDecl.prefixedLengthElementDecl.lengthKind == LengthKind.Prefixed) { |
| schemaDefinitionError( |
| "Nesting level for dfdl:prefixLengthType exceeds 1: %s > %s > %s > %s", |
| name, |
| prefixLengthType, |
| detachedElementDecl.prefixLengthType, |
| detachedElementDecl.prefixedLengthElementDecl.prefixLengthType) |
| } |
| |
| subset( |
| detachedElementDecl.lengthKind != LengthKind.Prefixed, |
| "Nested dfdl:lengthKind=\"prefixed\" is not supported.") |
| } |
| } |
| |
| final lazy val prefixedLengthAdjustmentInUnits: Long = prefixIncludesPrefixLength match { |
| case YesNo.Yes => { |
| // get the known length of the prefix element in lengthUnits |
| val pl = prefixedLengthElementDecl |
| pl.lengthKind match { |
| case LengthKind.Explicit => pl.lengthEv.optConstant.get // already in lengthUnits when explicit |
| case LengthKind.Implicit => |
| Assert.invariant(impliedRepresentation == Representation.Binary) |
| val len = pl.lengthEv.optConstant.get // always in bits when implicit |
| pl.lengthUnits match { |
| case LengthUnits.Bits => len |
| case LengthUnits.Bytes => len / 8 |
| case LengthUnits.Characters => Assert.impossible() |
| } |
| case _ => Assert.impossible() |
| } |
| } |
| case YesNo.No => 0 |
| } |
| final lazy val prefixedLengthBody = prefixedLengthElementDecl.parsedValue |
| |
| /** |
| * Quite tricky when we add padding or fill |
| * |
| * For complex types, there has to be a length defined. That's it. |
| * |
| * For simple types, we need the fill region processors |
| * to detect excess length |
| */ |
| final lazy val shouldAddPadding = { |
| val res = |
| if (isComplexType && |
| (this.maybeLengthEv.isDefined) && |
| (lengthUnits eq LengthUnits.Characters)) |
| maybeUnparseTargetLengthInBitsEv.isDefined // "pad" complex types. |
| else if (isDelimitedPrefixedPatternWithPadding) |
| true // simple type for unparse that needs to be padded. |
| else |
| // simple type, specified length. |
| (maybeUnparseTargetLengthInBitsEv.isDefined && |
| (justificationPad ne TextJustificationType.None)) // None if not text. |
| // if (res) |
| // println("%s should add padding.".format(this.diagnosticDebugName)) |
| // else |
| // println("%s NOT adding padding.".format(this.diagnosticDebugName)) |
| res |
| } |
| |
| /** |
| * We add fill to complex types of specified length so long as length units |
| * are bytes/bits. If characters then "pad" puts the characters on. |
| * |
| * We also add it to text elements of variable specified length again, unless |
| * length units are in characters. |
| */ |
| final lazy val shouldAddFill = { |
| val res = |
| (isComplexType && |
| isSpecifiedLengthForUnparsing && |
| (lengthUnits ne LengthUnits.Characters)) || |
| (isSimpleType && |
| isSpecifiedLengthForUnparsing && |
| couldBeVariableLengthInfoset && |
| (lengthUnits ne LengthUnits.Characters)) |
| res |
| } |
| |
| // /** |
| // * Means the representation could have varying sizes. |
| // * |
| // * Does not mean the infoset element might be bigger or smaller and |
| // * might get truncated or padded to fit a fixed size representation. |
| // * |
| // * This is about whether the box we're fitting it into is fixed or varying size. |
| // */ |
| // final protected lazy val isVariableLengthRep: Boolean = { |
| // isDelimitedPrefixedPattern || |
| // ((lengthKind eq LengthKind.Explicit) && |
| // !lengthEv.optConstant.isDefined) |
| // // |
| // // TODO: do we have to consider utf-8 situation for strings when |
| // // even though the length is constant, because it's utf-8 |
| // // the length units are characters but we don't know |
| // // how many bytes without knowing what characters are? |
| // // |
| // // This requires an Evaluatable, since it depends on charset. |
| // // |
| // // Also, whether we want this to be true/false depends on |
| // // context. If the context is a complex type with lengthUnits characters |
| // // then we're ok with utf-8 fixed number of characters. We're not |
| // // ok if the complex type has length its bytes, but contains an |
| // // element with length units bytes and charset utf-8. |
| // // |
| // } |
| |
| /** |
| * Means the specified length must, necessarily, be big enough to hold the representation |
| * so long as the value in the infoset is legal for the type. |
| * |
| * This does not include numeric range checking. So for example if you |
| * have an xs:unsignedInt but length is 3 bits, this will be true even though an |
| * integer value of greater than 7 cannot fit. |
| * |
| * Another way to think of this is that legal infoset values will have fixed |
| * length representations. |
| * |
| * This is a conservative analysis, meaning if true the property definitely |
| * holds, but if false it may mean we just couldn't tell if it holds or not. |
| * |
| * If this is true, then we never need to check how many bits were written when |
| * unparsing, because we know a legal value has to fit. If the value is illegal |
| * then we'll get an unparse error anyway. |
| * |
| * If this is false, then it's possible that the value, even a legal value, |
| * might not fit if the length is specified. We're unable to prove that all |
| * legal values WILL fit. |
| * |
| * A critical case is that fixed length binary integers should always |
| * return true here so that we're not doing excess length checks on them |
| * Or computing their value length unnecessarily. |
| */ |
| final protected lazy val mustBeFixedLengthInfoset = { |
| (isSimpleType && |
| (impliedRepresentation eq Representation.Binary) && |
| (primType ne PrimType.HexBinary) && |
| { |
| import NodeInfo._ |
| primType match { |
| case Float => true |
| case Double => true; |
| case n: Numeric.Kind => |
| binaryNumberRep match { |
| case BinaryNumberRep.Binary => true |
| case _ => false |
| } |
| case _ => true |
| } |
| } && |
| ((lengthKind eq LengthKind.Implicit) || |
| ((lengthKind eq LengthKind.Explicit) && |
| lengthEv.optConstant.isDefined)) // TODO: consider if delimiters matter, alignment, nil values,.... Are we excluding |
| // that stuff here? For example, if the element is nillable, but |
| // if isNilled, the representation will still be exactly the same |
| // length, then we'd like this to be true. |
| ) || |
| ( |
| // a fixed-length textual number, where a legal value |
| // (or nil value) must by definition be smaller than the |
| // fixed length. E.g., since the largest unsignedByte value |
| // is 256, and with textNumberPattern="###" the max width is 3 |
| // then so long as the fixed length is 3 or greater we know |
| // it's going to fit. |
| // |
| // Furthermore, if it is nillable, and the nilValue is "nil", which |
| // is also length 3, then it's going to fit. |
| // |
| // Also matters that if the textNumberPattern can output fewer |
| // characters than the fixed width, then padding must be enabled and a |
| // pad char specified, and that the padChar is the same width as |
| // a digit. (similarly if the nilValue was "-" we need to pad that too) |
| // |
| // This also assumes that in every charset encoding the digit |
| // characters are always the same width so the whole utf-8 |
| // variable-width encoding crud doesn't apply. |
| // |
| // However, textNumberPattern |
| // matters, because sometimes the lengths of positive and negative |
| // numbers can differ. So the fixed length has to be big enough |
| // for the largest possible textNumber matching the pattern. |
| // |
| false // TODO: implement this |
| ) || |
| (isComplexType && !isNillable && |
| ( |
| // |
| // recursively, the complex type can contain only isFixedLengthInfoset items |
| // but in addition, no delimiter can be varying width - so there can only be one |
| // delimiter value for each of these, nor can there be any variable-width |
| // alignment regions. |
| // |
| false // TODO: implement this |
| )) |
| } |
| |
| /** |
| * Means the infoset element could have varying length. |
| * |
| * And that means if there is a specified length box to fit it into |
| * that we have to check if it is too big/small for the box. |
| * |
| * So that means hexBinary, or representation text (for simple types) |
| * or any complex type unless everything in it is fixedLengthInfoset. |
| * |
| * So for example, a complex type containing only fixed length binary |
| * integers is itself fixed length. |
| */ |
| final protected lazy val couldBeVariableLengthInfoset: Boolean = { |
| !mustBeFixedLengthInfoset |
| } |
| |
| /** |
| * Only strings can be truncated, only if they are specified length, |
| * and only if truncateSpecifiedLengthString is 'yes'. |
| * |
| * Note that specified length might mean fixed length or variable (but specified) |
| * length. |
| */ |
| final protected lazy val isTruncatable = { |
| isSimpleType && |
| (primType eq PrimType.String) && |
| (truncateSpecifiedLengthString eq YesNo.Yes) && |
| isSpecifiedLengthForUnparsing |
| } |
| |
| /** |
| * Fixed length, or variable length with explicit length expression. |
| */ |
| final protected lazy val isSpecifiedLengthForUnparsing: Boolean = { |
| (isSimpleType && ( |
| (lengthKind eq LengthKind.Explicit) || |
| (lengthKind eq LengthKind.Implicit))) || |
| (isComplexType && |
| (lengthKind eq LengthKind.Explicit)) |
| } |
| |
| /** |
| * Check for excess length if it's variable length and we cannot truncate it. |
| */ |
| final lazy val shouldCheckExcessLength = { |
| val res = |
| couldBeVariableLengthInfoset && |
| !isTruncatable && |
| isSpecifiedLengthForUnparsing |
| res |
| } |
| |
| private lazy val rightFill = new RightFill(context) |
| |
| private lazy val elementUnused = new ElementUnused(context) |
| |
| private lazy val parsedNil = prod("parsedNil", NYI && isNillable && nilKind == NilKind.LogicalValue) { |
| nilElementInitiator ~ |
| captureLengthRegions(leftPadding, LogicalNilValue(this), rightPadding ~ rightFill) ~ |
| nilElementTerminator |
| } |
| |
| private def captureLengthRegions(leftPaddingArg: => Gram, bodyArg: => Gram, rightPadFillArg: => Gram) = { |
| lazy val leftPadding = leftPaddingArg |
| lazy val rightPadFill = rightPadFillArg |
| lazy val body = bodyArg |
| CaptureContentLengthStart(this) ~ |
| leftPadding ~ |
| CaptureValueLengthStart(this) ~ |
| body ~ |
| CaptureValueLengthEnd(this) ~ |
| rightPadFill ~ |
| CaptureContentLengthEnd(this) |
| } |
| |
| lazy val parsedValue = prod("parsedValue", isSimpleType) { |
| initiatorRegion ~ |
| valueMTA ~ |
| 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 = { |
| lazy val retry = retrySimpleType(value) // once only |
| schemaSet.sharedSimpleValueFactory.getShared( |
| shareKey, |
| captureLengthRegions(leftPadding, retry, 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. |
| * |
| * Evaporates for parsing, and when a dynamic simple type can't occur. |
| * |
| * Note: must be wrapped around the unparsed value only, not the framing. |
| * Because some framing is alignment regions, which are themselves possibly |
| * blocking/retrying. Setup of these must happen on the first pass, we cannot |
| * do setups of ustate/data-output-streams when unparsing the result of an OVC. |
| */ |
| private def retrySimpleType(allowedValueArg: => Gram) = { |
| lazy val allowedValue = allowedValueArg // once only |
| if (this.isOutputValueCalc) |
| SimpleTypeRetry(this, allowedValue) |
| else if (this.isInstanceOf[PrefixLengthQuasiElementDecl]) { |
| // |
| // If an element has a prefixed length, it |
| // means that the prefix length value will be calculated using a |
| // specified length unparser. This unparser works by suspending the |
| // prefix length unaprser, eventually calculating the length of the |
| // unparsed data, then setting the length to the value of the detached |
| // element. Since the prefixed length unparser will need to suspend until |
| // its data value is dynamically set, it needs this SimpleTypeRetry. |
| // |
| SimpleTypeRetry(this, allowedValue) |
| } else |
| allowedValue |
| } |
| |
| // Length is in bits, (size would be in bytes) (from DFDL Spec 12.3.3) |
| final protected lazy val implicitBinaryLengthInBits: Long = primType match { |
| case PrimType.Byte | PrimType.UnsignedByte => 8 |
| case PrimType.Short | PrimType.UnsignedShort => 16 |
| case PrimType.Float | PrimType.Int | PrimType.UnsignedInt | PrimType.Boolean => 32 |
| case PrimType.Double | PrimType.Long | PrimType.UnsignedLong => 64 |
| case PrimType.DateTime => binaryCalendarRep match { |
| case BinaryCalendarRep.BinarySeconds => 32 |
| case BinaryCalendarRep.BinaryMilliseconds => 64 |
| case _ => schemaDefinitionError("Length of binary data '" + primType.name + "' with binaryCalendarRep='" + binaryCalendarRep + "' cannot be determined implicitly.") |
| } |
| case _ => schemaDefinitionError("Length of binary data '" + primType.name + "' cannot be determined implicitly.") |
| } |
| |
| /** |
| * Property consistency check for called for all binary numbers |
| * |
| * Returns -1 if the binary number is not of known length,or is of a |
| * lengthKind inconsistent with knowing the length. |
| * |
| * SDE if the lengthKind is inconsistent with binary numbers, not yet implemented |
| * for binary numbers, or not supported by Daffodil. |
| */ |
| protected lazy val binaryNumberKnownLengthInBits: Long = lengthKind match { |
| case _ if optRepTypeElement.isDefined => optRepTypeElement.get.binaryNumberKnownLengthInBits |
| case LengthKind.Implicit => implicitBinaryLengthInBits |
| case LengthKind.Explicit if (lengthEv.isConstant) => explicitBinaryLengthInBits() |
| case LengthKind.Explicit => -1 // means must be computed at runtime. |
| case LengthKind.Prefixed => -1 // means must be computed at runtime |
| case LengthKind.Delimited => primType match { |
| case PrimType.DateTime | PrimType.Date | PrimType.Time => |
| if (binaryCalendarRep == BinaryCalendarRep.BinaryMilliseconds || binaryCalendarRep == BinaryCalendarRep.BinarySeconds) |
| SDE("lengthKind='delimited' only supported for packed binary formats.") |
| else -1 // only for packed binary data, length must be computed at runtime. |
| case _ => if (binaryNumberRep == BinaryNumberRep.Binary) SDE("lengthKind='delimited' only supported for packed binary formats.") |
| else -1 // only for packed binary data, length must be computed at runtime. |
| } |
| case LengthKind.Pattern => schemaDefinitionError("Binary data elements cannot have lengthKind='pattern'.") |
| case LengthKind.EndOfParent => schemaDefinitionError("Binary data elements cannot have lengthKind='endOfParent'.") |
| } |
| |
| private def explicitBinaryLengthInBits() = { |
| val lengthFromProp: JLong = lengthEv.optConstant.get |
| val nbits = lengthUnits match { |
| case LengthUnits.Bits => lengthFromProp.longValue() |
| case LengthUnits.Bytes => lengthFromProp.longValue() * 8 |
| case LengthUnits.Characters => SDE("The lengthUnits for the binary type %s must be either 'bits' or 'bytes'. Not 'characters'.", primType.name) |
| } |
| nbits |
| } |
| |
| private lazy val specifiedLengthHexBinary = prod("specifiedLengthHexBinary") { |
| lengthUnits match { |
| case LengthUnits.Bytes => HexBinarySpecifiedLength(this) |
| case LengthUnits.Bits => HexBinarySpecifiedLength(this) |
| case LengthUnits.Characters => SDE("lengthUnits='characters' is not valid for hexBinary.") |
| } |
| } |
| |
| private lazy val stringDelimitedEndOfData = prod("stringDelimitedEndOfData") { StringDelimitedEndOfData(this) } |
| |
| private lazy val stringValue = prod("stringValue") { stringPrim } |
| |
| private lazy val stringPrim = { |
| lengthKind match { |
| case LengthKind.Explicit => specifiedLength(StringOfSpecifiedLength(this)) |
| case LengthKind.Prefixed => specifiedLength(StringOfSpecifiedLength(this)) |
| case LengthKind.Delimited => stringDelimitedEndOfData |
| case LengthKind.Pattern => specifiedLength(StringOfSpecifiedLength(this)) |
| case LengthKind.Implicit => { |
| val pt = this.simpleType.primType |
| Assert.invariant(pt == PrimType.String) |
| specifiedLength(StringOfSpecifiedLength(this)) |
| } |
| case LengthKind.EndOfParent if isComplexType => notYetImplemented("lengthKind='endOfParent' for complex type") |
| case LengthKind.EndOfParent => notYetImplemented("lengthKind='endOfParent' for simple type") |
| case _ => SDE("Unimplemented lengthKind %s", lengthKind) |
| } |
| } |
| |
| private lazy val hexBinaryDelimitedEndOfData = prod("hexBinaryDelimitedEndOfData") { HexBinaryDelimitedEndOfData(this) } |
| |
| private lazy val hexBinaryLengthPattern = prod("hexBinaryLengthPattern") { new SpecifiedLengthPattern(this, new HexBinaryEndOfBitLimit(this)) } |
| |
| private lazy val hexBinaryLengthPrefixed = prod("hexBinaryLengthPrefixed") { new HexBinaryLengthPrefixed(this) } |
| |
| private lazy val hexBinaryValue = prod("hexBinaryValue") { |
| schemaDefinitionWhen(lengthUnits == LengthUnits.Characters, "Hex binary Numbers must have dfdl:lengthUnits of \"bits\" or \"bytes\".") |
| lengthKind match { |
| case LengthKind.Explicit => specifiedLengthHexBinary |
| case LengthKind.Implicit => specifiedLengthHexBinary |
| case LengthKind.Delimited => hexBinaryDelimitedEndOfData |
| case LengthKind.Pattern => hexBinaryLengthPattern |
| case LengthKind.Prefixed => hexBinaryLengthPrefixed |
| case LengthKind.EndOfParent if isComplexType => notYetImplemented("lengthKind='endOfParent' for complex type") |
| case LengthKind.EndOfParent => notYetImplemented("lengthKind='endOfParent' for simple type") |
| case _ => SDE("Unimplemented lengthKind %s", lengthKind) |
| } |
| } |
| |
| private lazy val specifiedLengthBlob = prod("specifiedLengthBlob") { |
| lengthUnits match { |
| case LengthUnits.Bytes => BlobSpecifiedLength(this) |
| case LengthUnits.Bits => BlobSpecifiedLength(this) |
| case LengthUnits.Characters => SDE("lengthUnits='characters' is not valid for blob data.") |
| } |
| } |
| |
| private lazy val blobValue = prod("blobValue") { |
| lengthKind match { |
| case LengthKind.Explicit => specifiedLengthBlob |
| case _ => SDE("objectKind='bytes' must have dfdl:lengthKind='explicit'") |
| } |
| } |
| |
| private lazy val clobValue = prod("clobValue") { |
| notYetImplemented("objectKind='chars'") |
| } |
| |
| private lazy val textNumber = textStandardNumber || textZonedNumber |
| |
| private lazy val textNonNumber = { |
| ConvertTextCombinator(this, stringValue, textConverter) |
| } |
| |
| private lazy val textStandardNumber = prod("textStandardNumber", textNumberRep == TextNumberRep.Standard) { |
| val converter = textStandardBaseDefaulted match { |
| case 10 => textConverter |
| case 2 | 8 | 16 => textStandardNonBaseTenConverter |
| case _ => Assert.impossible() |
| } |
| ConvertTextCombinator(this, stringValue, converter) |
| } |
| |
| private lazy val textZonedNumber = prod("textZonedNumber", textNumberRep == TextNumberRep.Zoned) { |
| ConvertZonedCombinator(this, stringValue, textZonedConverter) |
| } |
| |
| private lazy val textConverter = { |
| primType match { |
| case _: NodeInfo.Numeric.Kind => ConvertTextNumberPrim(this) |
| case PrimType.Boolean => ConvertTextBooleanPrim(this) |
| case PrimType.Date => ConvertTextDatePrim(this) |
| case PrimType.Time => ConvertTextTimePrim(this) |
| case PrimType.DateTime => ConvertTextDateTimePrim(this) |
| |
| case PrimType.HexBinary | PrimType.String | PrimType.AnyURI => |
| Assert.invariantFailed("textConverter not to be used for binary or string types") |
| } |
| } |
| |
| private lazy val textStandardNonBaseTenConverter = { |
| primType match { |
| case _: NodeInfo.Integer.Kind => ConvertNonBaseTenTextNumberPrim(this) |
| case _ => SDE("dfdl:textStandardBase=\"%s\" cannot be used with %s", textStandardBaseDefaulted, primType.globalQName) |
| } |
| } |
| |
| private lazy val textZonedConverter = { |
| primType match { |
| case _: NodeInfo.Decimal.Kind => ConvertZonedNumberPrim(this) |
| case _ => SDE("dfdl:textNumberRep=\"zoned\" cannot be used with %s", primType.globalQName) |
| } |
| } |
| |
| private lazy val bcdKnownLengthCalendar = prod("bcdKnownLengthCalendar", binaryCalendarRep == BinaryCalendarRep.Bcd) { |
| ConvertZonedCombinator(this, new BCDIntegerKnownLength(this, binaryNumberKnownLengthInBits), textConverter) |
| } |
| private lazy val bcdRuntimeLengthCalendar = prod("bcdRuntimeLengthCalendar", binaryCalendarRep == BinaryCalendarRep.Bcd) { |
| ConvertZonedCombinator(this, new BCDIntegerRuntimeLength(this), textConverter) |
| } |
| private lazy val bcdDelimitedLengthCalendar = prod("bcdDelimitedLengthCalendar", binaryCalendarRep == BinaryCalendarRep.Bcd) { |
| ConvertZonedCombinator(this, new BCDIntegerDelimitedEndOfData(this), textConverter) |
| } |
| private lazy val bcdPrefixedLengthCalendar = prod("bcdPrefixedLengthCalendar", binaryCalendarRep == BinaryCalendarRep.Bcd) { |
| ConvertZonedCombinator(this, new BCDIntegerPrefixedLength(this), textConverter) |
| } |
| |
| private lazy val ibm4690PackedKnownLengthCalendar = prod("ibm4690PackedKnownLengthCalendar", binaryCalendarRep == BinaryCalendarRep.Ibm4690Packed) { |
| ConvertZonedCombinator(this, new IBM4690PackedIntegerKnownLength(this, false, binaryNumberKnownLengthInBits), textConverter) |
| } |
| private lazy val ibm4690PackedRuntimeLengthCalendar = prod("ibm4690PackedRuntimeLengthCalendar", binaryCalendarRep == BinaryCalendarRep.Ibm4690Packed) { |
| ConvertZonedCombinator(this, new IBM4690PackedIntegerRuntimeLength(this, false), textConverter) |
| } |
| private lazy val ibm4690PackedDelimitedLengthCalendar = prod("ibm4690PackedDelimitedLengthCalendar", binaryCalendarRep == BinaryCalendarRep.Ibm4690Packed) { |
| ConvertZonedCombinator(this, new IBM4690PackedIntegerDelimitedEndOfData(this, false), textConverter) |
| } |
| private lazy val ibm4690PackedPrefixedLengthCalendar = prod("ibm4690PackedPrefixedLengthCalendar", binaryCalendarRep == BinaryCalendarRep.Ibm4690Packed) { |
| 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) |
| } |
| private lazy val packedRuntimeLengthCalendar = prod("packedRuntimeLengthCalendar", binaryCalendarRep == BinaryCalendarRep.Packed) { |
| ConvertZonedCombinator(this, new PackedIntegerRuntimeLength(this, false, packedSignCodes), textConverter) |
| } |
| private lazy val packedDelimitedLengthCalendar = prod("packedDelimitedLengthCalendar", binaryCalendarRep == BinaryCalendarRep.Packed) { |
| ConvertZonedCombinator(this, new PackedIntegerDelimitedEndOfData(this, false, packedSignCodes), textConverter) |
| } |
| private lazy val packedPrefixedLengthCalendar = prod("packedPrefixedLengthCalendar", binaryCalendarRep == BinaryCalendarRep.Packed) { |
| ConvertZonedCombinator(this, new PackedIntegerPrefixedLength(this, false, packedSignCodes), textConverter) |
| } |
| |
| def primType: PrimType |
| |
| protected final lazy val value = prod("value", isSimpleType) { |
| // TODO: Consider issues with matching a stopValue. Can't say isScalar here because |
| // this gets used for array contents also. |
| { |
| primType match { |
| case PrimType.String => stringValue |
| case PrimType.HexBinary => hexBinaryValue |
| case PrimType.AnyURI => { |
| val res = impliedRepresentation match { |
| case Representation.Binary => blobValue |
| case Representation.Text => clobValue |
| } |
| res |
| } |
| case _ => { |
| val res = impliedRepresentation match { |
| case Representation.Binary => binaryValue |
| case Representation.Text => textValue |
| } |
| res |
| } |
| } |
| } |
| } |
| |
| private lazy val staticBinaryFloatRep = { |
| subset(binaryFloatRepEv.isConstant, "Dynamic binaryFloatRep is not supported.") |
| binaryFloatRepEv.optConstant.get |
| } |
| |
| val ieee = BinaryFloatRep.Ieee |
| type BO = java.nio.ByteOrder |
| |
| private lazy val packedSignCodes = PackedSignCodes(binaryPackedSignCodes, binaryNumberCheckPolicy) |
| |
| private def binaryIntegerValue(isSigned: Boolean) = { |
| // |
| // Is it a single byte or smaller |
| // |
| if ((primType != PrimType.Byte) && |
| (binaryNumberKnownLengthInBits == -1 || |
| binaryNumberKnownLengthInBits > 8)) { |
| byteOrderRaw // must be defined or SDE |
| } |
| (binaryNumberRep, lengthKind, binaryNumberKnownLengthInBits) match { |
| case (BinaryNumberRep.Binary, LengthKind.Prefixed, _) => new BinaryIntegerPrefixedLength(this, isSigned) |
| case (BinaryNumberRep.Binary, _, -1) => new BinaryIntegerRuntimeLength(this, isSigned) |
| case (BinaryNumberRep.Binary, _, _) => new BinaryIntegerKnownLength(this, isSigned, binaryNumberKnownLengthInBits) |
| case (_, LengthKind.Implicit, _) => SDE("lengthKind='implicit' is not allowed with packed binary formats") |
| case (_, _, _) if ((binaryNumberKnownLengthInBits != -1) && (binaryNumberKnownLengthInBits % 4) != 0) => |
| SDE("The given length (%s bits) must be a multiple of 4 when using packed binary formats", binaryNumberKnownLengthInBits) |
| case (BinaryNumberRep.Packed, LengthKind.Delimited, -1) => new PackedIntegerDelimitedEndOfData(this, isSigned, packedSignCodes) |
| case (BinaryNumberRep.Packed, LengthKind.Prefixed, -1) => new PackedIntegerPrefixedLength(this, isSigned, packedSignCodes) |
| case (BinaryNumberRep.Packed, _, -1) => new PackedIntegerRuntimeLength(this, isSigned, packedSignCodes) |
| case (BinaryNumberRep.Packed, _, _) => new PackedIntegerKnownLength(this, isSigned, packedSignCodes, binaryNumberKnownLengthInBits) |
| case (BinaryNumberRep.Ibm4690Packed, LengthKind.Delimited, -1) => new IBM4690PackedIntegerDelimitedEndOfData(this, isSigned) |
| case (BinaryNumberRep.Ibm4690Packed, LengthKind.Prefixed, -1) => new IBM4690PackedIntegerPrefixedLength(this, isSigned) |
| case (BinaryNumberRep.Ibm4690Packed, _, -1) => new IBM4690PackedIntegerRuntimeLength(this, isSigned) |
| case (BinaryNumberRep.Ibm4690Packed, _, _) => new IBM4690PackedIntegerKnownLength(this, isSigned, binaryNumberKnownLengthInBits) |
| case (BinaryNumberRep.Bcd, _, _) => primType match { |
| case PrimType.Long | PrimType.Int | PrimType.Short | PrimType.Byte => SDE("%s is not an allowed type for bcd binary values", primType.name) |
| case _ => (lengthKind, binaryNumberKnownLengthInBits) match { |
| case (LengthKind.Delimited, -1) => new BCDIntegerDelimitedEndOfData(this) |
| case (LengthKind.Prefixed, -1) => new BCDIntegerPrefixedLength(this) |
| case (_, -1) => new BCDIntegerRuntimeLength(this) |
| case (_, _) => new BCDIntegerKnownLength(this, binaryNumberKnownLengthInBits) |
| } |
| } |
| } |
| } |
| |
| private lazy val binaryValue: Gram = { |
| Assert.invariant(primType != PrimType.String) |
| |
| schemaDefinitionWhen(lengthUnits == LengthUnits.Characters, "Binary Numbers must have dfdl:lengthUnits of \"bits\" or \"bytes\".") |
| |
| // We have to dispatch carefully here. We cannot force evaluation of properties |
| // that may not be necessary. E.g., float does not need property binaryNumberRep, so |
| // if our dispatch table uses that, it will create a false dependency on the property |
| // being defined. |
| // The DFDL spec has a section where it gives the precedence order of properties. |
| // This is in the spirit of that section. |
| val res: Gram = primType match { |
| |
| case PrimType.Byte | PrimType.Short | PrimType.Int | PrimType.Long | PrimType.Integer => { |
| binaryIntegerValue(true) |
| } |
| |
| case PrimType.UnsignedByte | PrimType.UnsignedShort | PrimType.UnsignedInt | PrimType.UnsignedLong | PrimType.NonNegativeInteger => { |
| binaryIntegerValue(false) |
| } |
| |
| case PrimType.Double | PrimType.Float => { |
| byteOrderRaw // is required. SDE if not defined |
| (primType, binaryNumberKnownLengthInBits, staticBinaryFloatRep) match { |
| case (_, -1, BinaryFloatRep.Ieee) => SDE("Floating point binary numbers may not have runtime-specified lengths.") |
| case (PrimType.Float, 32, BinaryFloatRep.Ieee) => new BinaryFloat(this) |
| case (PrimType.Float, n, BinaryFloatRep.Ieee) => SDE("binary xs:float must be 32 bits. Length in bits was %s.", n) |
| case (PrimType.Double, 64, BinaryFloatRep.Ieee) => new BinaryDouble(this) |
| case (PrimType.Double, n, BinaryFloatRep.Ieee) => SDE("binary xs:double must be 64 bits. Length in bits was %s.", n) |
| case (_, _, floatRep) => subsetError("binaryFloatRep='%s' not supported. Only binaryFloatRep='ieee'", floatRep.toString) |
| } |
| } |
| |
| case PrimType.Decimal => { |
| if (binaryDecimalVirtualPoint > tunable.maxBinaryDecimalVirtualPoint) |
| throw new TunableLimitExceededError(schemaFileLocation, "Property binaryDecimalVirtualPoint %s is greater than limit %s", binaryDecimalVirtualPoint, tunable.maxBinaryDecimalVirtualPoint) |
| if (binaryDecimalVirtualPoint < tunable.minBinaryDecimalVirtualPoint) |
| throw new TunableLimitExceededError(schemaFileLocation, "Property binaryDecimalVirtualPoint %s is less than limit %s", binaryDecimalVirtualPoint, tunable.minBinaryDecimalVirtualPoint) |
| if (binaryNumberKnownLengthInBits == -1 || |
| binaryNumberKnownLengthInBits > 8) byteOrderRaw // must have or SDE |
| |
| (binaryNumberRep, lengthKind, binaryNumberKnownLengthInBits) match { |
| case (BinaryNumberRep.Binary, LengthKind.Prefixed, _) => new BinaryDecimalPrefixedLength(this) |
| case (BinaryNumberRep.Binary, _, -1) => new BinaryDecimalRuntimeLength(this) |
| case (BinaryNumberRep.Binary, _, _) => new BinaryDecimalKnownLength(this, binaryNumberKnownLengthInBits) |
| case (_, LengthKind.Implicit, _) => SDE("lengthKind='implicit' is not allowed with packed binary formats") |
| case (_, _, _) if ((binaryNumberKnownLengthInBits != -1) && (binaryNumberKnownLengthInBits % 4) != 0) => |
| SDE("The given length (%s bits) must be a multiple of 4 when using packed binary formats", binaryNumberKnownLengthInBits) |
| case (BinaryNumberRep.Packed, LengthKind.Delimited, -1) => new PackedDecimalDelimitedEndOfData(this, packedSignCodes) |
| case (BinaryNumberRep.Packed, LengthKind.Prefixed, _) => new PackedDecimalPrefixedLength(this, packedSignCodes) |
| case (BinaryNumberRep.Packed, _, -1) => new PackedDecimalRuntimeLength(this, packedSignCodes) |
| case (BinaryNumberRep.Packed, _, _) => new PackedDecimalKnownLength(this, packedSignCodes, binaryNumberKnownLengthInBits) |
| case (BinaryNumberRep.Bcd, LengthKind.Delimited, -1) => new BCDDecimalDelimitedEndOfData(this) |
| case (BinaryNumberRep.Bcd, LengthKind.Prefixed, _) => new BCDDecimalPrefixedLength(this) |
| case (BinaryNumberRep.Bcd, _, -1) => new BCDDecimalRuntimeLength(this) |
| case (BinaryNumberRep.Bcd, _, _) => new BCDDecimalKnownLength(this, binaryNumberKnownLengthInBits) |
| case (BinaryNumberRep.Ibm4690Packed, LengthKind.Delimited, -1) => new IBM4690PackedDecimalDelimitedEndOfData(this) |
| case (BinaryNumberRep.Ibm4690Packed, LengthKind.Prefixed, _) => new IBM4690PackedDecimalPrefixedLength(this) |
| case (BinaryNumberRep.Ibm4690Packed, _, -1) => new IBM4690PackedDecimalRuntimeLength(this) |
| case (BinaryNumberRep.Ibm4690Packed, _, _) => new IBM4690PackedDecimalKnownLength(this, binaryNumberKnownLengthInBits) |
| } |
| } |
| |
| case PrimType.Boolean => { |
| lengthKind match { |
| case LengthKind.Prefixed => new BinaryBooleanPrefixedLength(this) |
| case _ => new BinaryBoolean(this) |
| } |
| } |
| |
| case PrimType.DateTime | PrimType.Date | PrimType.Time => { |
| (primType, binaryCalendarRep) match { |
| case (PrimType.DateTime, BinaryCalendarRep.BinarySeconds) => (lengthUnits, binaryNumberKnownLengthInBits) match { |
| case (LengthUnits.Bytes, 32) => new ConvertBinaryDateTimeSecMilliPrim(this, binaryNumberKnownLengthInBits) |
| case (_, 32) => SDE("lengthUnits must be 'bytes' when binaryCalendarRep='binarySeconds'") |
| case (_, n) => SDE("binary xs:dateTime must be 32 bits when binaryCalendarRep='binarySeconds'. Length in bits was %s.", n) |
| } |
| case (_, BinaryCalendarRep.BinarySeconds) => SDE("binaryCalendarRep='binarySeconds' is not allowed with type %s", primType.name) |
| case (PrimType.DateTime, BinaryCalendarRep.BinaryMilliseconds) => (lengthUnits, binaryNumberKnownLengthInBits) match { |
| case (LengthUnits.Bytes, 64) => new ConvertBinaryDateTimeSecMilliPrim(this, binaryNumberKnownLengthInBits) |
| case (_, 64) => SDE("lengthUnits must be 'bytes' when binaryCalendarRep='binaryMilliseconds'") |
| case (_, n) => SDE("binary xs:dateTime must be 64 bits when binaryCalendarRep='binaryMilliseconds'. Length in bits was %s.", n) |
| } |
| case (_, BinaryCalendarRep.BinaryMilliseconds) => SDE("binaryCalendarRep='binaryMilliseconds' is not allowed with type %s", primType.name) |
| case _ => { // Packed Decimal representations |
| if ((binaryNumberKnownLengthInBits != -1) && (binaryNumberKnownLengthInBits % 4) != 0) |
| SDE("The given length (%s bits) must be a multiple of 4 when using binaryCalendarRep='%s'.", binaryNumberKnownLengthInBits, binaryCalendarRep) |
| if (calendarPatternKind != CalendarPatternKind.Explicit) |
| SDE("calendarPatternKind must be 'explicit' when binaryCalendarRep='%s'", binaryCalendarRep) |
| |
| binaryCalendarRep match { |
| case (BinaryCalendarRep.Bcd) => { |
| (lengthKind, binaryNumberKnownLengthInBits) match { |
| case (LengthKind.Delimited, -1) => bcdDelimitedLengthCalendar |
| case (LengthKind.Prefixed, -1) => bcdPrefixedLengthCalendar |
| case (_, -1) => bcdRuntimeLengthCalendar |
| case (_, _) => bcdKnownLengthCalendar |
| } |
| } |
| case (BinaryCalendarRep.Ibm4690Packed) => { |
| (lengthKind, binaryNumberKnownLengthInBits) match { |
| case (LengthKind.Delimited, -1) => ibm4690PackedDelimitedLengthCalendar |
| case (LengthKind.Prefixed, -1) => ibm4690PackedPrefixedLengthCalendar |
| case (_, -1) => ibm4690PackedRuntimeLengthCalendar |
| case (_, _) => ibm4690PackedKnownLengthCalendar |
| } |
| } |
| case (BinaryCalendarRep.Packed) => { |
| (lengthKind, binaryNumberKnownLengthInBits) match { |
| case (LengthKind.Delimited, -1) => packedDelimitedLengthCalendar |
| case (LengthKind.Prefixed, -1) => packedPrefixedLengthCalendar |
| case (_, -1) => packedRuntimeLengthCalendar |
| case (_, _) => packedKnownLengthCalendar |
| } |
| } |
| case _ => notYetImplemented("Type %s when representation='binary' and binaryCalendarRep=%s", primType.name, binaryCalendarRep.toString) |
| } |
| } |
| } |
| } |
| |
| case _ => notYetImplemented("Type %s when representation='binary'", primType.name) |
| } |
| res |
| } |
| |
| private lazy val textValue: Gram = { |
| val pt = primType |
| Assert.invariant(pt != PrimType.String) |
| Assert.invariant(pt != PrimType.HexBinary) |
| Assert.invariant(pt != PrimType.AnyURI) |
| Assert.invariant(impliedRepresentation == Representation.Text) |
| schemaDefinitionWhen( |
| lengthKind == LengthKind.Implicit, |
| "Type %s cannot have lengthKind='implicit' when representation='text'", |
| pt.name) |
| |
| val res = primType match { |
| case _: NodeInfo.Numeric.Kind => textNumber |
| case PrimType.Boolean => textNonNumber |
| case PrimType.Date => textNonNumber |
| case PrimType.Time => textNonNumber |
| case PrimType.DateTime => textNonNumber |
| |
| case PrimType.HexBinary | PrimType.AnyURI | PrimType.String => |
| Assert.invariantFailed("types handled in 'value' grammer element") |
| } |
| res |
| } |
| |
| protected final lazy val empty = prod("empty", NYI && isEmptyAnObservableConcept) { EmptyGram } |
| |
| private lazy val nilElementInitiator = prod("nilElementInitiator", hasNilValueInitiator) { delimMTA ~ Initiator(this) } |
| private lazy val nilElementTerminator = prod("nilElementTerminator", hasNilValueTerminator) { delimMTA ~ Terminator(this) } |
| |
| private lazy val complexContent = complexType.group.termContentBody |
| |
| private lazy val isNilLit = isNillable && ((nilKind == NilKind.LiteralValue) || (nilKind == NilKind.LiteralCharacter)) |
| |
| /** |
| * In the below, we must have nilLitMTA because, in the case where it's textual, |
| * then to distinguish a lit nil from a value, we have to start at the same place. |
| */ |
| private lazy val nilLit = prod("nilLit", isNilLit) { |
| nilElementInitiator ~ |
| nilLitMTA ~ |
| 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) { |
| captureLengthRegions( |
| leftPadding, |
| specifiedLength(nilLitContent), |
| rightPadding ~ rightFill) |
| } |
| |
| private lazy val nilLitComplex = prod("nilLitComplex", isComplexType) { |
| // Note: the only allowed nil value for a complex type is ES. It's length will be zero always. (as of DFDL v1.0 - 2015-07-15) |
| schemaDefinitionUnless(this.hasESNilValue && cookedNilValuesForParse.length == 1, "Nillable complex type elements can only have '%ES;' as their dfdl:nilValue property.") |
| captureLengthRegions( |
| EmptyGram, |
| nilLitContent, |
| // |
| // Because nil complex can only be ES (e.g., length 0), there's no possible |
| // ElementUnused region after a nil. |
| EmptyGram) |
| |
| } |
| |
| /** |
| * mandatory text alignment for a literal nil value is only needed |
| * if there is no initiator that gets its own mandatory text alignment. |
| */ |
| private lazy val nilLitMTA = prod("nilLitMTA", isNilLit && !hasNilValueInitiator) { mtaBase } |
| |
| private lazy val nilLitContent = prod( |
| "nilLitContent", |
| isNillable && (nilKind == NilKind.LiteralValue || nilKind == NilKind.LiteralCharacter)) { |
| |
| nilKind match { |
| case NilKind.LiteralValue => { |
| lengthKind match { |
| case LengthKind.Delimited => LiteralNilDelimitedEndOfData(this) |
| case LengthKind.Pattern => LiteralValueNilOfSpecifiedLength(this) |
| case LengthKind.Explicit => LiteralValueNilOfSpecifiedLength(this) |
| case LengthKind.Implicit if isSimpleType => { |
| schemaDefinitionUnless(impliedRepresentation != Representation.Text, "LiteralValue Nils with lengthKind='implicit' cannot have representation='text'.") |
| LiteralValueNilOfSpecifiedLength(this) |
| } |
| case LengthKind.Implicit if isComplexType => |
| LiteralValueNilOfSpecifiedLength(this) |
| case LengthKind.Prefixed => LiteralValueNilOfSpecifiedLength(this) |
| case LengthKind.EndOfParent if isComplexType => notYetImplemented("lengthKind='endOfParent' for complex type") |
| case LengthKind.EndOfParent => notYetImplemented("lengthKind='endOfParent' for simple type") |
| } |
| } |
| case NilKind.LiteralCharacter => { |
| if (!isFixedLength) { SDE("dfdl:length must be fixed when nilKind='literalCharacter'.") } |
| |
| lengthKind match { |
| case LengthKind.Explicit => LiteralCharacterNilOfSpecifiedLength(this) |
| case LengthKind.Implicit if isSimpleType => LiteralCharacterNilOfSpecifiedLength(this) |
| case LengthKind.Implicit if isComplexType => Assert.invariantFailed("literal nil complex types aren't handled here.") |
| case LengthKind.Prefixed => SDE("nilKind='literalCharacter' is not valid for lengthKind='prefixed'") |
| case LengthKind.EndOfParent => SDE("nilKind='literalCharacter' is not valid for lengthKind='endOfParent'") |
| case LengthKind.Delimited => SDE("nilKind='literalCharacter' is not valid for lengthKind='delimited'") |
| case LengthKind.Pattern => SDE("nilKind='literalCharacter' is not valid for lengthKind='pattern'") |
| } |
| } |
| case NilKind.LogicalValue => notYetImplemented("nilLitContent nilKind='logicalValue'") |
| } |
| |
| } |
| |
| private def withDelimiterStack(body: => Gram) = { |
| if (hasDelimiters || immediatelyEnclosingModelGroup.exists(_.hasDelimiters)) DelimiterStackCombinatorElement(this, body) |
| else body |
| } |
| |
| private lazy val nilOrValue = prod("nilOrValue", isNillable) { // TODO: make it exclude emptyness once emptyness is implemented |
| SimpleNilOrValue(this, nilLit || parsedNil, parsedValue) |
| } |
| |
| private lazy val nonNillableParsedValue = prod("nonNilnonEmptyParsedValue", !isNillable) { |
| parsedValue |
| } |
| |
| private lazy val scalarDefaultableSimpleContent = prod("scalarDefaultableSimpleContent", isSimpleType) { |
| nilOrValue || nonNillableParsedValue |
| } |
| |
| /** |
| * Note: This must handle unspecified lengths, like lengthKind delimited, |
| * as well, by not enclosing the body in a specified length enforcer. |
| */ |
| private def specifiedLength(bodyArg: => Gram) = { |
| lazy val body = bodyArg |
| lazy val bitsMultiplier = lengthUnits match { |
| case LengthUnits.Bits => 1 |
| case LengthUnits.Bytes => 8 |
| case LengthUnits.Characters if knownEncodingIsFixedWidth => this.knownEncodingWidthInBits |
| case _ => 0 // zero means can't multiply to get width in bits. |
| } |
| val lk = lengthKind |
| lk match { |
| case LengthKind.Delimited => body |
| case LengthKind.Pattern => new SpecifiedLengthPattern(this, body) |
| case LengthKind.Explicit if bitsMultiplier != 0 => |
| new SpecifiedLengthExplicit(this, body, bitsMultiplier) |
| case LengthKind.Explicit => { |
| Assert.invariant(!knownEncodingIsFixedWidth) |
| Assert.invariant(lengthUnits eq LengthUnits.Characters) |
| new SpecifiedLengthExplicitCharacters(this, body) |
| } |
| case LengthKind.Prefixed if bitsMultiplier != 0 => |
| new SpecifiedLengthPrefixed(this, body, bitsMultiplier) |
| case LengthKind.Prefixed => { |
| Assert.invariant(!knownEncodingIsFixedWidth) |
| Assert.invariant(lengthUnits eq LengthUnits.Characters) |
| new SpecifiedLengthPrefixedCharacters(this, body) |
| } |
| case LengthKind.Implicit if isSimpleType && primType == PrimType.String && |
| encodingInfo.knownEncodingIsFixedWidth => { |
| // |
| // Important case to optimize |
| // If we can convert to a number of bits, then we should do so |
| // |
| val nBits = encodingInfo.knownFixedWidthEncodingInCharsToBits(this.maxLength.longValue) |
| new SpecifiedLengthImplicit(this, body, nBits) |
| } |
| case LengthKind.Implicit if isSimpleType && primType == PrimType.String => |
| new SpecifiedLengthImplicitCharacters(this, body, this.maxLength.longValue) |
| |
| case LengthKind.Implicit if isSimpleType && primType == PrimType.HexBinary => |
| new SpecifiedLengthImplicit(this, body, this.maxLength.longValue * bitsMultiplier) |
| case LengthKind.Implicit if isSimpleType && impliedRepresentation == Representation.Binary => |
| new SpecifiedLengthImplicit(this, body, implicitBinaryLengthInBits) |
| case LengthKind.Implicit if isComplexType => body // for complex types, implicit means "roll up from the bottom" |
| case LengthKind.EndOfParent if isComplexType => notYetImplemented("lengthKind='endOfParent' for complex type") |
| case LengthKind.EndOfParent => notYetImplemented("lengthKind='endOfParent' for simple type") |
| case _ => { |
| // TODO: implement other specified length like end of parent |
| // for now, no restriction |
| body |
| } |
| } |
| } |
| |
| private lazy val complexContentSpecifiedLength = prod("complexContentSpecifiedLength", isComplexType) { |
| 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) |
| |
| private lazy val scalarComplexContent = prod("scalarComplexContent", isComplexType) { |
| if (!nilLit.isEmpty) { |
| ComplexNilOrContent(this, nilLit, complexContentSpecifiedLength) |
| } else { |
| complexContentSpecifiedLength |
| } |
| } |
| |
| private lazy val hasDynamicEscapeScheme = this.optionEscapeScheme.isDefined && !this.optionEscapeScheme.get.escapeSchemeParseEv.isConstant |
| |
| private def withEscapeScheme(body: Gram) = { |
| if (hasDynamicEscapeScheme) DynamicEscapeSchemeCombinatorElement(this, body) |
| else body |
| } |
| |
| lazy val hasRepType = (isSimpleType && simpleType.optRepType.isDefined) |
| lazy val optRepType = if (hasRepType) Some(simpleType.optRepType.get) else None |
| lazy val optRepTypeElement = |
| if (isSimpleType && simpleType.optRepTypeElement.isDefined) |
| Some(simpleType.optRepTypeElement.get) |
| else |
| None |
| |
| /** |
| * the element left framing does not include the initiator nor the element right framing the terminator |
| */ |
| private lazy val alignAndSkipFraming = prod("alignAndSkipFraming") { |
| LeadingSkipRegion(this) ~ AlignmentFill(this) |
| } |
| |
| private lazy val elementLeftFraming = alignAndSkipFraming |
| |
| private lazy val elementRightFraming = prod("elementRightFraming") { TrailingSkipRegion(this) } |
| |
| lazy val enclosedElement = prod("enclosedElement") { |
| // |
| // not isScalar, because this is reused inside arrays |
| // that is, we're counting on reusuing this production for array elements |
| // which are enclosed by the enclosing array and model group. |
| // |
| // if we didn't reuse this way we'd have to reproduce much of the grammar |
| // for the array case and scalar case that is the same for both. |
| // |
| checkVariousPropertyconstraints |
| (inputValueCalcOption, outputValueCalcOption) match { |
| case (_: NotFound, _: NotFound) => scalarDefaultablePhysical |
| case (_: Found, _: NotFound) => inputValueCalcElement |
| case (_: NotFound, _: Found) => outputValueCalcElement |
| case _ => SDE("Element with both dfdl:inputValueCalc and dfdl:outputValueCalc is not allowed.") |
| } |
| } |
| |
| // |
| // Until empty detection is implemented, there really is no distinction between |
| // defaultable and non-defaulting elements. |
| // |
| protected final def enclosedElementNonDefault = enclosedElement |
| |
| private lazy val inputValueCalcPrim = InputValueCalc(self, inputValueCalcOption) |
| |
| protected final def ivcCompiledExpression = inputValueCalcPrim.expr |
| |
| private lazy val inputValueCalcElement = prod( |
| "inputValueCalcElement", |
| isSimpleType && inputValueCalcOption.isInstanceOf[Found]) { |
| // No framing surrounding inputValueCalc elements. |
| // Note that we need these elements even when unparsing, because they appear in the infoset |
| // as regular elements (most times), and so we have to have an unparser that consumes the corresponding events. |
| new ElementParseAndUnspecifiedLength(this, dfdlScopeBegin, |
| inputValueCalcPrim, dfdlScopeEnd, EmptyGram) |
| } |
| |
| protected final lazy val ovcCompiledExpression = { // ovcValueCalcObject.expr |
| val exprProp = outputValueCalcOption.asInstanceOf[Found] |
| val exprText = exprProp.value |
| val exprNamespaces = exprProp.location.namespaces |
| val qn = GlobalQName(Some("daf"), "outputValueCalc", XMLUtils.dafintURI) |
| val expr = ExpressionCompilers.AnyRef.compileExpression( |
| qn, primType, exprText, exprNamespaces, dpathCompileInfo, isEvaluatedAbove = false, self, dpathCompileInfo) |
| expr |
| } |
| |
| private lazy val outputValueCalcElement = |
| |
| prod( |
| "outputValueCalcElement", |
| isSimpleType && outputValueCalcOption.isInstanceOf[Found]) { |
| scalarDefaultablePhysical |
| } |
| |
| // Note: there is no such thing as defaultable complex content because you can't have a |
| // default value for a complex type element.... |
| // NOT TRUE: a defaultable complex type is one where everything within it is |
| // recursively defaultable and has no syntax. So you could recursively "parse" |
| // it, get default values for simple type elements in the complex type structure, |
| // yet consume zero bits. |
| // |
| // When parsing that's exactly what should happen. It should parse, come back with |
| // a complex type element, and have consumed no bits. |
| // |
| // When unparsing, we have to recognize that the complex type element matches |
| // this default value, and then choose to unparse nothing. |
| // |
| // TBD: is this actually required by DFDL? Perhaps it is legal to simply |
| // unparse the overall structure. When we unparse a simple type, and the |
| // representation happens to be empty (such as empty string) the question is |
| // are we to recognize this, and put down the emptyValueInitiator/Terminator based |
| // on the emptyValueDelimiterPolicy? That has to be right. In order for a complex |
| // type to be defaultable, and for us to output nothing (empty) corresponding |
| // to a complex type, then all the contained simple type elements must be empty |
| // and must have effectively no initiator nor terminator for empty values. |
| // |
| // But there are additional things like when empty string causes a Nil value to be |
| // created, or a zero to be created for a text number. |
| |
| private lazy val scalarDefaultablePhysical = prod("scalarDefaultablePhysical") { |
| |
| val elem = if (hasRepType) { |
| new ElementCombinator(this, dfdlScopeBegin, scalarDefaultableSimpleContent, dfdlScopeEnd, new TypeValueCalc(this)) |
| } else { |
| new ElementCombinator(this, elementLeftFraming ~ dfdlScopeBegin, |
| withDelimiterStack { |
| withEscapeScheme { |
| scalarDefaultableSimpleContent || scalarComplexContent |
| } |
| }, |
| elementRightFraming ~ dfdlScopeEnd) |
| } |
| elem |
| } |
| |
| private lazy val checkVariousPropertyconstraints: Unit = { |
| // |
| // check for consistency. If length units is bytes, and we're going to use the length facets |
| // of xs:string for implicit length, the encoding must be SBCS. Otherwise validation could fail when the |
| // number of characters in that many bytes doesn't satisfy the facet. |
| // |
| if (isSimpleType && |
| primType == PrimType.String && |
| lengthKind == LengthKind.Implicit && |
| lengthUnits == LengthUnits.Bytes) { |
| if (!isKnownEncoding) { |
| // |
| // TODO: this check is insisting on this being clear at compile time. But DFDL doesn't strictly speaking, require that. |
| // If encoding is runtime-valued, this check could be done at runtime. |
| // |
| SDE("dfdl:encoding is a runtime expression, but dfdl:lengthKind 'implicit' for type xs:string and dfdl:lengthUnits 'bytes' requires an explicit known single byte character set encoding (SBCS).") |
| } else if (knownEncodingWidthInBits != 8) { |
| SDE( |
| "dfdl:encoding '%s' is not a single-byte encoding, but dfdl:lengthKind 'implicit' for type xs:string and dfdl:lengthUnits 'bytes' a single byte character set encoding (SBCS) is required.", |
| knownEncodingName) |
| } |
| } |
| if (lengthKind != LengthKind.Explicit |
| && optionLengthRaw.isDefined |
| && !optionLengthRaw.isFromDefaultFormat) { |
| // |
| // this warning only applies if the dfdl:length is specified on the element, and |
| // not inherited from a default format. |
| // The NACHA format uses dfdl:length="0" in its default format so that some |
| // global elements can compile. |
| // |
| // JIRA DFDL-1690 |
| // |
| SDW(WarnID.InconsistentLengthKind, "dfdl:lengthKind '%s' is not consistent with dfdl:length specified (as %s). The dfdl:length will be ignored.", |
| lengthKind, |
| lengthExpr.prettyExpr) |
| } |
| if ((lengthKind == LengthKind.Explicit || lengthKind == LengthKind.Implicit) && |
| impliedRepresentation == Representation.Binary && |
| lengthUnits == LengthUnits.Characters) |
| SDE("Elements of dfdl:lengthKind '%s' cannot have dfdl:lengthUnits '%s' with binary representation.", lengthKind, lengthUnits) |
| (inputValueCalcOption, outputValueCalcOption) match { |
| case (_: Found, _: Found) => SDE("Cannot have both dfdl:inputValueCalc and dfdl:outputValueCalc on the same element.") |
| case _ => // ok |
| } |
| /* |
| * When lengthKind is explicit and length is a constant, it is a warning if |
| * the type is a type that respects minLength and maxLength, and the constant length |
| * is not in range. |
| */ |
| val isTypeUsingMinMaxLengthFacets = typeDef.typeNode match { |
| case s: NodeInfo.String.Kind => true |
| case s: NodeInfo.HexBinary.Kind => true |
| case _ => false |
| } |
| if ((lengthKind eq LengthKind.Explicit) && |
| isTypeUsingMinMaxLengthFacets && |
| optLengthConstant.isDefined) { |
| val len = optLengthConstant.get |
| val maxLengthLong = maxLength.longValueExact |
| val minLengthLong = minLength.longValueExact |
| def warn(m: String, value: Long): Unit = SDW(WarnID.FacetExplicitLengthOutOfRange, "Explicit dfdl:length of %s is out of range for facet %sLength='%s'.", len, m, value) |
| if (maxLengthLong != -1 && len > maxLengthLong) warn("max", maxLengthLong) |
| Assert.invariant(minLengthLong >= 0) |
| if (minLengthLong > 0 && len < minLengthLong) warn("min", minLengthLong) |
| } |
| |
| /* |
| * When length kind is explicit, and length is a constant, it is an SDE if |
| * the type is a type that uses dfdl:textOutputMinLength, and the length constant |
| * is not greater than or equal to that value. |
| */ |
| |
| val isTypeUsingTextOutputMinLength = typeDef.typeNode match { |
| case s: NodeInfo.String.Kind => false |
| case s: NodeInfo.HexBinary.Kind => false |
| case s: NodeInfo.AnySimpleType.Kind if (impliedRepresentation eq Representation.Text) && |
| this.textOutputMinLength > 0 => true |
| case _ => false |
| } |
| |
| if ((lengthKind eq LengthKind.Explicit) && |
| isTypeUsingTextOutputMinLength && |
| optLengthConstant.isDefined) { |
| val len = optLengthConstant.get |
| if (len < textOutputMinLength) |
| SDW( |
| WarnID.TextOutputMinLengthOutOfRange, |
| "Explicit dfdl:length of %s is out of range for dfdl:textOutputMinLength='%s'.", |
| len, |
| textOutputMinLength) |
| |
| } |
| } |
| |
| /** |
| * Mandatory text alignment or mta |
| * |
| * mta can only apply to things with encodings. No encoding, no MTA. |
| * |
| * In addition, it has to be textual data. Just because there's an encoding |
| * in the property environment shouldn't get you an MTA region. It has |
| * to be textual. |
| */ |
| protected final lazy val valueMTA = prod( |
| "mandatoryTextAlignment", |
| impliedRepresentation eq Representation.Text) { |
| mtaBase |
| } |
| |
| } |