La lunotipia. Tipografía digital, TeX y cafeína

Ejecutar Common-Lisp en LaTeX: el paquete lisp-on-tex

He estado probando estos primeros días soleados de octubre el paquete experimental lisp-on-tex, escrito por Hakuta Shizuya. Como su nombre anuncia (y los característicos guiones también), se trata de una biblioteca destinada a evaluar código Lisp a través del proceso de compilación de LaTeX. Algo que, a primera vista, me puso los dientes largos, tan devoto y apasionado como soy por este bello y potente lenguaje de programación, en gran parte en su dialecto Emacs-Lisp. Con Lisp siempre es bueno recordar estas palabras de Stallman:

The most powerful programming language is Lisp. If you don’t know Lisp (or its variant, Scheme), you don’t know what it means for a programming language to be powerful and elegant. Once you learn Lisp, you will see what is lacking in most other languages.

Unlike most languages today, which are focused on defining specialized data types, Lisp provides a few data types which are general. Instead of defining specific types, you build structures from these types. Thus, rather than offering a way to define a list-of-this type and a list-of-that type, Lisp has one type of lists which can hold any sort of data.

Where other languages allow you to define a function to search a list-of-this, and sometimes a way to define a generic list-search function that you can instantiate for list-of-this, Lisp makes it easy to write a function that will search any list — and provides a range of such functions.

In addition, functions and expressions in Lisp are represented as data in a way that makes it easy to operate on them.

