Ayer descubrí algo que me hizo flipar en colores: cómo crear tu propio DSL en Python. Y sólo necesitamos una clase del API estándar de Python.

En concreto la clase es “tokenize”, y es que resulta que python pone a nuestro alcance su propio párser del lenguaje… Vamos, que nos da ya el analizador léxico y sintáctico terminados. El semántico ya es cosa nuestra (o no, porque podemos utilizarlo también XD).

Pero veamos cómo funciona:

Voy a hacerme mi propio lenguaje. Va a ser uno simplón, como ejemplo. No sé… Algo parecido a Cucumber (mucho ojo, que he dicho “parecido”).

Veamos un ejemplo de lo que queremos procesar:

Given "bloque de inicialización" When "bloque de condiciones" Then "bloque de comprobaciones"

Y el código no nos va a costar más que unas pocas líneas:

import StringIO
import tokenize

codigo = 'Given "bloque de inicialización" When "bloque de condiciones" Then "bloque de comprobaciones"'
mode = None
for each in tokenize.generate_tokens(StringIO.StringIO(codigo).readline):
    if each[0] == tokenize.NAME:
        if each[1].capitalize() in ['Given', 'When', 'Then']:
            mode = each[1].capitalize()
            continue
        process(each[0], mode)

Para los seguidores del Clean Code: 3 niveles de anidación no me los suelo permitir, pero aquí es una excepción para haceros trabajar.

Veamos… ¿Qué ha pasado aquí? Bueno, lo primero comentaros qué es StringIO. Es un módulo muy curioso, que permite transformar Strings en objetos que se pueden tratar como ficheros. En Java, sería como crearnos nuestro StringReader. Si tuviésemos un fichero, no sería necesaria esta clase.

Ahora vamos con lo que importa :D

El bucle for va procesando cada uno de los elementos, teniendo en cuenta que en este caso sólo tenemos cadenas. Cada uno de los elementos consiste en una tupla, compuesta por:

  • Tipo de token. Un tokenize.NAME es una palabra reservada (variable, función, …); un tokenize.NL es un salto de línea; tokenize.NUMBER un número…
  • Contenido del token.
  • Posición de inicio. En concreto, una dupla con la línea y el carácter.
  • Posición de fin. Nuevamente, una dupla con la línea y el carácter.

¿Qué más podemos necesitar para procesar nuestro lenguaje? Pues estaría bien algo que me permita usar la máquina virtual de Python completa, de manera que los bloques puedan ser complejos… ¿Algo como eval?

Hasta ahora siempre he utilizado complicados sistemas de expresiones regulares para procesar mis DSL (bueno, hasta que decidí dejar de usar DSL XD), pero ayer descubrí la clase “tokenize” y me han encantado sus posibilidades XD.

En fin… Espero que os haya gustado el artículo XD

Más información

Para más información, la documentación de siempre de tokenize, aunque me temo que es bastante escueta.