Skip to content
Open
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
@@ -1,6 +1,6 @@
package me.rexim.morganey.helpers

import me.rexim.morganey.ast.{LambdaFunc, LambdaVar, LambdaTerm}
import me.rexim.morganey.ast._
import me.rexim.morganey.ast.LambdaTermHelpers._

trait TestTerms {
Expand All @@ -24,6 +24,9 @@ trait TestTerms {
lapp(lvar(consName), first),
second))

def binding(v1: LambdaVar, v2: LambdaVar): MorganeyBinding =
MorganeyBinding(v1, I(v2))

val random = scala.util.Random

def redex(body: LambdaTerm) = {
Expand Down
33 changes: 22 additions & 11 deletions src/main/scala/me/rexim/morganey/interpreter/MorganeyRepl.scala
Original file line number Diff line number Diff line change
Expand Up @@ -20,19 +20,35 @@ class MorganeyRepl(preludeModule: Option[Module]) {
case input => parseAndEval(context, line)
}

// TODO: Separate REPL side effects from ReplResult
//
// Right now ReplResult.result has two meanings:
// 1. Result of the term reduction
// 2. REPL message for the user
//
// We need to separate those meanings. The proposed idea is to
// attach some kind of side effect to ReplContext and use
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

*to ReplResult

// ReplResult.result ONLY for term reduction results.
private def parseAndEval(context: ReplContext, line: String): Computation[ReplResult[String]] = {
val parseResult = Computation(LambdaParser.parseAll(LambdaParser.replCommand, line).toTry)

// TODO(#197): discriminate bindings from the rest of the nodes here and inform the user if the binding was redefined
val evaluation = parseResult flatMap {
case Success(node) => evalNode(context, node)
parseResult flatMap {
case Success(binding: MorganeyBinding) => {
Computation(ReplResult(
context.addBinding(binding),
if (!context.contains(binding)) {
None
} else {
Some(s"${binding.variable} was redefined")
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

*Warning: ${binding.variable} was redefined

}
))
}
case Success(node) => evalNode(context, node) map (_ map smartShowTerm)
case Failure(e) => Computation.failed(e)
}

evaluation map (_ map smartShowTerm)
}

def evalNode(context: ReplContext, node: MorganeyNode): Computation[ReplResult[LambdaTerm]] = {
private def evalNode(context: ReplContext, node: MorganeyNode): Computation[ReplResult[LambdaTerm]] = {
node match {
case MorganeyLoading(Some(modulePath)) => {
new Module(CanonicalPath(modulePath), preludeModule).load() match {
Expand All @@ -44,11 +60,6 @@ class MorganeyRepl(preludeModule: Option[Module]) {
case MorganeyLoading(None) =>
Computation.failed(new IllegalArgumentException("Module path was not specified!"))

// TODO(#197): separate bindings evaluation from MorganeyRepl.evalNode
case binding: MorganeyBinding => {
Computation(ReplResult(context.addBinding(binding), None))
}

case term: LambdaTerm =>
term.addBindings(context.bindings).right.map { t =>
t.norReduceComputation().map { resultTerm =>
Expand Down
15 changes: 13 additions & 2 deletions src/main/scala/me/rexim/morganey/interpreter/ReplContext.scala
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,29 @@ object ReplContext {
}

case class ReplContext(bindings: List[MorganeyBinding] = Nil) {
def contains(binding: MorganeyBinding): Boolean =
bindings.find(_.variable.name == binding.variable.name).isDefined

// TODO(#360): Improve time asymptotic of the add binding to REPL context operation
//
// Right now it's O(N), but it can be improved
/** Adds a binding to the context.
*
* If a binding for a given variable already exists in the context,
* the binding is replaced with the new one.
*/
def addBinding(binding: MorganeyBinding): ReplContext = {
ReplContext(binding :: bindings.filter(_.variable != binding.variable))
}

/** Adds several bindings to the context.
*
* If a binding for a given variable already exists in the context,
* the binding is replaced with the new one.
*/
def addBindings(newBindings: List[MorganeyBinding]): ReplContext =
newBindings.foldLeft(this)(_.addBinding(_))

def clear(): ReplContext = ReplContext(List())

def removeBindings(predicate: MorganeyBinding => Boolean): (ReplContext, List[MorganeyBinding])= {
val (satisfyF, notSatisfyF) = bindings.partition(predicate)
(ReplContext(satisfyF), notSatisfyF)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,23 @@ import scala.util._
class ReplContextSpecs extends FlatSpec with Matchers with TestTerms with MockitoSugar {
behavior of "REPL context"

it should "answer if it contains a specific binding" in {
val context = ReplContext(List(
binding(x, x),
binding(y, y)
))

context.contains(MorganeyBinding(y, x)) should be (true)
context.contains(MorganeyBinding(z, x)) should be (false)
}

it should "allow add bindings to it" in {
val binding = MorganeyBinding(x, I(x))
val context = ReplContext().addBinding(binding)
context.bindings should be (List(binding))
}

it should "keep the last unique binding" in {
def binding(v1: LambdaVar, v2: LambdaVar): MorganeyBinding =
MorganeyBinding(v1, I(v2))

val context = ReplContext(List(binding(z, z)))

context.addBinding(binding(x, x)).bindings should
Expand All @@ -36,12 +43,6 @@ class ReplContextSpecs extends FlatSpec with Matchers with TestTerms with Mockit
be (List(binding(x, z), binding(z, z)))
}

it should "clear bindings on reset command" in {
val bindings = List(MorganeyBinding(m"x", m"\\x.x"))
val context = ReplContext(bindings)
context.clear().bindings.isEmpty should be (true)
}

it should "partition all known bindings" in {
val zero = MorganeyBinding(m"zero", m"0")
val one = MorganeyBinding(m"one", m"1")
Expand Down