Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Regex support in valid key #200

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
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
9 changes: 9 additions & 0 deletions shared/src/main/scala/io/kaitai/struct/datatype/KSError.scala
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ object KSError {
case "ValidationLessThanError" => ValidationLessThanError
case "ValidationGreaterThanError" => ValidationGreaterThanError
case "ValidationNotAnyOfError" => ValidationNotAnyOfError
case "ValidationRegexMatchError" => ValidationRegexMatchError
}
excClass(dataType)
}
Expand Down Expand Up @@ -64,6 +65,14 @@ case class ValidationNotAnyOfError(_dt: DataType) extends ValidationError(_dt) {
def name = "ValidationNotAnyOfError"
}

/**
* Error to be thrown when validation fails with actual not matching regex
* @param _dt data type used in validation process
*/
case class ValidationRegexMatchError(_dt: DataType) extends ValidationError(_dt) {
Copy link
Member

Choose a reason for hiding this comment

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

For equality checks (and storing original value in question for C++), we need to know original value data type. But regular expression matches would likely only work for strings. What's the point in passing _dt: DataType here?

def name = "ValidationRegexMatchError"
}

/**
* Exception that is thrown when we can't decided on endianness
* and thus can't proceed with parsing.
Expand Down
1 change: 1 addition & 0 deletions shared/src/main/scala/io/kaitai/struct/exprlang/Ast.scala
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ object Ast {
case class IfExp(condition: expr, ifTrue: expr, ifFalse: expr) extends expr
// case class Dict(keys: Seq[expr], values: Seq[expr]) extends expr
case class Compare(left: expr, ops: cmpop, right: expr) extends expr
case class RegexMatch(str: expr, regex: String) extends expr
case class Call(func: expr, args: Seq[expr]) extends expr
case class IntNum(n: BigInt) extends expr
case class FloatNum(n: BigDecimal) extends expr
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ 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 ValidationExpr(checkExpr: Ast.expr) extends ValidationSpec
case class ValidationRegex(checkExpr: String) extends ValidationSpec

object ValidationEq {
val LEGAL_KEYS = Set("eq")
Expand Down Expand Up @@ -68,16 +69,29 @@ object ValidationExpr {
}
}

object ValidationRegex {
val LEGAL_KEYS = Set("regex")

def fromMap(src: Map[String, Any], path: List[String]): Option[ValidationRegex] =
ParseUtils.getOptValueStr(src, "regex", path).map { case eqExpr =>
ParseUtils.ensureLegalKeys(src, LEGAL_KEYS, path)
ValidationRegex(eqExpr)
}
}


Copy link
Member

Choose a reason for hiding this comment

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

Nit: unnecessary double newline.

Suggested change

object ValidationSpec {
val LEGAL_KEYS =
ValidationEq.LEGAL_KEYS ++
ValidationRange.LEGAL_KEYS ++
ValidationAnyOf.LEGAL_KEYS ++
ValidationExpr.LEGAL_KEYS
ValidationExpr.LEGAL_KEYS ++
ValidationRegex.LEGAL_KEYS

def fromYaml(src: Any, path: List[String]): ValidationSpec = {
src match {
case value: String =>

Copy link
Member

Choose a reason for hiding this comment

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

Nit: unnecessary newline

Suggested change

fromString(value, path)
case x: Boolean =>
fromString(x.toString, path)
Expand Down Expand Up @@ -108,6 +122,9 @@ object ValidationSpec {
val opt4 = ValidationExpr.fromMap(src, path)
if (opt4.nonEmpty)
return opt4.get
val opt5 = ValidationRegex.fromMap(src, path)
if (opt5.nonEmpty)
return opt5.get

// No validation templates matched, check for any bogus keys
ParseUtils.ensureLegalKeys(src, LEGAL_KEYS, path)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -992,6 +992,7 @@ class CppCompiler(
case _: ValidationLessThanError => "validation_less_than_error"
case _: ValidationGreaterThanError => "validation_greater_than_error"
case _: ValidationNotAnyOfError => "validation_not_any_of_error"
case _: ValidationRegexMatchError => "validation_regex_match_error"
}
s"kaitai::$cppErrName<$cppType>"
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ class RustCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig)
importList.add("std::default::Default")
importList.add("kaitai_struct::KaitaiStream")
importList.add("kaitai_struct::KaitaiStruct")
importList.add("regex::Regex")

out.puts
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,19 @@ trait ValidateOps extends ExceptionNames {
case ValidationRange(min, max) =>
attrValidateExprCompare(attrId, attr, Ast.cmpop.GtE, min, ValidationLessThanError(attr.dataTypeComposite))
attrValidateExprCompare(attrId, attr, Ast.cmpop.LtE, max, ValidationGreaterThanError(attr.dataTypeComposite))
case ValidationRegex(regex) =>
val regexMatch = Ast.expr.RegexMatch(Ast.expr.Name(attrId.toAstIdentifier), regex)
attrValidateExpr(attrId, attr.dataTypeComposite,
checkExpr = regexMatch,
errName = ksErrorName(ValidationRegexMatchError(attr.dataTypeComposite)),
errArgs = List(
Ast.expr.Str(regex),
Ast.expr.Name(attrId.toAstIdentifier),
Ast.expr.Name(IoIdentifier.toAstIdentifier),
Ast.expr.Str(attr.path.mkString("/", "/", ""))
)
)

case ValidationAnyOf(values) =>
val bigOrExpr = Ast.expr.BoolOp(
Ast.boolop.Or,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,14 @@ abstract class BaseTranslator(val provider: TypeProvider)
case (ltype, rtype) =>
throw new TypeMismatchError(s"can't compare $ltype and $rtype")
}
case Ast.expr.RegexMatch(str: Ast.expr, regex: String) => {
detectType(str) match {
case (_: StrType) =>
doRegexMatchOp(translate(str), doRegexFullLine(regex))
case _ =>
throw new TypeMismatchError(s"regex match need strings")
}
}
case Ast.expr.BinOp(left: Ast.expr, op: Ast.operator, right: Ast.expr) =>
(detectType(left), detectType(right), op) match {
case (_: NumericType, _: NumericType, _) =>
Expand Down Expand Up @@ -198,4 +206,6 @@ abstract class BaseTranslator(val provider: TypeProvider)
// for the language
def anyField(value: Ast.expr, attrName: String): String =
s"${translate(value)}.${doName(attrName)}"

def doRegexMatchOp(str: String, regex: String): String
Copy link
Member

Choose a reason for hiding this comment

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

Nit: formatting.

Suggested change
def doRegexMatchOp(str: String, regex: String): String
def doRegexMatchOp(str: String, regex: String): String

}
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,13 @@ class CSharpTranslator(provider: TypeProvider, importList: ImportList) extends B
}
}

override def doRegexMatchOp(str: String, regex: String): String = {
importList.add("System.Text.RegularExpressions")
s"new Regex(${regex}).IsMatch(${str})"
}

override def doRegexLiteral(reg: String) : String = doStringLiteral(reg)

override def doBytesCompareOp(left: Ast.expr, op: Ast.cmpop, right: Ast.expr): String =
s"(${CSharpCompiler.kstreamName}.ByteArrayCompare(${translate(left)}, ${translate(right)}) ${cmpOp(op)} 0)"

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,4 +77,7 @@ trait CommonLiterals {
'"' -> "\\\"",
'\\' -> "\\\\"
)

def doRegexLiteral(reg: String): String = reg
def doRegexFullLine(regex: String) : String = doRegexLiteral("""\A""" + regex + """\Z""")
}
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,13 @@ class CppTranslator(provider: TypeProvider, importListSrc: CppImportList, import
}
}

override def doRegexMatchOp(str: String, regex: String): String = {
importListSrc.addSystem("regex")
s"std::regex_match(${str}, std::regex(${regex}))"
}

override def doRegexLiteral(reg: String): String = doStringLiteral(reg)

override def arraySubscript(container: expr, idx: expr): String =
s"${translate(container)}->at(${translate(idx)})"
override def doIfExp(condition: expr, ifTrue: expr, ifFalse: expr): String =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ class ExpressionValidator(val provider: TypeProvider)
case Ast.expr.Compare(left: Ast.expr, op: Ast.cmpop, right: Ast.expr) =>
validate(left)
validate(right)
case Ast.expr.RegexMatch(str: Ast.expr, regex: String) =>
validate(str)
case Ast.expr.BinOp(left: Ast.expr, op: Ast.operator, right: Ast.expr) =>
validate(left)
validate(right)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,14 @@ class GoTranslator(out: StringLanguageOutputWriter, provider: TypeProvider, impo
case (ltype, rtype, _) =>
throw new TypeMismatchError(s"can't do $ltype $op $rtype")
}
case Ast.expr.RegexMatch(str: Ast.expr, regex: String) => {
detectType(str) match {
case (_: StrType) =>
doRegexMatchOp(translate(str), doRegexFullLine(regex))
case _ =>
throw new TypeMismatchError(s"regex match need strings")
}
}
case Ast.expr.BoolOp(op, values) =>
trBooleanOp(op, values)
case Ast.expr.IfExp(condition, ifTrue, ifFalse) =>
Expand Down Expand Up @@ -253,6 +261,11 @@ class GoTranslator(out: StringLanguageOutputWriter, provider: TypeProvider, impo
}
}

def doRegexMatchOp(str: String, regex: String): TranslatorResult = {
importList.add("regexp")
outVarCheckRes(s"regexp.MatchString(`${regex}`, ${trStringLiteral(str)})")
}

override def doCast(value: Ast.expr, typeName: DataType): TranslatorResult = ???

override def doArrayLiteral(t: DataType, value: Seq[Ast.expr]) =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,10 @@ class JavaScriptTranslator(provider: TypeProvider) extends BaseTranslator(provid
override def enumToInt(v: expr, et: EnumType): String =
translate(v)

override def doRegexMatchOp(str: String, regex: String): String = {
s"/${regex}/.exec(${str})"
}

/**
* Converts a boolean (true or false) to integer (1 or 0, respectively) in
* JavaScript. There are quite a few methods to so, this one is generally
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,13 @@ class JavaTranslator(provider: TypeProvider, importList: ImportList) extends Bas
}
}

override def doRegexMatchOp(str: String, regex: String): String = {
importList.add("java.util.regex.Pattern")
s"Pattern.matches(${regex}, ${str})"
}

override def doRegexLiteral(reg: String) : String = doStringLiteral(reg)

override def arraySubscript(container: expr, idx: expr): String =
s"${translate(container)}.get((int) ${translate(idx)})"
override def doIfExp(condition: expr, ifTrue: expr, ifFalse: expr): String =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,8 @@ class LuaTranslator(provider: TypeProvider, importList: ImportList) extends Base
case _ => super.unaryOp(op)
}

override def doRegexMatchOp(str: String, regex: String): String = s"" //TODO

/**
* Converts byte array (Seq[Byte]) into decimal-escaped Lua-style literal
* characters (i.e. like \255).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ class NimTranslator(provider: TypeProvider, importList: ImportList) extends Base

override def strConcat(left: Ast.expr, right: Ast.expr): String = s"${translate(left)} & ${translate(right)}"

override def doRegexMatchOp(str: String, regex: String): String = s"" //TODO

// Members declared in io.kaitai.struct.translators.CommonMethods

override def unaryOp(op: Ast.unaryop): String = op match {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,12 @@ class PHPTranslator(provider: TypeProvider, config: RuntimeConfig) extends BaseT

override def doName(s: String) = s"${Utils.lowerCamelCase(s)}()"

override def doRegexMatchOp(str: String, regex: String): String = {
s"""preg_match(\"${regex}\", ${str})"""
}

override def doRegexLiteral(reg: String) : String = s"/" + reg + "/"

override def doEnumByLabel(enumTypeAbs: List[String], label: String): String = {
val enumClass = types2classAbs(enumTypeAbs)
s"$enumClass::${Utils.upperUnderscoreCase(label)}"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,10 @@ class PerlTranslator(provider: TypeProvider, importList: ImportList) extends Bas
}
}

override def doRegexMatchOp(str: String, regex: String): String = {
s"${str} ~= m/${regex}/"
}

override def doEnumByLabel(enumType: List[String], label: String): String = {
val enumClass = PerlCompiler.types2class(enumType.init)
val enumClassWithScope = if (enumClass.isEmpty) "" else s"$enumClass::"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,11 @@ class PythonTranslator(provider: TypeProvider, importList: ImportList) extends B
override def doIfExp(condition: Ast.expr, ifTrue: Ast.expr, ifFalse: Ast.expr): String =
s"(${translate(ifTrue)} if ${translate(condition)} else ${translate(ifFalse)})"

override def doRegexMatchOp(str: String, regex: String): String = {
importList.add("import re")
s"re.match('${regex}', ${str})"
}

// Predefined methods of various types
override def strToInt(s: Ast.expr, base: Ast.expr): String = {
val baseStr = translate(base)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,12 @@ class RubyTranslator(provider: TypeProvider) extends BaseTranslator(provider)
}
}

override def doRegexMatchOp(str: String, regex: String): String = {
s"${str}.match ${regex}"
}

override def doRegexLiteral(regex: String) : String = s"/" + regex + "/"

override def doEnumByLabel(enumTypeAbs: List[String], label: String): String =
s":${enumTypeAbs.last}_$label"
override def doEnumById(enumType: List[String], id: String): String =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@ class RustTranslator(provider: TypeProvider, config: RuntimeConfig) extends Base

override def doName(s: String) = s

override def doRegexMatchOp(str: String, regex: String): String = {
s"""Regex::new(r\"${regex}\").unwrap().is_match(${str});"""
}

override def doEnumByLabel(enumTypeAbs: List[String], label: String): String = {
val enumClass = types2classAbs(enumTypeAbs)
s"$enumClass::${Utils.upperUnderscoreCase(label)}"
Expand Down