Skip to content

Commit 02c287f

Browse files
committed
Fix compile error on unsigned 64-bit enum int constants
Related to kaitai-io/kaitai_struct#1288 (I consider the change in this commit to be a prerequisite for its full implementation; without it, enum support would be unnecessarily limited to *signed* 64-bit integers.) Fixes the compile error of the EnumLongRangeU test format after kaitai-io/kaitai_struct_tests@b88e1f5 This commit ensures that the compiler accepts integer constants between `2**63` (`0x8000_0000_0000_0000` or 9223372036854775808) and `2**64 - 1` (`0xffff_ffff_ffff_ffff` or 18446744073709551615) in enum definitions. This allows defining enums with integer values from the entire range of the unsigned 64-bit integer type in some target languages (where it is not broken for another reason). Previously, attempting to use an integer constant outside the range of the signed 64-bit integer type (i.e. over `2**63 - 1`) in the enum definition resulted in the following compile error: ``` formats/enum_long_range_u.ksy: /enums/constants: error: expected int, got 18446744073709551615 (class java.math.BigInteger) ```
1 parent 4c19506 commit 02c287f

17 files changed

Lines changed: 52 additions & 34 deletions

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

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import io.kaitai.struct.problems.KSYParseError
55
import scala.collection.immutable.SortedMap
66
import scala.collection.mutable
77

