From 793ba60476a3b4708f9750542815c04448ec62a9 Mon Sep 17 00:00:00 2001 From: Petr Pucil Date: Mon, 19 Aug 2024 19:48:57 +0200 Subject: [PATCH] Add `valid/in-enum` to validate enum values See https://github.com/kaitai-io/kaitai_struct/issues/435#issuecomment-389597122 --- .../kaitai/struct/GraphvizClassCompiler.scala | 2 + .../io/kaitai/struct/RuntimeConfig.scala | 4 +- .../io/kaitai/struct/datatype/KSError.scala | 9 +++++ .../kaitai/struct/format/ValidationSpec.scala | 29 ++++++++++++-- .../struct/languages/CSharpCompiler.scala | 24 ++++++++++- .../kaitai/struct/languages/CppCompiler.scala | 40 ++++++++++++++++++- .../kaitai/struct/languages/GoCompiler.scala | 25 +++++++++++- .../struct/languages/JavaCompiler.scala | 18 ++++++++- .../struct/languages/JavaScriptCompiler.scala | 17 +++++++- .../kaitai/struct/languages/LuaCompiler.scala | 20 +++++++++- .../kaitai/struct/languages/PHPCompiler.scala | 28 ++++++++++++- .../struct/languages/PythonCompiler.scala | 17 +++++++- .../struct/languages/RubyCompiler.scala | 16 +++++++- .../languages/components/ValidateOps.scala | 14 +++++++ 14 files changed, 245 insertions(+), 18 deletions(-) diff --git a/shared/src/main/scala/io/kaitai/struct/GraphvizClassCompiler.scala b/shared/src/main/scala/io/kaitai/struct/GraphvizClassCompiler.scala index 2b1126907..22c746953 100644 --- a/shared/src/main/scala/io/kaitai/struct/GraphvizClassCompiler.scala +++ b/shared/src/main/scala/io/kaitai/struct/GraphvizClassCompiler.scala @@ -193,6 +193,8 @@ class GraphvizClassCompiler(classSpecs: ClassSpecs, topClass: ClassSpec) extends s"and ${expression(max, fullPortName, STYLE_EDGE_VALID)}" case ValidationAnyOf(values) => s"must be any of ${values.map(expression(_, fullPortName, STYLE_EDGE_VALID)).mkString(", ")}" + case ValidationInEnum() => + "must be defined in the enum" case ValidationExpr(expr) => provider._currentIteratorType = Some(dataType) s"must satisfy ${expression(expr, fullPortName, STYLE_EDGE_VALID)}" diff --git a/shared/src/main/scala/io/kaitai/struct/RuntimeConfig.scala b/shared/src/main/scala/io/kaitai/struct/RuntimeConfig.scala index 59ee86001..ef613946f 100644 --- a/shared/src/main/scala/io/kaitai/struct/RuntimeConfig.scala +++ b/shared/src/main/scala/io/kaitai/struct/RuntimeConfig.scala @@ -8,8 +8,8 @@ package io.kaitai.struct * @param stdStringFrontBack If true, allow use of `front()` and `back()` methods * on `std::string`. If false, come up with simulation. * @param useListInitializers If true, allows use of list initializers for - * `std::vector`. Otherwise, throw a fatal unimplemented - * error. + * `std::vector` and `std::set`. Otherwise, throw a fatal + * "not implemented" error. * @param pointers Choose which style of pointers to use. */ case class CppRuntimeConfig( diff --git a/shared/src/main/scala/io/kaitai/struct/datatype/KSError.scala b/shared/src/main/scala/io/kaitai/struct/datatype/KSError.scala index 3f2f04142..2bbb2ba01 100644 --- a/shared/src/main/scala/io/kaitai/struct/datatype/KSError.scala +++ b/shared/src/main/scala/io/kaitai/struct/datatype/KSError.scala @@ -22,6 +22,7 @@ object KSError { case "ValidationLessThanError" => ValidationLessThanError case "ValidationGreaterThanError" => ValidationGreaterThanError case "ValidationNotAnyOfError" => ValidationNotAnyOfError + case "ValidationNotInEnumError" => ValidationNotInEnumError case "ValidationExprError" => ValidationExprError } excClass(dataType) @@ -66,6 +67,14 @@ case class ValidationNotAnyOfError(_dt: DataType) extends ValidationError(_dt) { def name = "ValidationNotAnyOfError" } +/** + * Error to be thrown when validation fails with actual not being in the enum. + * @param _dt data type used in validation process + */ +case class ValidationNotInEnumError(_dt: DataType) extends ValidationError(_dt) { + def name = "ValidationNotInEnumError" +} + /** * Error to be thrown when validation fails with actual not matching the custom * validation expression. diff --git a/shared/src/main/scala/io/kaitai/struct/format/ValidationSpec.scala b/shared/src/main/scala/io/kaitai/struct/format/ValidationSpec.scala index 5142517c4..3f404ce4f 100644 --- a/shared/src/main/scala/io/kaitai/struct/format/ValidationSpec.scala +++ b/shared/src/main/scala/io/kaitai/struct/format/ValidationSpec.scala @@ -10,6 +10,7 @@ case class ValidationMin(min: Ast.expr) extends ValidationSpec case class ValidationMax(max: Ast.expr) extends ValidationSpec case class ValidationRange(min: Ast.expr, max: Ast.expr) extends ValidationSpec case class ValidationAnyOf(values: List[Ast.expr]) extends ValidationSpec +case class ValidationInEnum() extends ValidationSpec case class ValidationExpr(checkExpr: Ast.expr) extends ValidationSpec object ValidationEq { @@ -59,13 +60,30 @@ object ValidationAnyOf { } } +object ValidationInEnum { + val LEGAL_KEYS = Set("in-enum") + + def fromMap(src: Map[String, Any], path: List[String]): Option[ValidationInEnum] = + ParseUtils.getOptValueBool(src, "in-enum", path).map { case boolVal => + ParseUtils.ensureLegalKeys(src, LEGAL_KEYS, path) + if (!boolVal) { + throw KSYParseError.withText( + "only `true` is supported as value, got `false`" + + " (if you don't want any validation, omit the `valid` key)", + path ++ List("in-enum") + ) + } + ValidationInEnum() + } +} + object ValidationExpr { val LEGAL_KEYS = Set("expr") def fromMap(src: Map[String, Any], path: List[String]): Option[ValidationExpr] = - ParseUtils.getOptValueExpression(src, "expr", path).map { case eqExpr => + ParseUtils.getOptValueExpression(src, "expr", path).map { case expr => ParseUtils.ensureLegalKeys(src, LEGAL_KEYS, path) - ValidationExpr(eqExpr) + ValidationExpr(expr) } } @@ -74,6 +92,7 @@ object ValidationSpec { ValidationEq.LEGAL_KEYS ++ ValidationRange.LEGAL_KEYS ++ ValidationAnyOf.LEGAL_KEYS ++ + ValidationInEnum.LEGAL_KEYS ++ ValidationExpr.LEGAL_KEYS def fromYaml(src: Any, path: List[String]): ValidationSpec = { @@ -106,10 +125,12 @@ object ValidationSpec { val opt3 = ValidationAnyOf.fromMap(src, path) if (opt3.nonEmpty) return opt3.get - val opt4 = ValidationExpr.fromMap(src, path) + val opt4 = ValidationInEnum.fromMap(src, path) if (opt4.nonEmpty) return opt4.get - + val opt5 = ValidationExpr.fromMap(src, path) + if (opt5.nonEmpty) + return opt5.get // No validation templates matched, check for any bogus keys ParseUtils.ensureLegalKeys(src, LEGAL_KEYS, path) diff --git a/shared/src/main/scala/io/kaitai/struct/languages/CSharpCompiler.scala b/shared/src/main/scala/io/kaitai/struct/languages/CSharpCompiler.scala index 5de5e9714..da132cd2b 100644 --- a/shared/src/main/scala/io/kaitai/struct/languages/CSharpCompiler.scala +++ b/shared/src/main/scala/io/kaitai/struct/languages/CSharpCompiler.scala @@ -597,9 +597,31 @@ class CSharpCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig) checkExpr: Ast.expr, err: KSError, errArgs: List[Ast.expr] + ): Unit = + attrValidate(s"!(${translator.translate(checkExpr)})", err, errArgs) + + override def attrValidateInEnum( + attrId: Identifier, + et: EnumType, + valueExpr: Ast.expr, + err: ValidationNotInEnumError, + errArgs: List[Ast.expr] ): Unit = { + // TODO: the non-generic overload `Enum.IsDefined(Type, object)` used here + // is supposedly slow because it uses reflection (see + // https://stackoverflow.com/q/13615#comment48178324_4807469). [This SO + // answer](https://stackoverflow.com/a/55028274) suggests to use the generic + // overload `Enum.IsDefined(TEnum)` instead, claiming that it fixes + // the performance issues. But it's only available since .NET 5, so we would + // need a command-line switch to allow the user to choose whether they need + // compabitility with older versions or not. + importList.add("System") + attrValidate(s"!Enum.IsDefined(typeof(${kaitaiType2NativeType(et)}), ${translator.translate(valueExpr)})", err, errArgs) + } + + private def attrValidate(failCondExpr: String, err: KSError, errArgs: List[Ast.expr]): Unit = { val errArgsStr = errArgs.map(translator.translate).mkString(", ") - out.puts(s"if (!(${translator.translate(checkExpr)}))") + out.puts(s"if ($failCondExpr)") out.puts("{") out.inc out.puts(s"throw new ${ksErrorName(err)}($errArgsStr);") 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 76ea1ad97..0b0ab4814 100644 --- a/shared/src/main/scala/io/kaitai/struct/languages/CppCompiler.scala +++ b/shared/src/main/scala/io/kaitai/struct/languages/CppCompiler.scala @@ -893,6 +893,21 @@ class CppCompiler( outHdr.dec outHdr.puts("};") + if (config.cppConfig.useListInitializers) { + // NOTE: declaration and initialization 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, _) => s"$id" }.mkString(", ") + outSrc.puts(s"const std::set::type> $valuesSetAbsRef{$setEntriesStr};") + } } override def classToString(toStringExpr: Ast.expr): Unit = { @@ -1007,6 +1022,7 @@ class CppCompiler( case _: ValidationLessThanError => "validation_less_than_error" case _: ValidationGreaterThanError => "validation_greater_than_error" case _: ValidationNotAnyOfError => "validation_not_any_of_error" + case _: ValidationNotInEnumError => "validation_not_in_enum_error" case _: ValidationExprError => "validation_expr_error" } s"kaitai::$cppErrName<$cppType>" @@ -1018,10 +1034,32 @@ class CppCompiler( checkExpr: Ast.expr, err: KSError, errArgs: List[Ast.expr] + ): Unit = + attrValidate(s"!(${translator.translate(checkExpr)})", err, errArgs) + + override def attrValidateInEnum( + attrId: Identifier, + et: EnumType, + valueExpr: Ast.expr, + 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) + } + + private def attrValidate(failCondExpr: String, err: KSError, errArgs: List[Ast.expr]): Unit = { val errArgsStr = errArgs.map(translator.translate).mkString(", ") importListSrc.addKaitai("kaitai/exceptions.h") - outSrc.puts(s"if (!(${translator.translate(checkExpr)})) {") + outSrc.puts(s"if ($failCondExpr) {") outSrc.inc outSrc.puts(s"throw ${ksErrorName(err)}($errArgsStr);") outSrc.dec diff --git a/shared/src/main/scala/io/kaitai/struct/languages/GoCompiler.scala b/shared/src/main/scala/io/kaitai/struct/languages/GoCompiler.scala index 7218f7e71..b98c80ddc 100644 --- a/shared/src/main/scala/io/kaitai/struct/languages/GoCompiler.scala +++ b/shared/src/main/scala/io/kaitai/struct/languages/GoCompiler.scala @@ -531,6 +531,15 @@ class GoCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig) out.dec out.puts(")") + // Inspired by https://gist.github.com/bgadrian/cb8b9344d9c66571ef331a14eb7a2e80 + val mapEntriesStr = enumColl.map { case (id, _) => s"$id: {}" }.mkString(", ") + out.puts(s"var values_$fullEnumNameStr = map[$fullEnumNameStr]struct{}{$mapEntriesStr}") + out.puts(s"func (v $fullEnumNameStr) isDefined() bool {") + out.inc + out.puts(s"_, ok := values_$fullEnumNameStr[v]") + out.puts("return ok") + out.dec + out.puts("}") } override def classToString(toStringExpr: Ast.expr): Unit = { @@ -569,9 +578,21 @@ class GoCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig) checkExpr: Ast.expr, err: KSError, errArgs: List[Ast.expr] - ): Unit = { + ): Unit = + attrValidate(s"!(${translator.translate(checkExpr)})", err, errArgs) + + override def attrValidateInEnum( + attrId: Identifier, + et: EnumType, + valueExpr: Ast.expr, + err: ValidationNotInEnumError, + errArgs: List[Ast.expr] + ): Unit = + attrValidate(s"!${translator.translate(valueExpr)}.isDefined()", err, errArgs) + + private def attrValidate(failCondExpr: String, err: KSError, errArgs: List[Ast.expr]): Unit = { val errArgsStr = errArgs.map(translator.translate).mkString(", ") - out.puts(s"if !(${translator.translate(checkExpr)}) {") + out.puts(s"if $failCondExpr {") out.inc val errInst = s"kaitai.New${err.name}($errArgsStr)" val noValueAndErr = translator.returnRes match { diff --git a/shared/src/main/scala/io/kaitai/struct/languages/JavaCompiler.scala b/shared/src/main/scala/io/kaitai/struct/languages/JavaCompiler.scala index b37fcd6b2..49938a3af 100644 --- a/shared/src/main/scala/io/kaitai/struct/languages/JavaCompiler.scala +++ b/shared/src/main/scala/io/kaitai/struct/languages/JavaCompiler.scala @@ -784,9 +784,25 @@ class JavaCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig) checkExpr: Ast.expr, err: KSError, errArgs: List[Ast.expr] + ): Unit = + attrValidate(s"!(${translator.translate(checkExpr)})", err, errArgs) + + override def attrValidateInEnum( + attrId: Identifier, + et: EnumType, + valueExpr: Ast.expr, + err: ValidationNotInEnumError, + errArgs: List[Ast.expr] ): Unit = { + // NOTE: this condition works for now because we haven't implemented + // https://github.com/kaitai-io/kaitai_struct/issues/778 for Java yet, but + // it will need to be changed when we do. + attrValidate(s"${translator.translate(valueExpr)} == null", err, errArgs) + } + + private def attrValidate(failCondExpr: String, err: KSError, errArgs: List[Ast.expr]): Unit = { val errArgsStr = errArgs.map(translator.translate).mkString(", ") - out.puts(s"if (!(${translator.translate(checkExpr)})) {") + out.puts(s"if ($failCondExpr) {") out.inc out.puts(s"throw new ${ksErrorName(err)}($errArgsStr);") out.dec diff --git a/shared/src/main/scala/io/kaitai/struct/languages/JavaScriptCompiler.scala b/shared/src/main/scala/io/kaitai/struct/languages/JavaScriptCompiler.scala index c0e3f7a88..11aae3361 100644 --- a/shared/src/main/scala/io/kaitai/struct/languages/JavaScriptCompiler.scala +++ b/shared/src/main/scala/io/kaitai/struct/languages/JavaScriptCompiler.scala @@ -586,9 +586,24 @@ class JavaScriptCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig) checkExpr: Ast.expr, err: KSError, errArgs: List[Ast.expr] + ): Unit = + attrValidate(s"!(${translator.translate(checkExpr)})", attrId, err, errArgs) + + override def attrValidateInEnum( + attrId: Identifier, + et: EnumType, + valueExpr: Ast.expr, + err: ValidationNotInEnumError, + errArgs: List[Ast.expr] ): Unit = { + val enumSpec = et.enumSpec.get + val enumRef = types2class(enumSpec.name, enumSpec.isExternal(typeProvider.nowClass)) + attrValidate(s"!Object.prototype.hasOwnProperty.call($enumRef, ${translator.translate(valueExpr)})", attrId, err, errArgs) + } + + private def attrValidate(failCondExpr: String, attrId: Identifier, err: KSError, errArgs: List[Ast.expr]): Unit = { val errArgsStr = errArgs.map(translator.translate).mkString(", ") - out.puts(s"if (!(${translator.translate(checkExpr)})) {") + out.puts(s"if ($failCondExpr) {") out.inc val errObj = s"new ${ksErrorName(err)}($errArgsStr)" if (attrDebugNeeded(attrId)) { diff --git a/shared/src/main/scala/io/kaitai/struct/languages/LuaCompiler.scala b/shared/src/main/scala/io/kaitai/struct/languages/LuaCompiler.scala index 63b06dd56..7b2c4d650 100644 --- a/shared/src/main/scala/io/kaitai/struct/languages/LuaCompiler.scala +++ b/shared/src/main/scala/io/kaitai/struct/languages/LuaCompiler.scala @@ -1,7 +1,7 @@ package io.kaitai.struct.languages import io.kaitai.struct.{ClassTypeProvider, RuntimeConfig, Utils, ExternalType} -import io.kaitai.struct.datatype.{DataType, FixedEndian, InheritedEndian, KSError, ValidationNotEqualError} +import io.kaitai.struct.datatype.{DataType, FixedEndian, InheritedEndian, KSError, ValidationNotEqualError, ValidationNotInEnumError} import io.kaitai.struct.datatype.DataType._ import io.kaitai.struct.exprlang.Ast import io.kaitai.struct.format._ @@ -426,9 +426,25 @@ class LuaCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig) checkExpr: Ast.expr, err: KSError, errArgs: List[Ast.expr] + ): Unit = + attrValidate(s"not(${translator.translate(checkExpr)})", err, errArgs) + + override def attrValidateInEnum( + attrId: Identifier, + et: EnumType, + valueExpr: Ast.expr, + err: ValidationNotInEnumError, + errArgs: List[Ast.expr] ): Unit = { + // NOTE: this condition works for now because we haven't implemented + // https://github.com/kaitai-io/kaitai_struct/issues/778 for Lua yet, but + // it will need to be changed when we do. + attrValidate(s"${translator.translate(valueExpr)} == nil", err, errArgs) + } + + private def attrValidate(failCondExpr: String, err: KSError, errArgs: List[Ast.expr]): Unit = { val errArgsCode = errArgs.map(translator.translate) - out.puts(s"if not(${translator.translate(checkExpr)}) then") + out.puts(s"if $failCondExpr then") out.inc val msg = err match { case _: ValidationNotEqualError => { diff --git a/shared/src/main/scala/io/kaitai/struct/languages/PHPCompiler.scala b/shared/src/main/scala/io/kaitai/struct/languages/PHPCompiler.scala index fae5dc510..20453406f 100644 --- a/shared/src/main/scala/io/kaitai/struct/languages/PHPCompiler.scala +++ b/shared/src/main/scala/io/kaitai/struct/languages/PHPCompiler.scala @@ -1,7 +1,7 @@ package io.kaitai.struct.languages import io.kaitai.struct.datatype.DataType._ -import io.kaitai.struct.datatype.{CalcEndian, DataType, FixedEndian, InheritedEndian, KSError, UndecidedEndiannessError} +import io.kaitai.struct.datatype.{CalcEndian, DataType, FixedEndian, InheritedEndian, KSError, UndecidedEndiannessError, ValidationNotInEnumError} import io.kaitai.struct.exprlang.Ast import io.kaitai.struct.format.{NoRepeat, RepeatEos, RepeatExpr, RepeatSpec, _} import io.kaitai.struct.languages.components._ @@ -439,6 +439,15 @@ class PHPCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig) universalDoc(label.doc) out.puts(s"const ${value2Const(label.name)} = ${translator.doIntLiteral(id)};") } + out.puts + val arrayEntriesStr = enumColl.map { case (id, _) => s"$id => true" }.mkString(", ") + out.puts(s"private const _VALUES = [$arrayEntriesStr];") + out.puts + out.puts("public static function isDefined(int $v): bool {") + out.inc + out.puts("return isset(self::_VALUES[$v]);") + out.dec + out.puts("}") classFooter(name) } @@ -500,9 +509,24 @@ class PHPCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig) checkExpr: Ast.expr, err: KSError, errArgs: List[Ast.expr] + ): Unit = + attrValidate(s"!(${translator.translate(checkExpr)})", err, errArgs) + + override def attrValidateInEnum( + attrId: Identifier, + et: EnumType, + valueExpr: Ast.expr, + err: ValidationNotInEnumError, + errArgs: List[Ast.expr] ): Unit = { + val enumSpec = et.enumSpec.get + val enumRef = translator.types2classAbs(enumSpec.name) + attrValidate(s"!$enumRef::isDefined(${translator.translate(valueExpr)})", err, errArgs) + } + + private def attrValidate(failCondExpr: String, err: KSError, errArgs: List[Ast.expr]): Unit = { val errArgsStr = errArgs.map(translator.translate).mkString(", ") - out.puts(s"if (!(${translator.translate(checkExpr)})) {") + out.puts(s"if ($failCondExpr) {") out.inc out.puts(s"throw new ${ksErrorName(err)}($errArgsStr);") out.dec diff --git a/shared/src/main/scala/io/kaitai/struct/languages/PythonCompiler.scala b/shared/src/main/scala/io/kaitai/struct/languages/PythonCompiler.scala index e883b7a6e..9385f93a3 100644 --- a/shared/src/main/scala/io/kaitai/struct/languages/PythonCompiler.scala +++ b/shared/src/main/scala/io/kaitai/struct/languages/PythonCompiler.scala @@ -503,9 +503,24 @@ class PythonCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig) checkExpr: Ast.expr, err: KSError, errArgs: List[Ast.expr] + ): Unit = + attrValidate(s"not ${translator.translate(checkExpr)}", err, errArgs) + + override def attrValidateInEnum( + attrId: Identifier, + et: EnumType, + valueExpr: Ast.expr, + err: ValidationNotInEnumError, + errArgs: List[Ast.expr] ): Unit = { + val enumSpec = et.enumSpec.get + val enumRef = types2class(enumSpec.name, enumSpec.isExternal(typeProvider.nowClass)) + attrValidate(s"not isinstance(${translator.translate(valueExpr)}, $enumRef)", err, errArgs) + } + + private def attrValidate(failCondExpr: String, err: KSError, errArgs: List[Ast.expr]): Unit = { val errArgsStr = errArgs.map(translator.translate).mkString(", ") - out.puts(s"if not ${translator.translate(checkExpr)}:") + out.puts(s"if $failCondExpr:") out.inc out.puts(s"raise ${ksErrorName(err)}($errArgsStr)") out.dec diff --git a/shared/src/main/scala/io/kaitai/struct/languages/RubyCompiler.scala b/shared/src/main/scala/io/kaitai/struct/languages/RubyCompiler.scala index 9e396d11b..0a483fd31 100644 --- a/shared/src/main/scala/io/kaitai/struct/languages/RubyCompiler.scala +++ b/shared/src/main/scala/io/kaitai/struct/languages/RubyCompiler.scala @@ -502,9 +502,23 @@ class RubyCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig) checkExpr: Ast.expr, err: KSError, errArgs: List[Ast.expr] + ): Unit = + attrValidate(s"not ${translator.translate(checkExpr)}", err, errArgs) + + override def attrValidateInEnum( + attrId: Identifier, + et: EnumType, + valueExpr: Ast.expr, + err: ValidationNotInEnumError, + errArgs: List[Ast.expr] ): Unit = { + val inverseMap = translator.enumInverseMap(et) + attrValidate(s"not ${inverseMap}.key?(${translator.translate(valueExpr)})", err, errArgs) + } + + private def attrValidate(failCondExpr: String, err: KSError, errArgs: List[Ast.expr]): Unit = { val errArgsStr = errArgs.map(translator.translate).mkString(", ") - out.puts(s"raise ${ksErrorName(err)}.new($errArgsStr) if not ${translator.translate(checkExpr)}") + out.puts(s"raise ${ksErrorName(err)}.new($errArgsStr) if $failCondExpr") } def types2class(names: List[String]) = names.map(type2class).mkString("::") diff --git a/shared/src/main/scala/io/kaitai/struct/languages/components/ValidateOps.scala b/shared/src/main/scala/io/kaitai/struct/languages/components/ValidateOps.scala index f869fedae..0f825350d 100644 --- a/shared/src/main/scala/io/kaitai/struct/languages/components/ValidateOps.scala +++ b/shared/src/main/scala/io/kaitai/struct/languages/components/ValidateOps.scala @@ -2,6 +2,7 @@ package io.kaitai.struct.languages.components import io.kaitai.struct.ClassTypeProvider import io.kaitai.struct.datatype._ +import io.kaitai.struct.datatype.DataType.EnumType import io.kaitai.struct.exprlang.Ast import io.kaitai.struct.format._ import io.kaitai.struct.translators.AbstractTranslator @@ -48,6 +49,18 @@ trait ValidateOps extends ExceptionNames { Ast.expr.Str(attr.path.mkString("/", "/", "")) ) ) + case ValidationInEnum() => + attrValidateInEnum( + attrId, + attr.dataType.asInstanceOf[EnumType], + itemValue, + ValidationNotInEnumError(attr.dataType), + List( + itemValue, + Ast.expr.InternalName(IoIdentifier), + Ast.expr.Str(attr.path.mkString("/", "/", "")) + ) + ) case ValidationExpr(expr) => blockScopeHeader typeProvider._currentIteratorType = Some(attr.dataType) @@ -92,6 +105,7 @@ trait ValidateOps extends ExceptionNames { } def attrValidateExpr(attrId: Identifier, attrType: DataType, checkExpr: Ast.expr, err: KSError, errArgs: List[Ast.expr]): Unit = {} + def attrValidateInEnum(attrId: Identifier, et: EnumType, valueExpr: Ast.expr, err: ValidationNotInEnumError, errArgs: List[Ast.expr]): Unit = {} def handleAssignmentTempVar(dataType: DataType, id: String, expr: String): Unit def blockScopeHeader: Unit def blockScopeFooter: Unit