2013-07-18 4 views
5

Я ищу способ сопоставить несколько строк Parslet. код выглядит следующим образом:Ruby parslet: синтаксический анализ нескольких строк

rule(:line) { (match('$').absent? >> any).repeat >> match('$') } 
rule(:lines) { line.repeat } 

Однако lines будет всегда в конечном итоге в бесконечном цикле, который из-за match('$') будет бесконечно повторять, чтобы соответствовать конец строки.

Возможно ли совместить несколько строк, которые могут быть пустыми?

irb(main)> lines.parse($stdin.read) 
This 
is 

a 
multiline 

string^D 

должно хорошо сочетаться. Я что-то упускаю? Я также пробовал (match('$').absent? >> any.maybe).repeat(1) >> match('$'), но это не соответствует пустым строкам.

С уважением,
Danyel.

ответ

3

Я думаю, что у вас есть два, связанные, проблемы с согласованием:

  • Матч псевдохарактера $ не потребляют никаких реальных персонажей. Вам все равно нужно как-то поглощать новые строки.

  • Parslet вносит свой вклад каким-либо образом, делая $ в тех местах, которые вы не ожидаете. Лучший результат, который я мог получить, используя $, соответствовал каждому отдельному персонажу.

Очень безопасно использовать \n как символ конца строки. Я получил следующие работы (я несколько начинающего с Parslet сам, поэтому извинения, если это может быть понятнее):

require 'parslet' 

class Lines < Parslet::Parser 
    rule(:text) { match("[^\n]") } 
    rule(:line) { (text.repeat(0) >> match("\n")) | text.repeat(1) } 
    rule(:lines) { line.as(:line).repeat } 
    root :lines 
end 

s = "This 
is 

a 
multiline 
string" 

p Lines.new.parse(s) 

Правило линии является сложным из-за необходимости соответствовать пустые строки и а возможная окончательная строка без \n.

Вам не нужно использовать синтаксис .as(:line) - я просто добавил его, чтобы четко показать, что правило :line соответствует каждой строке индивидуально, а не просто потребляет весь вход.

+0

Это похоже на приятное решение. Мое обходное решение заключалось в том, чтобы работать с '\ n', а также добавить новую строку для входящей строки, чтобы предотвратить сбой соответствия в конце. Однако это выглядит чище. Благодаря! – Danyel

6

Обычно я определяю правило для end_of_line. Это основано на трюке в http://kschiess.github.io/parslet/tricks.html для соответствия end_of_file.

class MyParser < Parslet::Parser 
    rule(:cr)   { str("\n") } 
    rule(:eol?)  { any.absent? | cr } 
    rule(:line_body) { (eol?.absent? >> any).repeat(1) } 
    rule(:line)  { cr | line_body >> eol? } 
    rule(:lines?)  { line.repeat (0)} 
    root(:lines?) 
end 

puts MyParser.new.parse(""" this is a line 
so is this 

that was too 
This ends""").inspect 

Очевидно, что если вы хотите сделать больше с анализатором, чем вы можете достичь с Последовательностью :: разделенного («\ п») вы замените line_body с чем-то полезным :)


I быстро перешел к ответу на этот вопрос и удалил его. Я просто объясню, что я сделал, и покажу вам, как избежать ошибок такого рода.

Вот мой первый ответ.

rule(:eol) { str('\n') | any.absent? } 
rule(:line) { (eol.absent? >> any).repeat >> eol } 
rule(:lines) { line.as(:line).repeat } 

я не следовать моим обычным правилам:

  • Всегда делают число повторов явного
  • Любое правило, которое может соответствовать нулевой строки длины, должны иметь название оканчивающиеся на «?»

Так позволяет применять эти ...

rule(:eol?) { str('\n') | any.absent? } 
# as the second option consumes nothing 

rule(:line?) { (eol.absent? >> any).repeat(0) >> eol? } 
# repeat(0) can consume nothing 

rule(:lines?) { line.as(:line?).repeat(0) } 
# We have a problem! We have a rule that can consume nothing inside a `repeat`! 

Вот видите, почему мы получаем бесконечный цикл. Когда вход потребляется, вы получаете только end of file, который соответствует eol? и, следовательно, line? (так как тело линии может быть пустым). Будучи внутри lines 'repeat, он сохраняет совпадение, не потребляя ничего и петли навсегда.

Нам нужно изменить правило линии, чтобы оно всегда что-то потребляло.

rule(:cr)   { str('\n') } 
rule(:eol?)  { cr | any.absent? } 
rule(:line_body) { (eol.absent? >> any).repeat(1) } 
rule(:line)  { cr | line_body >> eol? } 
rule(:lines?)  { line.as(:line).repeat(0) } 

Теперь line должен соответствовать что-то, либо в cr (пустых строк), или, по меньшей мере, один символ с последующим дополнительным eol?. Все repeat имеют тела, которые что-то потребляют. Мы теперь золотые.

+0

Это превращается в бесконечный цикл для меня. – Danyel

+0

oops. да, я исправлю это. –

+0

Бесконечные петли происходят, когда у вас есть правила, которые могут соответствовать, не потребляя никаких входных данных. Здесь 'line' совпадает с пустой строкой, за которой следует' '' '' '' '' '' '' '' '' '' '' '' '' s '', которая также не потребляет ничего, поэтому он может продолжать сопоставлять. –

Смежные вопросы