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

CommonMethods: declarative specification of methods #269

Merged
merged 4 commits into from
Mar 8, 2024
Merged
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
@@ -0,0 +1,181 @@
package io.kaitai.struct.translators

import io.kaitai.struct.datatype.DataType._
import io.kaitai.struct.exprlang.Expressions
import io.kaitai.struct.exprlang.Expressions.ParseException
import io.kaitai.struct.precompile.{MethodNotFoundErrorWithArg, TypeMismatchError, WrongMethodCall}
import org.scalatest.funspec.AnyFunSpec
import org.scalatest.matchers.should.Matchers._

class ExpressionValidator$Test extends AnyFunSpec {
val alwaysInt = TestTypeProviders.Always(CalcIntType)
val alwaysIntValidator = new ExpressionValidator(alwaysInt)

describe("simple literals") {
describe("valid") {
it("123") {
val ex = Expressions.parse("123")
alwaysIntValidator.validate(ex)
}

it("123.456e12") {
val ex = Expressions.parse("123.456e12")
alwaysIntValidator.validate(ex)
}

it("\"foo\"") {
val ex = Expressions.parse("\"foo\"")
alwaysIntValidator.validate(ex)
}
}
}

describe("integer methods") {
describe("valid") {
it("123.to_s") {
val ex = Expressions.parse("123.to_s")
alwaysIntValidator.validate(ex)
}

it("123.to_s()") {
val ex = Expressions.parse("123.to_s()")
alwaysIntValidator.validate(ex)
}
}

describe("broken") {
it("123.to_s(3)") {
val ex = Expressions.parse("123.to_s(3)")
val thrown = the[WrongMethodCall] thrownBy alwaysIntValidator.validate(ex)
thrown.getMessage should be("wrong arguments to method call `to_s` on integer: expected (), got (IntNum(3))")
}

it("123.unknown_method") {
val ex = Expressions.parse("123.unknown_method")
val thrown = the[MethodNotFoundErrorWithArg] thrownBy alwaysIntValidator.validate(ex)
thrown.getMessage should be("don't know how to call method 'unknown_method' of object type 'integer'")
}

it("123.unknown_method_with_param(true)") {
val ex = Expressions.parse("123.unknown_method_with_param(true)")
val thrown = the[MethodNotFoundErrorWithArg] thrownBy alwaysIntValidator.validate(ex)
thrown.getMessage should be("don't know how to call method 'unknown_method_with_param' of object type 'integer'")
}
}
}

describe("float methods") {
describe("valid") {
it("1.234.to_i") {
val ex = Expressions.parse("1.234.to_i")
alwaysIntValidator.validate(ex)
}
}

describe("broken") {
it("1.234.unknown_method") {
val ex = Expressions.parse("1.234.unknown_method")
val thrown = the[MethodNotFoundErrorWithArg] thrownBy alwaysIntValidator.validate(ex)
thrown.getMessage should be("don't know how to call method 'unknown_method' of object type 'float'")
}

it("1.234.unknown_method_with_param(true)") {
val ex = Expressions.parse("1.234.unknown_method_with_param(true)")
val thrown = the[MethodNotFoundErrorWithArg] thrownBy alwaysIntValidator.validate(ex)
thrown.getMessage should be("don't know how to call method 'unknown_method_with_param' of object type 'float'")
}
}
}

describe("string methods") {
it("\"123\".to_i") {
val ex = Expressions.parse("\"123\".to_i")
alwaysIntValidator.validate(ex)
}

it("\"123\".to_i()") {
val ex = Expressions.parse("\"123\".to_i()")
alwaysIntValidator.validate(ex)
}

it("\"123\".to_i(16)") {
val ex = Expressions.parse("\"123\".to_i(16)")
alwaysIntValidator.validate(ex)
}

it("\"123\".to_i(true)") {
val ex = Expressions.parse("\"123\".to_i(true)")
val thrown = the [WrongMethodCall] thrownBy alwaysIntValidator.validate(ex)
thrown.getMessage should be("wrong arguments to method call `to_i` on string: expected () or (integer), got (Bool(true))")
}

it("\"123\".to_i(16, true)") {
val ex = Expressions.parse("\"123\".to_i(16, true)")
val thrown = the [WrongMethodCall] thrownBy alwaysIntValidator.validate(ex)
thrown.getMessage should be("wrong arguments to method call `to_i` on string: expected () or (integer), got (IntNum(16), Bool(true))")
}

it("\"foobar\".substring(2, 3)") {
val ex = Expressions.parse("\"foobar\".substring(2, 3)")
alwaysIntValidator.validate(ex)
}

it("\"foobar\".substring(2, 3, 5)") {
val ex = Expressions.parse("\"foobar\".substring(2, 3, 5)")
val thrown = the [WrongMethodCall] thrownBy alwaysIntValidator.validate(ex)
thrown.getMessage should be("wrong arguments to method call `substring` on string: expected (integer, integer), got (IntNum(2), IntNum(3), IntNum(5))")
}

it("\"foobar\".substring(\"foo\", 5)") {
val ex = Expressions.parse("\"foobar\".substring(\"foo\", 5)")
val thrown = the [WrongMethodCall] thrownBy alwaysIntValidator.validate(ex)
thrown.getMessage should be("wrong arguments to method call `substring` on string: expected (integer, integer), got (Str(foo), IntNum(5))")
}
}

describe("array methods") {
describe("valid") {
it("[\"foo\", \"bar\"].size") {
val ex = Expressions.parse("[\"foo\", \"bar\"].size")
alwaysIntValidator.validate(ex)
}

it("[\"foo\", \"bar\"].min") {
val ex = Expressions.parse("[\"foo\", \"bar\"].min")
alwaysIntValidator.validate(ex)
}

it("[\"foo\", \"bar\"].min()") {
val ex = Expressions.parse("[\"foo\", \"bar\"].min")
alwaysIntValidator.validate(ex)
}
}

describe("broken") {
it("[\"foo\", \"bar\"].min(42)") {
val ex = Expressions.parse("[\"foo\", \"bar\"].min(42)")
val thrown = the[WrongMethodCall] thrownBy alwaysIntValidator.validate(ex)
thrown.getMessage should be("wrong arguments to method call `min` on array: expected (), got (IntNum(42))")
}
}
}

describe("subscripts") {
it("[1, 3, 14][2]") {
val ex = Expressions.parse("[1, 3, 14][2]")
alwaysIntValidator.validate(ex)
}

it("[1, 3, 14][\"foo\"]") {
val ex = Expressions.parse("[1, 3, 14][\"foo\"]")
val thrown = the [TypeMismatchError] thrownBy alwaysIntValidator.validate(ex)
thrown.getMessage should be("subscript operation on arrays require index to be integer, but found CalcStrType")
}

it("x[4]") {
val ex = Expressions.parse("x[4]")
val thrown = the [TypeMismatchError] thrownBy alwaysIntValidator.validate(ex)
thrown.getMessage should be("subscript operation is not supported on object type CalcIntType")
}
}
}
12 changes: 10 additions & 2 deletions shared/src/main/scala/io/kaitai/struct/precompile/Exceptions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package io.kaitai.struct.precompile

