diff --git a/jvm/src/test/scala/io/kaitai/struct/CalculateSeqSizes$Test.scala b/jvm/src/test/scala/io/kaitai/struct/CalculateSeqSizes$Test.scala new file mode 100644 index 000000000..f4129f91f --- /dev/null +++ b/jvm/src/test/scala/io/kaitai/struct/CalculateSeqSizes$Test.scala @@ -0,0 +1,159 @@ +package io.kaitai.struct + +import io.kaitai.struct.datatype.{BigEndian, BigBitEndian} +import io.kaitai.struct.datatype.DataType +import io.kaitai.struct.datatype.DataType._ +import io.kaitai.struct.exprlang.Ast +import io.kaitai.struct.format.{DynamicSized, FixedSized, MetaSpec, Sized, YamlAttrArgs} +import io.kaitai.struct.precompile.CalculateSeqSizes + +import org.scalatest.FunSpec +import org.scalatest.Matchers._ + +class CalculateSeqSizes$Test extends FunSpec { + private def parse( + typeName: Option[String], + size: Option[Int], + terminator: Option[Int], + contents: Option[Array[Byte]], + ): DataType = { + DataType.fromYaml( + typeName, + List(), + MetaSpec( + List(), // path + false, // isOpaque + None, // id + Some(BigEndian), // endian + Some(BigBitEndian), // bitEndian + Some("utf-8"), // encoding + false, // forceDebug + None, // opaqueTypes + List() // imports + ), + YamlAttrArgs( + size.map(s => Ast.expr.IntNum(s)), + false,// sizeEos + None, // encoding + terminator, + false,// include + false,// consume + false,// eosError + None, // padRight + contents, + None, // enumRef + None, // parent + None, // process + ) + ) + } + private def sizeof( + typeName: Option[String], + size: Option[Int], + terminator: Option[Int], + contents: Option[Array[Byte]], + ): Sized = { + CalculateSeqSizes.dataTypeBitsSize(parse(typeName, size, terminator, contents)) + } + /** Helper for testing built-in types */ + private def sizeof(typeName: String): Sized = { + sizeof(Some(typeName), None, None, None) + } + /** Helper for testing unsized built-in types which requires explicit size boundary. */ + private def sizeof(typeName: String, terminator: Int): Sized = { + sizeof(Some(typeName), None, Some(terminator), None) + } + /** Helper for testing the unnamed "bytes" built-in type defined implicitly via `size` key. */ + private def sizeof(size: Int): Sized = { + sizeof(None, Some(size), None, None) + } + /** Helper for testing the `contents` size. */ + private def sizeof(contents: Array[Byte]): Sized = { + sizeof(None, None, None, Some(contents)) + } + + describe("CalculateSeqSizes") { + it("built-in types has correct size") { + sizeof("s1") should be (FixedSized( 8)) + sizeof("s2") should be (FixedSized(16)) + sizeof("s4") should be (FixedSized(32)) + sizeof("s8") should be (FixedSized(64)) + + sizeof("s2be") should be (FixedSized(16)) + sizeof("s4be") should be (FixedSized(32)) + sizeof("s8be") should be (FixedSized(64)) + + sizeof("s2le") should be (FixedSized(16)) + sizeof("s4le") should be (FixedSized(32)) + sizeof("s8le") should be (FixedSized(64)) + + //----------------------------------------------------------------------- + + sizeof("u1") should be (FixedSized( 8)) + sizeof("u2") should be (FixedSized(16)) + sizeof("u4") should be (FixedSized(32)) + sizeof("u8") should be (FixedSized(64)) + + sizeof("u2be") should be (FixedSized(16)) + sizeof("u4be") should be (FixedSized(32)) + sizeof("u8be") should be (FixedSized(64)) + + sizeof("u2le") should be (FixedSized(16)) + sizeof("u4le") should be (FixedSized(32)) + sizeof("u8le") should be (FixedSized(64)) + + //----------------------------------------------------------------------- + + sizeof("f4") should be (FixedSized(32)) + sizeof("f8") should be (FixedSized(64)) + + sizeof("f4be") should be (FixedSized(32)) + sizeof("f8be") should be (FixedSized(64)) + + sizeof("f4le") should be (FixedSized(32)) + sizeof("f8le") should be (FixedSized(64)) + + //----------------------------------------------------------------------- + + sizeof("b1") should be (FixedSized(1)) + sizeof("b2") should be (FixedSized(2)) + sizeof("b3") should be (FixedSized(3)) + sizeof("b4") should be (FixedSized(4)) + sizeof("b5") should be (FixedSized(5)) + sizeof("b6") should be (FixedSized(6)) + sizeof("b7") should be (FixedSized(7)) + sizeof("b8") should be (FixedSized(8)) + sizeof("b9") should be (FixedSized(9)) + + sizeof("b2be") should be (FixedSized(2)) + sizeof("b3be") should be (FixedSized(3)) + sizeof("b4be") should be (FixedSized(4)) + sizeof("b5be") should be (FixedSized(5)) + sizeof("b6be") should be (FixedSized(6)) + sizeof("b7be") should be (FixedSized(7)) + sizeof("b8be") should be (FixedSized(8)) + sizeof("b9be") should be (FixedSized(9)) + + sizeof("b2le") should be (FixedSized(2)) + sizeof("b3le") should be (FixedSized(3)) + sizeof("b4le") should be (FixedSized(4)) + sizeof("b5le") should be (FixedSized(5)) + sizeof("b6le") should be (FixedSized(6)) + sizeof("b7le") should be (FixedSized(7)) + sizeof("b8le") should be (FixedSized(8)) + sizeof("b9le") should be (FixedSized(9)) + + //----------------------------------------------------------------------- + + sizeof("str", 0) should be (DynamicSized) + sizeof("strz" ) should be (DynamicSized) + + //TODO: Uncomment when https://github.com/kaitai-io/kaitai_struct/issues/799 + // will be implemented + // sizeof("bytes") should be (DynamicSized) + sizeof(10) should be (FixedSized(10*8))// size: 10 + + sizeof("abcdef".getBytes()) should be (FixedSized(6*8))// content: 'abcdef' + } + } +} diff --git a/shared/src/main/scala/io/kaitai/struct/datatype/DataType.scala b/shared/src/main/scala/io/kaitai/struct/datatype/DataType.scala index e2a5c0f07..c6bd57895 100644 --- a/shared/src/main/scala/io/kaitai/struct/datatype/DataType.scala +++ b/shared/src/main/scala/io/kaitai/struct/datatype/DataType.scala @@ -483,6 +483,13 @@ object DataType { private val ReFloatType = """f(4|8)(le|be)?""".r private val ReBitType = """b(\d+)(le|be)?""".r + /** + * @param dto Content of the `type` key or the case variant in case of `switch-on` type + * @param path YAML path to the type definition, for use in errors + * @param metaDef Default properties of the attribute from `meta` section of type definition + * @param arg Other properties of the attribute in the KSY definition, such as `size`, + `content`, `terminator`, etc. + */ def fromYaml( dto: Option[String], path: List[String], @@ -490,12 +497,12 @@ object DataType { arg: YamlAttrArgs ): DataType = { val r = dto match { - case None => + case None => // `type:` key is missing in the KSY definition arg.contents match { case Some(c) => BytesLimitType(Ast.expr.IntNum(c.length), None, false, None, arg.process) case _ => arg.getByteArrayType(path) } - case Some(dt) => dt match { + case Some(dt) => dt match {// type: dt case "u1" => Int1Type(false) case "s1" => Int1Type(true) case ReIntType(signStr, widthStr, endianStr) => diff --git a/shared/src/main/scala/io/kaitai/struct/format/ClassSpec.scala b/shared/src/main/scala/io/kaitai/struct/format/ClassSpec.scala index cc4fbc831..57c58f82b 100644 --- a/shared/src/main/scala/io/kaitai/struct/format/ClassSpec.scala +++ b/shared/src/main/scala/io/kaitai/struct/format/ClassSpec.scala @@ -21,10 +21,29 @@ case object GenericStructClassSpec extends ClassSpecLike { override def toDataType: DataType = CalcKaitaiStructType() } +/** + * Type that represent result of the `_sizeof` special property and `sizeof<>` + * meta-function. + */ sealed trait Sized +/** + * The size of type have no constant value. The examples is built-in unsized + * types: `str`, `strz`, and `bytes`. Those types has no natural size in contrary + * to the sized types, such as `u1` or `f4be`. + */ case object DynamicSized extends Sized +/** + * A marker object that indicates that size of the type has not been yet calculated + * and calculation should be performed when size will be requested. + */ case object NotCalculatedSized extends Sized +/** + * A marker object that indicates that calculation of the size of the type in the + * progress. If that object will be seen during calculation process it is mean that + * type is defined recursively. + */ case object StartedCalculationSized extends Sized +/** The size of type is `n` bytes. */ case class FixedSized(n: Int) extends Sized case class ClassSpec( diff --git a/shared/src/main/scala/io/kaitai/struct/precompile/CalculateSeqSizes.scala b/shared/src/main/scala/io/kaitai/struct/precompile/CalculateSeqSizes.scala index 5db0311cb..4537b3010 100644 --- a/shared/src/main/scala/io/kaitai/struct/precompile/CalculateSeqSizes.scala +++ b/shared/src/main/scala/io/kaitai/struct/precompile/CalculateSeqSizes.scala @@ -31,6 +31,12 @@ object CalculateSeqSizes { } } + /** + * Calculates size of the specified class instance for use in the `_sizeof` + * special property and `sizeof<>` meta-function. + * + * @param curClass + */ def getSeqSize(curClass: ClassSpec): Sized = { curClass.seqSize match { case DynamicSized | _: FixedSized =>