Для настоящего языка лексеру свой путь - , как сказал Гасс . Но если полный язык настолько сложен, как ваш пример, вы можете использовать этот быстрый взлом:
irb> text = %{Children^10 Health "sanitation management"^5}
irb> text.scan(/(?:(\w+)|"((?:\\.|[^\\"])*)")(?:\^(\d+))?/).map do |word,phrase,boost|
{ :keywords => (word || phrase).downcase, :boost => (boost.nil? ? nil : boost.to_i) }
end
#=> [{:boost=>10, :keywords=>"children"}, {:boost=>nil, :keywords=>"health"}, {:boost=>5, :keywords=>"sanitation management"}]
Если вы пытаетесь выполнить синтаксический анализ обычного языка, то этого метода будет достаточно - хотя для того, чтобы сделать язык нерегулярным, не потребовалось бы намного больше сложностей.
Быстрый анализ регулярного выражения:
\w+
соответствует любым однократным ключевым словам
(?:\\.|[^\\"]])*
использует скобки без захвата ((?:...)
) для сопоставления содержимого экранированной строки в двойных кавычках - либо экранированного символа (\n
, \"
, \\
и т. Д.), Либо любого другого символ, который не является escape-символом или конечной кавычкой.
"((?:\\.|[^\\"]])*)"
захватывает только содержимое цитируемой ключевой фразы.
(?:(\w+)|"((?:\\.|[^\\"])*)")
соответствует любому ключевому слову - одному термину или фразе, включая отдельные термины в $1
и содержание фраз в $2
\d+
соответствует номеру.
\^(\d+)
захватывает число после каретки (^
). Поскольку это третий набор скобок, он будет заключен в $3
.
(?:\^(\d+))?
захватывает число после каретки, если оно там, в противном случае соответствует пустой строке.
String#scan(regex)
сопоставляет регулярное выражение со строкой столько раз, сколько возможно, выводя массив "совпадений". Если регулярное выражение содержит захватывающие парены, «match» - это массив захваченных элементов, поэтому $1
становится match[0]
, $2
становится match[1]
и т. Д. Любая скобка захвата, которая не сопоставляется с частью строка соответствует записи nil
в полученном "совпадении".
Затем #map
берет эти совпадения, использует некоторую магию блока, чтобы разбить каждый захваченный термин на разные переменные (мы могли бы сделать do |match| ; word,phrase,boost = *match
), а затем создает желаемые хэши. Точно один из word
или phrase
будет nil
, поскольку оба не могут быть сопоставлены с входом, поэтому (word || phrase)
вернет не-1055 * единицу, а #downcase
преобразует его во все в нижнем регистре. boost.to_i
преобразует строку в целое число, в то время как (boost.nil? ? nil : boost.to_i)
гарантирует, что nil
повышает значение nil
.