
io.kaitai.struct.format.ClassSpec.scala Maven / Gradle / Ivy
package io.kaitai.struct.format
import io.kaitai.struct.datatype.DataType
import io.kaitai.struct.datatype.DataType.{KaitaiStructType, UserTypeInstream}
import scala.collection.mutable
/**
* Type that we use when we want to refer to a class specification or something
* close, but not (yet) that well defined.
*/
sealed trait ClassSpecLike
case object UnknownClassSpec extends ClassSpecLike
case object GenericStructClassSpec extends ClassSpecLike
sealed trait Sized
case object DynamicSized extends Sized
case object NotCalculatedSized extends Sized
case object StartedCalculationSized extends Sized
case class FixedSized(n: Int) extends Sized
case class ClassSpec(
path: List[String],
isTopLevel: Boolean,
meta: MetaSpec,
doc: DocSpec,
params: List[ParamDefSpec],
seq: List[AttrSpec],
types: Map[String, ClassSpec],
instances: Map[InstanceIdentifier, InstanceSpec],
enums: Map[String, EnumSpec]
) extends ClassSpecLike with YAMLPath {
var parentClass: ClassSpecLike = UnknownClassSpec
/**
* Full absolute name of the class (including all names of classes that
* it's nested into, as a namespace). Derived either from `meta`/`id`
* (for top-level classes), or from keys in `types` (for nested classes).
*/
var name = List[String]()
/**
* @return Absolute name of class as string, components separated by
* double colon operator `::`
*/
def nameAsStr = name.mkString("::")
/**
* The class specification that this class is nested into, if it exists.
* For top-level classes, it's None.
*/
var upClass: Option[ClassSpec] = None
var seqSize: Sized = NotCalculatedSized
def parentType: DataType = parentClass match {
case UnknownClassSpec | GenericStructClassSpec => KaitaiStructType
case t: ClassSpec => UserTypeInstream(t.name, None)
}
/**
* Recursively traverses tree of types starting from this type, calling
* certain function for every type, starting from this one.
*/
def forEachRec(proc: (ClassSpec) => Unit): Unit = {
proc.apply(this)
types.foreach { case (_, typeSpec) =>
typeSpec.forEachRec(proc)
}
}
}
object ClassSpec {
val LEGAL_KEYS = Set(
"meta",
"doc",
"doc-ref",
"params",
"seq",
"types",
"instances",
"enums"
)
def fromYaml(src: Any, path: List[String], metaDef: MetaSpec): ClassSpec = {
val srcMap = ParseUtils.asMapStr(src, path)
ParseUtils.ensureLegalKeys(srcMap, LEGAL_KEYS, path)
val metaPath = path ++ List("meta")
val explicitMeta = srcMap.get("meta").map(MetaSpec.fromYaml(_, metaPath)).getOrElse(MetaSpec.emptyWithPath(metaPath))
val meta = explicitMeta.fillInDefaults(metaDef)
val doc = DocSpec.fromYaml(srcMap, path)
val params: List[ParamDefSpec] = srcMap.get("params") match {
case Some(value) => paramDefFromYaml(value, path ++ List("params"))
case None => List()
}
val seq: List[AttrSpec] = srcMap.get("seq") match {
case Some(value) => seqFromYaml(value, path ++ List("seq"), meta)
case None => List()
}
val types: Map[String, ClassSpec] = srcMap.get("types") match {
case Some(value) => typesFromYaml(value, path ++ List("types"), meta)
case None => Map()
}
val instances: Map[InstanceIdentifier, InstanceSpec] = srcMap.get("instances") match {
case Some(value) => instancesFromYaml(value, path ++ List("instances"), meta)
case None => Map()
}
val enums: Map[String, EnumSpec] = srcMap.get("enums") match {
case Some(value) => enumsFromYaml(value, path ++ List("enums"))
case None => Map()
}
checkDupSeqInstIds(seq, instances)
val cs = ClassSpec(
path, path.isEmpty,
meta, doc,
params, seq, types, instances, enums
)
// If that's a top-level class, set its name from meta/id
if (path.isEmpty) {
explicitMeta.id match {
case None =>
throw new YAMLParseException("no `meta/id` encountered in top-level class spec", path ++ List("meta", "id"))
case Some(id) =>
cs.name = List(id)
}
}
cs
}
def paramDefFromYaml(src: Any, path: List[String]): List[ParamDefSpec] = {
src match {
case srcList: List[Any] =>
val params = srcList.zipWithIndex.map { case (attrSrc, idx) =>
ParamDefSpec.fromYaml(attrSrc, path ++ List(idx.toString), idx)
}
// FIXME: checkDupSeqIds(params)
params
case unknown =>
throw new YAMLParseException(s"expected array, found $unknown", path)
}
}
def seqFromYaml(src: Any, path: List[String], metaDef: MetaSpec): List[AttrSpec] = {
src match {
case srcList: List[Any] =>
val seq = srcList.zipWithIndex.map { case (attrSrc, idx) =>
AttrSpec.fromYaml(attrSrc, path ++ List(idx.toString), metaDef, idx)
}
checkDupSeqIds(seq)
seq
case unknown =>
throw new YAMLParseException(s"expected array, found $unknown", path)
}
}
def checkDupSeqIds(seq: List[AttrSpec]): Unit = {
val attrIds = mutable.Map[String, AttrSpec]()
seq.foreach { (attr) =>
attr.id match {
case NamedIdentifier(id) =>
checkDupId(attrIds.get(id), id, attr)
attrIds.put(id, attr)
case _ => // do nothing with non-named IDs
}
}
}
def checkDupSeqInstIds(seq: List[AttrSpec], instances: Map[InstanceIdentifier, InstanceSpec]): Unit = {
val attrIds: Map[String, AttrSpec] = seq.flatMap((attr) => attr.id match {
case NamedIdentifier(id) => Some(id -> attr)
case _ => None
}).toMap
instances.foreach { case (id, instSpec) =>
checkDupId(attrIds.get(id.name), id.name, instSpec)
}
}
private def checkDupId(prevAttrOpt: Option[AttrSpec], id: String, nowAttr: YAMLPath) {
prevAttrOpt match {
case Some(prevAttr) =>
throw new YAMLParseException(
s"duplicate attribute ID '$id', previously defined at /${prevAttr.pathStr}",
nowAttr.path
)
case None =>
// no dups, ok
}
}
def typesFromYaml(src: Any, path: List[String], metaDef: MetaSpec): Map[String, ClassSpec] = {
val srcMap = ParseUtils.asMapStr(src, path)
srcMap.map { case (typeName, body) =>
Identifier.checkIdentifierSource(typeName, "type", path ++ List(typeName))
typeName -> ClassSpec.fromYaml(body, path ++ List(typeName), metaDef)
}
}
def instancesFromYaml(src: Any, path: List[String], metaDef: MetaSpec): Map[InstanceIdentifier, InstanceSpec] = {
val srcMap = ParseUtils.asMap(src, path)
srcMap.map { case (key, body) =>
val instName = ParseUtils.asStr(key, path)
Identifier.checkIdentifierSource(instName, "instance", path ++ List(instName))
val id = InstanceIdentifier(instName)
id -> InstanceSpec.fromYaml(body, path ++ List(instName), metaDef, id)
}
}
def enumsFromYaml(src: Any, path: List[String]): Map[String, EnumSpec] = {
val srcMap = ParseUtils.asMap(src, path)
srcMap.map { case (key, body) =>
val enumName = ParseUtils.asStr(key, path)
Identifier.checkIdentifierSource(enumName, "enum", path ++ List(enumName))
enumName -> EnumSpec.fromYaml(body, path ++ List(enumName))
}
}
def fromYaml(src: Any): ClassSpec = fromYaml(src, List(), MetaSpec.OPAQUE)
def opaquePlaceholder(typeName: List[String]): ClassSpec = {
val placeholder = ClassSpec(
List(),
true,
meta = MetaSpec.OPAQUE,
doc = DocSpec.EMPTY,
params = List(),
seq = List(),
types = Map(),
instances = Map(),
enums = Map()
)
placeholder.name = typeName
placeholder
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy