2010-02-08 16 views
5

Estoy interesado en crear una DSL en Ruby para utilizarla en el análisis de actualizaciones de microblog. Específicamente, pensé que podría traducir texto a una cadena de Ruby de la misma manera que la gema Rails permite "4.days.ago". Ya tengo código expresiones regulares que se traducirá el textoCreación de una DSL de "lenguaje semi-natural" en Ruby

@USER_A: give X points to @USER_B for accomplishing some task 
@USER_B: take Y points from @USER_A for not giving me enough points 

en algo así como

Scorekeeper.new.give(x).to("USER_B").for("accomplishing some task").giver("USER_A") 
Scorekeeper.new.take(x).from("USER_A").for("not giving me enough points").giver("USER_B") 

Es aceptable para mí para formalizar la sintaxis de las actualizaciones de manera que sólo se proporciona texto estandarizado y analiza, lo que me para procesar inteligentemente las actualizaciones. Por lo tanto, parece que es más una cuestión de cómo implementar la clase DSL. Tengo el siguiente clase stub (eliminado toda la comprobación de errores y reemplazado algunos con comentarios para minimizar pegar):

class Scorekeeper 

    attr_accessor :score, :user, :reason, :sender 

    def give(num) 
    # Can 'give 4' or can 'give a -5'; ensure 'to' called 
    self.score = num 
    self 
    end 

    def take(num) 
    # ensure negative and 'from' called 
    self.score = num < 0 ? num : num * -1 
    self 
    end 

    def plus 
    self.score > 0 
    end 

    def to (str) 
    self.user = str 
    self 
    end 

    def from(str) 
    self.user = str 
    self 
    end 

    def for(str) 
    self.reason = str 
    self 
    end 

    def giver(str) 
    self.sender = str 
    self 
    end 

    def command 
    str = plus ? "giving @#{user} #{score} points" : "taking #{score * -1} points from @#{user}" 
    "@#{sender} is #{str} for #{reason}" 
    end 

end 

ejecutando los siguientes comandos:

t = eval('Scorekeeper.new.take(4).from("USER_A").for("not giving me enough points").giver("USER_B")') 
p t.command 
p t.inspect 

produce los resultados esperados:

"@USER_B is taking 4 points from @USER_A for not giving me enough points" 
"#<Scorekeeper:0x100152010 @reason=\"not giving me enough points\", @user=\"USER_A\", @score=4, @sender=\"USER_B\">" 

Así que mi pregunta es principalmente, ¿Estoy haciendo algo para dispararme en el pie al construir sobre esta implementación? ¿Alguien tiene algún ejemplo de mejora en la clase de DSL o alguna advertencia para mí?

BTW, para obtener la cadena eval, estoy usando sub/gsub y regex, pensé que era la manera más fácil, pero podría estar equivocado.

+0

debo añadir que Hice esto con métodos encadenados solo porque no pude descifrar cómo enviarlo usando la cadena vacía como eval ("Scorekeeper da 4 a USER_A por hacer algo") porque no sabía cómo conseguir el espacio incluido- cadenas en la lista de argumentos del método. Las ideas sobre eso son muy bienvenidas. – JohnMetta

Respuesta

0

Basándome en la respuesta de @David_James, he encontrado una solución solo para la expresión regex, ya que no estoy usando la DSL en ningún otro lado para generar puntajes y solo estoy analizando los puntos de los usuarios. Tengo dos patrones que voy a utilizar para buscar:

SEARCH_STRING = "@Scorekeeper give a healthy 4 to the great @USER_A for doing something 
really cool.Then give the friendly @USER_B a healthy five points for working on this. 
Then take seven points from the jerk @USER_C." 

PATTERN_A = /\b(give|take)[\s\w]*([+-]?[0-9]|one|two|three|four|five|six|seven|eight|nine|ten)[\s\w]*\b(to|from)[\s\w]*@([a-zA-Z0-9_]*)\b/i 

PATTERN_B = /\bgive[\s\w]*@([a-zA-Z0-9_]*)\b[\s\w]*([+-]?[0-9]|one|two|three|four|five|six|seven|eight|nine|ten)/i 

SEARCH_STRING.scan(PATTERN_A) # => [["give", "4", "to", "USER_A"], 
           #  ["take", "seven", "from", "USER_C"]] 
SEARCH_STRING.scan(PATTERN_B) # => [["USER_B", "five"]] 

La expresión regular podría ser limpiado un poco, pero esto me permite tener sintaxis que permite a unos pocos adjetivos divertidas al mismo tiempo tirando de la información básica usando ambas sintaxis "nombre-> puntos" y "puntos-> nombre". No me permite entender el motivo, pero es tan complejo que, por ahora, voy a almacenar toda la actualización, ya que toda la actualización se relacionará con el contexto de cada puntaje de todos modos salvo en casos atípicos. Obtener el nombre de usuario "dador" se puede hacer en otros lugares también.

He escrito a description of these expressions así, con la esperanza de que otras personas podrían encontrar que útil (y para que pueda volver a ella y recordar lo que se larga cadena de gobbledygook significa :)

5