When you start a Lisp system, it enters a read-eval-print loop. Most other languages have nothing comparable to `read’, nothing comparable to `eval’, and nothing comparable to `print’. What gaping deficiencies!

— Richard Stallman, How I do my computing, en su página personal.

Con todas esas premisas, cualquier caballo de Troya lispiano, aunque sea un potrillo, no puede sino ser bienvenido entre el engranaje de nuestro cajista binario TeX y su director editorial binario LaTeX. Porque (y es lo que aquí nos ocupa) las posibilidades tipográficas que se desprenderían de ello son más que interesantes. Echemos un vistazo a las primeras impresiones.

Primeros juegos

Antes de empezar, conviene insistir que este paquete se encuentra aún en desarrollo (más bien lento, al parecer, pues su última actualización es de 2015), y esto trae como consecuencia cosas como que la documentación es inexistente. Hay que conformarse con un escueto y espartano Readme, aunque suficiente para sacar algunas conclusiones de entrada. A saber:

  1. El código Lisp ha de encerrarse en el comando \lispinterp{...},
  2. Los símbolos de Lisp han de llevar siempre una barra invertida, como los comandos de TeX. Por ejemplo, un concat se convierte aquí en \concat,
  3. Las cadenas de texto se ponen con comillas simples, no dobles.

Añadido a eso, la plena funcionalidad del paquete depende de la infraestructura de LaTeX3, la futura versión de LaTeX, también en desarrollo. Por suerte, podemos usar gran parte del kernel de LaTeX3 sobre LaTeX2ε si cargamos en nuestro preámbulo el paquete expl3. Y es aquí donde vino el primer tropezón leve. Un módulo de lisp-on-tex que nos hacía mucha gracia probar, lisp-mod-l3regex, para trabajar las expresiones regulares sobre Lisp, requería del paquete de LaTeX3 l3regex.sty. Pero no encontraba el paquete y devolvía error. La causa estaba en que l3regex.sty se encontraba plegado dentro del propio expl3. Se solucionó fácilmente añadiendo la línea \expandafter\def\csname [email protected]\endcsname{}, y con eso ya teníamos nuestro preámbulo listo para el recreo:

\documentclass{article}
\usepackage{fontspec}
\setmainfont{Linux Libertine O}
\usepackage{expl3}
\expandafter\def\csname [email protected]\endcsname{}
\usepackage{lisp-on-tex}
\usepackage{lisp-mod-l3regex}

Las primeras pruebas que hice, como comenté antes, estaban relacionadas con expresiones regulares. El paquete viene ya con algunas funciones definidas. Por ejemplo, \regReplaceAll es lo más parecido a lo que en Elisp sería un replace-regex-in-string, con los mismos tres argumentos (el regex a reemplazar, el regex que reemplaza y la cadena donde reemplazar). Con esta función, ya he probado a definir algunos comandos sencillos para LaTeX. Como éste, que convertiría todo carácter «a» en «xxx»:

\def\prueba#1{%
\lispinterp{
  (\texprint
  (\regReplaceAll 'a' 'xxx' '#1'))
}}

Podemos probarlo si escribimos algo así como:

\prueba{la casa de arriba}

Con esa misma estructura, este otro comando nos pondría en negrita todos los números romanos. Ojo, para que no salga el literal \textbf{...} sino su resultado, obsérvese la sintaxis. Por otra parte, \1 para el grupo, es lo esperable:

\def\pruebabis#1{%
\lispinterp{
  (\texprint
  (\regReplaceAll '(\b[IVXLCDM]+\b)' '\c{textbf}\cB\{\1\cE\}' '#1'))
}}

Lo probamos con:

\pruebabis{El siglo XXI y el volumen VI}

Y este tercer comando nos pondría en negrita cualquier texto en griego, ya sea poli- o monotónico:

\def\pruebatris#1{%
\lispinterp{
  (\texprint
  (\regReplaceAll
'([\x{1F00}-\x{1FFE}\x{0370}-\x{03FF}]+)'
'\c{textbf}\cB\{\1\cE\}'
'#1'))
}}

Y podemos probarlo con:

\pruebatris{Lorem ipsum dolor sit amet, consectetuer adipiscing
elit. Donec hendrerit tempor tellus. Δαρείου καὶ Παρυσάτιδος
γίγνονται παῖδες δύο. Donec pretium posuere tellus. Proin quam
nisl, tincidunt et, mattis eget, convallis nec, purus.}

Un comando menos trivial. ¡Y hasta un entorno!

Hace no mucho, escribí para Emacs una sencilla función para evitar (como casi siempre) un trabajo latoso. El caso es que andaba trabajando en la composición de un extenso libro, cuyo autor tuvo a bien diseminar a través del texto infinidad de pasajes en griego: palabras unas veces; otras, frases de una o dos líneas, más o menos. Naturalmente, lo ideal sería incluir todos esos segmentos en un comando \foreignlanguage{greek}{...} de Babel, para asegurar el correcto guionado del griego antiguo, pero se puede morir cualquiera si tiene que ir haciéndolo a mano y uno por uno. Primero, se me ocurrió intentar escribir algún script en Perl, hasta que caí en la cuenta de que con Elisp sería más sencillo, así que, tras algunas pruebas ensayo/error di con una regex que funciona y que no devuelve ningún falso positivo. La tuve que segmentar en una expresión concat porque es bastante larga y entorpece la depuración del código. Pero traducida viene a decir: «localiza cadenas de texto dentro de unos parámetros determinados, y que incluyen los caracteres de los rangos Unicode ’griego básico’ (\u0370-\u03FF) y ’griego extendido’ (\u1F00-\u1FFE), signos de puntuación y espacios». Y la función quedó, finalmente, así:

(defun babel-griego-en-region ()
 (interactive)
  (save-restriction
   (narrow-to-region (region-beginning) (region-end))
  (save-excursion
   (goto-char (point-min))
      (replace-regexp
      (concat
  "\\(^\\|\\w+[[:blank:]]+\\|[,.;?¿:«»]+[[:blank:]]+\\|{\\)"
  "\\([,.;?¿:«»··\u1F00-\u1FFE\u0370-\u03FF ]+\\)"
  "\\([[:blank:]]+\\|\n\\|\\'\\|}\\)")
  "\\1\\\\foreignlanguage{greek}{\\2}\\3"
    nil)))
     (deactivate-mark))

Y ahora que andaba trasteando con este lisp-on-tex, lo siguiente que me vino a la cabeza fue intentar adaptar esa regex para que la sustitución se ejecutase durante la compilación. Que conste que ya se me había ocurrido hacerlo dentro de Lua (con LuaLaTeX, se entiende), pero las expresiones regulares complejas no son el fuerte de Lua, reconozcámoslo. Con lisp-on-tex, por contra, no fue muy difícil adaptar mi regex, mutatis mutandis. Y a lo siguiente que llegué es a una macro con la misma estructura de las de los ejemplos anteriores:

\def\babelgriego#1{%
\lispinterp{
  (\texprint
  (\regReplaceAll (\concat
'(^|\w+\s+|[,.;?¿:«»]+\s+)'
'([,.;?¿:«»··\n\s\x{1F00}-\x{1FFE}\x{0370}-\x{03FF}]+)'
'(\s+|\n)')
'\1\c{foreignlanguage}\cB\{greek\cE\}\cB\{\2\cE\}\3'
'#1'))
}}

Pero, claro, cuánto mejor sería que esto se convirtiese en un entorno. El problema es que el comando de este intérprete de Lisp, \lispinterp, es una macro «corta» y no puede actuar a través de los párrafos: TeX nos devolvería el típico error de «runaway argument». Podemos resolverlo echando mano de un \everypar, pero hay muchos paquetes que lo redefinen. Por suerte, viene en nuestra ayuda el paquete everyhook para hacernos el apaño, ya que (en palabras de su autor) «takes control of the six TEX token parameters \everypar, \everymath, \everydisplay, \everyhbox, \everyvbox and \everycr

Cargamos, entonces, el paquete (y de paso showhyphens, para comprobar que los puntos de corte de sílaba están activados para los pasajes en griego, y son los correctos). A continuación definimos la macro que aplicará por párrafo nuestra función de Elisp. Y, finalmente, el entorno:

\usepackage{showhyphens}
\usepackage{everyhook}
\def\bgriegopar#1\par{\babelgriego{#1}\par}
\newenvironment{fragsgriego}{%
\PushPreHook{par}{\bgriegopar\null}}
{\PopPreHook{par}}

Sólo nos queda ya probar nuestro entorno (aquí, el resultado de la compilación):

\begin{fragsgriego}
Consectetuer adipiscing elit Παρυσάτιδος γίγνονται. Consectetuer
adipiscing elit Παρυσάτιδος γίγνονται. Consectetuer adipiscing
elit Παρυσάτιδος γίγνονται. Consectetuer adipiscing elit
Παρυσάτιδος γίγνονται. Consectetuer adipiscing elit Παρυσάτιδος
γίγνονται. Consectetuer adipiscing elit Παρυσάτιδος γίγνονται.
Consectetuer adipiscing elit Παρυσάτιδος γίγνονται. Consectetuer
adipiscing elit Παρυσάτιδος γίγνονται. Consectetuer adipiscing
elit Παρυσάτιδος γίγνονται. Consectetuer adipiscing elit
Παρυσάτιδος γίγνονται.

Consectetuer adipiscing elit Παρυσάτιδος γίγνονται. Consectetuer
adipiscing elit Παρυσάτιδος γίγνονται. Consectetuer adipiscing
elit Παρυσάτιδος γίγνονται. Consectetuer adipiscing elit
Παρυσάτιδος γίγνονται. Consectetuer adipiscing elit Παρυσάτιδος
γίγνονται. Consectetuer adipiscing elit Παρυσάτιδος γίγνονται.
Consectetuer adipiscing elit Παρυσάτιδος γίγνονται. Consectetuer
adipiscing elit Παρυσάτιδος γίγνονται. Consectetuer adipiscing
elit Παρυσάτιδος γίγνονται. Consectetuer adipiscing elit
Παρυσάτιδος γίγνονται.

Consectetuer adipiscing elit Παρυσάτιδος γίγνονται. Consectetuer
adipiscing elit Παρυσάτιδος γίγνονται. Consectetuer adipiscing
elit Παρυσάτιδος γίγνονται. Consectetuer adipiscing elit
Παρυσάτιδος γίγνονται. Consectetuer adipiscing elit Παρυσάτιδος
γίγνονται. Consectetuer adipiscing elit Παρυσάτιδος γίγνονται.
Consectetuer adipiscing elit Παρυσάτιδος γίγνονται. Consectetuer
adipiscing elit Παρυσάτιδος γίγνονται. Consectetuer adipiscing
elit Παρυσάτιδος γίγνονται. Consectetuer adipiscing elit
Παρυσάτιδος γίγνονται.
\end{fragsgriego}

lipontex.png

Figura 1: Nuestro ejemplo compilado con los cortes de sílaba correctos para griego antiguo y español

Publicado: 07/10/19

Última actualización: 21/01/22


Índice general

Acerca de...

Esta obra está bajo una licencia de Creative Commons Reconocimiento-NoComercial 4.0 Internacional.

© Juan Manuel Macías
Creado con esmero en
GNU Emacs