Петрушка двойное отрицание

При разборе кавычек и экранировании (ср. Почему Parslet (в Ruby) возвращает пустой массив при анализе пустого строкового литерала?) Я наткнулся на странность в Parslet: (escape_char.absent? >> str('"')).absent? >> any Кажется, что Парслет на самом деле разрешает двойное отрицание и ожидает, что там будет спасательный персонаж.

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

class Parser < Parslet::Parser
  root(:quote)

  rule :quote do
    quote >> text >> quote
  end

  rule :text do
    (quote.absent? >> any).repeat
  end

  rule :quote do
    escape_char.absent? >> str('"')
  end

  rule :escape_char do
    str('\\')
  end
end

describe Parser do
  it 'should parse text in quotes' do
    is_expected.to parse('"hello"')
  end

  it 'should parse text in quotes with escaped quote' do
    is_expected.to parse('"foo\"bar"')
  end

  it 'should parse text in quotes with trailing escaped quote' do
    is_expected.to parse('"text\""')
  end
end

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

1 ответ

Parslet создает парсеры из меньших парсеров... в этом прелесть PEG.

"Отсутствует" - это парсер, который принимает парсер. Он пытается сопоставить входной поток с упакованным парсером. Если свернутый парсер совпадает, Absent сообщает "нет совпадения". Если внутренний синтаксический анализатор не совпадает, анализатор "Отсутствует" проходит.

Итак, парсер, который вы упомянули:(escape_char.absent? >> str('"')).absent? >> any

будет соответствовать одному символу, но только когда (escape_char.absent? >> str('"')) не соответствует тому же персонажу.

(escape_char.absent? >> str('"')) будет не соответствовать, только если первый символ является escape-символом или не является кавычкой.

Проверяя это, оказывается, что это правда.

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

class Parser < Parslet::Parser
  root(:x)

  rule :x do 
    (escape_char.absent? >> str('"')).absent? >> any 
  end

  rule :escape_char do
    str('\\')
  end
end

  begin
    Parser.new.parse('a') # passes
    Parser.new.parse('b') # passes
    Parser.new.parse('\\') # passes
    Parser.new.parse('"') # << this one fails
    puts "pass"
  rescue Parslet::ParseFailed => error
    puts error.cause.ascii_tree
  end
Другие вопросы по тегам