I have what might be a motivating reason to abandon the simple tail recursive approach and embrace the parser approach.
When I did some timing analysis, I discovered that for a very large benchmark example, my simple parser takes about 16 seconds to run, with most of the time being spent in converting the content of the file to a List[Char]
. Thereafter the parseListContent
function works very fast. I then re-wrote the function to operate on a BufferedIterator[Char]
, and the time drops to 2.5 seconds. Unfortunately can’t use the came code to parse a List[Char]
and BufferedIterator[Char]
because BufferedIterator
neither supports pattern matching, nor dropWhile
and friends.
So here is what the buffered version of the parser looks like now. Considerably uglier than before. Worse is that I have two versions of the function, one for BufferedIterator[Char]
and one for List[Char]
, the latter being used in several test cases which call parseListContent(string.toList)
. I could of course eliminate the latter and test using parseBufferedContent(string.toIterator.buffered)
, but perhaps biting the bullet and massaging the code from @sangamon, to fit is quickly become a more elegant alternative.
def parseBufferedContent(it:BufferedIterator[Char], clause: Clause, clauses: CNF): CNF = {
def dropWhile(p: Char => Boolean): BufferedIterator[Char] = {
while (it.hasNext && p(it.head)) {
it.next()
}
it
}
def takeWhile(p: Char => Boolean): List[Char] = {
def loop(buf: List[Char]):List[Char] = {
if (it.hasNext && p(it.head))
loop(it.next() :: buf)
else
buf.reverse
}
loop(Nil)
}
if (it.hasNext)
it.head match {
case 'p' |
'c' =>
parseBufferedContent(dropWhile(!endOfLine.contains(_)), clause, clauses)
case c if digits.contains(c) =>
val num = takeWhile(digits.contains)
val number = num.mkString.toInt
if (number == 0)
parseBufferedContent(it, Nil, clause :: clauses)
else
parseBufferedContent(it, number :: clause, clauses)
case c if whiteSpace.contains(c) =>
parseBufferedContent(dropWhile(whiteSpace.contains), clause, clauses)
}
else if (clause.nonEmpty)
parseBufferedContent(it, Nil, clause :: clauses)
else
clauses
}