Skip to content

Commit 5f561e1

Browse files
committed
Stabilized generation order of everything related to maps by switching from Map to
SortedMap where applicable, namely: * Subtypes (ordered by type name) * Instances (ordered by instance human readable name) * Enums (ordered by numberical value) * Switch cases within attribute's switch type (ordered by expr toString rendition)
1 parent 66ad703 commit 5f561e1

10 files changed

Lines changed: 86 additions & 63 deletions

File tree

‎jvm/src/test/scala/io/kaitai/struct/datatype/SwitchType$Test.scala‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,14 @@ import io.kaitai.struct.format.ClassSpec
66
import org.scalatest.funspec.AnyFunSpec
77
import org.scalatest.matchers.should.Matchers._
88

9+
import scala.collection.immutable.SortedMap
10+
911
class SwitchType$Test extends AnyFunSpec {
1012
describe("SwitchType.parseSwitch") {
1113
it ("combines ints properly") {
1214
val t = SwitchType(
1315
Expressions.parse("foo"),
14-
Map(
16+
SortedMap(
1517
Expressions.parse("1") -> DataType.IntMultiType(true, DataType.Width2, Some(LittleEndian)),
1618
Expressions.parse("2") -> DataType.IntMultiType(false, DataType.Width4, Some(LittleEndian))
1719
)
@@ -28,7 +30,7 @@ class SwitchType$Test extends AnyFunSpec {
2830

2931
val t = SwitchType(
3032
Expressions.parse("foo"),
31-
Map(
33+
SortedMap(
3234
Expressions.parse("1") -> ut1,
3335
Expressions.parse("2") -> ut2
3436
)

‎shared/src/main/scala/io/kaitai/struct/ClassCompiler.scala‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -358,7 +358,7 @@ class ClassCompiler(
358358
}
359359

360360
def compileEnum(curClass: ClassSpec, enumColl: EnumSpec): Unit =
361-
lang.enumDeclaration(curClass.name, enumColl.name.last, enumColl.sortedSeq)
361+
lang.enumDeclaration(curClass.name, enumColl.name.last, enumColl.map.toSeq)
362362

363363
def isUnalignedBits(dt: DataType): Boolean =
364364
dt match {

‎shared/src/main/scala/io/kaitai/struct/ConstructClassCompiler.scala‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ class ConstructClassCompiler(classSpecs: ClassSpecs, topClass: ClassSpec) extend
104104
importList.add("import enum")
105105
out.puts(s"class ${types2class(enumSpec.name)}(enum.IntEnum):")
106106
out.inc
107-
enumSpec.sortedSeq.foreach { case (id, valueSpec) =>
107+
enumSpec.map.foreach { case (id, valueSpec) =>
108108
out.puts(s"${valueSpec.name} = ${translator.doIntLiteral(id)}")
109109
}
110110
out.dec

‎shared/src/main/scala/io/kaitai/struct/HtmlClassCompiler.scala‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ class HtmlClassCompiler(classSpecs: ClassSpecs, topClass: ClassSpec) extends Doc
112112
out.puts("<th>ID</th><th>Name</th><th>Note</th>")
113113
out.puts("</tr>")
114114

115-
enumColl.sortedSeq.foreach { case (id, value) =>
115+
enumColl.map.foreach { case (id, value) =>
116116
out.puts("<tr>")
117117
out.puts(s"<td>$id</td><td>${value.name}</td><td>${value.doc.summary.getOrElse("")}</td></tr>")
118118
out.puts("</tr>")

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

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import io.kaitai.struct.format._
55
import io.kaitai.struct.problems.KSYParseError
66
import io.kaitai.struct.translators.TypeDetector
77

8+
import scala.collection.immutable.SortedMap
9+
810
sealed trait DataType {
911
/**
1012
* @return Data type as non-owning data type. Default implementation
@@ -228,7 +230,7 @@ object DataType {
228230

229231
case class SwitchType(
230232
on: Ast.expr,
231-
cases: Map[Ast.expr, DataType],
233+
cases: SortedMap[Ast.expr, DataType],
232234
isOwning: Boolean = true,
233235
override val isOwningInExpr: Boolean = false
234236
) extends ComplexDataType {
@@ -303,12 +305,13 @@ object DataType {
303305
val (_on, _cases) = fromYaml1(switchSpec, path)
304306

305307
val on = Expressions.parse(_on)
306-
val cases: Map[Ast.expr, DataType] = _cases.map { case (condition, typeName) =>
307-
Expressions.parse(condition) -> DataType.fromYaml(
308-
Some(typeName), path ++ List("cases"), metaDef,
309-
arg
310-
)
311-
}
308+
val cases: Map[Ast.expr, DataType] = SortedMap.from(
309+
_cases.map { case (condition, typeName) =>
310+
Expressions.parse(condition) -> DataType.fromYaml(
311+
Some(typeName), path ++ List("cases"), metaDef,
312+
arg
313+
)
314+
})
312315

313316
// If we have size defined, and we don't have any "else" case already, add
314317
// an implicit "else" case that will at least catch everything else as
@@ -328,7 +331,7 @@ object DataType {
328331
}
329332
}
330333

331-
SwitchType(on, cases ++ addCases)
334+
SwitchType(on, SortedMap.from(cases ++ addCases))
332335
}
333336
}
334337

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

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package io.kaitai.struct.exprlang
22

3-
import io.kaitai.struct.format.Identifier
3+
import io.kaitai.struct.format.{Identifier, InstanceIdentifier}
44

55
/**
66
* Loosely based on /pythonparse/shared/src/main/scala/pythonparse/
@@ -89,6 +89,12 @@ object Ast {
8989
/** For internal use in the compiler. It cannot appear in an AST parsed from a user-supplied string. */
9090
case class InternalName(id: Identifier) extends expr
9191
case class List(elts: Seq[expr]) extends expr
92+
93+
/**
94+
* Implicit declaration of ordering, so expressions can be used for ordering operations, e.g.
95+
* for `SortedMap.from(...)`
96+
*/
97+
implicit val ExprIdentifierOrdering: Ordering[expr] = Ordering.by(_.toString)
9298
}
9399

94100
sealed trait boolop

‎shared/src/main/scala/io/kaitai/struct/format/AttrSpec.scala‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import io.kaitai.struct.exprlang.Ast.expr
88
import io.kaitai.struct.exprlang.{Ast, Expressions}
99
import io.kaitai.struct.problems.KSYParseError
1010

11+
import scala.collection.immutable.SortedMap
12+
1113
case class ConditionalSpec(ifExpr: Option[Ast.expr], repeat: RepeatSpec)
1214

1315
trait AttrLikeSpec extends MemberSpec {
@@ -312,6 +314,6 @@ object AttrSpec {
312314
}
313315
}
314316

315-
SwitchType(on, cases ++ addCases)
317+
SwitchType(on, SortedMap.from(cases ++ addCases))
316318
}
317319
}

‎shared/src/main/scala/io/kaitai/struct/format/ClassSpec.scala‎

Lines changed: 37 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import io.kaitai.struct.datatype.DataType._
55
import io.kaitai.struct.exprlang.Ast
66
import io.kaitai.struct.problems.KSYParseError
77

8+
import scala.collection.immutable.SortedMap
89
import scala.collection.mutable
910

1011
/**
@@ -36,9 +37,9 @@ case class ClassSpec(
3637
toStringExpr: Option[Ast.expr],
3738
params: List[ParamDefSpec],
3839
seq: List[AttrSpec],
39-
types: Map[String, ClassSpec],
40-
instances: Map[InstanceIdentifier, InstanceSpec],
41-
enums: Map[String, EnumSpec]
40+
types: SortedMap[String, ClassSpec],
41+
instances: SortedMap[InstanceIdentifier, InstanceSpec],
42+
enums: SortedMap[String, EnumSpec]
4243
) extends ClassSpecLike with YAMLPath {
4344
var parentClass: ClassSpecLike = UnknownClassSpec
4445

@@ -154,20 +155,20 @@ object ClassSpec {
154155
case Some(value) => seqFromYaml(value, path ++ List("seq"), meta)
155156
case None => List()
156157
}
157-
val instances: Map[InstanceIdentifier, InstanceSpec] = srcMap.get("instances") match {
158+
val instances: SortedMap[InstanceIdentifier, InstanceSpec] = srcMap.get("instances") match {
158159
case Some(value) => instancesFromYaml(value, path ++ List("instances"), meta)
159-
case None => Map()
160+
case None => SortedMap()
160161
}
161162

162163
checkDupMemberIds(params ++ seq ++ instances.values)
163164

164-
val types: Map[String, ClassSpec] = srcMap.get("types") match {
165+
val types: SortedMap[String, ClassSpec] = srcMap.get("types") match {
165166
case Some(value) => typesFromYaml(value, fileName, path ++ List("types"), meta)
166-
case None => Map()
167+
case None => SortedMap()
167168
}
168-
val enums: Map[String, EnumSpec] = srcMap.get("enums") match {
169+
val enums: SortedMap[String, EnumSpec] = srcMap.get("enums") match {
169170
case Some(value) => enumsFromYaml(value, path ++ List("enums"))
170-
case None => Map()
171+
case None => SortedMap()
171172
}
172173

173174
val cs = ClassSpec(
@@ -240,31 +241,37 @@ object ClassSpec {
240241
}
241242
}
242243

243-
def typesFromYaml(src: Any, fileName: Option[String], path: List[String], metaDef: MetaSpec): Map[String, ClassSpec] = {
244+
def typesFromYaml(src: Any, fileName: Option[String], path: List[String], metaDef: MetaSpec): SortedMap[String, ClassSpec] = {
244245
val srcMap = ParseUtils.asMapStr(src, path)
245-
srcMap.map { case (typeName, body) =>
246-
Identifier.checkIdentifierSource(typeName, "type", path ++ List(typeName))
247-
typeName -> ClassSpec.fromYaml(body, fileName, path ++ List(typeName), metaDef)
248-
}
246+
SortedMap.from(
247+
srcMap.map { case (typeName, body) =>
248+
Identifier.checkIdentifierSource(typeName, "type", path ++ List(typeName))
249+
typeName -> ClassSpec.fromYaml(body, fileName, path ++ List(typeName), metaDef)
250+
}
251+
)
249252
}
250253

251-
def instancesFromYaml(src: Any, path: List[String], metaDef: MetaSpec): Map[InstanceIdentifier, InstanceSpec] = {
254+
def instancesFromYaml(src: Any, path: List[String], metaDef: MetaSpec): SortedMap[InstanceIdentifier, InstanceSpec] = {
252255
val srcMap = ParseUtils.asMap(src, path)
253-
srcMap.map { case (key, body) =>
254-
val instName = ParseUtils.asStr(key, path)
255-
Identifier.checkIdentifierSource(instName, "instance", path ++ List(instName))
256-
val id = InstanceIdentifier(instName)
257-
id -> InstanceSpec.fromYaml(body, path ++ List(instName), metaDef, id)
258-
}
256+
SortedMap.from(
257+
srcMap.map { case (key, body) =>
258+
val instName = ParseUtils.asStr(key, path)
259+
Identifier.checkIdentifierSource(instName, "instance", path ++ List(instName))
260+
val id = InstanceIdentifier(instName)
261+
id -> InstanceSpec.fromYaml(body, path ++ List(instName), metaDef, id)
262+
}
263+
)
259264
}
260265

261-
def enumsFromYaml(src: Any, path: List[String]): Map[String, EnumSpec] = {
266+
def enumsFromYaml(src: Any, path: List[String]): SortedMap[String, EnumSpec] = {
262267
val srcMap = ParseUtils.asMap(src, path)
263-
srcMap.map { case (key, body) =>
264-
val enumName = ParseUtils.asStr(key, path)
265-
Identifier.checkIdentifierSource(enumName, "enum", path ++ List(enumName))
266-
enumName -> EnumSpec.fromYaml(body, path ++ List(enumName))
267-
}
268+
SortedMap.from(
269+
srcMap.map { case (key, body) =>
270+
val enumName = ParseUtils.asStr(key, path)
271+
Identifier.checkIdentifierSource(enumName, "enum", path ++ List(enumName))
272+
enumName -> EnumSpec.fromYaml(body, path ++ List(enumName))
273+
}
274+
)
268275
}
269276

270277
def fromYaml(src: Any, fileName: Option[String]): ClassSpec = fromYaml(src, fileName, List(), MetaSpec.OPAQUE)
@@ -279,9 +286,9 @@ object ClassSpec {
279286
toStringExpr = None,
280287
params = List(),
281288
seq = List(),
282-
types = Map(),
283-
instances = Map(),
284-
enums = Map()
289+
types = SortedMap(),
290+
instances = SortedMap(),
291+
enums = SortedMap()
285292
)
286293
placeholder.name = typeName
287294
placeholder

‎shared/src/main/scala/io/kaitai/struct/format/EnumSpec.scala‎

Lines changed: 15 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,40 +2,37 @@ package io.kaitai.struct.format
22

33
import io.kaitai.struct.problems.KSYParseError
44

5+
import scala.collection.immutable.SortedMap
56
import scala.collection.mutable
67

7-
case class EnumSpec(path: List[String], map: Map[Long, EnumValueSpec]) extends YAMLPath {
8+
case class EnumSpec(path: List[String], map: SortedMap[Long, EnumValueSpec]) extends YAMLPath {
89
var name = List[String]()
910

1011
/**
1112
* @return Absolute name of enum as string, components separated by
1213
* double colon operator `::`
1314
*/
1415
def nameAsStr = name.mkString("::")
15-
16-
/**
17-
* Stabilize order of generated enums by sorting it by integer ID - it
18-
* both looks nicer and doesn't screw diffs in generated code.
19-
*/
20-
lazy val sortedSeq: Seq[(Long, EnumValueSpec)] = map.toSeq.sortBy(_._1)
2116
}
2217

2318
object EnumSpec {
2419
def fromYaml(src: Any, path: List[String]): EnumSpec = {
2520
val srcMap = ParseUtils.asMap(src, path)
2621
val memberNameMap = mutable.Map[String, Long]()
27-
EnumSpec(path, srcMap.map { case (id, desc) =>
28-
val idLong = ParseUtils.asLong(id, path)
29-
val value = EnumValueSpec.fromYaml(desc, path ++ List(idLong.toString))
22+
EnumSpec(path, SortedMap.from(
23+
srcMap.map { case (id, desc) =>
24+
val idLong = ParseUtils.asLong(id, path)
25+
val value = EnumValueSpec.fromYaml(desc, path ++ List(idLong.toString))
3026

31-
memberNameMap.get(value.name).foreach { (prevIdLong) =>
32-
throw KSYParseError.withText(
33-
s"duplicate enum member ID: '${value.name}', previously defined at /${(path ++ List(prevIdLong.toString)).mkString("/")}",
34-
path ++ List(idLong.toString)
35-
)
27+
memberNameMap.get(value.name).foreach { (prevIdLong) =>
28+
throw KSYParseError.withText(
29+
s"duplicate enum member ID: '${value.name}', previously defined at /${(path ++ List(prevIdLong.toString)).mkString("/")}",
30+
path ++ List(idLong.toString)
31+
)
32+
}
33+
memberNameMap.put(value.name, idLong)
34+
idLong -> value
3635
}
37-
memberNameMap.put(value.name, idLong)
38-
idLong -> value
39-
})
36+
))
4037
}
4138
}

‎shared/src/main/scala/io/kaitai/struct/format/Identifier.scala‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,12 @@ object Identifier {
8080
val SWITCH_ON = "_on"
8181
val IS_LE = "_is_le"
8282
val SIZEOF = "_sizeof"
83+
84+
/**
85+
* Implicit declaration of ordering, so identifiers can be used for ordering operations, e.g.
86+
* for `SortedMap.from(...)`
87+
*/
88+
implicit val InstanceIdentifierOrdering: Ordering[InstanceIdentifier] = Ordering.by(_.humanReadable)
8389
}
8490

8591
case class RawIdentifier(innerId: Identifier) extends Identifier {

0 commit comments

Comments
 (0)