Lo cierto es que, Ruby es un lenguaje que cuanto más aprendes, más te sorprende. Lo último que he visto sobre el fantástico lenguaje creado por Matz, es la facilidad con que se pueden fabricar «interpretes» de lenguajes específicos, o lo que también se conoce como DSL por ser las anglosajonas siglas de Domain-specific Languages.
Como se puede esperar, estos lenguajes se centran en resolver problemas muy específicos, en lugar de, como C o Java, estar enfocados a un ámbito genérico. Ejemplos de estos DSL pueden ser lenguajes como SQL, HTML, VHDL o YACC. Cada uno de ellos existen para resolver un problema, y difícilmente se podrá utilizar para otra cosa. No me imagino hacer una web con YACC o una consulta a una base de datos con VHDL :-S
Crear estos lenguajes suele ser una tarea compleja, y en muchas ocasiones se suele usar otro lenguaje mas genérico. De hecho, hacer un «interprete» de un lenguaje usando C++ mediante macros y templates es un entretenimiento que no esta accesible a cualquiera 😉
Sin embargo, cuando Ruby se nos cruza en el camino, todo parece resolverse como por arte de magia.
Antes de meternos de lleno en el objetivo del post, que es una especie de sencillo interprete de LaTeX (si, el lenguaje que describe como es un documento 😀 ), vamos a ver que es lo que Ruby hace por que esto sea posible.
Sin duda, el principal elemento que añade la mayor parte de la magia es una sola sentencia, dicha sentencia es: «alias method_missing tag«.
Esa sentencia hará que cualquier invocación de un método no existente de una clase, se «reenviará» al método que le decimos a continuación, en nuestro caso, «tag».
Si nos paramos a pensar la potencia de este mecanismo es tremenda, podemos hacer que nuestra clase «conteste» potencialmente a cualquier método. Sin duda le añade un factor dinámico al código que es difícil de encontrar en otros lenguajes, y sobre todo, con esa elegancia.
Otro elemento muy importante de la creación de DSL es el método instance_eval de cualquier clase de Ruby. Dicho método ejecutará lo que le pasemos como parámetro en el contexto del objeto sobre el que se invoca. La verdad es que es mas difícil explicarlo que verlo en acción, así que seguro que queda mas claro en este ejemplo:
class Prueba def metodo puts "Hola Mundo" end end p = Prueba.new p.instance_eval "metodo" => "Hola Mundo"
Como veis, lo que hace instance_eval es ejecutar el parámetro, como si fuera código que se ejecuta sobre la variable p.
El último elemento clave son los bloques en Ruby. Sin duda uno de mis elementos favoritos del lenguaje, todo método en Ruby, puede llevar asociado un bloque, y dicho bloque se puede capturar como parámetro de un metodo. Si combinamos los bloques con el instance_eval, podemos tener lo siguiente.
class Prueba def metodo1 puts "Método 1" end def metodo2 puts "Método 2" end def self.generate(&block) Prueba.new.instance_eval(&block) end end Prueba.generate do metodo1 metodo2 end => Método 1 => Método 2
Mola, ¿no? 🙂
Pues bien, básicamente con estos elementos, podemos empezar nuestro mini-generador de LaTeX.
El objetivo será que usando una sintaxis mas próxima a Ruby, seamos capaces de crear un documento de LaTeX sin usar casi ninguna {} y sin preocuparnos de que ponemos un \end cuando debemos.
Para ello, debemos crear una clase que sea capaz de que ejecutar el siguiente código en un interprete de Ruby tenga como salida un documento con formato LaTeX
LaTeX.generate(STDOUT) do documentclass "article" usepackage "amsmath" title "\\LaTeX" date "" start "document" do title "hola" "Hola, hoy son las #{Time.now} y \\sqrt{2} = #{Math.sqrt(2)}" end end
Como veis, ademas de tener una sintaxis mucho mas «limpia» ganamos la incorporación del propio código Ruby, pudiendo usar cosas como «Time.now» o operaciones como la raíz cuadrada.
Aunque parezca mentira, usando los mecanismos que hemos puesto anteriormente, la clase necesaria seria algo como:
class LaTeX def initialize(out) @out = out end def tag(tagname, name=nil) if tagname == :start tagname = :begin end @out << "\\#{tagname}" if name @out << "\{#{name}\}" end @out << "\n" if block_given? content = yield if content @out << content.to_s << "\n" end @out << "\\end" if name @out << "\{#{name}\}" end @out << "\n" end nil end alias method_missing tag def self.generate(out, &block) LaTeX.new(out).instance_eval(&block) end end
Unicamente, con ese fragmento sencillo de código, seremos capaces de que construir un documento LaTeX sea mucho mas sencillo que lo habitual, pues la ejecución tendrá como resultado
\documentclass{article} \usepackage{amsmath} \title{\LaTeX} \date{} \begin{document} \title{hola} Hola, hoy son las Sat Oct 02 23:49:35 +0200 2010 y \sqrt{2} = 1.4142135623731 \end{document}