Альтернативы Parslet Не разбирает всю строку

У меня есть следующие спецификации

  it "parses a document with only an expression" do
    puts parser.document.should parse("[b]Hello World[/b]")
  end
  it "parses a document with only text" do
    puts parser.document.should parse(" Hello World")
  end
  it "parses a document with both an expression and text" do
    puts parser.document.should parse("[b]Hello World[/b] Yes hello")
  end

Для следующего Parslet Parser

class Parser < Parslet::Parser

rule(:open_tag) do
  parslet = str('[')
  parslet = parslet >> (str(']').absent? >> match("[a-zA-Z]")).repeat(1).as(:open_tag_name)
  parslet = parslet >> str(']')
  parslet
end

rule(:close_tag) do
  parslet = str('[/')
  parslet = parslet >> (str(']').absent? >> match("[a-zA-Z]")).repeat(1).as(:close_tag_name)
  parslet = parslet >> str(']')
  parslet
end

rule(:text) { any.repeat(1).as(:text) }

rule(:expression) do
  # [b]Hello World[/b]
  # open tag, any text up until closing tag, closing tag
  open_tag.present?
  close_tag.present?
  parslet = open_tag >> match("[a-zA-Z\s?]").repeat(1).as(:enclosed_text) >> close_tag
  parslet
end

rule(:document) do
  expression | text
end

Первые два теста проходят просто отлично, и я вижу по putвывод их в командную строку, что атомы имеют правильный тип. Тем не менее, когда я пытаюсь проанализировать документ как с выражением, так и с простым текстом, он не может проанализировать простой текст, в результате чего возникает следующая ошибка

Parslet::UnconsumedInput: Don't know what to do with " Yes hello" at line 1 char 19.

Я думаю, что что-то упущено в определении правила: документа. То, что я хочу, это то, что потребляет любое количество выражений в последовательности и простой текст, и, хотя у меня есть правило, которое будет использовать каждый атом отдельно, использование их обоих в одной строке приводит к сбою.

2 ответа

Решение

Для тебя document Правило, которое вы хотите использовать repeat:

rule(:document) do
  (expression | text).repeat
end

Вам также необходимо изменить text править; в настоящее время, если он начинает совпадать, он будет потреблять все, включая любые [ это должно начать новый expression, Примерно так должно работать:

rule(:text) { match['^\['].repeat(1).as(:text) }

То, что вы искали, это что-то вроде этого...

require 'parslet'

class ExampleParser < Parslet::Parser
  rule(:open_tag) do
    str('[') >> 
      match["a-zA-Z"].repeat(1).as(:open_tag_name) >>
    str(']')
  end

Правило open_tag не должно исключать символ ']', так как совпадение допускает только буквы.

  rule(:close_tag) do
    str('[/') >> 
      match["a-zA-Z"].repeat(1).as(:close_tag_name) >>
    str(']')
  end

тоже самое

  rule(:text) do 
    (open_tag.absent? >> 
      close_tag.absent? >> 
        any).repeat(1).as(:text) 
  end

Если вы исключите открывающие и закрывающие теги здесь... вы знаете, что имеете дело только с текстом. Примечание: мне нравится эта техника использования "любого", как только вы исключили то, что вам не нужно, но помните об этом, если вы будете проводить рефакторинг позже, поскольку ваш список исключений может потребоваться увеличить. Примечание 2: Вы можете упростить это далее, как показано ниже.

  rule(:text) do 
    (str('[').absent? >> any).repeat(1).as(:text) 
  end

... если вам не нужны квадратные скобки в вашем тексте вообще.

  rule(:expression) do
    # [b]Hello World[/b]
    open_tag >> text.as(:enclosed_text) >> close_tag
  end

Это становится намного проще, так как текст не может содержать close_tag

  rule(:document) do
    (expression | text).repeat
  end

Я добавил в повторение, которое вы пропустили (как указал Мэтт)

end

require 'rspec'
require 'parslet/rig/rspec'

describe 'example' do
  let(:parser) { ExampleParser.new }
  context 'document' do
    it "parses a document with only an expression" do
      parser.document.should parse("[b]Hello World[/b]")
    end
    it "parses a document with only text" do
      parser.document.should parse(" Hello World")
    end
    it "parses a document with both an expression and text" do
      parser.document.should parse("[b]Hello World[/b] Yes hello")
    end
  end
end


RSpec::Core::Runner.run([])

Надеюсь, что это даст вам несколько советов по использованию Parslet.:)

Другие вопросы по тегам