kschiess / parslet

A small PEG based parser library. See the Hacking page in the Wiki as well.
kschiess.github.com/parslet
MIT License
805 stars 95 forks source link

NoMethodError: undefined method `charpos' for "+":String #112

Closed ravinggenius closed 10 years ago

ravinggenius commented 10 years ago

I'm getting a NoMethodError when calling Parslet::Slice#offset. The error only happens in 1.6.1, not 1.6.0. I'm using Ruby 2.1.2, but the issue also happens in Ruby 2.1.1.

kschiess commented 10 years ago

Can you post an example where this happens?

ravinggenius commented 10 years ago

Sure. https://github.com/rip-lang/rip/blob/master/lib/rip/utilities/location.rb#L17

If you are inclined, you can checkout master and run the tests. You should have plenty of errors when upgrading to 1.6.1 :). By the way I think the change that causes the issue is https://github.com/kschiess/parslet/compare/1.6.0...1.6.1#diff-a78d1f31f8b81cd6a311f6a9be948008R40.

kschiess commented 10 years ago

If you care for a fix, you should give me a more specific test case to work from. My time is limited.

ravinggenius commented 10 years ago

I'm sorry. I didn't mean to be unhelpful in the previous post.

I followed one of the errors in my test. parse_tree is Parslet's parse tree before any transforms are applied. Notice that :integer and :sign are both instances of Parslet::Slice. Also notice that the :sign slice is not displaying correctly.

[15] pry(#<Rip::Compiler::Parser>):1> parse_tree[:module].first
=> {:start=>{:sign=>#<Parslet::Slice:0x3ff5bddc3d70>, :integer=>"1"@0}, :location=>".."@1, :exclusivity=>nil, :end=>{:sign=>#<Parslet::Slice:0x3ff5bddbf70c>, :integer=>"3"@3}}
[16] pry(#<Rip::Compiler::Parser>):1> parse_tree[:module].first[:start]
=> {:sign=>#<Parslet::Slice:0x3ff5bdcb7634>, :integer=>"1"@0}
[17] pry(#<Rip::Compiler::Parser>):1> parse_tree[:module].first[:start][:sign]
=> #<Parslet::Slice:0x3ff5bdc04c3c>
[18] pry(#<Rip::Compiler::Parser>):1> parse_tree[:module].first[:start][:sign].offset
NoMethodError: undefined method `charpos' for "+":String
from /usr/local/opt/rbenv/versions/2.1.2/lib/ruby/gems/2.1.0/gems/parslet-1.6.1/lib/parslet/slice.rb:40:in `offset'
[19] pry(#<Rip::Compiler::Parser>):1> parse_tree[:module].first[:start][:integer]
=> "1"@0
[20] pry(#<Rip::Compiler::Parser>):1> parse_tree[:module].first[:start][:integer].offset
=> 0
[21] pry(#<Rip::Compiler::Parser>):1> parse_tree[:module].first[:start][:integer].class
=> Parslet::Slice
[22] pry(#<Rip::Compiler::Parser>):1> parse_tree[:module].first[:start][:sign].class
=> Parslet::Slice
[23] pry(#<Rip::Compiler::Parser>):1> parse_tree[:module].first[:start][:sign].inspect
NoMethodError: undefined method `charpos' for "+":String
from /usr/local/opt/rbenv/versions/2.1.2/lib/ruby/gems/2.1.0/gems/parslet-1.6.1/lib/parslet/slice.rb:40:in `offset'
[24] pry(#<Rip::Compiler::Parser>):1> parse_tree[:module].first[:start][:sign].to_s
=> 0
ravinggenius commented 10 years ago

After parsing answer = 42, I get an assignment node where the right hand side looks like {:integer=>"42"@9}. Before passing the tree to the transform class, I run it through a normalization class which, among other things, adds a sign to numbers. After normalization the integer node above looks like {:sign=>"+"@9, :integer=>"42"@9}. To do this, I have to manually create a Parslet::Slice.

In parslet 1.6.0, I could call Parslet::Slice.new("+", 9, <line-cache-from-the-integer-node>). In parslet 1.6.1 this no longer works, as Parslet::Slice#initialize has a different signature. Is Parslet::Slice part of parslet's public API, or is it an implementation detail that I shouldn't use directly?

kschiess commented 10 years ago
)`. In parslet 1.6.1 this no longer works, as`Parslet::Slice#initialize`has a different signature. Is `Parslet::Slice` part of parslet's public API, or is it an implementation detail that I shouldn't use directly?

Yeah, Parslet::Slice#initialize is as private as it gets. Not that I will change things around a lot, just that I will want to be able to.

k

ravinggenius commented 10 years ago

Got it. Instead of using a private class, would it be better for me to just have multiple transform rules (one for {:integer=>"42"@9} and another for {:sign=>"+"@9, :integer=>"42"@9}). Can transform rules call each other?

kschiess commented 10 years ago

No. I recommend code duplication.

ravinggenius commented 10 years ago

Okay. Thanks for the help.