diff --git a/src/main/scala/LambdaInterpreter.scala b/src/main/scala/LambdaInterpreter.scala index 7c2ad54..b59ce7f 100644 --- a/src/main/scala/LambdaInterpreter.scala +++ b/src/main/scala/LambdaInterpreter.scala @@ -1,4 +1,3 @@ - import scala.io.StdIn.readLine object LambdaInterpreter { @@ -36,10 +35,10 @@ object LambdaInterpreter { val reducedExp = Parser.buildWithAST(reducedAst) println(reducedExp) case FreeVariables => - val freeVariables = Reducer.freeVariables(ast) - println(freeVariables) + // Only print free variables when mode is set to FreeVariables + Reducer.printFreeVariables(ast) } } main() } -} \ No newline at end of file +} diff --git a/src/main/scala/Reductor.scala b/src/main/scala/Reducer.scala similarity index 61% rename from src/main/scala/Reductor.scala rename to src/main/scala/Reducer.scala index 9b07d79..72e5623 100644 --- a/src/main/scala/Reductor.scala +++ b/src/main/scala/Reducer.scala @@ -1,12 +1,25 @@ object Reducer { + + // ANSI escape codes for colors (you can disable them if not supported) + val RED = "\u001b[31m" + val GREEN = "\u001b[32m" + val YELLOW = "\u001b[33m" + val BLUE = "\u001b[34m" + val RESET = "\u001b[0m" + + // Alpha conversion - renaming bound variables to avoid collision private def alphaConversion(ast: AST, oldName: String, newName: String): AST = ast match { - case Variable(name) if name == oldName => Variable(newName) + case Variable(name) if name == oldName => + println(s"${YELLOW}Alpha conversion: renaming $oldName to $newName${RESET}") + Variable(newName) case Variable(name) => Variable(name) case Abstraction(Variable(param), body) if param == oldName => + println(s"${YELLOW}Alpha conversion: renaming $param to $newName${RESET}") Abstraction(Variable(newName), alphaConversion(body, oldName, newName)) case Abstraction(Variable(param), body) => if (freeVariables(body).contains(newName)) { val newParam = newName + "*" + println(s"${YELLOW}Alpha conversion: renaming $newName to avoid collision, new name: $newParam${RESET}") Abstraction(Variable(newParam), alphaConversion(body, oldName, newParam)) } else { Abstraction(Variable(param), alphaConversion(body, oldName, newName)) @@ -15,6 +28,7 @@ object Reducer { Application(alphaConversion(func, oldName, newName), alphaConversion(arg, oldName, newName)) } + // Substitution - replaces the variable with the argument in the body private def substitute(ast: AST, param: String, replacement: AST): AST = ast match { case Variable(name) if name == param => replacement case Variable(name) => Variable(name) @@ -22,6 +36,7 @@ object Reducer { case Abstraction(Variable(name), body) => if (freeVariables(replacement).contains(name)) { val newName = name + "*" + println(s"${YELLOW}Alpha conversion needed during substitution: renaming $name to $newName${RESET}") val renamedBody = alphaConversion(body, name, newName) Abstraction(Variable(newName), substitute(renamedBody, param, replacement)) } else { @@ -31,20 +46,29 @@ object Reducer { Application(substitute(func, param, replacement), substitute(arg, param, replacement)) } + // Beta reduction private def betaReduce(ast: AST): AST = ast match { case Application(Abstraction(Variable(name), body), arg) => + println(s"${GREEN}Beta reduction: applying argument ${Parser.buildWithAST(arg)} to function λ$name.${Parser.buildWithAST(body)}${RESET}") if (freeVariables(arg).contains(name)) { val newName = name + "*" val renamedBody = alphaConversion(body, name, newName) - substitute(renamedBody, newName, arg) + println(s"${GREEN}After alpha conversion: ${Parser.buildWithAST(renamedBody)}${RESET}") + val result = substitute(renamedBody, newName, arg) + println(s"${GREEN}After beta reduction: ${Parser.buildWithAST(result)}${RESET}") + result } else { - substitute(body, name, arg) + val result = substitute(body, name, arg) + println(s"${GREEN}After beta reduction: ${Parser.buildWithAST(result)}${RESET}") + result } case _ => ast } + // Call-by-name reduction def callByName(ast: AST): AST = ast match { case Application(Abstraction(Variable(param), body), arg) => + println(s"${RED}Reducing (λ$param.${Parser.buildWithAST(body)} ${Parser.buildWithAST(arg)}) by Call-by-Name${RESET}") callByName(betaReduce(Application(Abstraction(Variable(param), body), arg))) case Application(func, arg) => val reducedFunc = callByName(func) @@ -58,8 +82,10 @@ object Reducer { case Variable(name) => Variable(name) } + // Call-by-value reduction def callByValue(ast: AST): AST = ast match { case Application(func, arg) => + println(s"${RED}Reducing (Application ${Parser.buildWithAST(func)} ${Parser.buildWithAST(arg)}) by Call-by-Value${RESET}") val reducedFunc = callByValue(func) val reducedArg = callByValue(arg) reducedFunc match { @@ -72,13 +98,25 @@ object Reducer { case Variable(name) => Variable(name) } + // Finding free variables in the expression def freeVariables(ast: AST): Set[String] = ast match { case Variable(name) => Set(name) case Abstraction(Variable(param), body) => freeVariables(body) - param case Application(func, arg) => freeVariables(func) ++ freeVariables(arg) } + + // Print free variables of the expression (only called when mode is set to FreeVariables) + def printFreeVariables(ast: AST): Unit = { + val freeVars = freeVariables(ast) + if (freeVars.nonEmpty) { + println(s"${BLUE}Free variables: ${freeVars.mkString(", ")}${RESET}") + } else { + println(s"${BLUE}No free variables found${RESET}") + } + } } +// Function to generate unique names to avoid collisions private def generateUniqueName(base: String, usedNames: Set[String]): String = { var newName = base + "*" var counter = 1 @@ -87,4 +125,4 @@ private def generateUniqueName(base: String, usedNames: Set[String]): String = { counter += 1 } newName -} \ No newline at end of file +}