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 @@ -295,7 +295,7 @@ class GraphvizClassCompiler(classSpecs: ClassSpecs, topClass: ClassSpec) extends
case blt: BytesLimitType => expressionSize(blt.size, attrName)
case StrFromBytesType(basedOn, _, _) => dataTypeSizeAsString(basedOn, attrName)
case utb: UserTypeFromBytes => dataTypeSizeAsString(utb.bytes, attrName)
case EnumType(_, basedOn) => dataTypeSizeAsString(basedOn, attrName)
case EnumType(_, _, basedOn) => dataTypeSizeAsString(basedOn, attrName)
case _ =>
CalculateSeqSizes.dataTypeBitsSize(dataType) match {
case FixedSized(n) =>
Expand Down Expand Up @@ -507,8 +507,8 @@ object GraphvizClassCompiler extends LanguageCompilerStatic {
val bytesStr = dataTypeName(basedOn, valid)
val comma = if (bytesStr.isEmpty) "" else ", "
s"str($bytesStr$comma$encoding)"
case EnumType(name, basedOn) =>
s"${dataTypeName(basedOn, valid)}→${type2display(name)}"
case EnumType(name, owner, basedOn) =>
s"${dataTypeName(basedOn, valid)}→${type2display(owner :+ name)}"
case BitsType(width, bitEndian) => s"b$width${bitEndian.toSuffix}"
case BitsType1(bitEndian) => s"b1${bitEndian.toSuffix}→bool"
case _ => dataType.toString
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,7 @@ object DataType {
def isOwning = false
}

case class EnumType(name: List[String], basedOn: IntType) extends DataType {
case class EnumType(name: String, owner: List[String], basedOn: IntType) extends DataType {
var enumSpec: Option[EnumSpec] = None

/**
Expand Down Expand Up @@ -487,7 +487,10 @@ object DataType {
enumRef match {
case Some(enumName) =>
r match {
case numType: IntType => EnumType(classNameToList(enumName), numType)
case numType: IntType => {
val names = classNameToList(enumName)
EnumType(names.last, names.dropRight(1), numType)
}
case _ =>
throw KSYParseError(s"tried to resolve non-integer $r to enum", path).toException
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,7 @@ object AttrSpec {
case _: BytesType => LEGAL_KEYS_BYTES
case _: StrFromBytesType => LEGAL_KEYS_STR
case _: UserType => LEGAL_KEYS_BYTES
case EnumType(_, _) => LEGAL_KEYS_ENUM
case EnumType(_, _, _) => LEGAL_KEYS_ENUM
case _: SwitchType => LEGAL_KEYS_BYTES
case _ => Set()
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -688,7 +688,7 @@ object CSharpCompiler extends LanguageCompilerStatic
case KaitaiStreamType | OwnedKaitaiStreamType => kstreamName

case t: UserType => types2class(t.name)
case EnumType(name, _) => types2class(name)
case EnumType(name, owner, _) => types2class(owner :+ name)

case at: ArrayType => {
importList.add("System.Collections.Generic")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1149,7 +1149,7 @@ object CppCompiler extends LanguageCompilerStatic
types2class(if (absolute) {
t.enumSpec.get.name
} else {
t.name
t.owner :+ t.name
})

case at: ArrayType => {
Expand Down
141 changes: 100 additions & 41 deletions shared/src/main/scala/io/kaitai/struct/languages/JavaCompiler.scala
Original file line number Diff line number Diff line change
Expand Up @@ -535,37 +535,48 @@ class JavaCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig)
// doing this workaround for enums.

val onType = typeProvider._currentSwitchType.get
val isNullable = onType match {
case _: EnumType => true
case _ => false
}

if (isNullable) {
val nameSwitchStr = expression(NAME_SWITCH_ON)
out.puts("{")
out.inc
out.puts(s"${kaitaiType2JavaType(onType)} $nameSwitchStr = ${expression(on)};")
out.puts(s"if ($nameSwitchStr != null) {")
out.inc
onType match {
case EnumType(name, owner, _) => {
val enumName = types2class(owner :+ name)
// Open scope for "on" isolation
out.puts("{")
out.inc
out.puts(s"final ${enum2iface(name, owner)} on = ${expression(on)};")
out.puts(s"if (on instanceof $enumName) {")
out.inc
out.puts(s"switch (($enumName)on) {")
out.inc

cases.foreach { case (condition, result) =>
condition match {
case SwitchType.ELSE_CONST =>
// skip for now
case _ =>
switchCaseStart(condition)
normalCaseProc(result)
switchCaseEnd()
}
}

super.switchCasesRender(id, on, cases, normalCaseProc, elseCaseProc)
out.dec
out.puts("} // switch")
out.dec
cases.get(SwitchType.ELSE_CONST) match {
case Some(result) =>
out.puts("} else {")
out.inc
elseCaseProc(result)
out.dec
out.puts("}")
case None =>
out.puts("}")
}

out.dec
cases.get(SwitchType.ELSE_CONST) match {
case Some(result) =>
out.puts("} else {")
out.inc
elseCaseProc(result)
out.dec
out.puts("}")
case None =>
out.puts("}")
// Close "on" isolation scope
out.dec
out.puts("}")
}

out.dec
out.puts("}")
} else {
super.switchCasesRender(id, on, cases, normalCaseProc, elseCaseProc)
case _ => super.switchCasesRender(id, on, cases, normalCaseProc, elseCaseProc)
}
}

Expand Down Expand Up @@ -699,9 +710,44 @@ class JavaCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig)

override def enumDeclaration(curClass: String, enumName: String, enumColl: Seq[(Long, String)]): Unit = {
val enumClass = type2class(enumName)
val enumIface = enum2iface(enumName)

// Enums are always contained in some other class so we can generate two public items

// public interface I<enum> extends IKaitaiEnum { ... }
out.puts
out.puts(s"public interface $enumIface extends IKaitaiEnum {")
out.inc
out.puts(s"public static final class Unknown extends IKaitaiEnum.Unknown implements $enumIface {")
out.inc
out.puts("Unknown(long id) { super(id); }")
out.puts
out.puts("@Override");
out.puts(s"public String toString() { return \"${enumClass}(\" + this.id + \")\"; }");
out.puts
out.puts(s"public enum $enumClass {")
out.puts("@Override");
out.puts(s"public int hashCode() {")
out.inc
// Use the hashCode implementation that is generated by Eclipse and VSCode Java plugin.
// It uses prime 31 and this is how Arrays.hashCode() is implemented
out.puts(s"final int result = 31 + \"${enumClass}\".hashCode();")
out.puts("return 31 * result + Long.hashCode(this.id);")
out.dec
out.puts("}");
out.puts
out.puts("@Override");
out.puts(s"public boolean equals(Object other) {");
out.inc
out.puts("return other instanceof Unknown && this.id == ((Unknown)other).id;")
out.dec
out.puts("}");
out.dec
out.puts("}") // close class
out.dec
out.puts("}") // close interface

// public enum <enum> implements I<enum> { ... }
out.puts(s"public enum $enumClass implements $enumIface {")
out.inc

if (enumColl.size > 1) {
Expand All @@ -716,23 +762,32 @@ class JavaCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig)

out.puts
out.puts("private final long id;")
out.puts(s"$enumClass(long id) { this.id = id; }")
out.puts("public long id() { return id; }")
out.puts(s"private static final Map<Long, $enumClass> byId = new HashMap<Long, $enumClass>(${enumColl.size});")
out.puts(s"private static final HashMap<Long, $enumIface> variants = new HashMap<>(${enumColl.size});")
out.puts("static {")
out.inc
out.puts(s"for ($enumClass e : $enumClass.values())")
out.puts(s"for ($enumClass e : values()) {")
out.inc
out.puts(s"byId.put(e.id(), e);")
out.puts(s"variants.put(e.id, e);")
out.dec
out.puts("}")// for
out.dec
out.puts("}")
out.puts(s"public static $enumClass byId(long id) { return byId.get(id); }")
out.puts("}")// static initializer
out.puts
out.puts(s"public static $enumIface byId(final long id) {")
out.inc
out.puts(s"return variants.computeIfAbsent(id, _id -> new $enumIface.Unknown(id));")
out.dec
out.puts("}")
out.puts("}")// byId(...)
out.puts
out.puts(s"private $enumClass(long id) { this.id = id; }")
out.puts
out.puts("@Override")
out.puts("public long id() { return id; }")
out.dec
out.puts("}")// enum

importList.add("java.util.Map")
importList.add("java.util.HashMap")
importList.add("io.kaitai.struct.IKaitaiEnum")
}

override def debugClassSequence(seq: List[AttrSpec]) = {
Expand Down Expand Up @@ -874,7 +929,7 @@ object JavaCompiler extends LanguageCompilerStatic
case KaitaiStructType | CalcKaitaiStructType(_) => kstructName

case t: UserType => types2class(t.name)
case EnumType(name, _) => types2class(name)
case EnumType(name, owner, _) => enum2iface(name, owner)

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

Expand Down Expand Up @@ -918,7 +973,7 @@ object JavaCompiler extends LanguageCompilerStatic
case KaitaiStructType | CalcKaitaiStructType(_) => kstructName

case t: UserType => types2class(t.name)
case EnumType(name, _) => types2class(name)
case EnumType(name, owner, _) => enum2iface(name, owner)

case at: ArrayType => {
importList.add("java.util.ArrayList")
Expand All @@ -929,7 +984,11 @@ object JavaCompiler extends LanguageCompilerStatic
}
}

def types2class(names: List[String]) = names.map(x => type2class(x)).mkString(".")
def types2class(names: Iterable[String]) = names.map(x => type2class(x)).mkString(".")

def enum2iface(name: String) = s"I${type2class(name)}"
def enum2iface(name: String, owner: List[String]): String =
(owner.map(type2class) :+ enum2iface(name)).mkString(".")

override def kstreamName: String = "KaitaiStream"
override def kstructName: String = "KaitaiStruct"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1339,7 +1339,7 @@ object RustCompiler
case t: EnumType =>
val baseName = t.enumSpec match {
case Some(spec) => s"${types2class(spec.name)}"
case None => s"${types2class(t.name)}"
case None => s"${types2class(t.owner :+ t.name)}"
}
baseName

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ object CalculateSeqSizes {
dataType match {
case BitsType1(_) => FixedSized(1)
case BitsType(width, _) => FixedSized(width)
case EnumType(_, basedOn) => dataTypeBitsSize(basedOn)
case EnumType(_, _, basedOn) => dataTypeBitsSize(basedOn)
case ut: UserTypeInstream => getSeqSize(ut.classSpec.get)
case _ =>
dataTypeByteSize(dataType) match {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,9 @@ class ResolveTypes(specs: ClassSpecs, topClass: ClassSpec, opaqueTypes: Boolean)
ut.classSpec = resClassSpec
problems
case et: EnumType =>
et.enumSpec = resolveEnumSpec(curClass, et.name)
et.enumSpec = resolveEnumSpec(curClass, et.owner :+ et.name)
if (et.enumSpec.isEmpty) {
Some(EnumNotFoundErr(et.name, curClass, path ++ List("enum")))
Some(EnumNotFoundErr(et.owner :+ et.name, curClass, path ++ List("enum")))
} else {
None
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,11 @@ class TypeDetector(provider: TypeProvider) {
case Ast.expr.InterpolatedStr(_) => CalcStrType
case Ast.expr.Bool(_) => CalcBooleanType
case Ast.expr.EnumByLabel(enumType, _, inType) =>
val t = EnumType(inType.names.toList :+ enumType.name, CalcIntType)
val t = EnumType(enumType.name, inType.names.toList, CalcIntType)
t.enumSpec = Some(provider.resolveEnum(inType, enumType.name))
t
case Ast.expr.EnumById(enumType, _, inType) =>
val t = EnumType(List(enumType.name), CalcIntType)
val t = EnumType(enumType.name, List(), CalcIntType)
t.enumSpec = Some(provider.resolveEnum(inType, enumType.name))
t
case Ast.expr.Name(name: Ast.identifier) => provider.determineType(name.name).asNonOwning()
Expand Down Expand Up @@ -420,7 +420,7 @@ object TypeDetector {
}
case (t1: EnumType, t2: EnumType) =>
if (t1.enumSpec.get == t2.enumSpec.get) {
val t = EnumType(t1.name, CalcIntType)
val t = EnumType(t1.name, t1.owner, CalcIntType)
t.enumSpec = t1.enumSpec
t
} else {
Expand Down