Skip to content

Commit

Permalink
Bracket notation for list element access
Browse files Browse the repository at this point in the history
  • Loading branch information
melvic-ybanez committed Nov 3, 2023
1 parent da2cf21 commit 75ec614
Show file tree
Hide file tree
Showing 9 changed files with 80 additions and 121 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ like this:
[Success] Ducks should quack!
[Success] Denji should say 'Woof!'
[Success] Class properties should be updated
Ran 150 tests. Successful: 150. Failed: 0.
Ran 153 tests. Successful: 153. Failed: 0.
```

The tests themselves are written in Dry (while the `testDry` command is written in Scala). You can see the directory
Expand Down
6 changes: 3 additions & 3 deletions src/main/scala/com/melvic/dry/interpreter/eval/EvalExpr.scala
Original file line number Diff line number Diff line change
Expand Up @@ -212,13 +212,13 @@ private[eval] trait EvalExpr {
Evaluate.index(obj, key, token) {
case (dict: DDictionary, evaluatedKey) =>
Result.fromOption(dict.getByKey(evaluatedKey), RuntimeError.undefinedKey(key, token))
case (tuple: DTuple, evaluatedKey) =>
case (sequence: DSequence, evaluatedKey) =>
evaluatedKey match {
case Value.Num(index) if index % 1 == 0 =>
val intIndex = index.toInt
if (index < 0 || index >= tuple.elems.size)
if (index < 0 || index >= sequence.size)
RuntimeError.indexOutOfBounds(intIndex, token.line).fail[Value]
else tuple.getByIndex(intIndex).ok
else sequence.getByIndex(intIndex).ok
case _ => RuntimeError.invalidIndex(key, token).fail[Value]
}
}
Expand Down
15 changes: 0 additions & 15 deletions src/main/scala/com/melvic/dry/interpreter/values/Collections.scala

This file was deleted.

13 changes: 13 additions & 0 deletions src/main/scala/com/melvic/dry/interpreter/values/Countable.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.melvic.dry.interpreter.values

import com.melvic.dry.interpreter.Env
import com.melvic.dry.result.Result.implicits.ToResult

trait Countable {
def size: Int

def addSizeMethod(env: Env): AddProperty =
_ + ("size" -> Callable.noArg(env) {
Value.Num(size).ok
})
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
package com.melvic.dry.interpreter.values

import com.melvic.dry.Token
import com.melvic.dry.Token.TokenType
import com.melvic.dry.interpreter.Env
import com.melvic.dry.interpreter.values.Collections.Countable

import scala.collection.mutable

Expand Down
44 changes: 5 additions & 39 deletions src/main/scala/com/melvic/dry/interpreter/values/DList.scala
Original file line number Diff line number Diff line change
@@ -1,56 +1,22 @@
package com.melvic.dry.interpreter.values

import com.melvic.dry.Token
import com.melvic.dry.Token.TokenType
import com.melvic.dry.ast.Expr
import com.melvic.dry.interpreter.Env
import com.melvic.dry.result.Failure.RuntimeError
import com.melvic.dry.result.Result

import scala.collection.mutable
import scala.collection.mutable.ListBuffer
import scala.util.chaining.scalaUtilChainingOps

final case class DList(elems: ListBuffer[Value], env: Env) extends DObject with Collections.Countable {
final case class DList(elems: ListBuffer[Value], env: Env) extends DSequence {
override def klass: Metaclass = DClass("List", Map.empty, env)

override val fields: mutable.Map[String, Value] =
addIndexFields()
.pipe(addAtMethod)
.pipe(addSizeMethod(env))
.pipe(addAddMethod)
addSizeMethod(env)
.andThen(addAddMethod)(Map.empty)
.to(mutable.Map)

// TODO: Replace with bracket-notation
private def addIndexFields() =
elems.zipWithIndex
.map { case (elem, i) => ("_" + i) -> elem }
.to(Map)

// TODO: Replace with bracket-notation
private def addAtMethod: AddProperty =
_ + ("at" -> Callable.withLineNo(1, env)(line => { case indexValue :: _ =>
// This is temporary. We are going to replace this whole function
// with the [] operator anyway
val valueString = Value.show(indexValue)

indexValue.toNum
.fold(
RuntimeError
.invalidIndex(
// again, these values are temporary and should be removed
Left(Expr.Literal.Str(valueString)),
Token(TokenType.Str(valueString), valueString, line)
)
.fail[Value]
) { num =>
val index = num.value.toInt
Result.fromOption(elems.lift(index), RuntimeError.indexOutOfBounds(index, line))
}
}))

private def addAddMethod: AddProperty =
_ + ("add" -> Callable.unarySuccess(env)(elem => copy(elems = elems += elem)))

override def size: Int = elems.size

override def getByIndex(index: Int): Value = elems(index)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.melvic.dry.interpreter.values

trait DSequence extends DObject with Countable {
def getByIndex(index: Int): Value
}
5 changes: 2 additions & 3 deletions src/main/scala/com/melvic/dry/interpreter/values/DTuple.scala
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
package com.melvic.dry.interpreter.values
import com.melvic.dry.interpreter.Env
import com.melvic.dry.interpreter.values.Collections.Countable

import scala.collection.mutable

final case class DTuple(elems: List[Value], env: Env) extends DObject with Countable {
final case class DTuple(elems: List[Value], env: Env) extends DSequence {
override def klass: Metaclass = DClass("Tuple", Map.empty, env)

override def fields: mutable.Map[String, Value] =
Expand All @@ -13,7 +12,7 @@ final case class DTuple(elems: List[Value], env: Env) extends DObject with Count

override def size: Int = elems.size

def getByIndex(index: Int): Value =
override def getByIndex(index: Int): Value =
elems(index)
}

108 changes: 51 additions & 57 deletions tests/data_structs/test_lists.dry
Original file line number Diff line number Diff line change
@@ -1,59 +1,53 @@
def test_list_str() {
let xs = [1, "two", 3];
assert_equals("Stringify lists", str(xs), "[1, two, 3]");
assert_equals("Empty list string representation", str([]), "[]");
class TestList {
def test_str() {
let xs = [1, "two", 3];
assert_equals("Stringify lists", str(xs), "[1, two, 3]");
assert_equals("Empty list string representation", str([]), "[]");
}

def test_get() {
let xs = [1, "two", 3];

assert_equals("First element", xs[0], 1);
assert_equals("Second element", xs[1], "two");
assert_equals("Third element", xs[2], 3);

let empty = [];
assert_error("Reading list elements with string index", Errors.INVALID_INDEX, lambda() {
empty[""];
});
assert_error("Reading list elements with index < 0", Errors.INDEX_OUT_OF_BOUNDS, lambda() {
empty[-1];
});

assert_error("Reading list elements with index == size", Errors.INDEX_OUT_OF_BOUNDS, lambda() {
xs[3];
});
assert_error("Reading list elements with index > size", Errors.INDEX_OUT_OF_BOUNDS, lambda() {
xs[4];
});
}

def test_size() {
let xs = [1, 2, 3, 4, 5];
assert_equals("Non-empty list size", xs.size(), 5);
assert_equals("Empty list size", [].size(), 0);
}

def test_add_method() {
let xs = ["one"];
assert_equals("New element is added to the list", str(xs.add("two")), "[one, two]");
assert_equals("Size should increase after adding new item", xs.size(), 2);
}

def test_type() {
assert_equals("List type", typeof([1, 2]), "list");
}
}

def test_list_index() {
let xs = [1, "two", 3];

assert_equals("First element", xs._0, 1);
assert_equals("Second element", xs._1, "two");
assert_equals("Third element", xs._2, 3);

xs._0 = 10;
assert_equals("Updated first element", xs._0, 10);
}

def test_list_size() {
let xs = [1, 2, 3, 4, 5];
assert_equals("Non-empty list size", xs.size(), 5);
assert_equals("Empty list size", [].size(), 0);
}

def test_list_at_method() {
let xs = ["first", "second", "third"];
let success_count = 0;

if (xs._0 == xs.at(0)) success_count = success_count + 1;
if (xs._1 == xs.at(1)) success_count = success_count + 1;
if (xs._2 == xs.at(2)) success_count = success_count + 1;

// This description is a lie though
assert_equals("For all list `xs` and integer `i`, `xs.at(i)` is equal to `xs._<i>`", success_count, 3);
}

def test_list_add_method() {
let xs = ["one"];
assert_equals("New element is added to the list", str(xs.add("two")), "[one, two]");
assert_equals("Size should increase after adding new item", xs.size(), 2);
}

def test_out_of_bounds() {
assert_error("Index out of bounds", Errors.INDEX_OUT_OF_BOUNDS, lambda() {
let xs = [1];
xs.at(2);
});
}

def test_list_type() {
assert_equals("List type", typeof([1, 2]), "list");
}

test_list_str();
test_list_index();
test_list_size();
test_list_at_method();
test_out_of_bounds();
test_list_type();
test_list_add_method();
let test_list = TestList();
test_list.test_str();
test_list.test_get();
test_list.test_size();
test_list.test_type();
test_list.test_add_method();

0 comments on commit 75ec614

Please sign in to comment.