diff --git a/js/src/main/scala/io/kaitai/struct/format/JavaScriptClassSpecs.scala b/js/src/main/scala/io/kaitai/struct/format/JavaScriptClassSpecs.scala index b95b988a6..62e0eee33 100644 --- a/js/src/main/scala/io/kaitai/struct/format/JavaScriptClassSpecs.scala +++ b/js/src/main/scala/io/kaitai/struct/format/JavaScriptClassSpecs.scala @@ -13,9 +13,9 @@ class JavaScriptClassSpecs(importer: JavaScriptImporter, firstSpec: ClassSpec) val MODE_REL = "rel" val MODE_ABS = "abs" - override def importRelative(name: String, path: List[String], inFile: Option[String]): Future[Option[ClassSpec]] = + override def importRelative(name: String, path: List[String], inFile: String): Future[Option[ClassSpec]] = doImport(name, path, MODE_REL) - override def importAbsolute(name: String, path: List[String], inFile: Option[String]): Future[Option[ClassSpec]] = + override def importAbsolute(name: String, path: List[String], inFile: String): Future[Option[ClassSpec]] = doImport(name, path, MODE_ABS) def doImport(name: String, path: List[String], mode: String): Future[Option[ClassSpec]] = { diff --git a/jvm/src/main/scala/io/kaitai/struct/formats/JavaClassSpecs.scala b/jvm/src/main/scala/io/kaitai/struct/formats/JavaClassSpecs.scala index d5df4554a..49d5cf4f6 100644 --- a/jvm/src/main/scala/io/kaitai/struct/formats/JavaClassSpecs.scala +++ b/jvm/src/main/scala/io/kaitai/struct/formats/JavaClassSpecs.scala @@ -27,14 +27,14 @@ class JavaClassSpecs(relPath: String, absPaths: Seq[String], firstSpec: ClassSpe private val relFiles: concurrent.Map[String, ClassSpec] = new ConcurrentHashMap[String, ClassSpec]().asScala private val absFiles: concurrent.Map[String, ClassSpec] = new ConcurrentHashMap[String, ClassSpec]().asScala - override def importRelative(name: String, path: List[String], inFile: Option[String]): Future[Option[ClassSpec]] = Future { + override def importRelative(name: String, path: List[String], inFile: String): Future[Option[ClassSpec]] = Future { Log.importOps.info(() => s".. importing relative $name") JavaClassSpecs.cached(path, inFile, relFiles, name, (_) => JavaKSYParser.fileNameToSpec(s"$relPath/$name.ksy") ) } - override def importAbsolute(name: String, path: List[String], inFile: Option[String]): Future[Option[ClassSpec]] = Future { + override def importAbsolute(name: String, path: List[String], inFile: String): Future[Option[ClassSpec]] = Future { Log.importOps.info(() => s".. importing absolute $name") JavaClassSpecs.cached(path, inFile, absFiles, name, tryAbsolutePaths) } @@ -62,7 +62,7 @@ class JavaClassSpecs(relPath: String, absPaths: Seq[String], firstSpec: ClassSpe object JavaClassSpecs { def cached( path: List[String], - inFile: Option[String], + inFile: String, cacheMap: mutable.Map[String, ClassSpec], name: String, importOp: (String) => ClassSpec @@ -80,7 +80,7 @@ object JavaClassSpecs { cacheMap(name) = spec Some(spec) } catch { - case err: Throwable => throw ErrorInInput(err, path, inFile).toException + case err: Throwable => throw ErrorInInput(err, path, Some(inFile)).toException } } } diff --git a/jvm/src/test/scala/io/kaitai/struct/ClassTypeProvider$Test.scala b/jvm/src/test/scala/io/kaitai/struct/ClassTypeProvider$Test.scala new file mode 100644 index 000000000..2e3ff390c --- /dev/null +++ b/jvm/src/test/scala/io/kaitai/struct/ClassTypeProvider$Test.scala @@ -0,0 +1,891 @@ +package io.kaitai.struct + +import java.util.NoSuchElementException +import io.kaitai.struct.exprlang.Ast +import io.kaitai.struct.format.{ClassSpec, ClassSpecs} +import io.kaitai.struct.formats.{JavaClassSpecs, JavaKSYParser} +import io.kaitai.struct.precompile.{EnumNotFoundError, MarkupClassNames, TypeNotFoundError} +import org.scalatest.funspec.AnyFunSpec +import org.scalatest.matchers.should.Matchers._ + +class ClassTypeProvider$Test extends AnyFunSpec { + val root = ClassSpec.fromYaml(JavaKSYParser.stringToYaml(""" + meta: + id: root + enums: + e: {} # e_root + types: + child_1: + types: + one: # child_11 + enums: + e: {} # e_11 + two: # child_12 + enums: + e: {} # e_12 + types: + one: {} # child_121 + two: {} # child_122 + child_2: + enums: + e: {} # e_2 + types: + one: {} # child_21 + two: {} # child_22 + """), None) + val specs = new JavaClassSpecs("", Seq(), root) + // Calculates full class names needed for work of the provider + new MarkupClassNames(specs).run() + + val child_1 = root.types.get("child_1").getOrElse(throw new NoSuchElementException("'child_1' not found")) + val child_2 = root.types.get("child_2").getOrElse(throw new NoSuchElementException("'child_2' not found")) + + val child_11 = child_1.types.get("one").getOrElse(throw new NoSuchElementException("'child_11' not found")) + val child_12 = child_1.types.get("two").getOrElse(throw new NoSuchElementException("'child_12' not found")) + + val child_21 = child_2.types.get("one").getOrElse(throw new NoSuchElementException("'child_21' not found")) + val child_22 = child_2.types.get("two").getOrElse(throw new NoSuchElementException("'child_22' not found")) + + val child_121 = child_12.types.get("one").getOrElse(throw new NoSuchElementException("'child_121' not found")) + val child_122 = child_12.types.get("two").getOrElse(throw new NoSuchElementException("'child_122' not found")) + + describe("resolveTypeName") { + describe("in 'root' context") { + val resolver = new ClassTypeProvider(specs, root) + + it("resolves 'root'") { + resolver.resolveTypeName(root, "root") should be(root) // self-reference + } + + it("doesn't resolve 'one'") { + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveTypeName(root, "one") + thrown.getMessage should be("unable to find type 'one', searching from 'root'") + } + + it("doesn't resolve 'two'") { + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveTypeName(root, "two") + thrown.getMessage should be("unable to find type 'two', searching from 'root'") + } + + it("doesn't resolve 'unknown'") { + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveTypeName(root, "unknown") + thrown.getMessage should be("unable to find type 'unknown', searching from 'root'") + } + } + + describe("in 'child_1' context") { + val resolver = new ClassTypeProvider(specs, root) + resolver.nowClass = child_1 // Influences the error messages ("searching from '...'" part) + + it("resolves 'root'") { + resolver.resolveTypeName(child_1, "root") should be(root) + } + + it("resolves 'one'") { + resolver.resolveTypeName(child_1, "one") should be(child_11) + } + + it("resolves 'two'") { + resolver.resolveTypeName(child_1, "two") should be(child_12) + } + + it("doesn't resolve 'unknown'") { + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveTypeName(child_1, "unknown") + thrown.getMessage should be("unable to find type 'unknown', searching from 'root::child_1'") + } + } + + describe("in 'child_2' context") { + val resolver = new ClassTypeProvider(specs, root) + resolver.nowClass = child_2 // Influences the error messages ("searching from '...'" part) + + it("resolves 'root'") { + resolver.resolveTypeName(child_2, "root") should be(root) + } + + it("resolves 'one'") { + resolver.resolveTypeName(child_2, "one") should be(child_21) + } + + it("resolves 'two'") { + resolver.resolveTypeName(child_2, "two") should be(child_22) + } + + it("doesn't resolve 'unknown'") { + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveTypeName(child_2, "unknown") + thrown.getMessage should be("unable to find type 'unknown', searching from 'root::child_2'") + } + } + + describe("in 'child_11' context") { + val resolver = new ClassTypeProvider(specs, root) + resolver.nowClass = child_11 // Influences the error messages ("searching from '...'" part) + + it("resolves 'root'") { + resolver.resolveTypeName(child_11, "root") should be(root) + } + + it("resolves 'one'") { + resolver.resolveTypeName(child_11, "one") should be(child_11) // self-reference + } + + it("resolves 'two'") { + resolver.resolveTypeName(child_11, "two") should be(child_12) + } + + it("doesn't resolve 'unknown'") { + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveTypeName(child_11, "unknown") + thrown.getMessage should be("unable to find type 'unknown', searching from 'root::child_1::one'") + } + } + + describe("in 'child_12' context") { + val resolver = new ClassTypeProvider(specs, root) + resolver.nowClass = child_12 // Influences the error messages ("searching from '...'" part) + + it("resolves 'root'") { + resolver.resolveTypeName(child_12, "root") should be(root) + } + + it("resolves 'one'") { + resolver.resolveTypeName(child_12, "one") should be(child_121) + } + + it("resolves 'two'") { + resolver.resolveTypeName(child_12, "two") should be(child_12) // self-reference + } + + it("doesn't resolve 'unknown'") { + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveTypeName(child_12, "unknown") + thrown.getMessage should be("unable to find type 'unknown', searching from 'root::child_1::two'") + } + } + + describe("in 'child_21' context") { + val resolver = new ClassTypeProvider(specs, root) + resolver.nowClass = child_21 // Influences the error messages ("searching from '...'" part) + + it("resolves 'root'") { + resolver.resolveTypeName(child_21, "root") should be(root) + } + + it("resolves 'one'") { + resolver.resolveTypeName(child_21, "one") should be(child_21) // self-reference + } + + it("resolves 'two'") { + resolver.resolveTypeName(child_21, "two") should be(child_22) + } + + it("doesn't resolve 'unknown'") { + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveTypeName(child_21, "unknown") + thrown.getMessage should be("unable to find type 'unknown', searching from 'root::child_2::one'") + } + } + + describe("in 'child_22' context") { + val resolver = new ClassTypeProvider(specs, root) + resolver.nowClass = child_22 // Influences the error messages ("searching from '...'" part) + + it("resolves 'root'") { + resolver.resolveTypeName(child_22, "root") should be(root) + } + + it("resolves 'one'") { + resolver.resolveTypeName(child_22, "one") should be(child_21) + } + + it("resolves 'two'") { + resolver.resolveTypeName(child_22, "two") should be(child_22) // self-reference + } + + it("doesn't resolve 'unknown'") { + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveTypeName(child_22, "unknown") + thrown.getMessage should be("unable to find type 'unknown', searching from 'root::child_2::two'") + } + } + + describe("in 'child_121' context") { + val resolver = new ClassTypeProvider(specs, root) + resolver.nowClass = child_121 // Influences the error messages ("searching from '...'" part) + + it("resolves 'root'") { + resolver.resolveTypeName(child_121, "root") should be(root) + } + + it("resolves 'one'") { + resolver.resolveTypeName(child_121, "one") should be(child_121) // self-reference + } + + it("resolves 'two'") { + resolver.resolveTypeName(child_121, "two") should be(child_12) + } + + it("doesn't resolve 'unknown'") { + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveTypeName(child_121, "unknown") + thrown.getMessage should be("unable to find type 'unknown', searching from 'root::child_1::two::one'") + } + } + + describe("in 'child_122' context") { + val resolver = new ClassTypeProvider(specs, root) + resolver.nowClass = child_122 // Influences the error messages ("searching from '...'" part) + + it("resolves 'root'") { + resolver.resolveTypeName(child_122, "root") should be(root) + } + + it("resolves 'one'") { + resolver.resolveTypeName(child_122, "one") should be(child_121) + } + + it("resolves 'two'") { + resolver.resolveTypeName(child_122, "two") should be(child_122) // self-reference + } + + it("doesn't resolve 'unknown'") { + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveTypeName(child_122, "unknown") + thrown.getMessage should be("unable to find type 'unknown', searching from 'root::child_1::two::two'") + } + } + } + + describe("resolveTypePath") { + describe("in 'root' context") { + val resolver = new ClassTypeProvider(specs, root) + + it("resolves empty path") { + resolver.resolveTypePath(root, Seq()) should be(root) // self-reference + } + + it("resolves 'root'") { + resolver.resolveTypePath(root, Seq("root")) should be(root) // self-reference + } + + it("doesn't resolve 'one'") { + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveTypePath(root, Seq("one")) + thrown.getMessage should be("unable to find type 'one', searching from 'root'") + } + + it("doesn't resolve 'one::two'") { + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveTypePath(root, Seq("one", "two")) + thrown.getMessage should be("unable to find type 'one', searching from 'root'") + } + + it("doesn't resolve 'one::unknown'") { + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveTypePath(root, Seq("one", "unknown")) + thrown.getMessage should be("unable to find type 'one', searching from 'root'") + } + + it("doesn't resolve 'two'") { + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveTypePath(root, Seq("two")) + thrown.getMessage should be("unable to find type 'two', searching from 'root'") + } + + it("doesn't resolve 'two::one'") { + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveTypePath(root, Seq("two", "one")) + thrown.getMessage should be("unable to find type 'two', searching from 'root'") + } + + it("doesn't resolve 'two::unknown'") { + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveTypePath(root, Seq("two", "unknown")) + thrown.getMessage should be("unable to find type 'two', searching from 'root'") + } + } + + describe("in 'child_1' context") { + val resolver = new ClassTypeProvider(specs, root) + resolver.nowClass = child_1 // Influences the error messages ("searching from '...'" part) + + it("resolves empty path") { + resolver.resolveTypePath(child_1, Seq()) should be(child_1) // self-reference + } + + it("resolves 'root'") { + resolver.resolveTypePath(child_1, Seq("root")) should be(root) + } + + it("resolves 'one'") { + resolver.resolveTypePath(child_1, Seq("one")) should be(child_11) + } + + it("doesn't resolve 'one::two'") { + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveTypePath(child_1, Seq("one", "two")) + thrown.getMessage should be("unable to find type 'two' in 'root::child_1::one'") + } + + it("doesn't resolve 'one::unknown'") { + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveTypePath(child_1, Seq("one", "unknown")) + thrown.getMessage should be("unable to find type 'unknown' in 'root::child_1::one'") + } + + it("resolves 'two'") { + resolver.resolveTypePath(child_1, Seq("two")) should be(child_12) + } + + it("resolves 'two::one'") { + resolver.resolveTypePath(child_1, Seq("two", "one")) should be(child_121) + } + + it("doesn't resolve 'two::unknown'") { + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveTypePath(child_1, Seq("two", "unknown")) + thrown.getMessage should be("unable to find type 'unknown' in 'root::child_1::two'") + } + } + + describe("in 'child_2' context") { + val resolver = new ClassTypeProvider(specs, root) + resolver.nowClass = child_2 // Influences the error messages ("searching from '...'" part) + + it("resolves empty path") { + resolver.resolveTypePath(child_2, Seq()) should be(child_2) // self-reference + } + + it("resolves 'root'") { + resolver.resolveTypePath(child_2, Seq("root")) should be(root) + } + + it("resolves 'one'") { + resolver.resolveTypePath(child_2, Seq("one")) should be(child_21) + } + + it("doesn't resolve 'one::two'") { + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveTypePath(child_2, Seq("one", "two")) + thrown.getMessage should be("unable to find type 'two' in 'root::child_2::one'") + } + + it("doesn't resolve 'one::unknown'") { + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveTypePath(child_2, Seq("one", "unknown")) + thrown.getMessage should be("unable to find type 'unknown' in 'root::child_2::one'") + } + + it("resolves 'two'") { + resolver.resolveTypePath(child_2, Seq("two")) should be(child_22) + } + + it("doesn't resolve 'two::one'") { + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveTypePath(child_2, Seq("two", "one")) + thrown.getMessage should be("unable to find type 'one' in 'root::child_2::two'") + } + + it("doesn't resolve 'two::unknown'") { + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveTypePath(child_2, Seq("two", "unknown")) + thrown.getMessage should be("unable to find type 'unknown' in 'root::child_2::two'") + } + } + + describe("in 'child_11' context") { + val resolver = new ClassTypeProvider(specs, root) + resolver.nowClass = child_11 // Influences the error messages ("searching from '...'" part) + + it("resolves empty path") { + resolver.resolveTypePath(child_11, Seq()) should be(child_11) // self-reference + } + + it("resolves 'root'") { + resolver.resolveTypePath(child_11, Seq("root")) should be(root) + } + + it("resolves 'one'") { + resolver.resolveTypePath(child_11, Seq("one")) should be(child_11) // self-reference + } + + it("doesn't resolve 'one::two'") { + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveTypePath(child_11, Seq("one", "two")) + thrown.getMessage should be("unable to find type 'two' in 'root::child_1::one'") + } + + it("doesn't resolve 'one::unknown'") { + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveTypePath(child_11, Seq("one", "unknown")) + thrown.getMessage should be("unable to find type 'unknown' in 'root::child_1::one'") + } + + it("resolves 'two'") { + resolver.resolveTypePath(child_11, Seq("two")) should be(child_12) + } + + it("resolves 'two::one'") { + resolver.resolveTypePath(child_11, Seq("two", "one")) should be(child_121) + } + + it("doesn't resolve 'two::unknown'") { + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveTypePath(child_11, Seq("two", "unknown")) + thrown.getMessage should be("unable to find type 'unknown' in 'root::child_1::two'") + } + } + + describe("in 'child_12' context") { + val resolver = new ClassTypeProvider(specs, root) + resolver.nowClass = child_12 // Influences the error messages ("searching from '...'" part) + + it("resolves empty path") { + resolver.resolveTypePath(child_12, Seq()) should be(child_12) // self-reference + } + + it("resolves 'root'") { + resolver.resolveTypePath(child_12, Seq("root")) should be(root) + } + + it("resolves 'one'") { + resolver.resolveTypePath(child_12, Seq("one")) should be(child_121) + } + + it("doesn't resolve 'one::two'") { + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveTypePath(child_12, Seq("one", "two")) + thrown.getMessage should be("unable to find type 'two' in 'root::child_1::two::one'") + } + + it("doesn't resolve 'one::unknown'") { + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveTypePath(child_12, Seq("one", "unknown")) + thrown.getMessage should be("unable to find type 'unknown' in 'root::child_1::two::one'") + } + + it("resolves 'two'") { + resolver.resolveTypePath(child_12, Seq("two")) should be(child_12) // self-reference + } + + it("resolves 'two::one'") { + resolver.resolveTypePath(child_12, Seq("two", "one")) should be(child_121) + } + + it("doesn't resolve 'two::unknown'") { + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveTypePath(child_12, Seq("two", "unknown")) + thrown.getMessage should be("unable to find type 'unknown' in 'root::child_1::two'") + } + } + + describe("in 'child_21' context") { + val resolver = new ClassTypeProvider(specs, root) + resolver.nowClass = child_21 // Influences the error messages ("searching from '...'" part) + + it("resolves empty path") { + resolver.resolveTypePath(child_21, Seq()) should be(child_21) // self-reference + } + + it("resolves 'root'") { + resolver.resolveTypePath(child_21, Seq("root")) should be(root) + } + + it("resolves 'one'") { + resolver.resolveTypePath(child_21, Seq("one")) should be(child_21) // self-reference + } + + it("doesn't resolve 'one::two'") { + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveTypePath(child_21, Seq("one", "two")) + thrown.getMessage should be("unable to find type 'two' in 'root::child_2::one'") + } + + it("doesn't resolve 'one::unknown'") { + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveTypePath(child_21, Seq("one", "unknown")) + thrown.getMessage should be("unable to find type 'unknown' in 'root::child_2::one'") + } + + it("resolves 'two'") { + resolver.resolveTypePath(child_21, Seq("two")) should be(child_22) + } + + it("doesn't resolve 'two::one'") { + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveTypePath(child_21, Seq("two", "one")) + thrown.getMessage should be("unable to find type 'one' in 'root::child_2::two'") + } + + it("doesn't resolve 'two::unknown'") { + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveTypePath(child_21, Seq("two", "unknown")) + thrown.getMessage should be("unable to find type 'unknown' in 'root::child_2::two'") + } + } + + describe("in 'child_22' context") { + val resolver = new ClassTypeProvider(specs, root) + resolver.nowClass = child_22 // Influences the error messages ("searching from '...'" part) + + it("resolves empty path") { + resolver.resolveTypePath(child_22, Seq()) should be(child_22) // self-reference + } + + it("resolves 'root'") { + resolver.resolveTypePath(child_22, Seq("root")) should be(root) + } + + it("resolves 'one'") { + resolver.resolveTypePath(child_22, Seq("one")) should be(child_21) + } + + it("doesn't resolve 'one::two'") { + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveTypePath(child_22, Seq("one", "two")) + thrown.getMessage should be("unable to find type 'two' in 'root::child_2::one'") + } + + it("doesn't resolve 'one::unknown'") { + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveTypePath(child_22, Seq("one", "unknown")) + thrown.getMessage should be("unable to find type 'unknown' in 'root::child_2::one'") + } + + it("resolves 'two'") { + resolver.resolveTypePath(child_22, Seq("two")) should be(child_22) // self-reference + } + + it("doesn't resolve 'two::one'") { + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveTypePath(child_22, Seq("two", "one")) + thrown.getMessage should be("unable to find type 'one' in 'root::child_2::two'") + } + + it("doesn't resolve 'two::unknown'") { + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveTypePath(child_22, Seq("two", "unknown")) + thrown.getMessage should be("unable to find type 'unknown' in 'root::child_2::two'") + } + } + + describe("in 'child_121' context") { + val resolver = new ClassTypeProvider(specs, root) + resolver.nowClass = child_121 // Influences the error messages ("searching from '...'" part) + + it("resolves empty path") { + resolver.resolveTypePath(child_121, Seq()) should be(child_121) // self-reference + } + + it("resolves 'root'") { + resolver.resolveTypePath(child_121, Seq("root")) should be(root) + } + + it("resolves 'one'") { + resolver.resolveTypePath(child_121, Seq("one")) should be(child_121) // self-reference + } + + it("doesn't resolve 'one::two'") { + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveTypePath(child_121, Seq("one", "two")) + thrown.getMessage should be("unable to find type 'two' in 'root::child_1::two::one'") + } + + it("doesn't resolve 'one::unknown'") { + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveTypePath(child_121, Seq("one", "unknown")) + thrown.getMessage should be("unable to find type 'unknown' in 'root::child_1::two::one'") + } + + it("resolves 'two'") { + resolver.resolveTypePath(child_121, Seq("two")) should be(child_12) + } + + it("doesn't resolve 'two::one'") { + resolver.resolveTypePath(child_121, Seq("two", "one")) should be(child_121) // self-reference + } + + it("doesn't resolve 'two::unknown'") { + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveTypePath(child_121, Seq("two", "unknown")) + thrown.getMessage should be("unable to find type 'unknown' in 'root::child_1::two'") + } + } + + describe("in 'child_122' context") { + val resolver = new ClassTypeProvider(specs, root) + resolver.nowClass = child_122 // Influences the error messages ("searching from '...'" part) + + it("resolves empty path") { + resolver.resolveTypePath(child_122, Seq()) should be(child_122) // self-reference + } + + it("resolves 'root'") { + resolver.resolveTypePath(child_122, Seq("root")) should be(root) + } + + it("resolves 'one'") { + resolver.resolveTypePath(child_122, Seq("one")) should be(child_121) + } + + it("doesn't resolve 'one::two'") { + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveTypePath(child_122, Seq("one", "two")) + thrown.getMessage should be("unable to find type 'two' in 'root::child_1::two::one'") + } + + it("doesn't resolve 'one::unknown'") { + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveTypePath(child_122, Seq("one", "unknown")) + thrown.getMessage should be("unable to find type 'unknown' in 'root::child_1::two::one'") + } + + it("resolves 'two'") { + resolver.resolveTypePath(child_122, Seq("two")) should be(child_122) // self-reference + } + + it("doesn't resolve 'two::one'") { + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveTypePath(child_122, Seq("two", "one")) + thrown.getMessage should be("unable to find type 'one' in 'root::child_1::two::two'") + } + + it("doesn't resolve 'two::unknown'") { + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveTypePath(child_122, Seq("two", "unknown")) + thrown.getMessage should be("unable to find type 'unknown' in 'root::child_1::two::two'") + } + } + } + + describe("resolveEnum") { + val e_root = root.enums.get("e").getOrElse(throw new NoSuchElementException("'e_root' not found")) + val e_11 = child_11.enums.get("e").getOrElse(throw new NoSuchElementException("'e_11' not found")) + val e_12 = child_12.enums.get("e").getOrElse(throw new NoSuchElementException("'e_12' not found")) + val e_2 = child_2.enums.get("e").getOrElse(throw new NoSuchElementException("'e_2' not found")) + + val none = Ast.EnumRef(false, Seq(), "e") + val one_e = Ast.EnumRef(false, Seq("one"), "e") + val one_unk = Ast.EnumRef(false, Seq("one"), "unknown") + val one_two = Ast.EnumRef(false, Seq("one", "two"), "e") + val unknown = Ast.EnumRef(false, Seq("unknown"), "e") + + describe("in 'root' context") { + val resolver = new ClassTypeProvider(specs, root) + + it("resolves 'e'") { + resolver.resolveEnum(none) should be(e_root) + } + + it("doesn't resolve 'one::e'") { + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveEnum(one_e) + thrown.getMessage should be("unable to find type 'one', searching from 'root'") + } + + it("doesn't resolve 'one::two::e'") { + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveEnum(one_two) + thrown.getMessage should be("unable to find type 'one', searching from 'root'") + } + + it("doesn't resolve 'one::unknown'") { + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveEnum(one_unk) + thrown.getMessage should be("unable to find type 'one', searching from 'root'") + } + + it("doesn't resolve 'unknown::e'") { + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveEnum(unknown) + thrown.getMessage should be("unable to find type 'unknown', searching from 'root'") + } + } + + describe("in 'child_1' context") { + val resolver = new ClassTypeProvider(specs, root) + resolver.nowClass = child_1 + + it("resolves 'e'") { + resolver.resolveEnum(none) should be(e_root) + } + + it("resolves 'one::e'") { + resolver.resolveEnum(one_e) should be(e_11) + } + + it("doesn't resolve 'one::two::e'") { + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveEnum(one_two) + thrown.getMessage should be("unable to find type 'two' in 'root::child_1::one'") + } + + it("doesn't resolve 'one::unknown'") { + val thrown = the[EnumNotFoundError] thrownBy resolver.resolveEnum(one_unk) + thrown.getMessage should be("unable to find enum 'unknown' in 'root::child_1::one'") + } + + it("doesn't resolve 'unknown::e'") { + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveEnum(unknown) + thrown.getMessage should be("unable to find type 'unknown', searching from 'root::child_1'") + } + } + + describe("in 'child_2' context") { + val resolver = new ClassTypeProvider(specs, root) + resolver.nowClass = child_2 + + it("resolves 'e'") { + resolver.resolveEnum(none) should be(e_2) + } + + it("doesn't resolve 'one::e'") { + val thrown = the[EnumNotFoundError] thrownBy resolver.resolveEnum(one_e) + thrown.getMessage should be("unable to find enum 'e' in 'root::child_2::one'") + } + + it("doesn't resolve 'one::two::e'") { + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveEnum(one_two) + thrown.getMessage should be("unable to find type 'two' in 'root::child_2::one'") + } + + it("doesn't resolve 'one::unknown'") { + val thrown = the[EnumNotFoundError] thrownBy resolver.resolveEnum(one_unk) + thrown.getMessage should be("unable to find enum 'unknown' in 'root::child_2::one'") + } + + it("doesn't resolve 'unknown::e'") { + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveEnum(unknown) + thrown.getMessage should be("unable to find type 'unknown', searching from 'root::child_2'") + } + } + + describe("in 'child_11' context") { + val resolver = new ClassTypeProvider(specs, root) + resolver.nowClass = child_11 + + it("resolves 'e'") { + resolver.resolveEnum(none) should be(e_11) + } + + it("resolves 'one::e'") { + resolver.resolveEnum(one_e) should be(e_11) + } + + it("doesn't resolve 'one::two::e'") { + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveEnum(one_two) + thrown.getMessage should be("unable to find type 'two' in 'root::child_1::one'") + } + + it("doesn't resolve 'one::unknown'") { + val thrown = the[EnumNotFoundError] thrownBy resolver.resolveEnum(one_unk) + thrown.getMessage should be("unable to find enum 'unknown' in 'root::child_1::one'") + } + + it("doesn't resolve 'unknown::e'") { + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveEnum(unknown) + thrown.getMessage should be("unable to find type 'unknown', searching from 'root::child_1::one'") + } + } + + describe("in 'child_12' context") { + val resolver = new ClassTypeProvider(specs, root) + resolver.nowClass = child_12 + + it("resolves 'e'") { + resolver.resolveEnum(none) should be(e_12) + } + + it("doesn't resolve 'one::e'") { + val thrown = the[EnumNotFoundError] thrownBy resolver.resolveEnum(one_e) + thrown.getMessage should be("unable to find enum 'e' in 'root::child_1::two::one'") + } + + it("doesn't resolve 'one::two::e'") { + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveEnum(one_two) + thrown.getMessage should be("unable to find type 'two' in 'root::child_1::two::one'") + } + + it("doesn't resolve 'one::unknown'") { + val thrown = the[EnumNotFoundError] thrownBy resolver.resolveEnum(one_unk) + thrown.getMessage should be("unable to find enum 'unknown' in 'root::child_1::two::one'") + } + + it("doesn't resolve 'unknown::e'") { + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveEnum(unknown) + thrown.getMessage should be("unable to find type 'unknown', searching from 'root::child_1::two'") + } + } + + describe("in 'child_21' context") { + val resolver = new ClassTypeProvider(specs, root) + resolver.nowClass = child_21 + + it("resolves 'e'") { + resolver.resolveEnum(none) should be(e_2) + } + + it("doesn't resolve 'one::e'") { + val thrown = the[EnumNotFoundError] thrownBy resolver.resolveEnum(one_e) + thrown.getMessage should be("unable to find enum 'e' in 'root::child_2::one'") + } + + it("doesn't resolve 'one::two::e'") { + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveEnum(one_two) + thrown.getMessage should be("unable to find type 'two' in 'root::child_2::one'") + } + + it("doesn't resolve 'one::unknown'") { + val thrown = the[EnumNotFoundError] thrownBy resolver.resolveEnum(one_unk) + thrown.getMessage should be("unable to find enum 'unknown' in 'root::child_2::one'") + } + + it("doesn't resolve 'unknown::e'") { + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveEnum(unknown) + thrown.getMessage should be("unable to find type 'unknown', searching from 'root::child_2::one'") + } + } + + describe("in 'child_22' context") { + val resolver = new ClassTypeProvider(specs, root) + resolver.nowClass = child_22 + + it("resolves 'e'") { + resolver.resolveEnum(none) should be(e_2) + } + + it("doesn't resolve 'one::e'") { + val thrown = the[EnumNotFoundError] thrownBy resolver.resolveEnum(one_e) + thrown.getMessage should be("unable to find enum 'e' in 'root::child_2::one'") + } + + it("doesn't resolve 'one::two::e'") { + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveEnum(one_two) + thrown.getMessage should be("unable to find type 'two' in 'root::child_2::one'") + } + + it("doesn't resolve 'one::unknown'") { + val thrown = the[EnumNotFoundError] thrownBy resolver.resolveEnum(one_unk) + thrown.getMessage should be("unable to find enum 'unknown' in 'root::child_2::one'") + } + + it("doesn't resolve 'unknown::e'") { + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveEnum(unknown) + thrown.getMessage should be("unable to find type 'unknown', searching from 'root::child_2::two'") + } + } + + describe("in 'child_121' context") { + val resolver = new ClassTypeProvider(specs, root) + resolver.nowClass = child_121 + + it("resolves 'e'") { + resolver.resolveEnum(none) should be(e_12) + } + + it("doesn't resolve 'one::e'") { + val thrown = the[EnumNotFoundError] thrownBy resolver.resolveEnum(one_e) + thrown.getMessage should be("unable to find enum 'e' in 'root::child_1::two::one'") + } + + it("doesn't resolve 'one::two::e'") { + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveEnum(one_two) + thrown.getMessage should be("unable to find type 'two' in 'root::child_1::two::one'") + } + + it("doesn't resolve 'one::unknown'") { + val thrown = the[EnumNotFoundError] thrownBy resolver.resolveEnum(one_unk) + thrown.getMessage should be("unable to find enum 'unknown' in 'root::child_1::two::one'") + } + + it("doesn't resolve 'unknown::e'") { + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveEnum(unknown) + thrown.getMessage should be("unable to find type 'unknown', searching from 'root::child_1::two::one'") + } + } + + describe("in 'child_122' context") { + val resolver = new ClassTypeProvider(specs, root) + resolver.nowClass = child_122 + + it("resolves 'e'") { + resolver.resolveEnum(none) should be(e_12) + } + + it("doesn't resolve 'one::e'") { + val thrown = the[EnumNotFoundError] thrownBy resolver.resolveEnum(one_e) + thrown.getMessage should be("unable to find enum 'e' in 'root::child_1::two::one'") + } + + it("doesn't resolve 'one::two::e'") { + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveEnum(one_two) + thrown.getMessage should be("unable to find type 'two' in 'root::child_1::two::one'") + } + + it("doesn't resolve 'one::unknown'") { + val thrown = the[EnumNotFoundError] thrownBy resolver.resolveEnum(one_unk) + thrown.getMessage should be("unable to find enum 'unknown' in 'root::child_1::two::one'") + } + + it("doesn't resolve 'unknown::e'") { + val thrown = the[TypeNotFoundError] thrownBy resolver.resolveEnum(unknown) + thrown.getMessage should be("unable to find type 'unknown', searching from 'root::child_1::two::two'") + } + } + } +} diff --git a/jvm/src/test/scala/io/kaitai/struct/exprlang/EnumRefSpec.scala b/jvm/src/test/scala/io/kaitai/struct/exprlang/EnumRefSpec.scala new file mode 100644 index 000000000..7b953cf5a --- /dev/null +++ b/jvm/src/test/scala/io/kaitai/struct/exprlang/EnumRefSpec.scala @@ -0,0 +1,56 @@ +package io.kaitai.struct.exprlang + +import org.scalatest.funspec.AnyFunSpec +import org.scalatest.matchers.should.Matchers._ + +class EnumRefSpec extends AnyFunSpec { + describe("Expressions.parseEnumRef") { + describe("parses local enum refs") { + it("some_enum") { + Expressions.parseEnumRef("some_enum") should be(Ast.EnumRef( + false, Seq(), "some_enum" + )) + } + it("with spaces: ' some_enum '") { + Expressions.parseEnumRef(" some_enum ") should be(Ast.EnumRef( + false, Seq(), "some_enum" + )) + } + + it("::some_enum") { + Expressions.parseEnumRef("::some_enum") should be(Ast.EnumRef( + true, Seq(), "some_enum" + )) + } + it("with spaces: ' :: some_enum '") { + Expressions.parseEnumRef(" :: some_enum ") should be(Ast.EnumRef( + true, Seq(), "some_enum" + )) + } + } + + describe("parses path enum refs") { + it("some::enum") { + Expressions.parseEnumRef("some::enum") should be(Ast.EnumRef( + false, Seq("some"), "enum" + )) + } + it("with spaces: ' some :: enum '") { + Expressions.parseEnumRef(" some :: enum ") should be(Ast.EnumRef( + false, Seq("some"), "enum" + )) + } + + it("::some::enum") { + Expressions.parseEnumRef("::some::enum") should be(Ast.EnumRef( + true, Seq("some"), "enum" + )) + } + it("with spaces: ' :: some :: enum '") { + Expressions.parseEnumRef(" :: some :: enum ") should be(Ast.EnumRef( + true, Seq("some"), "enum" + )) + } + } + } +} diff --git a/jvm/src/test/scala/io/kaitai/struct/exprlang/ExpressionsSpec.scala b/jvm/src/test/scala/io/kaitai/struct/exprlang/ExpressionsSpec.scala index 309630fac..16bc7bee0 100644 --- a/jvm/src/test/scala/io/kaitai/struct/exprlang/ExpressionsSpec.scala +++ b/jvm/src/test/scala/io/kaitai/struct/exprlang/ExpressionsSpec.scala @@ -133,15 +133,14 @@ class ExpressionsSpec extends AnyFunSpec { // Enums it("parses port::http") { - Expressions.parse("port::http") should be (EnumByLabel(identifier("port"), identifier("http"))) + Expressions.parse("port::http") should be (EnumByLabel(EnumRef(false, Seq(), "port"), identifier("http"))) } it("parses some_type::port::http") { Expressions.parse("some_type::port::http") should be ( EnumByLabel( - identifier("port"), + EnumRef(false, Seq("some_type"), "port"), identifier("http"), - typeId(absolute = false, Seq("some_type")) ) ) } @@ -149,9 +148,8 @@ class ExpressionsSpec extends AnyFunSpec { it("parses parent_type::child_type::port::http") { Expressions.parse("parent_type::child_type::port::http") should be ( EnumByLabel( - identifier("port"), + EnumRef(false, Seq("parent_type", "child_type"), "port"), identifier("http"), - typeId(absolute = false, Seq("parent_type", "child_type")) ) ) } @@ -159,9 +157,8 @@ class ExpressionsSpec extends AnyFunSpec { it("parses ::parent_type::child_type::port::http") { Expressions.parse("::parent_type::child_type::port::http") should be ( EnumByLabel( - identifier("port"), + EnumRef(true, Seq("parent_type", "child_type"), "port"), identifier("http"), - typeId(absolute = true, Seq("parent_type", "child_type")) ) ) } @@ -171,7 +168,7 @@ class ExpressionsSpec extends AnyFunSpec { Compare( BinOp( Attribute( - EnumByLabel(identifier("port"),identifier("http")), + EnumByLabel(EnumRef(false, Seq(), "port"), identifier("http")), identifier("to_i") ), Add, diff --git a/jvm/src/test/scala/io/kaitai/struct/translators/TestTypeProviders.scala b/jvm/src/test/scala/io/kaitai/struct/translators/TestTypeProviders.scala index 051ee216d..981ed227d 100644 --- a/jvm/src/test/scala/io/kaitai/struct/translators/TestTypeProviders.scala +++ b/jvm/src/test/scala/io/kaitai/struct/translators/TestTypeProviders.scala @@ -15,7 +15,7 @@ object TestTypeProviders { abstract class FakeTypeProvider extends TypeProvider { val nowClass = ClassSpec.opaquePlaceholder(List("top_class")) - override def resolveEnum(inType: Ast.typeId, enumName: String) = + override def resolveEnum(ref: Ast.EnumRef) = throw new NotImplementedError override def resolveType(typeName: Ast.typeId): DataType = { diff --git a/shared/src/main/scala/io/kaitai/struct/ClassTypeProvider.scala b/shared/src/main/scala/io/kaitai/struct/ClassTypeProvider.scala index 0dee55827..11dc24e2f 100644 --- a/shared/src/main/scala/io/kaitai/struct/ClassTypeProvider.scala +++ b/shared/src/main/scala/io/kaitai/struct/ClassTypeProvider.scala @@ -4,7 +4,7 @@ import io.kaitai.struct.datatype.DataType import io.kaitai.struct.datatype.DataType._ import io.kaitai.struct.exprlang.Ast import io.kaitai.struct.format._ -import io.kaitai.struct.precompile.{EnumNotFoundError, FieldNotFoundError, TypeNotFoundError, TypeUndecidedError} +import io.kaitai.struct.precompile.{EnumNotFoundInHierarchyError, EnumNotFoundInTypeError, FieldNotFoundError, TypeNotFoundInHierarchyError, TypeNotFoundInTypeError, TypeUndecidedError} import io.kaitai.struct.translators.TypeProvider class ClassTypeProvider(classSpecs: ClassSpecs, var topClass: ClassSpec) extends TypeProvider { @@ -90,46 +90,59 @@ class ClassTypeProvider(classSpecs: ClassSpecs, var topClass: ClassSpec) extends throw new FieldNotFoundError(attrName, inClass) } - override def resolveEnum(inType: Ast.typeId, enumName: String): EnumSpec = - resolveEnum(resolveClassSpec(inType), enumName) + override def resolveEnum(ref: Ast.EnumRef): EnumSpec = { + val inClass = if (ref.absolute) topClass else nowClass + // When concrete type is not defined, search enum definition in all enclosing types + if (ref.typePath.isEmpty) { + resolveEnumName(inClass, ref.name) + } else { + val ty = resolveTypePath(inClass, ref.typePath) + ty.enums.get(ref.name) match { + case Some(spec) => + spec + case None => + throw new EnumNotFoundInTypeError(ref.name, ty) + } + } + } - def resolveEnum(inClass: ClassSpec, enumName: String): EnumSpec = { + private def resolveEnumName(inClass: ClassSpec, enumName: String): EnumSpec = { inClass.enums.get(enumName) match { case Some(spec) => spec case None => // let's try upper levels of hierarchy inClass.upClass match { - case Some(upClass) => resolveEnum(upClass, enumName) + case Some(upClass) => resolveEnumName(upClass, enumName) case None => - throw new EnumNotFoundError(enumName, nowClass) + throw new EnumNotFoundInHierarchyError(enumName, nowClass) } } } override def resolveType(typeName: Ast.typeId): DataType = - resolveClassSpec(typeName).toDataType - - def resolveClassSpec(typeName: Ast.typeId): ClassSpec = - resolveClassSpec( + resolveTypePath( if (typeName.absolute) topClass else nowClass, typeName.names - ) + ).toDataType - def resolveClassSpec(inClass: ClassSpec, typeName: Seq[String]): ClassSpec = { - if (typeName.isEmpty) + def resolveTypePath(inClass: ClassSpec, path: Seq[String]): ClassSpec = { + if (path.isEmpty) return inClass - val headTypeName :: restTypesNames = typeName.toList - val nextClass = resolveClassSpec(inClass, headTypeName) - if (restTypesNames.isEmpty) { - nextClass - } else { - resolveClassSpec(nextClass, restTypesNames) + val headTypeName :: restTypesNames = path.toList + var nextClass = resolveTypeName(inClass, headTypeName) + for (name <- restTypesNames) { + nextClass = nextClass.types.get(name) match { + case Some(spec) => spec + case None => + throw new TypeNotFoundInTypeError(name, nextClass) + } } + nextClass } - def resolveClassSpec(inClass: ClassSpec, typeName: String): ClassSpec = { + def resolveTypeName(inClass: ClassSpec, typeName: String): ClassSpec = { if (inClass.name.last == typeName) return inClass @@ -139,12 +152,16 @@ class ClassTypeProvider(classSpecs: ClassSpecs, var topClass: ClassSpec) extends case None => // let's try upper levels of hierarchy inClass.upClass match { - case Some(upClass) => resolveClassSpec(upClass, typeName) + case Some(upClass) => resolveTypeName(upClass, typeName) case None => classSpecs.get(typeName) match { - case Some(spec) => spec - case None => - throw new TypeNotFoundError(typeName, nowClass) + // We should use that spec if it is imported in our file (which is represented + // by our top-level class). If `topClass` imports `classSpec`, we could try to + // resolve type in it + // TODO: if type is defined in spec, we could add a suggestion to error to add missing import + case Some(spec) if (topClass.imports.contains(spec)) => spec + case _ => + throw new TypeNotFoundInHierarchyError(typeName, nowClass) } } } diff --git a/shared/src/main/scala/io/kaitai/struct/GraphvizClassCompiler.scala b/shared/src/main/scala/io/kaitai/struct/GraphvizClassCompiler.scala index 22c746953..caf54dd53 100644 --- a/shared/src/main/scala/io/kaitai/struct/GraphvizClassCompiler.scala +++ b/shared/src/main/scala/io/kaitai/struct/GraphvizClassCompiler.scala @@ -347,8 +347,8 @@ class GraphvizClassCompiler(classSpecs: ClassSpecs, topClass: ClassSpec) extends List() case _: Ast.expr.EnumByLabel => List() - case Ast.expr.EnumById(_, id, _) => - affectedVars(id) + case Ast.expr.EnumById(_, expr) => + affectedVars(expr) case Ast.expr.Attribute(value, attr) => if (attr.name == Identifier.SIZEOF) { val vars = value match { @@ -484,7 +484,7 @@ object GraphvizClassCompiler extends LanguageCompilerStatic { ): LanguageCompiler = ??? def type2class(name: List[String]) = name.last - def type2display(name: List[String]) = name.map(Utils.upperCamelCase).mkString("::") + def type2display(name: Seq[String]) = name.map(Utils.upperCamelCase).mkString("::") def dataTypeName(dataType: DataType, valid: Option[ValidationSpec]): String = { dataType match { @@ -508,7 +508,7 @@ object GraphvizClassCompiler extends LanguageCompilerStatic { val comma = if (bytesStr.isEmpty) "" else ", " s"str($bytesStr$comma$encoding)" case EnumType(name, basedOn) => - s"${dataTypeName(basedOn, valid)}→${type2display(name)}" + s"${dataTypeName(basedOn, valid)}→${type2display(name.fullName)}" case BitsType(width, bitEndian) => s"b$width${bitEndian.toSuffix}" case BitsType1(bitEndian) => s"b1${bitEndian.toSuffix}→bool" case _ => dataType.toString 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 96e6f4036..db868c521 100644 --- a/shared/src/main/scala/io/kaitai/struct/datatype/DataType.scala +++ b/shared/src/main/scala/io/kaitai/struct/datatype/DataType.scala @@ -252,7 +252,7 @@ object DataType { def isOwning = false } - case class EnumType(name: List[String], basedOn: IntType) extends DataType { + case class EnumType(name: Ast.EnumRef, basedOn: IntType) extends DataType { var enumSpec: Option[EnumSpec] = None /** @@ -487,7 +487,7 @@ object DataType { enumRef match { case Some(enumName) => r match { - case numType: IntType => EnumType(classNameToList(enumName), numType) + case numType: IntType => EnumType(Expressions.parseEnumRef(enumName), numType) case _ => throw KSYParseError(s"tried to resolve non-integer $r to enum", path).toException } diff --git a/shared/src/main/scala/io/kaitai/struct/exprlang/Ast.scala b/shared/src/main/scala/io/kaitai/struct/exprlang/Ast.scala index 2acf1943b..9523ea5de 100644 --- a/shared/src/main/scala/io/kaitai/struct/exprlang/Ast.scala +++ b/shared/src/main/scala/io/kaitai/struct/exprlang/Ast.scala @@ -76,8 +76,10 @@ object Ast { case class FloatNum(n: BigDecimal) extends expr case class Str(s: String) extends expr case class Bool(n: Boolean) extends expr - case class EnumByLabel(enumName: identifier, label: identifier, inType: typeId = EmptyTypeId) extends expr - case class EnumById(enumName: identifier, id: expr, inType: typeId = EmptyTypeId) extends expr + /** Take named enumeration constant from the specified enumeration. */ + case class EnumByLabel(ref: EnumRef, label: identifier) extends expr + /** Cast specified expression to the enumerated type. Used only by value instances with `enum` key. */ + case class EnumById(ref: EnumRef, expr: expr) extends expr case class Attribute(value: expr, attr: identifier) extends expr case class CastToType(value: expr, typeName: typeId) extends expr @@ -141,5 +143,18 @@ object Ast { case object GtE extends cmpop } + /** + * Reference to an enum in scope. Scope is defined by the `absolute` flag and + * a path to a type (which can be empty) in which enum is defined. + */ + case class EnumRef(absolute: Boolean, typePath: Seq[String], name: String) { + /** @return Type path and name of enum in one list. */ + def fullName: Seq[String] = typePath :+ name + /** + * @return Enum designation name as human-readable string, to be used in compiler + * error messages. + */ + def asStr: String = fullName.mkString(if (absolute) "::" else "", "::", "") + } case class TypeWithArguments(typeName: typeId, arguments: expr.List) } diff --git a/shared/src/main/scala/io/kaitai/struct/exprlang/Expressions.scala b/shared/src/main/scala/io/kaitai/struct/exprlang/Expressions.scala index 0ef98ce71..02c5994be 100644 --- a/shared/src/main/scala/io/kaitai/struct/exprlang/Expressions.scala +++ b/shared/src/main/scala/io/kaitai/struct/exprlang/Expressions.scala @@ -171,14 +171,11 @@ object Expressions { case (first, names: Seq[Ast.identifier]) => val isAbsolute = first.nonEmpty val (enumName, enumLabel) = names.takeRight(2) match { - case Seq(a, b) => (a, b) - } - val typePath = names.dropRight(2) - if (typePath.isEmpty) { - Ast.expr.EnumByLabel(enumName, enumLabel, Ast.EmptyTypeId) - } else { - Ast.expr.EnumByLabel(enumName, enumLabel, Ast.typeId(isAbsolute, typePath.map(_.name))) + case Seq(a, b) => (a.name, b) } + val typePath = names.dropRight(2).map(n => n.name) + val ref = Ast.EnumRef(isAbsolute, typePath, enumName) + Ast.expr.EnumByLabel(ref, enumLabel) } def byteSizeOfType[$: P]: P[Ast.expr.ByteSizeOfType] = @@ -195,6 +192,13 @@ object Expressions { case (path, Some(args)) => Ast.TypeWithArguments(path, args) } + def enumRef[$: P]: P[Ast.EnumRef] = P(Start ~ "::".!.? ~ NAME.rep(1, "::") ~ End).map { + case (absolute, names) => + // List have at least one element, so we always can split it into head and the last element + val typePath :+ enumName = names + Ast.EnumRef(absolute.nonEmpty, typePath.map(i => i.name), enumName.name) + } + class ParseException(val src: String, val failure: Parsed.Failure) extends RuntimeException(failure.msg) @@ -211,6 +215,14 @@ object Expressions { */ def parseTypeRef(src: String): Ast.TypeWithArguments = realParse(src, typeRef(_)) + /** + * Parse string with reference to enumeration definition, optionally in full path format. + * + * @param src Enum reference as string, like `::path::to::enum` + * @return Object that represents path to enum + */ + def parseEnumRef(src: String): Ast.EnumRef = realParse(src, enumRef(_)) + private def realParse[T](src: String, parser: P[_] => P[T]): T = { val r = fastparse.parse(src.trim, parser) r match { 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 6a3cb0135..d2282f3ad 100644 --- a/shared/src/main/scala/io/kaitai/struct/format/ClassSpec.scala +++ b/shared/src/main/scala/io/kaitai/struct/format/ClassSpec.scala @@ -81,6 +81,16 @@ case class ClassSpec( var seqSize: Sized = NotCalculatedSized + /** + * The list of top-level type specifications which is imported to the file, + * which top-level type is represented by this class. + * + * This collection filled only for top-level classes (for which [[upClass]] is `None`). + * + * This collection is filled by the [[io.kaitai.struct.precompile.LoadImports]] pass. + */ + var imports = mutable.ListBuffer[ClassSpec]() + def toDataType: DataType = { val cut = CalcUserType(name, None) cut.classSpec = Some(this) diff --git a/shared/src/main/scala/io/kaitai/struct/format/ClassSpecs.scala b/shared/src/main/scala/io/kaitai/struct/format/ClassSpecs.scala index ec803d136..5d36450be 100644 --- a/shared/src/main/scala/io/kaitai/struct/format/ClassSpecs.scala +++ b/shared/src/main/scala/io/kaitai/struct/format/ClassSpecs.scala @@ -59,6 +59,6 @@ abstract class ClassSpecs(val firstSpec: ClassSpec) extends mutable.HashMap[Stri } } - def importRelative(name: String, path: List[String], inFile: Option[String]): Future[Option[ClassSpec]] - def importAbsolute(name: String, path: List[String], inFile: Option[String]): Future[Option[ClassSpec]] + def importRelative(name: String, path: List[String], inFile: String): Future[Option[ClassSpec]] + def importAbsolute(name: String, path: List[String], inFile: String): Future[Option[ClassSpec]] } diff --git a/shared/src/main/scala/io/kaitai/struct/format/InstanceSpec.scala b/shared/src/main/scala/io/kaitai/struct/format/InstanceSpec.scala index 4a66307ed..8011bdb84 100644 --- a/shared/src/main/scala/io/kaitai/struct/format/InstanceSpec.scala +++ b/shared/src/main/scala/io/kaitai/struct/format/InstanceSpec.scala @@ -61,7 +61,7 @@ object InstanceSpec { case None => value case Some(enumName) => - Ast.expr.EnumById(Ast.identifier(enumName), value) + Ast.expr.EnumById(Expressions.parseEnumRef(enumName), value) } val ifExpr = ParseUtils.getOptValueExpression(srcMap, "if", path) diff --git a/shared/src/main/scala/io/kaitai/struct/languages/CSharpCompiler.scala b/shared/src/main/scala/io/kaitai/struct/languages/CSharpCompiler.scala index 97693c473..4c338140a 100644 --- a/shared/src/main/scala/io/kaitai/struct/languages/CSharpCompiler.scala +++ b/shared/src/main/scala/io/kaitai/struct/languages/CSharpCompiler.scala @@ -688,7 +688,7 @@ object CSharpCompiler extends LanguageCompilerStatic case KaitaiStreamType | OwnedKaitaiStreamType => kstreamName case t: UserType => types2class(t.name) - case EnumType(name, _) => types2class(name) + case EnumType(ref, _) => types2class(ref.fullName) case at: ArrayType => { importList.add("System.Collections.Generic") diff --git a/shared/src/main/scala/io/kaitai/struct/languages/CppCompiler.scala b/shared/src/main/scala/io/kaitai/struct/languages/CppCompiler.scala index b0fdee657..5dad73d59 100644 --- a/shared/src/main/scala/io/kaitai/struct/languages/CppCompiler.scala +++ b/shared/src/main/scala/io/kaitai/struct/languages/CppCompiler.scala @@ -1149,7 +1149,7 @@ object CppCompiler extends LanguageCompilerStatic types2class(if (absolute) { t.enumSpec.get.name } else { - t.name + t.name.fullName }) case at: ArrayType => { @@ -1210,7 +1210,7 @@ object CppCompiler extends LanguageCompilerStatic ) } - def types2class(components: List[String]) = + def types2class(components: Seq[String]) = components.map(type2class).mkString("::") def type2class(name: String) = Utils.lowerUnderscoreCase(name) + "_t" diff --git a/shared/src/main/scala/io/kaitai/struct/languages/JavaCompiler.scala b/shared/src/main/scala/io/kaitai/struct/languages/JavaCompiler.scala index ee19058d3..8db9076be 100644 --- a/shared/src/main/scala/io/kaitai/struct/languages/JavaCompiler.scala +++ b/shared/src/main/scala/io/kaitai/struct/languages/JavaCompiler.scala @@ -874,7 +874,7 @@ object JavaCompiler extends LanguageCompilerStatic case KaitaiStructType | CalcKaitaiStructType(_) => kstructName case t: UserType => types2class(t.name) - case EnumType(name, _) => types2class(name) + case EnumType(ref, _) => types2class(ref.fullName) case _: ArrayType => kaitaiType2JavaTypeBoxed(attrType, importList) @@ -918,7 +918,7 @@ object JavaCompiler extends LanguageCompilerStatic case KaitaiStructType | CalcKaitaiStructType(_) => kstructName case t: UserType => types2class(t.name) - case EnumType(name, _) => types2class(name) + case EnumType(ref, _) => types2class(ref.fullName) case at: ArrayType => { importList.add("java.util.ArrayList") @@ -929,7 +929,7 @@ object JavaCompiler extends LanguageCompilerStatic } } - def types2class(names: List[String]) = names.map(x => type2class(x)).mkString(".") + def types2class(names: Seq[String]) = names.map(x => type2class(x)).mkString(".") override def kstreamName: String = "KaitaiStream" override def kstructName: String = "KaitaiStruct" diff --git a/shared/src/main/scala/io/kaitai/struct/languages/RustCompiler.scala b/shared/src/main/scala/io/kaitai/struct/languages/RustCompiler.scala index b0e885e7b..2bd3f5970 100644 --- a/shared/src/main/scala/io/kaitai/struct/languages/RustCompiler.scala +++ b/shared/src/main/scala/io/kaitai/struct/languages/RustCompiler.scala @@ -1312,7 +1312,7 @@ object RustCompiler def classTypeName(c: ClassSpec): String = s"${types2class(c.name)}" - def types2class(names: List[String]): String = + def types2class(names: Seq[String]): String = // TODO: Use `mod` to scope types instead of weird names names.map(x => type2class(x)).mkString("_") @@ -1339,7 +1339,7 @@ object RustCompiler case t: EnumType => val baseName = t.enumSpec match { case Some(spec) => s"${types2class(spec.name)}" - case None => s"${types2class(t.name)}" + case None => s"${types2class(t.name.fullName)}" } baseName diff --git a/shared/src/main/scala/io/kaitai/struct/precompile/Exceptions.scala b/shared/src/main/scala/io/kaitai/struct/precompile/Exceptions.scala index 1ac6035bb..37abe2ecf 100644 --- a/shared/src/main/scala/io/kaitai/struct/precompile/Exceptions.scala +++ b/shared/src/main/scala/io/kaitai/struct/precompile/Exceptions.scala @@ -15,12 +15,20 @@ class WrongMethodCall(val dataType: MethodArgType, val methodName: String, val e extends ExpressionError(s"wrong arguments to method call `$methodName` on $dataType: expected ${expectedSigs.mkString(" or ")}, got $actualSig") sealed abstract class NotFoundError(msg: String) extends ExpressionError(msg) -class TypeNotFoundError(val name: String, val curClass: ClassSpec) - extends NotFoundError(s"unable to find type '$name', searching from ${curClass.nameAsStr}") +sealed abstract class TypeNotFoundError(msg: String) extends NotFoundError(msg) +class TypeNotFoundInHierarchyError(val name: String, val curClass: ClassSpec) + extends TypeNotFoundError(s"unable to find type '$name', searching from '${curClass.nameAsStr}'") +class TypeNotFoundInTypeError(val name: String, val curClass: ClassSpec) + extends TypeNotFoundError(s"unable to find type '$name' in '${curClass.nameAsStr}'") class FieldNotFoundError(val name: String, val curClass: ClassSpec) - extends NotFoundError(s"unable to access '$name' in ${curClass.nameAsStr} context") -class EnumNotFoundError(val name: String, val curClass: ClassSpec) - extends NotFoundError(s"unable to find enum '$name', searching from ${curClass.nameAsStr}") + extends NotFoundError(s"unable to access '$name' in '${curClass.nameAsStr}' context") + +sealed abstract class EnumNotFoundError(msg: String) extends NotFoundError(msg) +class EnumNotFoundInHierarchyError(val name: String, val curClass: ClassSpec) + extends EnumNotFoundError(s"unable to find enum '$name', searching from '${curClass.nameAsStr}'") +class EnumNotFoundInTypeError(val name: String, val curClass: ClassSpec) + extends EnumNotFoundError(s"unable to find enum '$name' in '${curClass.nameAsStr}'") + class EnumMemberNotFoundError(val label: String, val enumName: String, val enumDefPath: String) extends NotFoundError(s"unable to find enum member '$enumName::$label' (enum '$enumName' defined at /$enumDefPath)") diff --git a/shared/src/main/scala/io/kaitai/struct/precompile/LoadImports.scala b/shared/src/main/scala/io/kaitai/struct/precompile/LoadImports.scala index d25bfeb9b..ab6a36772 100644 --- a/shared/src/main/scala/io/kaitai/struct/precompile/LoadImports.scala +++ b/shared/src/main/scala/io/kaitai/struct/precompile/LoadImports.scala @@ -32,7 +32,7 @@ class LoadImports(specs: ClassSpecs) { loadImport( name, curClass.meta.path ++ List("imports", idx.toString), - Some(curClass.fileNameAsStr), + curClass, workDir ) }).map((x) => x.flatten) @@ -44,9 +44,10 @@ class LoadImports(specs: ClassSpecs) { Future.sequence(List(thisMetaFuture, nestedFuture)).map((x) => x.flatten) } - private def loadImport(name: String, path: List[String], inFile: Option[String], workDir: ImportPath): Future[List[ClassSpec]] = { + private def loadImport(name: String, path: List[String], curClass: ClassSpec, workDir: ImportPath): Future[List[ClassSpec]] = { Log.importOps.info(() => s".. LoadImports: loadImport($name, workDir = $workDir)") + val inFile = curClass.fileNameAsStr val impPath = ImportPath.fromString(name) val fullPath = ImportPath.add(workDir, impPath) @@ -63,8 +64,9 @@ class LoadImports(specs: ClassSpecs) { s".. LoadImports: loadImport($name, workDir = $workDir), got spec=$specNameAsStr" }) optSpec match { - case Some(spec) => - val specName = spec.name.head + case Some(importedSpec) => + curClass.imports += importedSpec + val specName = importedSpec.name.head // Check if spec name does not match file name. If it doesn't match, // it is probably already a serious error. if (name != specName) @@ -88,12 +90,12 @@ class LoadImports(specs: ClassSpecs) { val isNewSpec = specs.synchronized { val isNew = !specs.contains(specName) if (isNew) { - specs(specName) = spec + specs(specName) = importedSpec } isNew } if (isNewSpec) { - processClass(spec, ImportPath.updateWorkDir(workDir, impPath)) + processClass(importedSpec, ImportPath.updateWorkDir(workDir, impPath)) } else { Log.importOps.warn(() => s"... we have that already, ignoring") Future { List() } diff --git a/shared/src/main/scala/io/kaitai/struct/precompile/ResolveTypes.scala b/shared/src/main/scala/io/kaitai/struct/precompile/ResolveTypes.scala index f6f0c888b..ae69ffe83 100644 --- a/shared/src/main/scala/io/kaitai/struct/precompile/ResolveTypes.scala +++ b/shared/src/main/scala/io/kaitai/struct/precompile/ResolveTypes.scala @@ -1,16 +1,20 @@ package io.kaitai.struct.precompile -import io.kaitai.struct.Log +import io.kaitai.struct.{ClassTypeProvider, Log} import io.kaitai.struct.datatype.DataType import io.kaitai.struct.datatype.DataType.{ArrayType, EnumType, SwitchType, UserType} +import io.kaitai.struct.exprlang.Ast import io.kaitai.struct.format._ import io.kaitai.struct.problems._ /** * A collection of methods that resolves user types and enum types, i.e. * converts names into ClassSpec / EnumSpec references. + * + * This step runs for each top-level [[format.ClassSpec]]. */ class ResolveTypes(specs: ClassSpecs, topClass: ClassSpec, opaqueTypes: Boolean) extends PrecompileStep { + /** Resolves references to types and enums in `topClass` and all its nested types. */ override def run(): Iterable[CompilationProblem] = topClass.mapRec(resolveUserTypes).map(problem => problem.localizedInType(topClass)) @@ -19,7 +23,7 @@ class ResolveTypes(specs: ClassSpecs, topClass: ClassSpec, opaqueTypes: Boolean) * ClassSpec. * @param curClass class to start from, might be top-level class */ - def resolveUserTypes(curClass: ClassSpec): Iterable[CompilationProblem] = { + private def resolveUserTypes(curClass: ClassSpec): Iterable[CompilationProblem] = { val seqProblems: Iterable[CompilationProblem] = curClass.seq.flatMap((attr) => resolveUserTypeForMember(curClass, attr)) @@ -40,21 +44,57 @@ class ResolveTypes(specs: ClassSpecs, topClass: ClassSpec, opaqueTypes: Boolean) seqProblems ++ instancesProblems ++ paramsProblems } - def resolveUserTypeForMember(curClass: ClassSpec, attr: MemberSpec): Iterable[CompilationProblem] = + private def resolveUserTypeForMember(curClass: ClassSpec, attr: MemberSpec): Iterable[CompilationProblem] = resolveUserType(curClass, attr.dataType, attr.path) - def resolveUserType(curClass: ClassSpec, dataType: DataType, path: List[String]): Iterable[CompilationProblem] = { + /** + * Resolves any references to typee or enum in `dataType` used in `curClass` to + * a type definition, or returns [[TypeNotFoundErr]] or [[EnumNotFoundErr]] error. + * + * @param curClass Class that contains member + * @param dataType Definition of an attribute type which references to a type or enum need to be resolved + * @param path A path to the attribute in KSY where the error should be reported if reference is unknown + * + * @returns [[TypeNotFoundErr]] and/or [[EnumNotFoundErr]] error (several in case of `switch-on` type). + */ + private def resolveUserType(curClass: ClassSpec, dataType: DataType, path: List[String]): Iterable[CompilationProblem] = { dataType match { case ut: UserType => - val (resClassSpec, problems) = resolveUserType(curClass, ut.name, path ++ List("type")) - ut.classSpec = resClassSpec - problems + try { + val resolver = new ClassTypeProvider(specs, curClass) + val ty = resolver.resolveTypePath(curClass, ut.name) + Log.typeResolve.info(() => s" => ${ty.nameAsStr}") + ut.classSpec = Some(ty) + None + } catch { + case _: TypeNotFoundError => + // Type definition not found + if (opaqueTypes) { + // Generate special "opaque placeholder" ClassSpec + Log.typeResolve.info(() => " => ??? (generating opaque type)") + ut.classSpec = Some(ClassSpec.opaquePlaceholder(ut.name)) + None + } else { + // Opaque types are disabled => that is an error + Log.typeResolve.info(() => " => ??? (opaque type are disabled => error)") + Some(TypeNotFoundErr(ut.name, curClass, path :+ "type")) + } + } case et: EnumType => - et.enumSpec = resolveEnumSpec(curClass, et.name) - if (et.enumSpec.isEmpty) { - Some(EnumNotFoundErr(et.name, curClass, path ++ List("enum"))) - } else { + try { + val resolver = new ClassTypeProvider(specs, curClass) + val ty = resolver.resolveEnum(et.name) + Log.enumResolve.info(() => s" => ${ty.nameAsStr}") + et.enumSpec = Some(ty) None + } catch { + case ex: TypeNotFoundError => + Log.typeResolve.info(() => s" => ??? (while resolving enum '${et.name}'): $ex") + Log.enumResolve.info(() => s" => ??? (enclosing type not found, enum '${et.name}'): $ex") + Some(TypeNotFoundErr(et.name.typePath, curClass, path :+ "enum")) + case ex: EnumNotFoundError => + Log.enumResolve.info(() => s" => ??? (enum '${et.name}'): $ex") + Some(EnumNotFoundErr(et.name, curClass, path :+ "enum")) } case st: SwitchType => st.cases.flatMap { case (caseName, ut) => @@ -67,132 +107,4 @@ class ResolveTypes(specs: ClassSpecs, topClass: ClassSpec, opaqueTypes: Boolean) None } } - - def resolveUserType(curClass: ClassSpec, typeName: List[String], path: List[String]): (Option[ClassSpec], Option[CompilationProblem]) = { - val res = realResolveUserType(curClass, typeName, path) - - res match { - case None => - // Type definition not found - if (opaqueTypes) { - // Generate special "opaque placeholder" ClassSpec - Log.typeResolve.info(() => " => ??? (generating opaque type)") - (Some(ClassSpec.opaquePlaceholder(typeName)), None) - } else { - // Opaque types are disabled => that is an error - Log.typeResolve.info(() => " => ??? (opaque type are disabled => error)") - (None, Some(TypeNotFoundErr(typeName, curClass, path))) - } - case Some(x) => - Log.typeResolve.info(() => s" => ${x.nameAsStr}") - (res, None) - } - } - - private def realResolveUserType(curClass: ClassSpec, typeName: List[String], path: List[String]): Option[ClassSpec] = { - Log.typeResolve.info(() => s"resolveUserType: at ${curClass.name} doing ${typeName.mkString("|")}") - - // First, try to do it in current class - - // If we're seeking composite name, we only have to resolve the very first - // part of it at this stage - val firstName :: restNames = typeName - - val resolvedHere = curClass.types.get(firstName).flatMap((nestedClass) => - if (restNames.isEmpty) { - // No further names to resolve, here's our answer - Some(nestedClass) - } else { - // Try to resolve recursively - realResolveUserType(nestedClass, restNames, path) - } - ) - - resolvedHere match { - case Some(_) => resolvedHere - case None => - // No luck resolving here, let's try upper levels, if they exist - curClass.upClass match { - case Some(upClass) => - realResolveUserType(upClass, typeName, path) - case None => - // Check this class if it's top-level class - if (curClass.name.head == firstName) { - Some(curClass) - } else { - // Check if top-level specs has this name - // If there's None => no luck at all - val resolvedTop = specs.get(firstName) - resolvedTop match { - case None => None - case Some(classSpec) => if (restNames.isEmpty) { - resolvedTop - } else { - realResolveUserType(classSpec, restNames, path) - } - } - } - } - } - } - - def resolveEnumSpec(curClass: ClassSpec, typeName: List[String]): Option[EnumSpec] = { - Log.enumResolve.info(() => s"resolveEnumSpec: at ${curClass.name} doing ${typeName.mkString("|")}") - - val res = realResolveEnumSpec(curClass, typeName) - res match { - case None => { - Log.enumResolve.info(() => s" => ???") - res - } - case Some(x) => { - Log.enumResolve.info(() => s" => ${x.nameAsStr}") - res - } - } - } - - private def realResolveEnumSpec(curClass: ClassSpec, typeName: List[String]): Option[EnumSpec] = { - // First, try to do it in current class - - // If we're seeking composite name, we only have to resolve the very first - // part of it at this stage - val firstName :: restNames = typeName - - val resolvedHere = if (restNames.isEmpty) { - curClass.enums.get(firstName) - } else { - curClass.types.get(firstName).flatMap((nestedClass) => - resolveEnumSpec(nestedClass, restNames) - ) - } - - resolvedHere match { - case Some(_) => resolvedHere - case None => - // No luck resolving here, let's try upper levels, if they exist - curClass.upClass match { - case Some(upClass) => - resolveEnumSpec(upClass, typeName) - case None => - // Check this class if it's top-level class - if (curClass.name.head == firstName) { - resolveEnumSpec(curClass, restNames) - } else { - // Check if top-level specs has this name - // If there's None => no luck at all - val resolvedTop = specs.get(firstName) - resolvedTop match { - case None => None - case Some(classSpec) => if (restNames.isEmpty) { - // resolved everything, but this points to a type name, not enum name - None - } else { - resolveEnumSpec(classSpec, restNames) - } - } - } - } - } - } } diff --git a/shared/src/main/scala/io/kaitai/struct/problems/CompilationProblem.scala b/shared/src/main/scala/io/kaitai/struct/problems/CompilationProblem.scala index 2779887fb..036a407cb 100644 --- a/shared/src/main/scala/io/kaitai/struct/problems/CompilationProblem.scala +++ b/shared/src/main/scala/io/kaitai/struct/problems/CompilationProblem.scala @@ -2,7 +2,7 @@ package io.kaitai.struct.problems import io.kaitai.struct.{JSON, Jsonable, Utils, problems} import io.kaitai.struct.datatype.DataType -import io.kaitai.struct.exprlang.Expressions +import io.kaitai.struct.exprlang.{Ast, Expressions} import io.kaitai.struct.format.{ClassSpec, Identifier, KSVersion} import fastparse.Parsed.Failure @@ -170,20 +170,20 @@ case class ParamMismatchError(idx: Int, argType: DataType, paramName: String, pa override def severity: ProblemSeverity = ProblemSeverity.Error } -case class TypeNotFoundErr(name: List[String], curClass: ClassSpec, path: List[String], fileName: Option[String] = None) +case class TypeNotFoundErr(name: Seq[String], curClass: ClassSpec, path: List[String], fileName: Option[String] = None) extends CompilationProblem { - override def text = s"unable to find type '${name.mkString("::")}', searching from ${curClass.nameAsStr}" + override def text = s"unable to find type '${name.mkString("::")}', searching from '${curClass.nameAsStr}'" override val coords: ProblemCoords = ProblemCoords(fileName, Some(path)) override def localizedInFile(fileName: String): CompilationProblem = copy(fileName = Some(fileName)) override def severity: ProblemSeverity = ProblemSeverity.Error } -case class EnumNotFoundErr(name: List[String], curClass: ClassSpec, path: List[String], fileName: Option[String] = None) +case class EnumNotFoundErr(ref: Ast.EnumRef, curClass: ClassSpec, path: List[String], fileName: Option[String] = None) extends CompilationProblem { - override def text = s"unable to find enum '${name.mkString("::")}', searching from ${curClass.nameAsStr}" + override def text = s"unable to find enum '${ref.asStr}', searching from '${curClass.nameAsStr}'" override val coords: ProblemCoords = ProblemCoords(fileName, Some(path)) override def localizedInFile(fileName: String): CompilationProblem = copy(fileName = Some(fileName)) diff --git a/shared/src/main/scala/io/kaitai/struct/translators/BaseTranslator.scala b/shared/src/main/scala/io/kaitai/struct/translators/BaseTranslator.scala index 1dd550666..a36452bd9 100644 --- a/shared/src/main/scala/io/kaitai/struct/translators/BaseTranslator.scala +++ b/shared/src/main/scala/io/kaitai/struct/translators/BaseTranslator.scala @@ -59,11 +59,11 @@ abstract class BaseTranslator(val provider: TypeProvider) doInterpolatedStringLiteral(s) case Ast.expr.Bool(n) => doBoolLiteral(n) - case Ast.expr.EnumById(enumType, id, inType) => - val enumSpec = provider.resolveEnum(inType, enumType.name) - doEnumById(enumSpec, translate(id)) - case Ast.expr.EnumByLabel(enumType, label, inType) => - val enumSpec = provider.resolveEnum(inType, enumType.name) + case Ast.expr.EnumById(ref, expr) => + val enumSpec = provider.resolveEnum(ref) + doEnumById(enumSpec, translate(expr)) + case Ast.expr.EnumByLabel(ref, label) => + val enumSpec = provider.resolveEnum(ref) doEnumByLabel(enumSpec, label.name) case Ast.expr.Name(name: Ast.identifier) => if (name.name == Identifier.SIZEOF) { diff --git a/shared/src/main/scala/io/kaitai/struct/translators/ExpressionValidator.scala b/shared/src/main/scala/io/kaitai/struct/translators/ExpressionValidator.scala index 8d2741282..5b214d467 100644 --- a/shared/src/main/scala/io/kaitai/struct/translators/ExpressionValidator.scala +++ b/shared/src/main/scala/io/kaitai/struct/translators/ExpressionValidator.scala @@ -31,13 +31,13 @@ class ExpressionValidator(val provider: TypeProvider) _: Ast.expr.FloatNum | _: Ast.expr.Str | _: Ast.expr.Bool => // all simple literals are good and valid - case Ast.expr.EnumById(enumType, id, inType) => - provider.resolveEnum(inType, enumType.name) - validate(id) - case Ast.expr.EnumByLabel(enumType, label, inType) => - val enumSpec = provider.resolveEnum(inType, enumType.name) + case Ast.expr.EnumById(ref, expr) => + provider.resolveEnum(ref) + validate(expr) + case Ast.expr.EnumByLabel(ref, label) => + val enumSpec = provider.resolveEnum(ref) if (!enumSpec.map.values.exists(_.name == label.name)) { - throw new EnumMemberNotFoundError(label.name, enumType.name, enumSpec.path.mkString("/")) + throw new EnumMemberNotFoundError(label.name, ref.name, enumSpec.path.mkString("/")) } case Ast.expr.Name(name: Ast.identifier) => if (name.name == Identifier.SIZEOF) { diff --git a/shared/src/main/scala/io/kaitai/struct/translators/GoTranslator.scala b/shared/src/main/scala/io/kaitai/struct/translators/GoTranslator.scala index 28647aec8..f9d600b5b 100644 --- a/shared/src/main/scala/io/kaitai/struct/translators/GoTranslator.scala +++ b/shared/src/main/scala/io/kaitai/struct/translators/GoTranslator.scala @@ -47,11 +47,11 @@ class GoTranslator(out: StringLanguageOutputWriter, provider: TypeProvider, impo trInterpolatedStringLiteral(s) case Ast.expr.Bool(n) => trBoolLiteral(n) - case Ast.expr.EnumById(enumType, id, inType) => - val enumSpec = provider.resolveEnum(inType, enumType.name) - trEnumById(enumSpec.name, translate(id)) - case Ast.expr.EnumByLabel(enumType, label, inType) => - val enumSpec = provider.resolveEnum(inType, enumType.name) + case Ast.expr.EnumById(ref, expr) => + val enumSpec = provider.resolveEnum(ref) + trEnumById(enumSpec.name, translate(expr)) + case Ast.expr.EnumByLabel(ref, label) => + val enumSpec = provider.resolveEnum(ref) trEnumByLabel(enumSpec.name, label.name) case Ast.expr.Name(name: Ast.identifier) => if (name.name == Identifier.SIZEOF) { diff --git a/shared/src/main/scala/io/kaitai/struct/translators/RustTranslator.scala b/shared/src/main/scala/io/kaitai/struct/translators/RustTranslator.scala index 788d49182..c56d73b2d 100644 --- a/shared/src/main/scala/io/kaitai/struct/translators/RustTranslator.scala +++ b/shared/src/main/scala/io/kaitai/struct/translators/RustTranslator.scala @@ -396,10 +396,10 @@ class RustTranslator(provider: TypeProvider, config: RuntimeConfig) override def translate(v: Ast.expr): String = { v match { - case Ast.expr.EnumById(enumType, id, inType) => - id match { + case Ast.expr.EnumById(ref, expr) => + expr match { case ifExp: Ast.expr.IfExp => - val enumSpec = provider.resolveEnum(inType, enumType.name) + val enumSpec = provider.resolveEnum(ref) val enumName = RustCompiler.types2class(enumSpec.name) def toStr(ex: Ast.expr) = ex match { case Ast.expr.IntNum(n) => s"$enumName::try_from($n)?" diff --git a/shared/src/main/scala/io/kaitai/struct/translators/TypeDetector.scala b/shared/src/main/scala/io/kaitai/struct/translators/TypeDetector.scala index 49cf61ef2..7866d764f 100644 --- a/shared/src/main/scala/io/kaitai/struct/translators/TypeDetector.scala +++ b/shared/src/main/scala/io/kaitai/struct/translators/TypeDetector.scala @@ -50,13 +50,13 @@ class TypeDetector(provider: TypeProvider) { case Ast.expr.Str(_) => CalcStrType case Ast.expr.InterpolatedStr(_) => CalcStrType case Ast.expr.Bool(_) => CalcBooleanType - case Ast.expr.EnumByLabel(enumType, _, inType) => - val t = EnumType(inType.names.toList :+ enumType.name, CalcIntType) - t.enumSpec = Some(provider.resolveEnum(inType, enumType.name)) + case Ast.expr.EnumByLabel(ref, _) => + val t = EnumType(ref, CalcIntType) + t.enumSpec = Some(provider.resolveEnum(ref)) t - case Ast.expr.EnumById(enumType, _, inType) => - val t = EnumType(List(enumType.name), CalcIntType) - t.enumSpec = Some(provider.resolveEnum(inType, enumType.name)) + case Ast.expr.EnumById(ref, _) => + val t = EnumType(ref, CalcIntType) + t.enumSpec = Some(provider.resolveEnum(ref)) t case Ast.expr.Name(name: Ast.identifier) => provider.determineType(name.name).asNonOwning() case Ast.expr.InternalName(id) => provider.determineType(id) diff --git a/shared/src/main/scala/io/kaitai/struct/translators/TypeProvider.scala b/shared/src/main/scala/io/kaitai/struct/translators/TypeProvider.scala index 9cf5548ad..e2d4eb117 100644 --- a/shared/src/main/scala/io/kaitai/struct/translators/TypeProvider.scala +++ b/shared/src/main/scala/io/kaitai/struct/translators/TypeProvider.scala @@ -16,7 +16,7 @@ trait TypeProvider { def determineType(attrId: Identifier): DataType def determineType(inClass: ClassSpec, attrName: String): DataType def determineType(inClass: ClassSpec, attrId: Identifier): DataType - def resolveEnum(typeName: Ast.typeId, enumName: String): EnumSpec + def resolveEnum(ref: Ast.EnumRef): EnumSpec def resolveType(typeName: Ast.typeId): DataType def isLazy(attrName: String): Boolean def isLazy(inClass: ClassSpec, attrName: String): Boolean