Skip to content

Commit

Permalink
Added support for preferred terminal parsers in disjunctive disambigu…
Browse files Browse the repository at this point in the history
…ation
  • Loading branch information
djspiewak committed Nov 6, 2012
1 parent 2b4d2a8 commit 5dc58de
Show file tree
Hide file tree
Showing 2 changed files with 68 additions and 18 deletions.
75 changes: 57 additions & 18 deletions src/main/scala/com/codecommit/gll/Parsers.scala
Expand Up @@ -204,6 +204,8 @@ trait Parsers {
set flatMap { x => x }
}

def isPreferred = false

/**
* @return The FIRST set for this parser, or the empty set
* if the production goes to \epsilon.
Expand Down Expand Up @@ -232,6 +234,8 @@ trait Parsers {
}

def filter(f: R => Boolean): Parser[R] = new NonTerminalParser[R] {
override def isPreferred = self.isPreferred

def computeFirst(seen: Set[Parser[Any]]) = self.computeFirst(seen + this)

def chain(t: Trampoline, in: LineStream)(f2: Result[R] => Unit) {
Expand Down Expand Up @@ -386,6 +390,8 @@ trait Parsers {
}

override def \(not: TerminalParser[Any]) = new TerminalParser[R] {
override def isPreferred = self.isPreferred

def computeFirst(s: Set[Parser[Any]]) = self.computeFirst(s)

def parse(in: LineStream) = self.parse(in) match {
Expand All @@ -403,6 +409,8 @@ trait Parsers {
}

def mapWithTail[R2](f: (LineStream, R) => R2): Parser[R2] = new MappedParser[R, R2](self, f) with TerminalParser[R2] {
override def isPreferred = self.isPreferred

def parse(in: LineStream) = {
val newTail = handleWhitespace(in)
self.parse(newTail) match {
Expand All @@ -411,6 +419,8 @@ trait Parsers {
}
}
}

def preferred: TerminalParser[R] = PreferredParser(this)
}

trait NonTerminalParser[+R] extends Parser[R] { self =>
Expand Down Expand Up @@ -498,6 +508,8 @@ trait Parsers {
* alternatives (for the sake of left-recursion).
*/
private[gll] abstract class ThunkParser[+A](private val self: Parser[A]) extends NonTerminalParser[A] {
override def isPreferred = self.isPreferred

def computeFirst(s: Set[Parser[Any]]) = self.computeFirst(s)

override def toString = self.toString
Expand All @@ -510,6 +522,14 @@ trait Parsers {
override def hashCode = self.hashCode
}

private[gll] case class PreferredParser[+R](delegate: TerminalParser[R]) extends TerminalParser[R] {
override def isPreferred = true

def computeFirst(s: Set[Parser[Any]]) = delegate.computeFirst(s)

protected[gll] def parse(in: LineStream) = delegate.parse(in)
}

//////////////////////////////////////////////////////////////////////////////

case class LiteralParser(str: String) extends TerminalParser[String] {
Expand Down Expand Up @@ -673,29 +693,48 @@ trait Parsers {
var predicted = false
val results = mutable.Set[Result[A]]() // merge results

for {
p <- gather

// [(S = {}) -> (FIRST = U)] /\ [~(S = {}) -> (S[0] \in FIRST)]
if !in.isEmpty || p.first == UniversalCharSet
if in.isEmpty || p.first.contains(in.head) // lookahead
} {
predicted = true
t.add(p, in) { res =>
val preferred = gather filter { _.isPreferred }
val prefResults = preferred flatMap { _(in) }

val prefSuccess = prefResults exists {
case _: Success[_] => true
case _ => false
}

if (prefSuccess) {
prefResults foreach { res =>
if (!results.contains(res)) {
tracef("Reduced: %s *=> %s%n", this, res)

tracef("Reduced preferred: %s *=> %s%n", this, res)
f(res)
results += res
}
}
}

if (!predicted) {
if (in.isEmpty)
f(Failure(UnexpectedEndOfStream(None), in))
else
f(Failure(UnexpectedChars(in.head.toString), in))
} else {
for {
p <- gather

// [(S = {}) -> (FIRST = U)] /\ [~(S = {}) -> (S[0] \in FIRST)]
if !in.isEmpty || p.first == UniversalCharSet
if in.isEmpty || p.first.contains(in.head) // lookahead
} {
predicted = true
t.add(p, in) { res =>
if (!results.contains(res)) {
tracef("Reduced: %s *=> %s%n", this, res)

f(res)
results += res
}
}
}

if (!predicted) {
if (in.isEmpty)
f(Failure(UnexpectedEndOfStream(None), in))
else
f(Failure(UnexpectedChars(in.head.toString), in))
}
}
}
}
Expand Down
11 changes: 11 additions & 0 deletions src/test/scala/DisjunctionSpecs.scala
Expand Up @@ -391,5 +391,16 @@ object DisjunctionSpecs extends Specification

p.first mustEqual Set('a')
}

"prefer an appropriately annotated terminal parser in case of ambiguity" in {
lazy val p = (
("ab" preferred) ^^^ 3
| "a" ~ "b" ^^^ 2
)

p("ab") must beLike {
case Stream(Success(3, LineStream())) => ok
}
}
}
}

0 comments on commit 5dc58de

Please sign in to comment.