import io.kaitai.struct.datatype.DataType
import io.kaitai.struct.format.ClassSpec
import io.kaitai.struct.translators.MethodArgType

/**
* Base class for all expression-related errors, not localized to a certain path
Expand All @@ -10,6 +11,8 @@ import io.kaitai.struct.format.ClassSpec
sealed abstract class ExpressionError(msg: String) extends RuntimeException(msg)
class TypeMismatchError(msg: String) extends ExpressionError(msg)
class TypeUndecidedError(msg: String) extends ExpressionError(msg)
class WrongMethodCall(val dataType: MethodArgType, val methodName: String, val expectedSigs: Iterable[String], val actualSig: String)
extends ExpressionError(s"wrong arguments to method call `$methodName` on $dataType: expected ${expectedSigs.mkString(" or ")}, got $actualSig")

sealed abstract class NotFoundError(msg: String) extends ExpressionError(msg)
class TypeNotFoundError(val name: String, val curClass: ClassSpec)
Expand All @@ -18,10 +21,15 @@ class FieldNotFoundError(val name: String, val curClass: ClassSpec)
extends NotFoundError(s"unable to access '$name' in ${curClass.nameAsStr} context")
class EnumNotFoundError(val name: String, val curClass: ClassSpec)
extends NotFoundError(s"unable to find enum '$name', searching from ${curClass.nameAsStr}")
class EnumMemberNotFoundError(val label: String, val enum: String, val enumDefPath: String)
extends NotFoundError(s"unable to find enum member '$enum::$label' (enum '$enum' defined at /$enumDefPath)")
class EnumMemberNotFoundError(val label: String, val enumName: String, val enumDefPath: String)
extends NotFoundError(s"unable to find enum member '$enumName::$label' (enum '$enumName' defined at /$enumDefPath)")

// TODO: get rid of MethodNotFoundError in favor of MethodNotFoundErrorWithArg, rename it back
// requires refactoring of [[TypeDetector]]
class MethodNotFoundError(val name: String, val dataType: DataType)
extends NotFoundError(s"don't know how to call method '$name' of object type '$dataType'")
class MethodNotFoundErrorWithArg(val name: String, val argType: MethodArgType)
extends NotFoundError(s"don't know how to call method '$name' of object type '$argType'")

/**
* Internal compiler logic error: should never happen, but at least we want to
Expand Down
Loading
Loading