DSL en Ruby. Generador de LaTeX.

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}

Deja un comentario