From 1a8dd6f130e6d010ae242e023768dd85597f5fe8 Mon Sep 17 00:00:00 2001 From: Melvic Ybanez Date: Fri, 3 Nov 2023 17:00:11 +0800 Subject: [PATCH] Add support for list element deletion --- README.md | 2 +- .../dry/interpreter/eval/EvalExpr.scala | 16 ++--------- .../dry/interpreter/eval/EvalStmt.scala | 9 ++++-- .../dry/interpreter/eval/Evaluate.scala | 15 ++++++++++ .../melvic/dry/interpreter/values/DList.scala | 3 ++ tests/data_structs/test_lists.dry | 28 ++++++++++++++++--- 6 files changed, 51 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index 722f955..c5fd145 100644 --- a/README.md +++ b/README.md @@ -175,7 +175,7 @@ like this: [Success] Ducks should quack! [Success] Denji should say 'Woof!' [Success] Class properties should be updated -Ran 158 tests. Successful: 158. Failed: 0. +Ran 163 tests. Successful: 163. Failed: 0. ``` The tests themselves are written in Dry (while the `testDry` command is written in Scala). You can see the directory diff --git a/src/main/scala/com/melvic/dry/interpreter/eval/EvalExpr.scala b/src/main/scala/com/melvic/dry/interpreter/eval/EvalExpr.scala index feab1cd..8c21707 100644 --- a/src/main/scala/com/melvic/dry/interpreter/eval/EvalExpr.scala +++ b/src/main/scala/com/melvic/dry/interpreter/eval/EvalExpr.scala @@ -213,7 +213,7 @@ private[eval] trait EvalExpr { case (dict: DDictionary, evaluatedKey) => Result.fromOption(dict.getByKey(evaluatedKey), RuntimeError.undefinedKey(key, token)) case (sequence: DSequence, evaluatedKey) => - accessNumericIndex(evaluatedKey, key, sequence.size, token)(sequence.getByIndex(_).ok) + Evaluate.accessNumericIndex(evaluatedKey, key, sequence.size, token)(sequence.getByIndex(_).ok) } } @@ -223,7 +223,7 @@ private[eval] trait EvalExpr { case (dict: DDictionary, evaluatedKey) => Evaluate.expr(value).map(dict.setByKey(evaluatedKey, _)) case (list: DList, evaluatedKey) => - accessNumericIndex(evaluatedKey, key, list.size, token) { index => + Evaluate.accessNumericIndex(evaluatedKey, key, list.size, token) { index => Evaluate.expr(value).map(list.setByIndex(index, _)) } } @@ -253,18 +253,6 @@ private[eval] trait EvalExpr { dictFields.map(fields => DDictionary(fields.to(mutable.Map), env)) } - private[eval] def accessNumericIndex(evaluatedKey: Value, key: IndexKey, limit: Int, token: Token)( - access: Int => Out - ): Out = - evaluatedKey match { - case Value.Num(index) if index % 1 == 0 => - val intIndex = index.toInt - if (index < 0 || index >= limit) - RuntimeError.indexOutOfBounds(intIndex, token.line).fail[Value] - else access(intIndex) - case _ => RuntimeError.invalidIndex(key, token).fail[Value] - } - private[eval] def exprList(elems: List[Expr])(implicit context: Context[Expr]): Result[List[Value]] = elems.foldFailFast(Result.succeed(List.empty[Value])) { (result, elem) => Evaluate.expr(elem).map(_ :: result) diff --git a/src/main/scala/com/melvic/dry/interpreter/eval/EvalStmt.scala b/src/main/scala/com/melvic/dry/interpreter/eval/EvalStmt.scala index 63fe57a..5571f66 100644 --- a/src/main/scala/com/melvic/dry/interpreter/eval/EvalStmt.scala +++ b/src/main/scala/com/melvic/dry/interpreter/eval/EvalStmt.scala @@ -9,7 +9,7 @@ import com.melvic.dry.interpreter.Value.{Returned, Unit => VUnit} import com.melvic.dry.interpreter.eval.Context.implicits._ import com.melvic.dry.interpreter.eval.Evaluate.Out import com.melvic.dry.interpreter.values.Value.ToValue -import com.melvic.dry.interpreter.values.{DDictionary, DModule, Value} +import com.melvic.dry.interpreter.values.{DDictionary, DList, DModule, Value} import com.melvic.dry.interpreter.{Env, ModuleManager, Run} import com.melvic.dry.result.Failure.RuntimeError import com.melvic.dry.result.Result @@ -79,8 +79,11 @@ private[eval] trait EvalStmt { def deleteStmt(implicit context: Context[DeleteStmt]): Out = node match { case DeleteStmt(obj, key, token) => - Evaluate.index(obj, key, token) { case (dict: DDictionary, evaluatedKey) => - Result.fromOption(dict.deleteByKey(evaluatedKey), RuntimeError.undefinedKey(key, token)) + Evaluate.index(obj, key, token) { + case (dict: DDictionary, evaluatedKey) => + Result.fromOption(dict.deleteByKey(evaluatedKey), RuntimeError.undefinedKey(key, token)) + case (list: DList, evaluatedKey) => + Evaluate.accessNumericIndex(evaluatedKey, key, list.size, token)(list.deleteByIndex(_).ok) } } diff --git a/src/main/scala/com/melvic/dry/interpreter/eval/Evaluate.scala b/src/main/scala/com/melvic/dry/interpreter/eval/Evaluate.scala index 7416231..0799ef7 100644 --- a/src/main/scala/com/melvic/dry/interpreter/eval/Evaluate.scala +++ b/src/main/scala/com/melvic/dry/interpreter/eval/Evaluate.scala @@ -1,8 +1,23 @@ package com.melvic.dry.interpreter.eval +import com.melvic.dry.Token +import com.melvic.dry.ast.Expr.IndexKey import com.melvic.dry.interpreter.values.Value +import com.melvic.dry.result.Failure.RuntimeError import com.melvic.dry.result.Result.Result object Evaluate extends EvalExpr with EvalDecl { type Out = Result[Value] + + private[eval] def accessNumericIndex(evaluatedKey: Value, key: IndexKey, limit: Int, token: Token)( + access: Int => Out + ): Out = + evaluatedKey match { + case Value.Num(index) if index % 1 == 0 => + val intIndex = index.toInt + if (index < 0 || index >= limit) + RuntimeError.indexOutOfBounds(intIndex, token.line).fail[Value] + else access(intIndex) + case _ => RuntimeError.invalidIndex(key, token).fail[Value] + } } diff --git a/src/main/scala/com/melvic/dry/interpreter/values/DList.scala b/src/main/scala/com/melvic/dry/interpreter/values/DList.scala index a1467cf..f2926b3 100644 --- a/src/main/scala/com/melvic/dry/interpreter/values/DList.scala +++ b/src/main/scala/com/melvic/dry/interpreter/values/DList.scala @@ -23,4 +23,7 @@ final case class DList(elems: ListBuffer[Value], env: Env) extends DSequence { def setByIndex(index: Int, value: Value): Value = (elems(index) = value).unit + + def deleteByIndex(index: Int): Value = + elems.remove(index).unit } diff --git a/tests/data_structs/test_lists.dry b/tests/data_structs/test_lists.dry index 351adb5..ef48a8c 100644 --- a/tests/data_structs/test_lists.dry +++ b/tests/data_structs/test_lists.dry @@ -35,14 +35,14 @@ class TestList { xs[""] = 20; }); assert_error("Updating list with index < 0", Errors.INDEX_OUT_OF_BOUNDS, lambda() { - xs[-1]; + xs[-1] = 0; }); assert_error("Updating list with index == size", Errors.INDEX_OUT_OF_BOUNDS, lambda() { - xs[3]; + xs[3] = 19; }); assert_error("Updating list with index > size", Errors.INDEX_OUT_OF_BOUNDS, lambda() { - xs[4]; + xs[4] = 1; }); xs[0] = 10; @@ -50,6 +50,25 @@ class TestList { assert_equals("Updating list successfully", xs, [10, 2, 3]); } + def test_delete() { + let xs = ["apple", "banana", "orange"]; + assert_error("Deleting a list element with string index", Errors.INVALID_INDEX, lambda() { + del xs[""]; + }); + assert_error("Deleting a list element with index < 0", Errors.INDEX_OUT_OF_BOUNDS, lambda() { + del xs[-1]; + }); + + assert_error("Deleting a list element with index == size", Errors.INDEX_OUT_OF_BOUNDS, lambda() { + del xs[3]; + }); + assert_error("Deleting a list element with index > size", Errors.INDEX_OUT_OF_BOUNDS, lambda() { + del xs[4]; + }); + del xs[1]; + assert_equals("Deleting a list element successfully", xs, ["apple", "orange"]); + } + def test_size() { let xs = [1, 2, 3, 4, 5]; assert_equals("Non-empty list size", xs.size(), 5); @@ -73,4 +92,5 @@ test_list.test_get(); test_list.test_size(); test_list.test_type(); test_list.test_add_method(); -test_list.test_set(); \ No newline at end of file +test_list.test_set(); +test_list.test_delete(); \ No newline at end of file