kschiess/parslet — Issue #60: compatibility with mathn library
Author: hynkle
Created: 2011-12-13 22:01:43+00:00
State: open

I'm using another gem ([ruby-units](https://github.com/olbrich/ruby-units)) that happens to require the [mathn](http://www.ruby-doc.org/stdlib-1.9.3/libdoc/mathn/rdoc/index.html) library from Ruby's standard library. One of the effects of this library is to override some very basic operations, such as division on Integer. When mathn is required, `99/100` doesn't return the Integer `0` as one would normally expect, but rather the Rational `(99/100)`. This is somewhat contrary to general expectations as to how Ruby should behave.

I ran into this issue while constructing my parslet grammar, where it would on some expressions get caught in an infinite loop. I assumed at first that I just had some left-recursion that I wasn't aware of, but digging into parslet's source I found that it had actually finished its parsing attempt and was getting caught in a loop while trying to generate the message for the parsing exception. I eventually traced it down to [RangeSearch's lbound method](https://github.com/kschiess/parslet/blob/master/lib/parslet/source/line_cache.rb#L66-87). The algorithm expects the division by 2 to eventually result in `mid` being equal to 0, but with mathn required, `mid` instead just becomes an increasingly infinitesimal fraction each iteration, never actually reaching 0.

The "problem" is easy enough to fix, just by adding a `mid = mid.floor` after doing the division.

However, as you might guess by my scare quotes, I don't really think this is parslet's problem, and I'm not quite sure if a fix for such an arcane issue (seriously, who uses mathn?) belongs in parslet. I certainly know my first thought upon coming across `x = y/2; x = x.floor` in a codebase would be that the programmer obviously didn't know Ruby.

Thoughts? If you don't feel like having `mid = mid.floor` in there would just be code noise, I'd be happy to submit a pull request with a test.

Here's a close-to-minimal case that causes the infinite loop:

```
require 'parslet'
include Parslet

def attempt_parse
possible_whitespace = match('\s').repeat

cephalopod =
str('octopus') |
str('squid')

parenthesized_cephalopod =
str('(') >>
possible_whitespace >>
cephalopod >>
possible_whitespace >>
str(')')

parser =
possible_whitespace >>
parenthesized_cephalopod >>
possible_whitespace

parser.parse %{(\nsqeed)\n}
end

attempt_parse and puts 'it terminates before we require mathn'
require 'mathn'
attempt_parse # but it doesn't terminate after requiring mathn
```

(Note that if you change `sqeed` to `squeed`, it does successfully terminate with mathn because the dividend just doesn't happen to end up as an odd number.)