Ответ 1
Для реального языка, lexer способ пойти - как сказал Guss. Но если полный язык настолько сложный, как ваш пример, вы можете использовать этот быстрый хак:
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
,\"
,\\
и т.д.), либо любой символ, который не является символом перехода или конечной цитатой. -
"((?:\\.|[^\\"]])*)"
захватывает только содержимое цитируемой ключевой фразы. -
(?:(\w+)|"((?:\\.|[^\\"])*)")
соответствует любому ключевому слову - одному термину или фразе, захватывая отдельные термины в$1
и содержимое фразы в$2
-
\d+
соответствует числу. -
\^(\d+)
фиксирует число после каретки (^
). Так как это третий набор скобок для скобок, он будет передан в$3
. -
(?:\^(\d+))?
фиксирует число, следующее за кареткой, если оно там, в противном случае соответствует пустой строке.
String#scan(regex)
соответствует регулярному выражению по строке столько раз, сколько возможно, выводя массив "совпадений". Если регулярное выражение содержит захват parens, "совпадение" представляет собой массив элементов, захваченных - поэтому $1
становится match[0]
, $2
становится match[1]
и т.д. Любая скользящая скобка, которая не сопоставляется с частью строковые карты в запись nil
в результате "соответствия".
Затем #map
принимает эти совпадения, использует некоторую магию блока, чтобы разбить каждый захваченный термин на разные переменные (мы могли бы сделать do |match| ; word,phrase,boost = *match
), а затем создаем ваши желаемые хэши. Точно один из word
или phrase
будет nil
, так как оба не могут быть сопоставлены с входом, поэтому (word || phrase)
вернет не nil
один, а #downcase
преобразует его ко всем в нижнем регистре. boost.to_i
преобразует строку в целое число, а (boost.nil? ? nil : boost.to_i)
гарантирует, что nil
увеличивает время останова nil
.