Darshan Patel
Darshan Patel

Reputation: 3256

Parslet Not parsing whole string

For the following Parslet Parser

require 'parslet'
require 'parslet/convenience'

class Lines < Parslet::Parser
        rule(:open_tag) {str('[')}
    rule(:close_tag) {str(']')}
    rule(:data) {str('name') | str('name_id') }
    rule(:text) { open_tag >> data >> close_tag }
    root :text
end

begin
    p Lines.new.parse("[name_id]")    <---- It throws error
rescue Parslet::ParseFailed => failure
    Lines.new.parse_with_debug("[name_id]")
end

It gives following error

Failed to match sequence (OPEN_TAG NAME CLOSE_TAG) at line 1 char 6.
`- Expected "]", but got "_" at line 1 char 6.

If I change data rule from

rule(:data) {str('name') | str('name_id') }

to

rule(:data) {str('name_id') | str('name') }

then it works as expected.

But, I am generating rules dynamically based on user input. So this solution wont work for me.

Thanks in advance.

Upvotes: 2

Views: 162

Answers (2)

Nigel Thorne
Nigel Thorne

Reputation: 21548

As mudasobwa says... name will match, so it doesn't get a chance to try name_id. You either need to change the order so name_id is tried first, or you need to make name fail to match. How you do this depends on your grammar.

require 'parslet'
require 'parslet/convenience'

class Lines < Parslet::Parser
    rule(:open_tag) {str('[')}
    rule(:close_tag) {str(']')}
    rule(:data) { str('name]') | str('name_id]')  } # <-- you can't let a matcher match unless it really is a match, so here it works because name] fails for name_id

    rule(:text) { open_tag >> data  }
    root :text
end

begin
    p Lines.new.parse("[name_id]")   
rescue Parslet::ParseFailed => failure
    Lines.new.parse_with_debug("[name_id]")
end

I think I would instead let the parser break the text up for me, then inspect the structure afterwards.. e.g.

require 'parslet'
require 'parslet/convenience'

class Lines < Parslet::Parser
    rule(:open_tag) {str('[')}
    rule(:close_tag) {str(']')}
    rule(:data) { (close_tag.absnt? >> any).repeat(1).as(:data)  }
    rule(:text) { open_tag >> data >> close_tag }
    root :text
end

begin
    p Lines.new.parse("[name_id]")   # =>  {:data=>"name_id"@1}
rescue Parslet::ParseFailed => failure
    Lines.new.parse_with_debug("[name_id]")
end

Parslet is intended to work in two phases.. the first converts your doc to a tree. The second converts your tree to a data representation that you want.

In this case the first parse pulls out the structure. The seconds pass could check that "name_id" is valid. etc.

Upvotes: 1

Aleksei Matiushkin
Aleksei Matiushkin

Reputation: 121010

Rule :data is being built and then checked in the order the items were provided. To enforce longer matchers occur before the shorter ones, one might simply sort them:

data = %w|name name_id|

data = data.sort { |a, b| b <=> a }

rule(:data) { data.map(&method(:str)).reduce(:|) }

Upvotes: 2

Related Questions