Захват экранированного многострочного синтаксиса с помощью 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}]
Другие вопросы по тегам