From 75026ee55a3d58b0b84aa4b59b01f231da05d28e Mon Sep 17 00:00:00 2001 From: Petr Pucil Date: Sun, 25 Aug 2024 16:32:15 +0200 Subject: [PATCH] Implement `valid/in-enum` for C++98, fix MSVC compat for C++11 Fixes the ValidFailInEnum test for C++98. Should also fix EnumIntRangeU and EnumLongRange{S,U} for `cpp_stl_11/msvc141-windows-x64`. I haven't tested these locally, but if this change doesn't fix them, I don't know what would. Instead of creating a set of integers that the enum represents, this commit creates a set of the defined enum values themselves. This should be more portable, as the code no longer needs to take into account what integer type is the underlying type of the enum (despite my best efforts, the existing approach didn't work in MSVC anyway for large integer enum values, so the new approach seems to be the only way to make it work on MSVC). Also, given that it was no longer necessary to use `std::underlying_type` (which is only available since C++11), this adds support for C++98. It required a workaround for the missing initializer lists, but the result doesn't look too bad. --- .../kaitai/struct/languages/CppCompiler.scala | 57 ++++++++++++------- 1 file changed, 38 insertions(+), 19 deletions(-) diff --git a/shared/src/main/scala/io/kaitai/struct/languages/CppCompiler.scala b/shared/src/main/scala/io/kaitai/struct/languages/CppCompiler.scala index 8a75414d5..3b113a0a9 100644 --- a/shared/src/main/scala/io/kaitai/struct/languages/CppCompiler.scala +++ b/shared/src/main/scala/io/kaitai/struct/languages/CppCompiler.scala @@ -893,21 +893,46 @@ class CppCompiler( outHdr.dec outHdr.puts("};") + + outHdr.puts(s"static bool _is_defined_$enumClass($enumClass v);") + importListHdr.addSystem("set") + val inClassRef = types2class(curClass) + val enumClassAbs = s"$inClassRef::$enumClass" + val valuesSetAbsRef = s"$inClassRef::_values_$enumClass" + ensureMode(PrivateAccess) if (config.cppConfig.useListInitializers) { - // NOTE: declaration and initialization must be separate in this case, + // NOTE: declaration and definition must be separate in this case, // see https://stackoverflow.com/a/12856069 - importListHdr.addSystem("set") - importListHdr.addSystem("type_traits") // because of `std::underlying_type` - // NOTE: `std::underlying_type` is only available since C++11. At the time - // of writing, it's not a problem because we're relying on list - // initializers anyway (which is a C++11 feature), but it will become a - // problem when we add C++98 support here. - outHdr.puts(s"static const std::set::type> _values_$enumClass;") - val enumClassAbs = types2class(curClass ++ List(enumName)) - val valuesSetAbsRef = s"${types2class(curClass)}::_values_$enumClass" - val setEntriesStr = enumColl.map { case (id, _) => translator.doIntLiteral(id) }.mkString(", ") - outSrc.puts(s"const std::set::type> $valuesSetAbsRef{$setEntriesStr};") + outHdr.puts(s"static const std::set<$enumClass> _values_$enumClass;") + + outSrc.puts(s"const std::set<$enumClassAbs> $valuesSetAbsRef{") + outSrc.inc + enumColl.foreach { case (_, label) => + outSrc.puts(s"$inClassRef::${value2Const(enumName, label.name)},") + } + outSrc.dec + outSrc.puts("};") + } else { + outHdr.puts(s"static std::set<$enumClass> _values_$enumClass;") + outHdr.puts(s"static std::set<$enumClass> _build_values_$enumClass();") + + outSrc.puts(s"std::set<$enumClassAbs> $inClassRef::_build_values_$enumClass() {") + outSrc.inc + outSrc.puts(s"std::set<$enumClassAbs> _t;") + enumColl.foreach { case (_, label) => + outSrc.puts(s"_t.insert($inClassRef::${value2Const(enumName, label.name)});") + } + outSrc.puts("return _t;") + outSrc.dec + outSrc.puts("}") + outSrc.puts(s"std::set<$enumClassAbs> $valuesSetAbsRef = $inClassRef::_build_values_$enumClass();") } + ensureMode(PublicAccess) + outSrc.puts(s"bool $inClassRef::_is_defined_$enumClass($enumClassAbs v) {") + outSrc.inc + outSrc.puts(s"return $valuesSetAbsRef.find(v) != $valuesSetAbsRef.end();") + outSrc.dec + outSrc.puts("}") } override def classToString(toStringExpr: Ast.expr): Unit = { @@ -1044,16 +1069,10 @@ class CppCompiler( err: ValidationNotInEnumError, errArgs: List[Ast.expr] ): Unit = { - if (!config.cppConfig.useListInitializers) { - throw new NotImplementedError( - "`valid/in-enum` is not yet implemented for C++98 (switch to C++11 or avoid this feature)" - ) - } val enumSpec = et.enumSpec.get val inClassRef = types2class(enumSpec.name.dropRight(1)) val enumNameStr = type2class(enumSpec.name.last) - val valuesSetRef = s"$inClassRef::_values_$enumNameStr" - attrValidate(s"$valuesSetRef.find(${translator.translate(valueExpr)}) == $valuesSetRef.end()", err, errArgs) + attrValidate(s"!$inClassRef::_is_defined_$enumNameStr(${translator.translate(valueExpr)})", err, errArgs) } private def attrValidate(failCondExpr: String, err: KSError, errArgs: List[Ast.expr]): Unit = {