Захват экранированного многострочного синтаксиса с помощью Parslet и Ruby
Я хочу написать парсер с Parslet на Ruby, который понимает несколько простой синтаксис конфигурации:
alpha = one
beta = two\
three
gamma = four
С точки зрения синтаксического анализатора, обратная косая черта выходит за пределы новой строки, поэтому при разборе значения beta
является twothree
, Обратная косая черта находится в файле конфигурации (т. Е. Приведенный выше текст является прямым представлением - это не то, что вы поместили бы внутри строковых кавычек Ruby). В Ruby это можно представить как "alpha = one\nbeta = two\\\nthree\ngamma = four"
,
Моя текущая попытка подходит для однострочных настроек, но не справляется с многострочным подходом:
require "parslet"
class SettingParser < Parslet::Parser
rule(:term) { match("[a-zA-Z0-9_]").repeat(1) }
rule(:value) do
(match("[^\n]").repeat(1) >> match("[^\\\n]") >> str("\\\n")).repeat(0) >>
match("[^\n]").repeat(0)
end
rule(:space) { match("\\s").repeat(1) }
rule(:setting) do
term.as(:key) >> space.maybe >> str("=") >> space.maybe >>
value.as(:value)
end
rule(:input) { setting.repeat >> space.maybe }
root(:input)
end
Интересно, связана ли проблема с тем, как Парслет разбирает вещи? Собирает ли первая часть моего правила значения как можно больше символов, не заботясь о контексте последующих частей?
2 ответа
Ага. Правила Parslet будут с нетерпением поглощать, поэтому вам нужно сначала сопоставить escape-регистр, а затем, только если это не совпадает, использовать не экранированный символ.
require "parslet"
require "pp"
class SettingParser < Parslet::Parser
rule(:term) { match("[a-zA-Z0-9_]").repeat(1) }
rule(:char) { str("\\\n") | match("[^\n]").as(:keep) }
rule(:value) do
char.repeat(1)
end
rule(:space) { match("\\s").repeat(1) }
rule(:setting) do
term.as(:key) >> space.maybe >> str("=") >> space.maybe >>
value.as(:value) >> str("\n")
end
rule(:input) { setting.repeat.as(:settings) >> space.maybe }
root(:input)
end
s = SettingParser.new
tree = s.parse("alpha = one\nbeta = two\\\nthree\ngamma = four\n")
pp tree
Это создает следующее...
{:settings=>
[{:key=>"alpha"@0,
:value=>[{:keep=>"o"@8}, {:keep=>"n"@9}, {:keep=>"e"@10}]},
{:key=>"beta"@12,
:value=>
[{:keep=>"t"@19},
{:keep=>"w"@20},
{:keep=>"o"@21},
{:keep=>"t"@24},
{:keep=>"h"@25},
{:keep=>"r"@26},
{:keep=>"e"@27},
{:keep=>"e"@28}]},
{:key=>"gamma"@30,
:value=>
[{:keep=>"f"@38}, {:keep=>"o"@39}, {:keep=>"u"@40}, {:keep=>"r"@41}]}]}
Здесь я отмечаю символы, которые не являются экранированными, возвращается... поэтому я могу преобразовать их позже... но вы можете просто захватить всю строку, включая их, и вместо этого искать / заменять их в постобработке.
Во всяком случае... Теперь вы можете извлечь данные из дерева с помощью преобразования.
class SettingTransform < Parslet::Transform
rule(:keep => simple(:c)) {c}
rule({:key => simple(:k), :value => sequence(:v)}) { {k => v.join} }
rule(:settings => subtree(:s)) {s.each_with_object({}){|p,a| a[p.keys[0]] = p.values[0]}}
end
pp SettingTransform.new.apply(tree)
# => {"alpha"@0=>"one", "beta"@12=>"twothree", "gamma"@30=>"four"}
Возможно, вам придется добавить некоторую логику "Конец строки". В настоящее время я предполагаю, что ваш конфиг заканчивается на "\n". Вы можете обнаружить EOF с помощью 'any.absent' (или просто всегда добавлять '\n' в конец;)
Вам нужно запустить правило для setting
с пространством.
Следующий фрагмент работал для меня. я добавил pp
а также space?
для лучшего понимания
require "parslet"
require 'pp'
class SettingParser < Parslet::Parser
rule(:term) { match("[a-zA-Z0-9_]").repeat(1) >> space? }
rule(:value) do
(match("[^\n]").repeat(1) >> match("[^\\\n]") >> str("\\\n")).repeat(0) >>
match("[^\n]").repeat(0)
end
rule(:space) { match("\\s").repeat(1) }
rule(:space?) { space.maybe }
rule(:setting) do
space? >> term.as(:key) >> space? >> str("=") >> space? >>
value.as(:value)
end
rule(:input) { setting.repeat >> space.maybe }
root(:input)
end
str = %{
alpha = one
beta = two\
three
gamma = four
}
begin
pp SettingParser.new.parse(str, reporter: Parslet::ErrorReporter::Deepest.new)
rescue Parslet::ParseFailed => error
puts error.parse_failure_cause.ascii_tree
end
Выход
[{:key=>"alpha "@1, :value=>"one"@9},
{:key=>"beta "@13, :value=>"twothree"@20},
{:key=>"gamma "@29, :value=>"four"@37}]