Ruby parslet: разбор нескольких строк

Я ищу способ сочетания нескольких строк 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.

2 ответа

Решение

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

  • Псевдосимвольное совпадение $ не потребляет никаких реальных символов. Вам все еще нужно как-то использовать новые строки.

  • 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 правило соответствует каждой строке индивидуально, а не просто потребляет весь ввод.

Я обычно определяю правило для 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

Очевидно, что если вы хотите сделать с парсером больше, чем вы можете достичь с помощью String::split("\n"), вы замените line_body с чем-то полезным:)


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

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

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У них есть тела, которые что-то потребляют. Мы сейчас золотые.

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