Skip to content

Commit 6ac1562

Browse files
committed
Add support of absolute paths for enums on parse level
1 parent 3e68dcc commit 6ac1562

File tree

12 files changed

+116
-38
lines changed

12 files changed

+116
-38
lines changed
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package io.kaitai.struct.exprlang
2+
3+
import org.scalatest.funspec.AnyFunSpec
4+
import org.scalatest.matchers.should.Matchers._
5+
6+
class EnumRefSpec extends AnyFunSpec {
7+
describe("Expressions.parseEnumRef") {
8+
describe("parses local enum refs") {
9+
it("some_enum") {
10+
Expressions.parseEnumRef("some_enum") should be(Ast.EnumRef(
11+
false, Seq(), "some_enum"
12+
))
13+
}
14+
it("with spaces: ' some_enum '") {
15+
Expressions.parseEnumRef(" some_enum ") should be(Ast.EnumRef(
16+
false, Seq(), "some_enum"
17+
))
18+
}
19+
20+
it("::some_enum") {
21+
Expressions.parseEnumRef("::some_enum") should be(Ast.EnumRef(
22+
true, Seq(), "some_enum"
23+
))
24+
}
25+
it("with spaces: ' :: some_enum '") {
26+
Expressions.parseEnumRef(" :: some_enum ") should be(Ast.EnumRef(
27+
true, Seq(), "some_enum"
28+
))
29+
}
30+
}
31+
32+
describe("parses path enum refs") {
33+
it("some::enum") {
34+
Expressions.parseEnumRef("some::enum") should be(Ast.EnumRef(
35+
false, Seq("some"), "enum"
36+
))
37+
}
38+
it("with spaces: ' some :: enum '") {
39+
Expressions.parseEnumRef(" some :: enum ") should be(Ast.EnumRef(
40+
false, Seq("some"), "enum"
41+
))
42+
}
43+
44+
it("::some::enum") {
45+
Expressions.parseEnumRef("::some::enum") should be(Ast.EnumRef(
46+
true, Seq("some"), "enum"
47+
))
48+
}
49+
it("with spaces: ' :: some :: enum '") {
50+
Expressions.parseEnumRef(" :: some :: enum ") should be(Ast.EnumRef(
51+
true, Seq("some"), "enum"
52+
))
53+
}
54+
}
55+
}
56+
}

shared/src/main/scala/io/kaitai/struct/GraphvizClassCompiler.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -484,7 +484,7 @@ object GraphvizClassCompiler extends LanguageCompilerStatic {
484484
): LanguageCompiler = ???
485485

486486
def type2class(name: List[String]) = name.last
487-
def type2display(name: List[String]) = name.map(Utils.upperCamelCase).mkString("::")
487+
def type2display(name: Seq[String]) = name.map(Utils.upperCamelCase).mkString("::")
488488

