Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@ abstract trait CommonMethods[T] extends TypeDetector {
case et: EnumType =>
attr.name match {
case "to_i" => enumToInt(value, et)
case "to_s" => enumToStr(value, et)
case _ => throw new TypeMismatchError(s"called invalid attribute '${attr.name}' on expression of type $valType")
}
case _ =>
Expand Down Expand Up @@ -278,6 +279,7 @@ abstract trait CommonMethods[T] extends TypeDetector {
def arrayMax(a: Ast.expr): T

def enumToInt(value: Ast.expr, et: EnumType): T
def enumToStr(value: Ast.expr, et: EnumType): T = ???

def boolToInt(value: Ast.expr): T

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ class ExpressionValidator(val provider: TypeProvider)
override def arrayMax(a: Ast.expr): Unit = validate(a)

override def enumToInt(value: Ast.expr, et: DataType.EnumType): Unit = validate(value)
override def enumToStr(value: Ast.expr, et: DataType.EnumType): Unit = validate(value)

override def boolToInt(value: Ast.expr): Unit = validate(value)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,12 @@ class JavaScriptTranslator(provider: TypeProvider, importList: ImportList) exten

override def enumToInt(v: expr, et: EnumType): String =
translate(v)
override def enumToStr(v: expr, et: EnumType): String = {
val enumSpec = et.enumSpec.get
val isExternal = enumSpec.isExternal(provider.nowClass)
val enumObjName = JavaScriptCompiler.types2class(enumSpec.name, isExternal)
s"$enumObjName[${translate(v)}].toLowerCase()"
}

/**
* Converts a boolean (true or false) to integer (1 or 0, respectively) in
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
package io.kaitai.struct.translators

import io.kaitai.struct.{ClassTypeProvider, ImportList, RuntimeConfig, Utils}
import io.kaitai.struct.exprlang.Ast
import io.kaitai.struct.exprlang.Ast._
import io.kaitai.struct.datatype.DataType
import io.kaitai.struct.datatype.DataType._
import io.kaitai.struct.exprlang.Ast
import io.kaitai.struct.exprlang.Ast._
import io.kaitai.struct.format.{EnumSpec, Identifier}
import io.kaitai.struct.languages.JavaCompiler
import io.kaitai.struct.{ImportList, RuntimeConfig, Utils}

class JavaTranslator(provider: TypeProvider, importList: ImportList, config: RuntimeConfig) extends BaseTranslator(provider) {
override def doIntLiteral(n: BigInt): String = {
Expand Down Expand Up @@ -114,8 +114,14 @@ class JavaTranslator(provider: TypeProvider, importList: ImportList, config: Run
// Predefined methods of various types
override def strToInt(s: expr, base: expr): String =
s"Long.parseLong(${translate(s)}, ${translate(base)})"

override def enumToInt(v: expr, et: EnumType): String =
s"${translate(v)}.id()"
override def enumToStr(v: Ast.expr, et: EnumType): String = {
importList.add("java.util.Locale");
s"${translate(v, METHOD_PRECEDENCE)}.toString().toLowerCase(Locale.ROOT)"
}

override def floatToInt(v: expr): String =
doCast(v, CalcIntType)
override def intToStr(i: expr): String =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -140,8 +140,12 @@ class PythonTranslator(provider: TypeProvider, importList: ImportList, config: R
}
s"int(${translate(s)}$add)"
}

override def enumToInt(v: Ast.expr, et: EnumType): String =
s"int(${translate(v)})"
override def enumToStr(v: Ast.expr, et: EnumType): String =
s"${translate(v)}.name"
Copy link
Copy Markdown
Member

@generalmimon generalmimon Nov 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will only work for known/defined enum values, but fail for unknown enum values, which are represented as int in Python and thus won't have the .name attribute.

Since we use IntEnum as the base class for the enums, it isn't super simple to implement this in a way that works for both known and unknown enum values. str(enum_val) will always give you the stringified integer value, since IntEnum tries to be compatible with int. This is nicely explained in https://docs.python.org/3/library/enum.html#notes:

IntEnum, StrEnum, and IntFlag

These three enum types are designed to be drop-in replacements for existing integer- and string-based values; as such, they have extra limitations:

  • __str__ uses the value and not the name of the enum member
  • __format__, because it uses __str__, will also use the value of the enum member instead of its name

If you do not need/want those limitations, you can either create your own base class by mixing in the int or str type yourself:

>>> from enum import Enum
>>> class MyIntEnum(int, Enum):
...     pass

or you can reassign the appropriate str(), etc., in your enum:

>>> from enum import Enum, IntEnum
>>> class MyIntEnum(IntEnum):
...     __str__ = Enum.__str__

Actually, we'll want to override __str__ with our own implementation so that it returns just the member name without the enum name as a prefix (like enum.Enum.__str__ does), as shown in the example for https://docs.python.org/3/library/enum.html#enum.Enum.__str__:

    def __str__(self):
        return self.name

Then the compiler can translate the enum_val.to_s simply as str(enum_val) and it will return the enum member name for known values and stringified integer for unknown values.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's an interesting design question.

Up until now, we've respected per-language design, which pretty much boils down to some languages retaining existing undeclared integer value (e.g. C/C++, JavaScript, Python), and some (e.g. Java or Ruby) having no default way to keep it stored:

  • Java will pretty much have null on invalid enums — and it's rather hard to do anything about that
  • Ruby will have nil in its current implementation we have — but, to be fair, we can influence that.

This pretty much means that if we'll try to validate it with tests, we won't get consistent results.

I can think of 3 possible choices here for handling invalid enum values:

  • Don't do anything — leave it different, some will blow up with NPE, don't care
  • Lowest common denominator — consistently return a fixed string like <UNKNOWN> or <INVALID> for all languages.
  • Have similar but slightly different values:
    • For languages who keep the value — return something like unknown(123)
    • For languages who can't keep — return something like unknown()

If we'll do (2) or (3) — we can make the test: (2) with equality, (3) with something like "starts with unknown(".

Any preferences?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For languages who keep the value — return something like unknown(123)

Note, that for Java this is implemented in #288


override def boolToInt(v: Ast.expr): String =
s"int(${translate(v)})"
override def floatToInt(v: Ast.expr): String =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,15 @@ class RubyTranslator(provider: TypeProvider) extends BaseTranslator(provider)
val value = translate(v)
s"(${enumInverseMap(et)}[$value] || $value)"
}
override def enumToStr(v: Ast.expr, et: EnumType): String = {
// Ruby enums are implemented as symbols which have bare enum name + `_` + label.
// Here we just need the label part, so we'll strip the enum name prefix.

val enumPrefixLength = et.name.last.length + 1 // +1 for the underscore

s"${translate(v, METHOD_PRECEDENCE)}.to_s[$enumPrefixLength..-1]"
}

override def floatToInt(v: Ast.expr): String =
s"${translate(v, METHOD_PRECEDENCE)}.to_i"
override def intToStr(i: Ast.expr): String =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,7 @@ class TypeDetector(provider: TypeProvider) {
case et: EnumType =>
attr.name match {
case "to_i" => CalcIntType
case "to_s" => CalcStrType
case _ => throw new MethodNotFoundError(attr.name, valType)
}
case _: BooleanType =>
Expand Down
Loading