Skip to content

Commit 7c98108

Browse files
Merge pull request #8 from kevinvandervlist/minify-parentheses
Minimize the number of parenthesis with Stringification
2 parents dc5abb2 + 3d942d8 commit 7c98108

File tree

4 files changed

+57
-11
lines changed

4 files changed

+57
-11
lines changed

interpreter/src/main/scala/nl/soqua/lcpi/interpreter/transformation/Stringify.scala

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,36 @@ import scala.language.implicitConversions
77
object Stringify {
88
implicit def expression2String(e: Expression): String = Stringify(e)
99

10-
def apply(e: Expression): String = e match {
11-
case v: Variable => v.symbol
12-
case Application(t, s) => s"(${apply(t)} ${apply(s)})"
13-
case LambdaAbstraction(x, a) => s"${apply(x)}.${apply(a)})"
10+
private sealed trait ExpressionType
11+
12+
private case object Function extends ExpressionType
13+
14+
private case object Argument extends ExpressionType
15+
16+
// Mark the parent as nothing special
17+
private case object Nothing extends ExpressionType
18+
19+
def apply(e: Expression): String = display(Nothing, e)("")
20+
21+
private def display(parent: ExpressionType, e: Expression): String => String = (parent, e) match {
22+
case (Function, LambdaAbstraction(_, _)) => parenthesize(display(Nothing, e))
23+
case (_, LambdaAbstraction(x, a)) => lambda compose display(Nothing, x) compose dot compose display(Nothing, a)
24+
case (Function, Application(_: Variable, _: Variable)) => display(Nothing, e)
25+
case (Function, Application(_, _)) => parenthesize(display(Nothing, e))
26+
case (Argument, Application(_, _)) => parenthesize(display(Nothing, e))
27+
case (_, Application(t, s)) => display(Function, t) compose space compose display(Argument, s)
28+
case (_, v: Variable) => show(v.symbol)
1429
}
30+
31+
private def show(s: String): String => String = (suffix: String) =>
32+
s"$s$suffix"
33+
34+
private def space = show(" ")
35+
36+
private def lambda = show("λ")
37+
38+
private def dot = show(".")
39+
40+
private def parenthesize(f: String => String): String => String =
41+
show("(") compose f compose show(")")
1542
}

interpreter/src/test/scala/nl/soqua/lcpi/interpreter/transformation/IsomorphismSpec.scala

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package nl.soqua.lcpi.interpreter.transformation
22

3+
import nl.soqua.lcpi.ast.lambda.Expression
34
import nl.soqua.lcpi.ast.lambda.Expression.A
45
import nl.soqua.lcpi.interpreter.transformation.Stringify._
56
import nl.soqua.lcpi.parser.lambda.LambdaCalcParser
@@ -17,14 +18,25 @@ class IsomorphismSpec extends WordSpec with Matchers {
1718
case `deBruijn` => plainIsomorphism
1819
}
1920

21+
private def compareWithClue(expected: Expression, actual: Expression): Assertion = {
22+
withClue(
23+
s"""
24+
|expected: ${Stringify(expected)}
25+
|actual: ${Stringify(actual)}
26+
|
27+
""".stripMargin) {
28+
expected shouldBe actual
29+
}
30+
}
31+
2032
def plainIsomorphism: Assertion = {
2133
val result = for {
2234
p1 <- LambdaCalcParser(expr)
2335
p2 <- LambdaCalcParser(p1)
2436
} yield (p1, p2)
2537
result match {
2638
case Left(ex) => fail(s"Expression $expr failed: $ex")
27-
case Right((e1, e2)) => e1 shouldBe e2
39+
case Right((e1, e2)) => compareWithClue(e1, e2)
2840
}
2941
}
3042

@@ -35,7 +47,7 @@ class IsomorphismSpec extends WordSpec with Matchers {
3547
} yield (p1, p2)
3648
result match {
3749
case Left(ex) => fail(s"Expression $expr failed: $ex")
38-
case Right((e1, e2)) => e1 shouldBe e2
50+
case Right((e1, e2)) => compareWithClue(e1, e2)
3951
}
4052
}
4153
}

interpreter/src/test/scala/nl/soqua/lcpi/interpreter/transformation/StringifySpec.scala

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,20 @@ class StringifySpec extends WordSpec with Matchers {
77

88
val x = V("x")
99
val y = V("y")
10+
val z = V("z")
1011

1112
"Stringification of λ-expressions" should {
1213
"correctly stringify t s" in {
13-
Stringify(A(x, y)) shouldBe "(x y)"
14+
Stringify(A(x, y)) shouldBe "x y"
1415
}
1516
"correctly stringify t s where t is a λx.x and s is y" in {
16-
Stringify(A(λ(x, x), y)) shouldBe "((λx.x) y)"
17+
Stringify(A(λ(x, x), y)) shouldBe "(λx.x) y"
18+
}
19+
"left-recursive with application" in {
20+
Stringify(A(A(x, y), z)) shouldBe "x y z"
21+
}
22+
"S-expression" in {
23+
Stringify(λ(x, λ(y, λ(z, A(A(x, z), A(y, z)))))) shouldBe "λx.λy.λz.x z (y z)"
1724
}
1825
}
1926
}

repl/src/test/scala/nl/soqua/lcpi/repl/monad/ReplCompilerSpec.scala

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,8 @@ class ReplCompilerSpec extends ReplMonadTester with WordSpecLike with Matchers {
6060
implicit val state = emptyState.copy(traceMode = Enabled)
6161
val e = Application(Variable("I"), Variable("x"))
6262
ReplMonad.expression(e) >> List(
63-
"S => ((λx.x) x)",
64-
"α => ((λx.x) x)",
63+
"S => (λx.x) x",
64+
"α => (λx.x) x",
6565
"β => x",
6666
"η => x",
6767
"x"
@@ -100,7 +100,7 @@ class ReplCompilerSpec extends ReplMonadTester with WordSpecLike with Matchers {
100100
}
101101
"render an expression in De Bruijn Index notation" in {
102102
val x = V("x")
103-
ReplMonad.deBruijnIndex(λ(x, x)) >> "(λ.1)"
103+
ReplMonad.deBruijnIndex(λ(x, x)) >> "λ.1"
104104
}
105105
"not be able to render a failure as de bruijn index" in {
106106
val x = V("x")

0 commit comments

Comments
 (0)