489489
def dataTypeName(dataType: DataType, valid: Option[ValidationSpec]): String = {
490490
dataType match {
@@ -508,7 +508,7 @@ object GraphvizClassCompiler extends LanguageCompilerStatic {
508508
val comma = if (bytesStr.isEmpty) "" else ", "
509509
s"str($bytesStr$comma$encoding)"
510510
case EnumType(name, basedOn) =>
511-
s"${dataTypeName(basedOn, valid)}${type2display(name)}"
511+
s"${dataTypeName(basedOn, valid)}${type2display(name.fullName)}"
512512
case BitsType(width, bitEndian) => s"b$width${bitEndian.toSuffix}"
513513
case BitsType1(bitEndian) => s"b1${bitEndian.toSuffix}→bool"
514514
case _ => dataType.toString

shared/src/main/scala/io/kaitai/struct/datatype/DataType.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -252,7 +252,7 @@ object DataType {
252252
def isOwning = false
253253
}
254254

255-
case class EnumType(name: List[String], basedOn: IntType) extends DataType {
255+
case class EnumType(name: Ast.EnumRef, basedOn: IntType) extends DataType {
256256
var enumSpec: Option[EnumSpec] = None
257257

258258
/**
@@ -487,7 +487,7 @@ object DataType {
487487
enumRef match {
488488
case Some(enumName) =>
489489
r match {
490-
case numType: IntType => EnumType(classNameToList(enumName), numType)
490+
case numType: IntType => EnumType(Expressions.parseEnumRef(enumName), numType)
491491
case _ =>
492492
throw KSYParseError(s"tried to resolve non-integer $r to enum", path).toException
493493
}

shared/src/main/scala/io/kaitai/struct/exprlang/Ast.scala

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,5 +141,18 @@ object Ast {
141141
case object GtE extends cmpop
142142
}
143143

144+
/**
145+
* Reference to an enum in scope. Scope is defined by the `absolute` flag and
146+
* a path to a type (which can be empty) in which enum is defined.
147+
*/
148+
case class EnumRef(absolute: Boolean, typePath: Seq[String], name: String) {
149+
/** @return Type path and name of enum in one list. */
150+
def fullName: Seq[String] = typePath :+ name
151+
/**
152+
* @return Enum designation name as human-readable string, to be used in compiler
153+
* error messages.
154+
*/
155+
def asStr: String = fullName.mkString(if (absolute) "::" else "", "::", "")
156+
}
144157
case class TypeWithArguments(typeName: typeId, arguments: expr.List)
145158
}

shared/src/main/scala/io/kaitai/struct/exprlang/Expressions.scala

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,13 @@ object Expressions {
195195
case (path, Some(args)) => Ast.TypeWithArguments(path, args)
196196
}
197197

198+
def enumRef[$: P]: P[Ast.EnumRef] = P(Start ~ "::".!.? ~ NAME.rep(1, "::") ~ End).map {
199+
case (absolute, names) =>
200+
// List have at least one element, so we always can split it into head and the last element
201+
val typePath :+ enumName = names
202+
Ast.EnumRef(absolute.nonEmpty, typePath.map(i => i.name), enumName.name)
203+
}
204+
198205
class ParseException(val src: String, val failure: Parsed.Failure)
199206
extends RuntimeException(failure.msg)
200207

@@ -211,6 +218,14 @@ object Expressions {
211218
*/
212219
def parseTypeRef(src: String): Ast.TypeWithArguments = realParse(src, typeRef(_))
213220

221+
/**
222+
* Parse string with reference to enumeration definition, optionally in full path format.
223+
*
224+
* @param src Enum reference as string, like `::path::to::enum`
225+
* @return Object that represents path to enum
226+
*/
227+
def parseEnumRef(src: String): Ast.EnumRef = realParse(src, enumRef(_))
228+
214229
private def realParse[T](src: String, parser: P[_] => P[T]): T = {
215230
val r = fastparse.parse(src.trim, parser)
216231
r match {

shared/src/main/scala/io/kaitai/struct/languages/CSharpCompiler.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -688,7 +688,7 @@ object CSharpCompiler extends LanguageCompilerStatic
688688
case KaitaiStreamType | OwnedKaitaiStreamType => kstreamName
689689

690690
case t: UserType => types2class(t.name)
691-
case EnumType(name, _) => types2class(name)
691+
case EnumType(ref, _) => types2class(ref.fullName)
692692

693693
case at: ArrayType => {
694694
importList.add("System.Collections.Generic")

shared/src/main/scala/io/kaitai/struct/languages/CppCompiler.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1149,7 +1149,7 @@ object CppCompiler extends LanguageCompilerStatic
11491149
types2class(if (absolute) {
11501150
t.enumSpec.get.name
11511151
} else {
1152-
t.name
1152+
t.name.fullName
11531153
})
11541154

11551155
case at: ArrayType => {
@@ -1210,7 +1210,7 @@ object CppCompiler extends LanguageCompilerStatic
12101210
)
12111211
}
12121212

1213-
def types2class(components: List[String]) =
1213+
def types2class(components: Seq[String]) =
12141214
components.map(type2class).mkString("::")
12151215

12161216
def type2class(name: String) = Utils.lowerUnderscoreCase(name) + "_t"

shared/src/main/scala/io/kaitai/struct/languages/JavaCompiler.scala

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -874,7 +874,7 @@ object JavaCompiler extends LanguageCompilerStatic
874874
case KaitaiStructType | CalcKaitaiStructType(_) => kstructName
875875

876876
case t: UserType => types2class(t.name)
877-
case EnumType(name, _) => types2class(name)
877+
case EnumType(ref, _) => types2class(ref.fullName)
878878

879879
case _: ArrayType => kaitaiType2JavaTypeBoxed(attrType, importList)
880880

@@ -918,7 +918,7 @@ object JavaCompiler extends LanguageCompilerStatic
918918
case KaitaiStructType | CalcKaitaiStructType(_) => kstructName
919919

920920
case t: UserType => types2class(t.name)
921-
case EnumType(name, _) => types2class(name)
921+
case EnumType(ref, _) => types2class(ref.fullName)
922922

923923
case at: ArrayType => {
924924
importList.add("java.util.ArrayList")
@@ -929,7 +929,7 @@ object JavaCompiler extends LanguageCompilerStatic
929929
}
930930
}
931931

932-
def types2class(names: List[String]) = names.map(x => type2class(x)).mkString(".")
932+
def types2class(names: Seq[String]) = names.map(x => type2class(x)).mkString(".")
933933

934934
override def kstreamName: String = "KaitaiStream"
935935
override def kstructName: String = "KaitaiStruct"

shared/src/main/scala/io/kaitai/struct/languages/RustCompiler.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1312,7 +1312,7 @@ object RustCompiler
13121312
def classTypeName(c: ClassSpec): String =
13131313
s"${types2class(c.name)}"
13141314

1315-
def types2class(names: List[String]): String =
1315+
def types2class(names: Seq[String]): String =
13161316
// TODO: Use `mod` to scope types instead of weird names
13171317
names.map(x => type2class(x)).mkString("_")
13181318

@@ -1339,7 +1339,7 @@ object RustCompiler
13391339
case t: EnumType =>
13401340
val baseName = t.enumSpec match {
13411341
case Some(spec) => s"${types2class(spec.name)}"
1342-
case None => s"${types2class(t.name)}"
1342+
case None => s"${types2class(t.name.fullName)}"
13431343
}
13441344
baseName
13451345

shared/src/main/scala/io/kaitai/struct/precompile/ResolveTypes.scala

Lines changed: 13 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -68,26 +68,19 @@ class ResolveTypes(specs: ClassSpecs, topClass: ClassSpec, opaqueTypes: Boolean)
6868
}
6969
}
7070
case et: EnumType =>
71-
et.name match {
72-
case typePath :+ name =>
73-
try {
74-
val resolver = new ClassTypeProvider(specs, curClass)
75-
val ty = resolver.resolveEnum(Ast.typeId(false, typePath), name)
76-
Log.enumResolve.info(() => s" => ${ty.nameAsStr}")
77-
et.enumSpec = Some(ty)
78-
None
79-
} catch {
80-
case ex: TypeNotFoundError =>
81-
Log.typeResolve.info(() => s" => ??? (while resolving enum '${et.name}'): $ex")
82-
Log.enumResolve.info(() => s" => ??? (enclosing type not found, enum '${et.name}'): $ex")
83-
Some(TypeNotFoundErr(typePath, curClass, path :+ "enum"))
84-
case ex: EnumNotFoundError =>
85-
Log.enumResolve.info(() => s" => ??? (enum '${et.name}'): $ex")
86-
Some(EnumNotFoundErr(et.name, curClass, path :+ "enum"))
87-
}
88-
case _ =>
89-
Log.enumResolve.info(() => s" => ??? (enum '${et.name}' without name)")
90-
// TODO: Maybe more specific error about empty name?
71+
try {
72+
val resolver = new ClassTypeProvider(specs, curClass)
73+
val ty = resolver.resolveEnum(Ast.typeId(et.name.absolute, et.name.typePath), et.name.name)
74+
Log.enumResolve.info(() => s" => ${ty.nameAsStr}")
75+
et.enumSpec = Some(ty)
76+
None
77+
} catch {
78+
case ex: TypeNotFoundError =>
79+
Log.typeResolve.info(() => s" => ??? (while resolving enum '${et.name}'): $ex")
80+
Log.enumResolve.info(() => s" => ??? (enclosing type not found, enum '${et.name}'): $ex")
81+
Some(TypeNotFoundErr(et.name.typePath, curClass, path :+ "enum"))
82+
case ex: EnumNotFoundError =>
83+
Log.enumResolve.info(() => s" => ??? (enum '${et.name}'): $ex")
9184
Some(EnumNotFoundErr(et.name, curClass, path :+ "enum"))
9285
}
9386
case st: SwitchType =>

shared/src/main/scala/io/kaitai/struct/problems/CompilationProblem.scala

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ package io.kaitai.struct.problems
22

33
import io.kaitai.struct.{JSON, Jsonable, Utils, problems}
44
import io.kaitai.struct.datatype.DataType
5-
import io.kaitai.struct.exprlang.Expressions
5+
import io.kaitai.struct.exprlang.{Ast, Expressions}
66
import io.kaitai.struct.format.{ClassSpec, Identifier, KSVersion}
77
import fastparse.Parsed.Failure
88

@@ -170,7 +170,7 @@ case class ParamMismatchError(idx: Int, argType: DataType, paramName: String, pa
170170
override def severity: ProblemSeverity = ProblemSeverity.Error
171171
}
172172

173-
case class TypeNotFoundErr(name: List[String], curClass: ClassSpec, path: List[String], fileName: Option[String] = None)
173+
case class TypeNotFoundErr(name: Seq[String], curClass: ClassSpec, path: List[String], fileName: Option[String] = None)
174174
extends CompilationProblem {
175175

176176
override def text = s"unable to find type '${name.mkString("::")}', searching from '${curClass.nameAsStr}'"
@@ -180,10 +180,10 @@ case class TypeNotFoundErr(name: List[String], curClass: ClassSpec, path: List[S
180180
override def severity: ProblemSeverity = ProblemSeverity.Error
181181
}
182182

183-
case class EnumNotFoundErr(name: List[String], curClass: ClassSpec, path: List[String], fileName: Option[String] = None)
183+
case class EnumNotFoundErr(ref: Ast.EnumRef, curClass: ClassSpec, path: List[String], fileName: Option[String] = None)
184184
extends CompilationProblem {
185185

186-
override def text = s"unable to find enum '${name.mkString("::")}', searching from '${curClass.nameAsStr}'"
186+
override def text = s"unable to find enum '${ref.asStr}', searching from '${curClass.nameAsStr}'"
187187
override val coords: ProblemCoords = ProblemCoords(fileName, Some(path))
188188
override def localizedInFile(fileName: String): CompilationProblem =
189189
copy(fileName = Some(fileName))

shared/src/main/scala/io/kaitai/struct/translators/TypeDetector.scala

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,11 +51,12 @@ class TypeDetector(provider: TypeProvider) {
5151
case Ast.expr.InterpolatedStr(_) => CalcStrType
5252
case Ast.expr.Bool(_) => CalcBooleanType
5353
case Ast.expr.EnumByLabel(enumType, _, inType) =>
54-
val t = EnumType(inType.names.toList :+ enumType.name, CalcIntType)
54+
val t = EnumType(Ast.EnumRef(false, inType.names.toList, enumType.name), CalcIntType)
5555
t.enumSpec = Some(provider.resolveEnum(inType, enumType.name))
5656
t
5757
case Ast.expr.EnumById(enumType, _, inType) =>
58-
val t = EnumType(List(enumType.name), CalcIntType)
58+
// TODO: May be create a type with a name that includes surrounding type?
59+
val t = EnumType(Ast.EnumRef(false, List(), enumType.name), CalcIntType)
5960
t.enumSpec = Some(provider.resolveEnum(inType, enumType.name))
6061
t
6162
case Ast.expr.Name(name: Ast.identifier) => provider.determineType(name.name).asNonOwning()

0 commit comments

Comments
 (0)