Para un lenguaje real, un lexer es el camino a seguir - like Guss said. Pero si el lenguaje completo solamente es tan complicado como su ejemplo, puede utilizar este truco rápido:
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"}]
Si usted está tratando de analizar un lenguaje regular, entonces será suficiente este método - a pesar de que no se llevaría a muchos más complicaciones para hacer que el idioma no sea regular.
Un rápido resumen de la expresión regular:
\w+
coincide con las palabras clave de un solo plazo
(?:\\.|[^\\"]])*
utiliza paréntesis no captura ((?:...)
) para que coincida con el contenido de una cadena entre comillas dobles escapado - ya sea un escaparon símbolo (\n
, \"
, \\
, etc.) o cualquier carácter que no sea un símbolo de escape o una cita final.
"((?:\\.|[^\\"]])*)"
capta solo el contenido de una frase de palabra clave entre comillas.
(?:(\w+)|"((?:\\.|[^\\"])*)")
coincide con cualquier palabra clave - un término o frase, capturando términos individuales en $1
y frase contenido en $2
\d+
coincide con un número.
\^(\d+)
captura un número siguiendo un símbolo de intercalación (^
). Como este es el tercer conjunto de paréntesis de captura, se incluirá en $3
.
(?:\^(\d+))?
captura un número siguiendo un símbolo de intercalación si está allí, coincide con la cadena vacía de lo contrario.
String#scan(regex)
coincide con la expresión regular contra la cadena tantas veces como sea posible, produciendo una matriz de "coincidencias". Si la expresión regular contiene capturas de parens, una "coincidencia" es una matriz de elementos capturados, por lo que $1
se convierte en match[0]
, $2
se convierte en match[1]
, etc.Cualquier paréntesis de captura que no coincida con una parte de la cadena se correlaciona con una entrada nil
en la "coincidencia" resultante.
El #map
toma estas coincidencias, utiliza un poco de magia de bloque para dividir cada término capturado en diferentes variables (podríamos haber hecho do |match| ; word,phrase,boost = *match
), y luego crea los valores hash deseados. Exactamente uno de word
o phrase
será nil
, ya que ambos no pueden coincidir con la entrada, por lo que (word || phrase)
devolverá el que no sea nil
, y #downcase
lo convertirá a minúsculas. boost.to_i
convertirá una cadena en un entero, mientras que (boost.nil? ? nil : boost.to_i)
asegurará que nil
aumente nil
.
Si lo estuviera usando en código, probablemente usaría // x y agregaría comentarios en su lugar. – rampion
Si fuera posible, les daría dos votos favorables. 1 para el enfoque pragmático y uno para la expresión regular bien descrita. – slothbear