¿Lo entiendo bien? ¿Quiere tomar una cadena de un usuario y provocar un comportamiento?

De acuerdo con los dos ejemplos que enumeró, probablemente pueda usar expresiones regulares.

Por ejemplo, para analizar este ejemplo:

@USER_A: give X points to @USER_B for accomplishing some task 

Con Ruby:

input = "@abe: give 2 points to @bob for writing clean code" 
PATTERN = /^@(.+?): give ([0-9]+) points to @(.+?) for (.+?)$/ 
input =~ PATTERN 
user_a = $~[1] # => "abe" 
x  = $~[2] # => "2" 
user_b = $~[3] # => "bob" 
why = $~[4] # => "writing clean code" 

Pero si hay una mayor complejidad, en algún momento, puede que le resulte más fácil y más fácil de mantener para utilizar una analizador real Si quieres un analizador sintáctico que funcione bien con Ruby, recomiendo Treetop: http://treetop.rubyforge.org/

La idea de tomar una cadena y convertirla en código para ser evacuada me pone nervioso. Usar eval es un gran riesgo y se debe evitar si es posible. Hay otras maneras de lograr su objetivo. Estaré encantado de dar algunas ideas si lo desea.

Una pregunta acerca de la DSL que sugiere: ¿la va a usar de forma nativa en otra parte de su aplicación? ¿O simplemente planea usarlo como parte del proceso para convertir la cadena en el comportamiento que deseas? No estoy seguro de qué es lo mejor sin saber más, pero es posible que no necesite la DSL si solo está analizando las cadenas.

+0

Estoy tratando de encontrar un método robusto para analizar de forma inteligente una cadena como "Scorekeeper, dar puntos Y a @USER_A y tomar puntos X de @USER_B por ser un imbécil". A partir de esto, cadena, tengo que sacar "+ Y-> USER_A" y "-X-> USER_B para 'ser un idiota'" Usar Regex se estaba volviendo menosweildy (es decir, le daría USER_A X puntos b/c Analicé para "usuario + puntos" antes de "puntos + usuario" - No soy un gurú regex, obviamente). Simplemente pensé que explorar la opción de DSL podría ser acertado, pero como en realidad no la estoy usando en otra parte, quizás la expresión regular sólida sea una mejor opción. Me encantaría algunas ideas. Gracias. – JohnMetta

+0

Acabo de publicar un ejemplo de expresiones regulares en la respuesta anterior. (Intenté pegarlo aquí, pero el formato no fue muy bueno.) –

+0

¡Gracias! Esa es una cantidad de expresiones regulares mucho más limpia que la que tengo. Una pregunta que estaba tratando de responder al crear un DSL es cómo analizar de manera inteligente ese y el último ejemplo de comentario. Por ejemplo, permitir que los puntos lleguen después de que el usuario "dé @bob 4 puntos" también debería ser válido, "para" es opcional, etc. Pero lo que ahora tengo coincide con bits más pequeños y las afirmaciones completas coincidentes tal como lo has escrito podrían permitirme hacer coincidir múltiples patrones posibles mejor sin, digamos, anotar el número equivocado de puntos en la persona equivocada. Gracias. – JohnMetta

1

Esto hace eco de algunos de mis pensamientos sobre un proyecto tangencial (un MOO de texto antiguo).

No estoy convencido de que un analizador de estilo de compilador sea la mejor manera para que el programa se ocupe de los vaguaries del texto en inglés.Mis pensamientos actuales me hacen dividir la comprensión del inglés en objetos separados, por lo que una caja entiende "caja abierta" pero no "presionar el botón", etc. y luego hacer que los objetos usen algún tipo de DSL para llamar al código centralizado que en realidad hace que las cosas sucedan

No estoy seguro de que haya llegado al punto en el que comprenda cómo la DSL realmente lo ayudará. Quizás primero deba ver cómo el texto en inglés se convierte en DSL. No estoy diciendo que no necesites una DSL; es posible que tengas razón.

En cuanto a sugerencias sobre cómo hacer eso? Bueno, creo que si fuera tú estaría buscando verbos específicos. Cada verbo "sabría" qué tipo de cosas debería esperar del texto a su alrededor. Por lo tanto, en su ejemplo "a" y "desde" se esperaría que un usuario lo siga inmediatamente.

Esto no es especialmente divergente del código que ha publicado aquí, IMO.

Es posible que obtenga un cierto kilometraje de mirar las respuestas a my question. Un comentarista me señaló el patrón de intérprete, que encontré especialmente esclarecedor: hay un buen ejemplo de Ruby here.

+0

Geez. Tan pronto como leí "patrón de intérprete" pensé "¡Bueno, duh!" Ahora me siento tonto. No estoy seguro de por qué no exploré esa solución más simple en primer lugar. Incluso estoy leyendo "Patrones de diseño en Ruby" en este momento. Qué divertido. Esa es probablemente la ruta que debería tomar si la expresión regular no es lo suficientemente sólida. – JohnMetta

+0

Te hace más inteligente que yo, nunca había oído hablar de eso. – Shadowfirebird

Cuestiones relacionadas