Skip to content

Commit

Permalink
Expressions as dictionary keys
Browse files Browse the repository at this point in the history
  • Loading branch information
melvic-ybanez committed Nov 4, 2023
1 parent 1d81f4c commit ceb5c70
Show file tree
Hide file tree
Showing 8 changed files with 32 additions and 42 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ like this:
[Success] Ducks should quack!
[Success] Denji should say 'Woof!'
[Success] Class properties should be updated
Ran 167 tests. Successful: 167. Failed: 0.
Ran 168 tests. Successful: 168. Failed: 0.
```

The tests themselves are written in Dry (while the `testDry` command is written in Scala). You can see the directory
Expand Down
19 changes: 7 additions & 12 deletions src/main/scala/com/melvic/dry/ast/Expr.scala
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@ import scala.{List => SList}
sealed trait Expr

object Expr {
type IndexKey = Either[Literal, Unary]

sealed trait Literal extends Expr

object Literal {
Expand Down Expand Up @@ -43,20 +41,20 @@ object Expr {

final case class Get(obj: Expr, name: Token) extends Expr
final case class Set(obj: Expr, name: Token, value: Expr) extends Expr
final case class IndexGet(obj: Expr, key: Either[Literal, Unary], token: Token) extends Expr
final case class IndexSet(obj: Expr, key: Either[Literal, Unary], value: Expr, token: Token) extends Expr
final case class IndexGet(obj: Expr, key: Expr, token: Token) extends Expr
final case class IndexSet(obj: Expr, key: Expr, value: Expr, token: Token) extends Expr

final case class Self(keyword: Token) extends Expr

final case class List(elems: SList[Expr]) extends Expr
final case class Tuple(elems: SList[Expr]) extends Expr

final case class Dictionary(table: Map[Expr.IndexKey, Expr]) extends Expr
final case class Dictionary(table: Map[Expr, Expr]) extends Expr

object Dictionary {
def show: Show[Dictionary] = { case Dictionary(table) =>
def fieldToString(field: (IndexKey, Expr)): String =
show"${Expr.showIndexKey(field._1)}: ${Expr.show(field._2)}"
def fieldToString(field: (Expr, Expr)): String =
show"${Expr.show(field._1)}: ${Expr.show(field._2)}"

show"{ ${table.map(fieldToString).mkString(", ")} }"
}
Expand All @@ -77,14 +75,11 @@ object Expr {
show"lambda(${params.map(Token.show).toCsv}) ${BlockStmt.fromDecls(body: _*)}"
case Get(obj, name) => show"$obj.$name"
case Set(obj, name, value) => show"$obj.$name = $value"
case IndexGet(obj, name, _) => show"$obj[${showIndexKey(name)}]"
case IndexSet(obj, name, value, _) => show"$obj[${showIndexKey(name)}] = $value"
case IndexGet(obj, name, _) => show"$obj[$name]"
case IndexSet(obj, name, value, _) => show"$obj[$name}] = $value"
case Self(_) => "self"
case List(elems) => show"[${Show.list(elems)}]"
case Tuple(elems) => show"(${Show.list(elems)})"
case dictionary: Dictionary => Dictionary.show(dictionary)
}

def showIndexKey(key: Expr.IndexKey): String =
key.fold(literal => show"$literal", unary => show"$unary")
}
2 changes: 1 addition & 1 deletion src/main/scala/com/melvic/dry/ast/Stmt.scala
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ object Stmt {
}

final case class ReturnStmt(keyword: Token, value: Expr) extends Stmt
final case class DeleteStmt(obj: Expr, key: Expr.IndexKey, token: Token) extends Stmt
final case class DeleteStmt(obj: Expr, key: Expr, token: Token) extends Stmt

final case class Import(path: List[Token]) extends Stmt {
def name: Token = path.last
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 @@ -245,7 +245,7 @@ private[eval] trait EvalExpr {
val dictFields = table.toList.foldFailFast(Result.succeed(Map.empty[Value, Value])) {
case (result, (key, value)) =>
for {
evaluatedKey <- key.fold(Evaluate.literal(_), Evaluate.unary(_))
evaluatedKey <- Evaluate.expr(key)
evaluatedValue <- Evaluate.expr(value)
} yield result + (evaluatedKey -> evaluatedValue)
}
Expand All @@ -258,12 +258,12 @@ private[eval] trait EvalExpr {
Evaluate.expr(elem).map(_ :: result)
}

private[eval] def index[A](obj: Expr, key: Expr.IndexKey, token: Token)(
private[eval] def index[A](obj: Expr, key: Expr, token: Token)(
ifCanBeIndexed: PartialFunction[(Value, Value), Out]
)(implicit context: Context[A]): Out = {
for {
evaluatedObj <- Evaluate.expr(obj)
evaluatedKey <- key.fold(Evaluate.literal(_), Evaluate.unary(_))
evaluatedKey <- Evaluate.expr(key)
orElse: PartialFunction[(Value, Value), Out] = { case (_: Value, _: Value) =>
RuntimeError.canNotApplyIndexOperator(obj, token).fail[Value]
}
Expand Down
4 changes: 2 additions & 2 deletions src/main/scala/com/melvic/dry/interpreter/eval/Evaluate.scala
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
package com.melvic.dry.interpreter.eval

import com.melvic.dry.Token
import com.melvic.dry.ast.Expr.IndexKey
import com.melvic.dry.ast.Expr
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)(
private[eval] def accessNumericIndex(evaluatedKey: Value, key: Expr, limit: Int, token: Token)(
access: Int => Out
): Out =
evaluatedKey match {
Expand Down
19 changes: 4 additions & 15 deletions src/main/scala/com/melvic/dry/parsers/ExprParser.scala
Original file line number Diff line number Diff line change
Expand Up @@ -171,19 +171,8 @@ private[parsers] trait ExprParser { _: Parser =>
}
}

private[parsers] def indexAccess[A](keyF: IndexKey => A): ParseResult[A] =
indexKey(keyF).flatMapParser(_.consumeAfter(TokenType.RightBracket, "]", "index access"))

private[parsers] def indexKey[A](keyF: IndexKey => A): ParseResult[A] =
expression
.flatMap {
case Step(literal: Literal, next) => ParseResult.succeed(keyF(Left(literal)), next)
case Step(unary @ Unary(Token(TokenType.Plus, _, _), Literal.Number(_)), next) =>
ParseResult.succeed(keyF(Right(unary)), next)
case Step(unary @ Unary(Token(TokenType.Minus, _, _), Literal.Number(_)), next) =>
ParseResult.succeed(keyF(Right(unary)), next)
case Step(_, next) => ParseResult.fail(ParseError.expected(peek, "key name", "after ["), next)
}
private[parsers] def indexAccess[A](keyF: Expr => A): ParseResult[A] =
expression.mapValue(keyF).flatMapParser(_.consumeAfter(TokenType.RightBracket, "]", "index access"))

/**
* Decides whether to construct a Call or a Lambda node. This is useful for partial application.
Expand Down Expand Up @@ -290,15 +279,15 @@ private[parsers] trait ExprParser { _: Parser =>
* }}}
*/
def dictionary: ParseResult[Dictionary] =
sequence[(IndexKey, Expr)](
sequence[(Expr, Expr)](
TokenType.LeftBrace,
"{",
TokenType.RightBrace,
"}",
"at the start of dictionary",
"dictionary elements"
)(
_.indexKey(identity)
_.expression
.flatMap { case Step(key, next) =>
next
.consumeAfter(TokenType.Colon, ":", "dictionary key")
Expand Down
14 changes: 7 additions & 7 deletions src/main/scala/com/melvic/dry/result/Failure.scala
Original file line number Diff line number Diff line change
Expand Up @@ -71,10 +71,10 @@ object Failure {
final case class IncorrectArity(token: Token, expected: Int, got: Int) extends RuntimeError
final case class DoesNotHaveProperties(obj: Expr, token: Token) extends RuntimeError
final case class UndefinedProperty(token: Token) extends RuntimeError
final case class UndefinedKey(key: Expr.IndexKey, token: Token) extends RuntimeError
final case class UndefinedKey(key: Expr, token: Token) extends RuntimeError
final case class CanNotApplyIndexOperator(obj: Expr, token: Token) extends RuntimeError
final case class IndexOutOfBounds(index: Int, line: Int) extends RuntimeError
final case class InvalidIndex(index: Expr.IndexKey, token: Token) extends RuntimeError
final case class InvalidIndex(index: Expr, token: Token) extends RuntimeError
final case class InvalidArgument(expected: String, got: String, line: Int) extends RuntimeError
final case class ModuleNotFound(name: String, token: Token) extends RuntimeError

Expand Down Expand Up @@ -102,7 +102,7 @@ object Failure {
def undefinedProperty(token: Token): RuntimeError =
UndefinedProperty(token)

def undefinedKey(key: Expr.IndexKey, token: Token): RuntimeError =
def undefinedKey(key: Expr, token: Token): RuntimeError =
UndefinedKey(key, token)

def canNotApplyIndexOperator(obj: Expr, token: Token): RuntimeError =
Expand All @@ -111,7 +111,7 @@ object Failure {
def indexOutOfBounds(index: Int, line: Int): RuntimeError =
IndexOutOfBounds(index, line)

def invalidIndex(index: Expr.IndexKey, token: Token): RuntimeError =
def invalidIndex(index: Expr, token: Token): RuntimeError =
InvalidIndex(index, token)

def invalidArgument(expected: String, got: String, line: Int): RuntimeError =
Expand All @@ -132,12 +132,12 @@ object Failure {
errorMsg(token, s"Incorrect arity. Expected: $expected. Got: $got")
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: ${Expr.showIndexKey(key)}")
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) =>
show"Runtime Error. Index out of bounds: $index\n[line $line]."
case InvalidIndex(index, token) => errorMsg(token, show"Invalid index: ${Expr.showIndexKey(index)}")
case InvalidIndex(index, token) => errorMsg(token, show"Invalid index: $index")
case InvalidArgument(expected, got, line) =>
show"Runtime Error. Invalid argument. Expected: $expected. Got: $got\n${showLine(line)}."
case ModuleNotFound(name, token) => errorMsg(token, show"Module not found: $name")
Expand Down
8 changes: 7 additions & 1 deletion tests/data_structs/test_dictionaries.dry
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,11 @@ class TestDictionary {
// this will at least check if it works on a non-singleton, non-empty dictionary
assert_equals("Size of retrieved fields", 2, {1: 2, 3: 4}.fields().size());
}

def test_non_constant_keys() {
let dict = {1 + 1: 1};
assert_equals("Dictionary with a non-constant key", 1, dict[2]);
}
}

let test_dict = TestDictionary();
Expand All @@ -99,4 +104,5 @@ test_dict.test_set();
test_dict.test_delete();
test_dict.test_nested();
test_dict.test_type();
test_dict.test_fields_retrieval();
test_dict.test_fields_retrieval();
test_dict.test_non_constant_keys();

0 comments on commit ceb5c70

Please sign in to comment.