8-
case class EnumSpec(path: List[String], map: SortedMap[Long, EnumValueSpec]) extends YAMLPath {
8+
case class EnumSpec(path: List[String], map: SortedMap[BigInt, EnumValueSpec]) extends YAMLPath {
99
var name = List[String]()
1010

1111
/**
@@ -28,20 +28,20 @@ case class EnumSpec(path: List[String], map: SortedMap[Long, EnumValueSpec]) ext
2828
object EnumSpec {
2929
def fromYaml(src: Any, path: List[String]): EnumSpec = {
3030
val srcMap = ParseUtils.asMap(src, path)
31-
val memberNameMap = mutable.Map[String, Long]()
31+
val memberNameMap = mutable.Map[String, BigInt]()
3232
EnumSpec(path, SortedMap.from(
3333
srcMap.map { case (id, desc) =>
34-
val idLong = ParseUtils.asLong(id, path)
35-
val value = EnumValueSpec.fromYaml(desc, path ++ List(idLong.toString))
34+
val idBigInt = ParseUtils.asBigInt(id, path)
35+
val value = EnumValueSpec.fromYaml(desc, path ++ List(idBigInt.toString))
3636

37-
memberNameMap.get(value.name).foreach { (prevIdLong) =>
37+
memberNameMap.get(value.name).foreach { (prevIdBigInt) =>
3838
throw KSYParseError.withText(
39-
s"duplicate enum member ID: '${value.name}', previously defined at /${(path ++ List(prevIdLong.toString)).mkString("/")}",
40-
path ++ List(idLong.toString)
39+
s"duplicate enum member ID: '${value.name}', previously defined at /${(path ++ List(prevIdBigInt.toString)).mkString("/")}",
40+
path ++ List(idBigInt.toString)
4141
)
4242
}
43-
memberNameMap.put(value.name, idLong)
44-
idLong -> value
43+
memberNameMap.put(value.name, idBigInt)
44+
idBigInt -> value
4545
}
4646
))
4747
}

shared/src/main/scala/io/kaitai/struct/format/ParseUtils.scala

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -177,19 +177,35 @@ object ParseUtils {
177177
}
178178
}
179179

180-
def asLong(src: Any, path: List[String]): Long = {
180+
def asBigInt(src: Any, path: List[String]): BigInt = {
181181
src match {
182+
case n: Int =>
183+
n
182184
case n: Long =>
183185
n
184-
case n: Int =>
186+
// NB: at the time of writing, JavaKSYParser.yamlJavaToScala() only
187+
// converts Java types to Scala types in mapping values, not mapping keys.
188+
// Since this method is called on the keys of enum entries, we might get
189+
// `java.math.BigInteger` (not `BigInt`) if the YAML tree comes from
190+
// SnakeYAML.
191+
case n: java.math.BigInteger =>
192+
n
193+
// At the time of writing, we can actually never get `BigInt` here - it's
194+
// only included for potential "forward compatibility" in case we tweak
195+
// our YAML parsing.
196+
case n: BigInt =>
185197
n
186198
case str: String =>
187199
// Generally should not happen, but when the data comes from JavaScript,
188-
// all object keys are forced to be strings.
200+
// all object keys are forced to be strings. Nevertheless, since the
201+
// YAML parser parses the integer value first, we can only expect
202+
// strings returned by the `Number.prototype.toString()` method, which
203+
// formats numbers into canonical decimal format for all practically
204+
// large values.
189205
try {
190-
Utils.strToLong(str)
206+
BigInt(str)
191207
} catch {
192-
case ex: MatchError =>
208+
case _: NumberFormatException =>
193209
throw KSYParseError.withText(s"unable to parse `$str` as int", path)
194210
}
195211
case unknown =>

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -547,7 +547,7 @@ class CSharpCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig)
547547

548548
def flagForInstName(ksName: Identifier) = s"f_${idToStr(ksName)}"
549549

550-
override def enumDeclaration(curClass: String, enumName: String, enumColl: Seq[(Long, String)]): Unit = {
550+
override def enumDeclaration(curClass: String, enumName: String, enumColl: Seq[(BigInt, String)]): Unit = {
551551
val enumClass = type2class(enumName)
552552

553553
out.puts

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -885,7 +885,7 @@ class CppCompiler(
885885
handleAssignmentSimple(instName, valExprConverted)
886886
}
887887

888-
override def enumDeclaration(curClass: List[String], enumName: String, enumColl: Seq[(Long, EnumValueSpec)]): Unit = {
888+
override def enumDeclaration(curClass: List[String], enumName: String, enumColl: Seq[(BigInt, EnumValueSpec)]): Unit = {
889889
val enumClass = types2class(List(enumName))
890890

891891
outHdr.puts

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -491,7 +491,7 @@ class GoCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig)
491491
override def instanceSetCalculated(instName: InstanceIdentifier): Unit =
492492
out.puts(s"this.${calculatedFlagForName(instName)} = true")
493493

494-
override def enumDeclaration(curClass: List[String], enumName: String, enumColl: Seq[(Long, EnumValueSpec)]): Unit = {
494+
override def enumDeclaration(curClass: List[String], enumName: String, enumColl: Seq[(BigInt, EnumValueSpec)]): Unit = {
495495
val fullEnumName: List[String] = curClass ++ List(enumName)
496496
val fullEnumNameStr = types2class(fullEnumName)
497497

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -947,7 +947,7 @@ class JavaCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig)
947947
out.puts(s"public void _invalidate${idToSetterStr(instName)}() { ${privateMemberName(instName)} = null; }")
948948
}
949949

950-
override def enumDeclaration(curClass: String, enumName: String, enumColl: Seq[(Long, String)]): Unit = {
950+
override def enumDeclaration(curClass: String, enumName: String, enumColl: Seq[(BigInt, String)]): Unit = {
951951
val enumClass = type2class(enumName)
952952

953953
out.puts

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -525,7 +525,7 @@ class JavaScriptCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig)
525525
out.puts(s"return ${privateMemberName(instName)};")
526526
}
527527

528-
override def enumDeclaration(curClass: List[String], enumName: String, enumColl: Seq[(Long, EnumValueSpec)]): Unit = {
528+
override def enumDeclaration(curClass: List[String], enumName: String, enumColl: Seq[(BigInt, EnumValueSpec)]): Unit = {
529529
out.puts(s"${type2class(curClass.last)}.${type2class(enumName)} = Object.freeze({")
530530
out.inc
531531

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -273,12 +273,14 @@ class LuaCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig)
273273
override def instanceReturn(instName: InstanceIdentifier, attrType: DataType, isNullable: Boolean): Unit =
274274
out.puts(s"return ${privateMemberName(instName)}")
275275

276-
override def enumDeclaration(curClass: List[String], enumName: String, enumColl: Seq[(Long, EnumValueSpec)]): Unit = {
276+
override def enumDeclaration(curClass: List[String], enumName: String, enumColl: Seq[(BigInt, EnumValueSpec)]): Unit = {
277277
importList.add("local enum = require(\"enum\")")
278278

279279
out.puts(s"${types2class(curClass)}.${type2class(enumName)} = enum.Enum {")
280280
out.inc
281-
enumColl.foreach { case (id, label) => out.puts(s"${label.name} = ${translator.doIntLiteral(id)},") }
281+
enumColl.foreach { case (id, label) =>
282+
out.puts(s"${label.name} = ${translator.doIntLiteral(id)},")
283+
}
282284
out.dec
283285
out.puts("}")
284286
out.puts

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,7 @@ class NimCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig)
183183
out.dec
184184
}
185185
// For this to work, we need a {.lenientCase.} pragma which disables nim's exhaustive case coverage check
186-
override def enumDeclaration(curClass: List[String], enumName: String, enumColl: Seq[(Long, EnumValueSpec)]): Unit = {
186+
override def enumDeclaration(curClass: List[String], enumName: String, enumColl: Seq[(BigInt, EnumValueSpec)]): Unit = {
187187
val enumClass = namespaced(curClass)
188188
out.puts(s"${enumClass}_${camelCase(enumName, true)}* = enum")
189189
out.inc

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -436,7 +436,7 @@ class PHPCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig)
436436
out.puts(s"return ${privateMemberName(instName)};")
437437
}
438438

439-
override def enumDeclaration(curClass: List[String], enumName: String, enumColl: Seq[(Long, EnumValueSpec)]): Unit = {
439+
override def enumDeclaration(curClass: List[String], enumName: String, enumColl: Seq[(BigInt, EnumValueSpec)]): Unit = {
440440
val name = curClass ::: List(enumName)
441441
classHeader(name, None)
442442
enumColl.foreach { case (id, label) =>

0 commit comments

Comments
 (0)