Skip to content

Commit

Permalink
Better explanation of trampolining
Browse files Browse the repository at this point in the history
Trampolining is reification of calls, and exploits the duality between
calls and returns
  • Loading branch information
noelwelsh committed Nov 16, 2023
1 parent 60d8c8e commit 62047be
Show file tree
Hide file tree
Showing 9 changed files with 412 additions and 86 deletions.
10 changes: 5 additions & 5 deletions src/main/scala/regexp/Cps.scala
Original file line number Diff line number Diff line change
Expand Up @@ -15,26 +15,26 @@ object Cps {

def matches(input: String): Boolean = {
// Define a type alias so we can easily write continuations
type Cont = Option[Int] => Option[Int]
type Continuation = Option[Int] => Option[Int]

def loop(regexp: Regexp, idx: Int, cont: Cont): Option[Int] =
def loop(regexp: Regexp, idx: Int, cont: Continuation): Option[Int] =
regexp match {
case Append(left, right) =>
val k: Cont = _ match {
val k: Continuation = _ match {
case None => cont(None)
case Some(i) => loop(right, i, cont)
}
loop(left, idx, k)

case OrElse(first, second) =>
val k: Cont = _ match {
val k: Continuation = _ match {
case None => loop(second, idx, cont)
case some => cont(some)
}
loop(first, idx, k)

case Repeat(source) =>
val k: Cont =
val k: Continuation =
_ match {
case None => cont(Some(idx))
case Some(i) => loop(regexp, i, cont)
Expand Down
68 changes: 20 additions & 48 deletions src/main/scala/regexp/ReifiedCps.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,37 +2,9 @@ package regexp

object ReifiedCps {

// Append:
// - free variables: right: Regexp, cont: Continuation
// val k: Cont = _ match {
// case None => cont(None)
// case Some(i) => loop(right, i, cont)
// }
//
// OrElse
// - free variables: second: Regexp, idx: Int, cont: Continuation
// val k: Cont = _ match {
// case None => loop(second, idx, cont)
// case some => cont(some)
// }
//
// Repeat
// - free variables: regexp: Regexp, idx: Int, cont: Continuation
// val k: Cont =
// _ match {
// case None => cont(Some(idx))
// case Some(i) => loop(regexp, i, cont)
// }
//
// Apply
// - free variables: none
//
// Empty
// - free variables: none

enum Next {
enum Call {
case Loop(regexp: Regexp, index: Int, continuation: Continuation)
case Apply(index: Option[Int], continuation: Continuation)
case Continue(index: Option[Int], continuation: Continuation)
case Done(index: Option[Int])
}

Expand All @@ -42,28 +14,28 @@ object ReifiedCps {
case RepeatK(regexp: Regexp, index: Int, next: Continuation)
case DoneK

def apply(idx: Option[Int]) =
def apply(idx: Option[Int]): Call =
this match {
case AppendK(right, next) =>
idx match {
case None => Next.Apply(None, next)
case Some(i) => Next.Loop(right, i, next)
case None => Call.Continue(None, next)
case Some(i) => Call.Loop(right, i, next)
}

case OrElseK(second, index, next) =>
idx match {
case None => Next.Loop(second, index, next)
case some => Next.Apply(some, next)
case None => Call.Loop(second, index, next)
case some => Call.Continue(some, next)
}

case RepeatK(regexp, index, next) =>
idx match {
case None => Next.Apply(Some(index), next)
case Some(i) => Next.Loop(regexp, i, next)
case None => Call.Continue(Some(index), next)
case Some(i) => Call.Loop(regexp, i, next)
}

case DoneK =>
Next.Done(idx)
Call.Done(idx)
}

}
Expand All @@ -87,37 +59,37 @@ object ReifiedCps {
regexp: Regexp,
idx: Int,
cont: Continuation
): Next =
): Call =
regexp match {
case Append(left, right) =>
val k: Continuation = AppendK(right, cont)
Next.Loop(left, idx, k)
Call.Loop(left, idx, k)

case OrElse(first, second) =>
val k: Continuation = OrElseK(second, idx, cont)
Next.Loop(first, idx, k)
Call.Loop(first, idx, k)

case Repeat(source) =>
val k: Continuation = RepeatK(regexp, idx, cont)
Next.Loop(source, idx, k)
Call.Loop(source, idx, k)

case Apply(string) =>
Next.Apply(
Call.Continue(
Option.when(input.startsWith(string, idx))(idx + string.size),
cont
)

case Empty =>
Next.Apply(None, cont)
Call.Continue(None, cont)
}

def trampoline(next: Next): Option[Int] =
def trampoline(next: Call): Option[Int] =
next match {
case Next.Loop(regexp, index, continuation) =>
case Call.Loop(regexp, index, continuation) =>
trampoline(loop(regexp, index, continuation))
case Next.Apply(index, continuation) =>
case Call.Continue(index, continuation) =>
trampoline(continuation(index))
case Next.Done(index) => index
case Call.Done(index) => index
}

// Check we matched the entire input
Expand Down
87 changes: 87 additions & 0 deletions src/main/scala/regexp/Trampolined.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package regexp

object Trampolined {
// Define a type alias so we can easily write continuations
type Continuation = Option[Int] => Call

enum Call {
case Loop(regexp: Regexp, index: Int, continuation: Continuation)
case Continue(index: Option[Int], continuation: Continuation)
case Done(index: Option[Int])
}

enum Regexp extends regexp.Regexp[Regexp] {
def ++(that: Regexp): Regexp =
Append(this, that)

def orElse(that: Regexp): Regexp =
OrElse(this, that)

def repeat: Regexp =
Repeat(this)

def `*` : Regexp = this.repeat

def matches(input: String): Boolean = {
def loop(regexp: Regexp, idx: Int, cont: Continuation): Call =
regexp match {
case Append(left, right) =>
val k: Continuation = _ match {
case None => Call.Continue(None, cont)
case Some(i) => Call.Loop(right, i, cont)
}
Call.Loop(left, idx, k)

case OrElse(first, second) =>
val k: Continuation = _ match {
case None => Call.Loop(second, idx, cont)
case some => Call.Continue(some, cont)
}
Call.Loop(first, idx, k)

case Repeat(source) =>
val k: Continuation =
_ match {
case None => Call.Continue(Some(idx), cont)
case Some(i) => Call.Loop(regexp, i, cont)
}
Call.Loop(source, idx, k)

case Apply(string) =>
Call.Continue(
Option.when(input.startsWith(string, idx))(idx + string.size),
cont
)

case Empty =>
Call.Continue(None, cont)
}

def trampoline(next: Call): Option[Int] =
next match {
case Call.Loop(regexp, index, continuation) =>
trampoline(loop(regexp, index, continuation))
case Call.Continue(index, continuation) =>
trampoline(continuation(index))
case Call.Done(index) => index
}

// Check we matched the entire input
trampoline(loop(this, 0, opt => Call.Done(opt)))
.map(idx => idx == input.size)
.getOrElse(false)
}

case Append(left: Regexp, right: Regexp)
case OrElse(first: Regexp, second: Regexp)
case Repeat(source: Regexp)
case Apply(string: String)
case Empty
}
object Regexp extends regexp.RegexpConstructors[Regexp] {
val empty: Regexp = Empty

def apply(string: String): Regexp =
Apply(string)
}
}
Loading

0 comments on commit 62047be

Please sign in to comment.