From bcf90a1f8b85f8913456ae5f2ebc661210280f3b Mon Sep 17 00:00:00 2001 From: Melvic Ybanez Date: Sat, 11 Nov 2023 13:37:03 +0800 Subject: [PATCH] Support raising a does-not-have-properties exception --- .../dry/interpreter/natives/Exceptions.scala | 14 +++++----- .../dry/interpreter/values/DException.scala | 1 + .../scala/com/melvic/dry/result/Failure.scala | 26 ++++++++++--------- .../com/melvic/dry/tests/ExceptionsSpec.scala | 4 +++ 4 files changed, 27 insertions(+), 18 deletions(-) diff --git a/src/main/scala/com/melvic/dry/interpreter/natives/Exceptions.scala b/src/main/scala/com/melvic/dry/interpreter/natives/Exceptions.scala index 7ea45a5..603f6da 100644 --- a/src/main/scala/com/melvic/dry/interpreter/natives/Exceptions.scala +++ b/src/main/scala/com/melvic/dry/interpreter/natives/Exceptions.scala @@ -18,6 +18,7 @@ object Exceptions { .defineWith(InvalidOperands.name, DException(InvalidOperands, _)) .defineWith(NotCallable.name, DException(NotCallable, _)) .defineWith(IncorrectArity.name, DException(IncorrectArity, _)) + .defineWith(DoesNotHaveProperties.name, DException(DoesNotHaveProperties, _)) private def raise(env: Env): Callable = Callable.withLineNo(1, env) { line => def invalidArgument(got: Value): Result[Value] = @@ -32,12 +33,13 @@ object Exceptions { error(Token.fromLine(line), message).fail DException.Kind.of(exception).fold(invalidArgument(exception)) { - case DivisionByZero.name => fail(RuntimeError.divisionByZero) - case UndefinedVariable.name => fail(RuntimeError.undefinedVariable) - case InvalidOperand.name => fail(RuntimeError.invalidOperand) - case InvalidOperands.name => fail(RuntimeError.invalidOperands) - case NotCallable.name => fail(RuntimeError.notCallable) - case IncorrectArity.name => fail(RuntimeError.incorrectArity) + case DivisionByZero.name => fail(RuntimeError.divisionByZero) + case UndefinedVariable.name => fail(RuntimeError.undefinedVariable) + case InvalidOperand.name => fail(RuntimeError.invalidOperand) + case InvalidOperands.name => fail(RuntimeError.invalidOperands) + case NotCallable.name => fail(RuntimeError.notCallable) + case IncorrectArity.name => fail(RuntimeError.incorrectArity) + case DoesNotHaveProperties.name => fail(RuntimeError.doesNotHaveProperties) } case arg :: _ => invalidArgument(arg) } diff --git a/src/main/scala/com/melvic/dry/interpreter/values/DException.scala b/src/main/scala/com/melvic/dry/interpreter/values/DException.scala index 9954afe..91476d9 100644 --- a/src/main/scala/com/melvic/dry/interpreter/values/DException.scala +++ b/src/main/scala/com/melvic/dry/interpreter/values/DException.scala @@ -35,6 +35,7 @@ object DException { case object InvalidOperands extends Kind case object NotCallable extends Kind case object IncorrectArity extends Kind + case object DoesNotHaveProperties extends Kind def apply(kind: Kind, env: Env): DException = new DException(kind, env) diff --git a/src/main/scala/com/melvic/dry/result/Failure.scala b/src/main/scala/com/melvic/dry/result/Failure.scala index 5240cb1..720f8a3 100644 --- a/src/main/scala/com/melvic/dry/result/Failure.scala +++ b/src/main/scala/com/melvic/dry/result/Failure.scala @@ -69,7 +69,7 @@ object Failure { final case class UndefinedVariable(token: Token, message: String) extends RuntimeError final case class NotCallable(token: Token, message: String) extends RuntimeError final case class IncorrectArity(token: Token, message: String) extends RuntimeError - final case class DoesNotHaveProperties(obj: Expr, token: Token) extends RuntimeError + final case class DoesNotHaveProperties(token: Token, message: String) extends RuntimeError final case class UndefinedProperty(token: Token) extends RuntimeError final case class UndefinedKey(key: Expr, token: Token) extends RuntimeError final case class CanNotApplyIndexOperator(obj: Expr, token: Token) extends RuntimeError @@ -111,8 +111,11 @@ object Failure { def incorrectArity(token: Token, expected: Int, got: Int): RuntimeError = incorrectArity(token, s"Incorrect arity. Expected: $expected. Got: $got") + def doesNotHaveProperties(token: Token, message: String): RuntimeError = + DoesNotHaveProperties(token, message) + def doesNotHaveProperties(obj: Expr, token: Token): RuntimeError = - DoesNotHaveProperties(obj, token) + doesNotHaveProperties(token, show"$obj does not have properties or fields.") def undefinedProperty(token: Token): RuntimeError = UndefinedProperty(token) @@ -137,16 +140,15 @@ object Failure { // TODO: Once all runtime errors get their own message fields, refactor this def show: Show[RuntimeError] = { - case DivisionByZero(token, msg) => errorMsg(token, msg) - case InvalidOperand(token, message) => errorMsg(token, message) - case InvalidOperands(token, message) => errorMsg(token, message) - case UndefinedVariable(token, msg) => errorMsg(token, msg) - case NotCallable(token, message) => errorMsg(token, message) - case IncorrectArity(token, message) => errorMsg(token, message) - case DoesNotHaveProperties(obj, token) => - errorMsg(token, show"$obj does not have properties or fields.") - case UndefinedProperty(token) => errorMsg(token, show"Undefined property: $token") - case UndefinedKey(key, token) => errorMsg(token, show"Undefined key: $key") + case DivisionByZero(token, msg) => errorMsg(token, msg) + case InvalidOperand(token, message) => errorMsg(token, message) + case InvalidOperands(token, message) => errorMsg(token, message) + case UndefinedVariable(token, msg) => errorMsg(token, msg) + case NotCallable(token, message) => errorMsg(token, message) + case IncorrectArity(token, message) => errorMsg(token, message) + case DoesNotHaveProperties(token, message) => errorMsg(token, message) + case UndefinedProperty(token) => errorMsg(token, show"Undefined property: $token") + case UndefinedKey(key, token) => errorMsg(token, show"Undefined key: $key") case CanNotApplyIndexOperator(obj, token) => errorMsg(token, show"Can not apply [] operator to $obj") case IndexOutOfBounds(index, line) => diff --git a/src/test/scala/com/melvic/dry/tests/ExceptionsSpec.scala b/src/test/scala/com/melvic/dry/tests/ExceptionsSpec.scala index 029bcee..53f6c3c 100644 --- a/src/test/scala/com/melvic/dry/tests/ExceptionsSpec.scala +++ b/src/test/scala/com/melvic/dry/tests/ExceptionsSpec.scala @@ -34,6 +34,10 @@ class ExceptionsSpec extends AnyFlatSpec with should.Matchers with ScalaCheckPro checkException("IncorrectArity")(RuntimeError.incorrectArity) } + "Raising a DoesNotHaveProperties exception" should "return a runtime error" in { + checkException("DoesNotHaveProperties")(RuntimeError.doesNotHaveProperties) + } + def checkException(exception: String)(error: (Token, String) => RuntimeError): Unit = { val errorMsg = "No money for you!"