Skip to content

Commit

Permalink
ResolveTypes: convert to proper PrecompileStep, returning list of pro…
Browse files Browse the repository at this point in the history
…blems rather than just throwing an exception on first one encountered
  • Loading branch information
GreyCat committed Aug 7, 2022
1 parent ee42eb3 commit 6929c0e
Show file tree
Hide file tree
Showing 3 changed files with 68 additions and 31 deletions.
11 changes: 9 additions & 2 deletions shared/src/main/scala/io/kaitai/struct/Main.scala
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,14 @@ object Main {
def precompile(classSpecs: ClassSpecs, topClass: ClassSpec, config: RuntimeConfig): Iterable[CompilationProblem] = {
classSpecs.foreach { case (_, curClass) => MarkupClassNames.markupClassNames(curClass) }
val opaqueTypes = topClass.meta.opaqueTypes.getOrElse(config.opaqueTypes)
new ResolveTypes(classSpecs, opaqueTypes).run()
val resolveTypeProblems = new ResolveTypes(classSpecs, opaqueTypes).run()

// For now, bail out early in case we have any type resolution problems
// TODO: try to recover and do some more passes even in face of these
if (resolveTypeProblems.nonEmpty) {
return resolveTypeProblems
}

new ParentTypes(classSpecs).run()
new SpecsValueTypeDerive(classSpecs).run()
new CalculateSeqSizes(classSpecs).run()
Expand All @@ -67,7 +74,7 @@ object Main {

topClass.parentClass = GenericStructClassSpec

typeValidatorProblems ++ styleWarnings
resolveTypeProblems ++ typeValidatorProblems ++ styleWarnings
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,61 +4,70 @@ import io.kaitai.struct.Log
import io.kaitai.struct.datatype.DataType
import io.kaitai.struct.datatype.DataType.{ArrayType, EnumType, SwitchType, UserType}
import io.kaitai.struct.format._
import io.kaitai.struct.problems.KSYParseError
import io.kaitai.struct.problems._

/**
* A collection of methods that resolves user types and enum types, i.e.
* converts names into ClassSpec / EnumSpec references.
*/
class ResolveTypes(specs: ClassSpecs, opaqueTypes: Boolean) {
def run(): Unit = specs.forEachRec(resolveUserTypes)
class ResolveTypes(specs: ClassSpecs, opaqueTypes: Boolean) extends PrecompileStep {
override def run(): Iterable[CompilationProblem] = specs.mapRec(resolveUserTypes)

/**
* Resolves user types and enum types recursively starting from a certain
* ClassSpec.
* @param curClass class to start from, might be top-level class
*/
def resolveUserTypes(curClass: ClassSpec): Unit = {
curClass.seq.foreach((attr) => resolveUserTypeForMember(curClass, attr))

curClass.instances.foreach { case (_, instSpec) =>
instSpec match {
case pis: ParseInstanceSpec =>
resolveUserTypeForMember(curClass, pis)
case _: ValueInstanceSpec =>
// ignore all other types of instances
def resolveUserTypes(curClass: ClassSpec): Iterable[CompilationProblem] = {
val seqProblems: Iterable[CompilationProblem] =
curClass.seq.flatMap((attr) => resolveUserTypeForMember(curClass, attr))

val instancesProblems: Iterable[CompilationProblem] =
curClass.instances.flatMap { case (_, instSpec) =>
instSpec match {
case pis: ParseInstanceSpec =>
resolveUserTypeForMember(curClass, pis)
case _: ValueInstanceSpec =>
// ignore all other types of instances
None
}
}
}

curClass.params.foreach((paramDef) => resolveUserTypeForMember(curClass, paramDef))
val paramsProblems: Iterable[CompilationProblem] =
curClass.params.flatMap((paramDef) => resolveUserTypeForMember(curClass, paramDef))

seqProblems ++ instancesProblems ++ paramsProblems
}

def resolveUserTypeForMember(curClass: ClassSpec, attr: MemberSpec): Unit =
def resolveUserTypeForMember(curClass: ClassSpec, attr: MemberSpec): Iterable[CompilationProblem] =
resolveUserType(curClass, attr.dataType, attr.path)

def resolveUserType(curClass: ClassSpec, dataType: DataType, path: List[String]): Unit = {
def resolveUserType(curClass: ClassSpec, dataType: DataType, path: List[String]): Iterable[CompilationProblem] = {
dataType match {
case ut: UserType =>
ut.classSpec = resolveUserType(curClass, ut.name, path)
val (resClassSpec, problems) = resolveUserType(curClass, ut.name, path)
ut.classSpec = resClassSpec
problems
case et: EnumType =>
et.enumSpec = resolveEnumSpec(curClass, et.name)
if (et.enumSpec.isEmpty) {
val err = new EnumNotFoundError(et.name.mkString("::"), curClass)
throw KSYParseError.withText(err.getMessage, path)
Some(EnumNotFoundErr(et.name, curClass, path))
} else {
None
}
case st: SwitchType =>
st.cases.foreach { case (caseName, ut) =>
st.cases.flatMap { case (caseName, ut) =>
resolveUserType(curClass, ut, path ++ List("type", "cases", caseName.toString))
}
case at: ArrayType =>
resolveUserType(curClass, at.elType, path)
case _ =>
// not a user type, nothing to resolve
None
}
}

def resolveUserType(curClass: ClassSpec, typeName: List[String], path: List[String]): Option[ClassSpec] = {
Log.typeResolve.info(() => s"resolveUserType: at ${curClass.name} doing ${typeName.mkString("|")}")
def resolveUserType(curClass: ClassSpec, typeName: List[String], path: List[String]): (Option[ClassSpec], Option[CompilationProblem]) = {
val res = realResolveUserType(curClass, typeName, path)

res match {
Expand All @@ -67,19 +76,20 @@ class ResolveTypes(specs: ClassSpecs, opaqueTypes: Boolean) {
if (opaqueTypes) {
// Generate special "opaque placeholder" ClassSpec
Log.typeResolve.info(() => " => ??? (generating opaque type)")
Some(ClassSpec.opaquePlaceholder(typeName))
(Some(ClassSpec.opaquePlaceholder(typeName)), None)
} else {
// Opaque types are disabled => that is an error
val err = new TypeNotFoundError(typeName.mkString("::"), curClass)
throw KSYParseError.withText(err.getMessage, path)
(None, Some(TypeNotFoundErr(typeName, curClass, path)))
}
case Some(x) =>
Log.typeResolve.info(() => s" => ${x.nameAsStr}")
res
(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
Expand All @@ -92,7 +102,7 @@ class ResolveTypes(specs: ClassSpecs, opaqueTypes: Boolean) {
Some(nestedClass)
} else {
// Try to resolve recursively
resolveUserType(nestedClass, restNames, path)
realResolveUserType(nestedClass, restNames, path)
}
)

Expand All @@ -102,7 +112,7 @@ class ResolveTypes(specs: ClassSpecs, opaqueTypes: Boolean) {
// No luck resolving here, let's try upper levels, if they exist
curClass.upClass match {
case Some(upClass) =>
resolveUserType(upClass, typeName, path)
realResolveUserType(upClass, typeName, path)
case None =>
// Check this class if it's top-level class
if (curClass.name.head == firstName) {
Expand All @@ -116,7 +126,7 @@ class ResolveTypes(specs: ClassSpecs, opaqueTypes: Boolean) {
case Some(classSpec) => if (restNames.isEmpty) {
resolvedTop
} else {
resolveUserType(classSpec, restNames, path)
realResolveUserType(classSpec, restNames, path)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,26 @@ 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)
extends CompilationProblem {

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)
extends CompilationProblem {

override def text = s"unable to find enum '${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
}

abstract class StyleWarning(val coords: ProblemCoords) extends CompilationProblem {
/**
* @return main warning text, without references to the style guide
Expand Down

0 comments on commit 6929c0e

Please sign in to comment.