Петрушка двойное отрицание
При разборе кавычек и экранировании (ср. Почему 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