Elisp para no programadores

Índice

1 lisp para no programadores

1.1 Avisado quedas

Uno de mis propósitos para año nuevo era escribir una introducción a elisp para no programadores como yo. El objetivo es aprender: tengo comprobado que aprendo más cuando explico las cosas que cuando me las explican. Además, la documentación que encuentro sobre el tema en español es escasa. Si alguien decide leer esta serie de posts le debo advertir primero de una serie de hechos que pueden resultarle extraños:

Soy psicólogo, no informático. No espere rigor en el vocabulario informático ni se extrañe de que llame pichorros o chismáticos a cosas que para un no programador se llaman pichorro o chismático. No obstante procuraré ser lo más preciso en el lenguaje y utilizar los términos correctamente.

Debido a lo anterior, es posible que se encuentre uno explicaciones automágicas o no demasiado precisas, pero tal y como las entiende o puede entender un profano.

También es posible que alguien haga las cosas de otra manera más lógica, más sencilla, más mejor o más friki. Para eso están abiertos los comentarios ─pero le recuero al supuesto lector que la lógica, la sencillez o el frikismo son subjetivos─. Si quieres que entienda otra lógica a la mía tendrás que explicar tu lógica de programación como para que lo entienda un psicólogo, no un informático.

No esperes tampoco una explicación concienzuda y sistemática sobre todos los aspectos del lenguaje. Sólo hablaré sobre los temas que más utilizo o sobre los que quiero utilizar o los que conozco, para lo demás está el manual que viene incluido con emacs.

Pues eso, avisado quedas.

1.2 ¿Qué necesito para hacer los ejercicios de este minicurso?

¿Tienes Emacs instalado?

Si la respuesta es , no necesitarás nada más. Si la respuesta es no ¿para qué estás leyendo esto?: Instala Emacs.

Cuando lo tengas instalado seguramente te gustará configurarlo a tu manera, si lo haces te encontrarás que tienes que escribir tu primer programa en elisp, aunque aún no sepas muy bien cómo funciona todo, conseguirás hacerlo. Al principio copiarás código de donde lo encuentres sin saber muy bien qué estás haciendo

1.3 Lisp

Lisp es uno de esos lenguajes viejunos que me gustan. De hecho, es el segundo más antiguo. Cuando empezaba en esto de programar lo petaban lenguajes como Smalltalk, Fortran o Lisp. En aquellos años me decanté más por Smalltalk, aunque aprendí esos tres, los dos últimos los olvidé por desuso. Luego vinieron otros lenguajes a ocupar sitio en mi cabeza, Pascal, C/C++, Python… y desplazaron incluso a mi querido Smalltalk.

Lisp significa LISts Processor, que viene a ser como un procesador de listas. Algo de lo que hablaré más en profundidad cuando llegue al tema de los tipos de datos, pero lo dejo aquí apuntado.

El caso es que Lisp volvió a aparecer en mis necesidades en el momento en que descubrí org-mode y me cambié a Emacs como herramienta de edición de cabecera. Emacs es un entorno Lisp y yo me resisto a llamarlo editor: es un entorno de programas Lisp que sirve para editar ficheros de texto. Por algún sitio leí, que los programas de Lisp en realidad son entornos Lisp que simulan ser otra cosa, como Autocad/® o /Emacs.

1.4 Emacs-Lisp

Como entorno Lisp que es, Emacs viene cargado con un lenguaje orientado al manejo de ficheros de texto, ventanas, buffers, bloques de texto y todo lo necesario para simular que es un editor de texto. Conlleva la ejecución de un lenguaje Lisp que conocemos todos como el elisp. No es un Lisp completo como podría ser el clisp, sino uno enfocado a la edición de texto. Sin embargo, se pueden utilizar aspectos más completos del clisp utilizando el módulo cl.el.

También se puede utilizar como un lenguaje de script más, pero lo habitual es que cualquier cosa que programemos con elisp se ejecute dentro del entorno de Emacs.

1.5 Manos a la obra

Bueno, pues manos a la obra. Vamos a trabajar un poco pero antes vamos a ver con qué herramientas contamos: Emacs nos proporciona todo un sistema de elementos para trabajar y que no necesitemos ninguna herramienta externa para programar. Si eres de los que cree que Emacs es un editor me gustaría que te replantearas la cuestión. Si utilizas Emacs sólo como editor, te estás perdiendo muchas posibilidades de la herramienta: apenas le estás sacando rendimiento.

1.6 Buffers

Es posible confundir lo que es un buffer con un fichero. Si sólo editas ficheros con él, lo que hace emacs es cargar el texto en un buffer de edición. Pero hay muchos más tipos de buffers que descubres cuando utilizas algunas herramientas. Un buffer es el espacio que utiliza emacs para interactuar con la información. El primero que nos encontramos es scratch.

1.7 scratch para todo

Este es el buffer para probar cosas, como expresiones, funciones, hacer cálculos sencillos, etc. Para ver cómo funciona, vamos a hacer una prueba muy sencilla. Vamos al buffer de scratch y escribimos, por ejemplo:

(message "Esto es un mensaje de prueba en scratch.")

Ahora, con el cursor situado en el final de esa expresión, vamos a pulsar la combinación de teclas C-x C-e. Al hacerlo estamos llamando al comando eval-last-sexp, que traducido es algo así como evaluar la última S-expresión. También podemos utilizar el más corto y socorrido C-j, que llamará al comando eval-print-last-sexp. ¿En qué se diferencia además de añadir un print en el nombre? Pues básicamente en que nos muestra en el mismo buffer scratch el resultado de evaluar la expresión. El primero nos lo muestra sólo en el minibuffer, esto es: mira en la línea inferior de emacs la que está por debajo de la línea de estado. Si te has perdido el mensaje que has lanzado, puedes ver que también está escrito en el buffer Messages. Sin embargo, cuando lleva el apellido print además de mostrarlo allí, lo muestra en el mismo buffer scratch.

Algo muy socorrido cuando queremos hacer algún cálculo sencillo. Por ejemplo:

(+ 9 5)

Si pulsamos C-x C-e veremos que en la última línea nos muestra algo parecido a 14 (#o16, #xe, ?\C-n). El primer número nos dice el valor decimal y luego, entre paréntesis, el valor octal, el hexadecimal y el valor del carácter. En este caso, como el número es muy bajo, el valor que nos muestra es un valor especial que significa C-n. Si el valor fuera, por ejemplo, 65, el carácter representado sería la letra A. Sin embargo, si utilizamos el atajo C-j se limitará a escribirnos en el mismo buffer scratch el resultado en decimal y no nos dará más información.

1.8 Conclusión

El artículo de hoy es sólo una introducción a las herramientas y quedan muchas cosas, más de las que me gustaría, pero sería demasiado largo explicar más cosas. De momento me quedo con que tenemos entre manos un entorno de programación completo funcionando, un entorno vivo, que permite que le pasemos código sobre la marcha, que nos permite guardar ese código en ficheros y cargarlo cuando lo necesitemos.

El ejemplo de este tipo de ficheros es el de configuración de emacs. Quizá intimide un poco al principio pensar que para usar el editor hay que programar, sin embargo es más sencillo de lo que parece a primera vista y mucha gente sin conocimientos de programación es capaz de configurarlo.

2 Sobre listas y atoms

2.1 Sobre listas y atoms

Hoy toca ver un poco más sobre la programación en elisp. Concretamente cómo funcionan las listas y los tipos de datos que pueden contener. Hablaré también un poco sobre algunas funciones básicas para procesar listas.

2.2 El primer programa: “Hola Mundo”

Es tradición en los textos sobre programación incitar al lector a hacer un primer programa muy sencillo que consiste en imprimir por pantalla un mensaje que diga Hola Mundo!. Para no faltar a la tradición, aunque esto ya lo hicimos en el artículo anterior sobre elisp, escribiremos lo siguiente en el buffer scratch:

(message "Hola Mundo!")

Es un programa muy sencillo y consiste sólo en usar una función que muestra el mensaje cuando pulsamos C-x C-e. Bueno, vale, era sólo para refrescar un poco la memoria al repasar lo del primer día, vamos con el tema de hoy.

2.3 Listas

¿Qué es una lista? Pues básicamente todo lo que se encuentra encerrado entre paréntesis. Es decir, en el ejemplo anterior hemos empleado una lista. Si la observamos bien veremos que consta de dos elementos, el primero es la función message y el segundo la cadena Hola Mundo!, separados por un espacio.

El concepto de lista es central en elisp, como en cualquier otro Lisp ─que recuerdo que significa básicamente procesador de listas… si añadimos que emacs significa editor de macros, podemos decir que elisp significará procesador de listas del editor de macros, o algo así─.

'(uno 2 "tres")        ; Una lista de tres elementos
'()                    ; Una lista vacía

2.4 Cómo están construidas las listas

Las listas se construyen encadenando lo que se llaman cons cell. Un cons cell es un objet1o que consiste en dos partes que se llaman la parte CAR y la parte CDR. Cada una de las partes puede contener cualquier tipo de objeto soportado por Lisp. Un esquema gráfico de una lista sería el siguiente:

curs_elisp1.png

Obsérvese el último cdr apunta a nil. En Lisp una lista vacía es equivalente a nil. Es decir, las siguientes expresiones son iguales:

'(1 ())
'(1 nil)

Una de las cosas que más puede liar a la gente es el uso del apóstrofe ('). ¿Por qué lo llevan unas listas y otras no? ¿Cuándo hay que ponerlo? Pues es fácil: lo usamos cuando queremos que elisp acepte ese dato tal cual, sin intentar interpretarlo.

Lisp intenta ejectuar como función el primer elemento de una lista utilizando el resto como los parámetros de la misma. Cuando no necesitamos o no queremos que interprete una lista de esa manera se le debe poner el «'», tal que 'elemento en lugar del más largo (quote elemento), que es una lista que devuelve elemento. Hay quien llamaría a eso azúcar sintáctico (cada vez que alguien habla de ese tema me imagino a Celia Cruz cantando eso de «Asssúcar»).

2.5 Atoms

Las listas ya hemos visto que se basan en los cons cell y también sabemos cómo funcionan. Algún adelantadillo que lea esto dirá, pero si son las listas enlazadas de toda la vida. Bueno, sí, son listas y están enlazadas pero no me voy a poner a estas alturas a hablar de punteros y otros chismáticos de más bajo nivel. ¿Y esto qué tiene que ver con los Atoms? Pues básicamente un Atom es toda otra estructura o dato que Lisp reconoce, que no se basa en los cons cell. Dicho a lo bruto ─y no siendo demasiado preciso─ lo que no es una lista, es un Atom. Por lo que podríamos decir también que los Atoms son pichorros que se pueden almacenar en una lista.

Los hay de muchos tipos: números, cadenas, vectores, hashs, arrays, en fin muchos tipos y sería largo ponerme a enumerar todos. Mejor los dejamos en que existen y cuando los necesitemos nos detenemos a explicarlos con más detalle. Si alguien tiene prisa por conocerlos la ayuda de Emacs está ahí para algo. Jugando un poco con las listas

2.6 Jugando un poco con las listas

Bueno, vamos a ver cómo funcionan los cons cell y las listas con un poco de código.

(cons 'a 'b)                      ; ==> (a . b)
(cons 'a (cons 'b 'c))            ; ==> (a b . c)
(cons 'a (cons 'b (cons 'c nil))) ; ==> (a b c)

Como se puede ver la función cons utiliza un cons cell para endosar en sus huecos los valores que le pasamos. Vamos a imaginarlo gráficamente para ver si nos aclaramos mejor. El gran disléxico Einstein decía: Si no lo puedo dibujar no lo entiendo.

curs_elisp2.png

La última de las opciones se puede conseguir también con la función list, que utilizará todos los argumentos que le pasemos para formar una lista:

(list 'a 'b 'c) ; ==> (a b c)

También podemos utiliza cons para añadir elementos a una lista por el inicio. Por ejemplo:

(cons 'c (list 'a 'b))  ; ==> (c a b)

2.7 Funciones funcionales

Muchos lenguajes de los llamados funcionales utilizan el concepto iniciado por Lisp para obtener la cabecera de una lista y el resto de la lista por separado. Esas funciones son car y cdr.

Primero, para este ejemplo un poco más de información sobre cosas básicas, como dotar de valor a una variable. En Lisp una de las cosas que me desconcertaron al principio fue que las variables y las funciones pueden llamarse igual. En otros derivados de Lisp como scheme, no. Lisp tiene dos espacios de nombres y scheme uno.

2.8 set y setq

(set 'primera (list 'a 'b 'c)) ; ==> primera == (a b c)
(setq segunda (list 'd 'e 'f)) ; ==> segunda == (d e f)

Las dos funciones hacen básicamente lo mismo: establecer el valor de una variable. La diferencia entre las dos es que la primera tenemos que marcar el nombre de la variable también con el apóstrofe (quote en inglés) y la segunda lo hace ella por nosotros. La mayoría del código que podemos encontrar en el fichero de configuración de Emacs utiliza alguna de estas dos funciones para establecer valores que luego utiliza una determinada librería o módulo para ajustar su funcionamiento.

2.9 car y cdr

Estas dos funciones son la base del trabajo con listas. car devuelve el primer elemento de una lista, por ejemplo:

(car primera)    ; ==> a
(car segunda)    ; ==> d

Por el contrario, cdr nos devuelve una lista con la cola, es decir la lista que contiene el resto del argumento.

(cdr primera)              ; ==> (b c)
(cdr (cdr primera))        ; ==> (c)
(cdr segunda)              ; ==> (e f)
(cdr (cdr (cdr segunda)))  ; ==> () == nil

2.10 Conclusión

Hoy hemos avanzado un poco más con el elisp. No mucho, pero lo suficiente como para ir entendiendo cómo funciona. Así a brochazos gordos todo es fácil, luego el diablo lo encontraremos en los detalles.

De momento no hemos hecho nada más que rascar un poquito la superficie del lenguaje. Pero ya se puede observar la belleza del mismo. Todo en él es una lista, llamar a una función es utilizar una lista con el nombre de la misma y los argumentos que necesita, pero los argumentos pueden ser listas que procesan otras listas más internas. Eso hará que aparezcan paréntesis por todos lados, que es lo primero que llama la atención cuando estamos aprendiendo el lenguaje (y la principal crítica de los que no lo conocen… ¿alguien a quien le intimidan los paréntesis puede llamarse programador?) ¡Ooopppsss!

3 Un poco más sobre funciones

3.1 Un poco de historia para comenzar

Una de las bellezas de Lisp, o de Smalltalk, es que se basan en un concepto claro y lo llevan hasta sus últimas consecuencias. En Smalltalk todo el sistema se basa en objetos enviándose mensajes entre ellos. Así, por ejemplo, en Smalltalk la forma de ejecutar código de manera condicional no depende de una palabra clave del lenguaje, sino que es un mensaje que se manda a un objeto lógico. Otros lenguajes recurren en su sintaxis a expresiones definidas por el intérprete o el compilador con lo que se llaman palabras reservadas. No voy a darle muchas vueltas más al tema de las palabras reservadas.

Sólo comentar que Alan Kay, desarrollador principal de Smalltalk afirmó verse muy influenciado por Lisp, y también remarcar que Smalltalk influyó en Lisp haciendo que se pudiera utilizar POO (Programación Orientada a Objetos). La clave fundamental de ambos sistemas es que están pensados para que un programador ─que sepa lo que hace─ pueda redefinir todo el sistema y también en ambos, se puede hacer en caliente, mientras se está ejecutando. Por lo tanto, todo el código del sistema es accesible para el programador. Lo digo por propia experiencia, me he cargado unas cuantas imágenes de trabajo de Smalltalk, intentando hacer lo que no debía. Lo bueno, es que a pesar de eso nunca pasa nada: reinicias el trabajo, quitas lo que has puesto o metes lo que has quitado y las cosas se suelen arreglar, no tengáis miedo. Al contrario, equivocándote aprendes más que no haciendo y si no eres capaz de arreglarlo, siempre puedes reinstalarlo (que tampoco es tan grave).

El Lisp fue presentado por John McCarthy en 1960 en el MIT en un artículo con el título «Funciones recursivas de expresiones simbólicas y su cómputo a máquina, Parte I2, demostrando en él que con algunos operadores simples y una notación para las funciones, se puede construir un lenguaje de programación completo para procesar algoritmos.

3.2 Buenas maneras

También comparten, ambos sistemas, algunas buenas costumbres de programación. Por ejemplo, el documentar bien su código. Ambos parten del hecho de que el código se escribe una vez y se lee muchas para modificarlo algunas, así pues el código suele estar documentado. Además, aunque sea un lenguaje interpretado los comentarios no disminuyen la efectividad del código, porque el sistema lo compila a un byte code que interpreta la máquina del sistema. No seáis rácanos con los comentarios, lo que ahora sentado con tranquilidad haciendo nuestras cosas es lógico, deja de serlo unos meses, o semanas, o días después, cuando el problema que nos llevó a escribir ese código dejó de estar en nuestra cabeza, porque lo dejamos solucionado.

Otra de las cosas que aprendí con Smalltalk y que mantengo desde hace tiempo son las pruebas unitarias o Units tests. Algo muy lógico cuando lo entiendes pero a lo que no se llega con el sentido común. Las pruebas unitarias se basan en que hay que escribir primero código suplementario que pruebe que nuestro código productivo funciona. Lo iremos viendo cuando nos metamos en faena.

3.3 Listas, funciones y cálculo lambda

Lisp se desarrolló basándose en el cálculo lambda, que es un sistema formal presentado en 1930 por Alonzo Church y Stephen Kleene para investigar la definición de función, la noción de aplicaciones de funciones y la recursión.

Como estos son conceptos matemáticos que van mas allá de lo que puede abarcar éste artículo, lo dejaremos aquí. Sólo avanzar que el cálculo lambda tiene una gran influencia en los lenguajes llamados funcionales como Lisp o Haskell.

El elemento fundamental de Lisp son las listas. Dado que en Lisp las listas se delimitan con paréntesis, hay quien dice que LISP es un acrónimo de Lost In Stupid Parentheses. Y la verdad es que a los que se acercan por primera vez al lenguaje les puede llamar la atención la profusión de los mismos.

3.4 Definir funciones

Una función se define llamando a defun de la siguiente manera:

(defun incremento (num)
  "Realiza el incremento de un número."
  (+ 1 num))

Podemos ver una serie de elementos que se van a repetir siempre en la estructura de defun. El primero tras la llamada es el nombre de la función que estamos creando, en nuestro caso incremento. A continuación viene una lista que son los argumentos de la función, en nuestro caso sólo hay uno y lo llamamos num, lo podríamos haber llamado número, pero es más largo. El tercer elemento de la definición es una cadena de texto que describe qué es lo que hace nuestra función3. La última parte es el cuerpo de la función, en nuestro caso el cálculo de sumar uno al número que le hemos pasado. En elisp ya existe una función que hace lo mismo: 1+, pero esto sólo es para nuestro ejemplo. Un ejercicio sencillo: teclea el texto de la definición anterior y cuando lo tengas evalúalo, con C-j o con C-x C-e. Cuando lo tengas haz la siguiente prueba:

Teclea M-x apropos y <RET>.
Teclea incremento... ¿Qué sucede?

Efectivamente, la cadena que hemos escrito en nuestra función pasa a estar disponible en uno de los sistemas de ayuda de Emacs: apropos. Recuérdalo, porque muchas veces la forma de enterarse de para qué sirve una variable o cómo funciona una función (valga la «rebuznancia») es llamar a apropos. Y es muy frustrante acudir a la ayuda y no encontrar nada, así que recuerdo: Documenta todo el código que escribas, por tu propio bien.

3.5 Usar funciones

Vamos con un ejemplo y sobre él explicaré algunas cosas más delicadas o detalles. Vamos a aprovechar que tenemos definida ya la función incremento y vamos a teclear el siguiente código. De momento iremos evaluando cada expresión al finalizarla en el buffer scratch.

(set 'lista1 '(1 5 9))
(setq lista2 (list 1 5 9))

Estoy definiendo dos listas diferentes con el mismo contenido. Lo hago de dos maneras para apreciar el detalle del apóstrofe en el código. Ya he comentado que el apóstrofe se coloca sólo en los casos en que queramos que Lisp no interprete el elemento como ejecutable. En ambos casos definimos una variable global que contendrá una lista con los elementos 1, 5 y 9. Ambas definiciones tienen tres elementos: la función de asignación: set y setq; el nombre de la variable: lista1 y lista2 y el contenido que se asigna a ellas. Si nos fijamos, vemos que en algunos lugares hemos añadido un carácter «'». ¿Cuándo se pone y cuándo no? Pues como he dicho antes, se pone cuando queremos que Lisp no evalúe la expresión que le pasamos. Por ejemplo, no queremos que evalúe la expresión lista1, así que le ponemos el «'»; en el segundo caso lista2 no lleva porque setq hace lo mismo que set pero no evalúa la siguiente expresión, o dicho de otro modo (no exacto): pone por nosotros ese apóstrofe (quote en inglés). Si vemos cómo formamos la expresión que asignamos, en el primer caso se lo damos hecho, es la lista (1 5 9), mientras que en el segundo caso llamamos a la función list para que forme la lista con los elementos que le pasamos. En el primer caso, no queremos que Lisp evalúe la lista4, sin embargo, en el segundo, queremos que evalúe list y nos devuelva su valor. ¿Lioso? Al principio puede serlo, pero con la práctica se verá que es sencillo de entender.

3.6 Un apunte sobre macros

No quiero liarnos con las macros. Los menciono sólo porque Emacs se llama editor de macros y algo hay que saber sobre ellas. Pero ¿qué es una macro?

Vamos a ver un ejemplo de macro: defun. Sí, defun no es una función es una macro que define el símbolo que le pasamos como función, crea una expresión lambda y la asigna al símbolo creado. Su estructura es la siguiente:

(defun nombre (argumentos)
  "documentación"
  cuerpo)

La documentación es opcional, claro, pero muy recomendable utilizarla5.

Tampoco hay que preocuparse demasiado por las macros si ahora no se entienden. Una macro es una estructura que se parece mucho a una función: tiene un nombre al que llamar y una lista de elementos que son los argumentos de la macro. La diferencia fundamental es que los argumentos que se pasan a la macro no se evalúan, se pasan tal cual con la expresión que se suministre; sin embargo, los argumentos de una función son el resultado de evaluar los elementos que se le pasen como argumentos. Un lío a estas alturas ¿no?

Digo que no hay que preocuparse demasiado por ellas, porque normalmente se utilizan para extender el lenguaje Lisp y no estamos en ese nivel aún.

Vemos un ejemplo sencillo con nuestro incrementador para aplicarlo a variables:

(defmacro inc (var)
  (list 'setq var (list 'incremento var)))

Si lo probamos en el buffer scratch veremos que no funciona llamándolo con (inc 4), porque trabaja con variables (por eso, el argumento lo hemos llamado var). ¿Cómo trabaja la macro? Supongamos que tenemos una variable x con valor 4. Lo que hace la macro es convertir su cuerpo en la siguiente expresión:

(setq x (incremento x))

Esta expresión es la que evalúa Lisp para devolvernos la variable x incrementada.

3.7 Un pequeño apunte sobre tipos de datos

Vamos a hacer un experimento con nuestra función incremento. Como vemos el parámetro que trabaja lo hemos llamado num, lo podríamos haber llamado pepe como mi vecino o juan como mi cuñado, pero no es la función de los nombres. Es aconsejable que utilicemos los nombres también para dar una pista de lo que es: en nuestro caso un número. Si a nuestra función le enviamos un valor numérico no hay problema. Evaluad las siguientes expresiones una a una:

(incremento 2)
(incremento 0.5)

No hay problema. La primer nos devolverá 3 y la segunda 1.5. Funciona como esperamos, pero ¿qué ocurre si le pasamos una cadena?

(incremento "hola")

Evalúa la expresión anterior y verás qué sucede… Aparecerá un buffer de error que nos dirá algo así:

Debugger entered--Lisp error: (wrong-type-argument number-or-marker-p "hola")
  +(1 "hola")
  incremento("hola")
  eval((incremento "hola") nil)
  elisp--eval-last-sexp(t)
  eval-last-sexp(t)
  eval-print-last-sexp(nil)
  funcall-interactively(eval-print-last-sexp nil)
  call-interactively(eval-print-last-sexp nil nil)
  command-execute(eval-print-last-sexp)

A destacar el wrong-type-argument… es decir, que el tipo del argumento no concuerda con algo que espera utilizar en una función +.

¿Podéis suponer qué ocurrirá si evaluamos la expresión (incremento ?A)? Probadlo… 66 ¿qué…? ¡Esto es un sindiós!

Bueno, no lo es: la expresión ?A es la forma de pasar a la máquina el carácter A, lo que ha hecho nuestra función es coger el carácter A, cuyo valor numérico es 65 y sumarle 1. En la próxima entrega, veremos más despacio esto de los tipos.

Hay otras estructuras opcionales en la definición de una función, pero ya las veremos más adelante, cuando toque.

4 Tipos de datos

4.1 Tipos de datos

Cuando escribimos un programa es porque necesitamos tratar la información como nosotros necesitamos que sea tratada. Esa información es lo que llamamos datos.

En otros lenguajes, los datos y los programas son cosas separadas, sin embargo, en Lisp, los datos y los programas son la misma cosa. Dicho de otro modo, los programas pueden ser tratados como si fueran un tipo de datos más. Ese es el motivo principal de que, como vimos en el artículo anterior, usáramos el «'» para advertir a Lisp cuándo una estructura es un programa o es un dato… ya me perdonaréis por utilizar programa como sinónimo de código ejecutable.

En el código, aparte de programas y datos, existe otro tipo de información: los comentarios. Un comentario es un texto que se añade en el código con el objeto de hacer anotaciones o aclaraciones sobre el funcionamiento del mismo. En una línea, todo texto que aparezca tras un carácter «;», será ignorado por Lisp.

4.2 Tipos de datos básicos

Voy a explicar los tipos más habituales y sencillos: números, caracteres, cadenas, cons, secuencias, símbolos. Dejaré los de edición, propios de Emacs, para más adelante en algún ejemplo. Si alguno de los presentes tiene prisa, en la ayuda de Emacs, en el manual de Elisp hay todo un capítulo dedicado a ese tipo de datos.

4.3 Datos lógicos

En realidad, todos los datos pueden funcionar como lógicos. También se llaman datos booleanos en referencia al álgebra de Bool. Sólo hay dos valores posibles: verdadero y falso.

Algunos os habréis fijado que en algunos valores que se establecen en el fichero de configuración de Emacs aparecen expresiones como:

(setq variable1 nil)
(setq variable2 t)

En ese caso, nil es el valor lógico para falso y t es el valor lógico para verdadero.

4.4 Números

En elisp podemos trabajar con enteros o con decimales. Los números enteros pueden contener un rango mínimo de -536.870.912 a 536.870.911. Es decir, el rango que se pueden utilizar con 30 bits. Si necesitamos números más grandes, Lisp añadirá espacio para contenerlos, por ejemplo, podemos evaluar el siguiente código en el buffer scratch sin obtener un mensaje de error:

(1+ 536870911)

Claro, que lo que hace lo que hace Emacs para cargar ese resultado es utilizar un número de coma flotante y aunque muestre 536870912 internamente se utilizará el número 536870912.0.

La forma de definir un entero (en base diez) es mediante una secuencia de cifras precedidas de un signo (+ o -), opcional si el número es positivo, o también seguido de un . final. Por ejemplo, son números enteros válidos, junto con lo que devuelven si los evaluamos:

+12       ; ==> 12
-3        ; ==> -3
4.        ; ==> 4
200       ; ==> 200

Los números de coma flotante tienen una parte entera y una parte decimal. Veamos cinco formas de escribir el mismo número:

700.0
+7e2
7.0e+2
+7000000e-4
.7e3

Donde e indica una potencia de 10.

4.5 Caracteres

Los caracteres los he puesto aparte de los números, no porque internamente sean algo distinto a un número, sino porque representan otros contenidos: letras, signos ortográficos o caracteres de control, por ejemplo. En Emacs los valores que representan caracteres tienen 32 bits. Si el valor es menor de 127 se considera un carácter ASCII; el resto de valores se consideran no-ASCII.

Hay algunos caracteres especiales que si estás utilizando Emacs y lees sus manuales, estarás acostumbrado a ver escritos en ellos:

?\a    ; ==> 7   control-g,               ‘C-g’
?\b    ; ==> 8   backspace,        <BS>,  ‘C-h’
?\t    ; ==> 9   tabulador,       <TAB>,  ‘C-i’
?\n    ; ==> 10  salto de línea,          ‘C-j’
?\v    ; ==> 11  tab. vertical,           ‘C-k’
?\f    ; ==> 12  formfeed,                ‘C-l’
?\r    ; ==> 13  retorno de carro, <RET>, ‘C-m’
?\e    ; ==> 27  escape,           <ESC>, ‘C-[’
?\s    ; ==> 32  espacio,          <SPC>
?\\    ; ==> 92  contrabarra,       ‘\’
?\d    ; ==> 127 delete,           <DEL>

Evidentemente, la forma más habitual de expresar un carácter en Emacs es escribir el carácter en cuestión anteponiendo un ?, si evaluamos ?A en el buffer scratch nos devolverá 65, pero también podemos conseguir ese carácter expresándolo como ?\o101, ?\x41, en formatos octal y hexadecimal, también puedes utilizar el valor Unicode ?\u0041 o incluso el nombre de la letra en cuestión, como en ?\N{latin capital letter A}.

Más interesante que aprenderse la tabla de caracteres unicode es conocer los caracteres de control. Para ellos podemos utilizar la notación ^. Por ejemplo, si tomamos el último carácter de la lista anterior, <DEL>, además de ?\d, podemos utilizar la secuencia ?\^? o incluso la secuencia ?\C-?.

Igual que los caracteres control también podemos acceder a los caracteres escapados con <META>. Por ejemplo, podemos escapar el valor ?\M-x o incluso mezclar ambas formas para escribir C-M-c como ?\M-\C-c, o ?\C-\M-c, o ?\M-\003.

Del mismo modo podemos utilizar las teclas hyper, super y alt, con los modificadores \H-, \s- y \A-. Por ejemplo, la secuencia de teclas Alt-Hyper-Meta-x podemos tenerla con ?\H-\M-\A-x. Numéricamente, los valores son 222 para alt, 223 para super y 224 para hyper.

4.6 Símbolos

Un símbolo podemos definirlo como la manera de referirnos a un objeto mediante un nombre.

Un símbolo puede contener cualquier carácter. Lo recomendable es utilizar letras, dígitos y algún signo de puntuación normal, como -+=*/ para nombrar nuestras variables y funciones. Aunque tampoco necesitan una notación especial, los caracteres _~!@$%^&:<>{}? suelen ser menos utilizados. Cualquier otro carácter se puede utilizar en un símbolo escapándolo con un carácter \. Pero teniendo en cuenta que mientras \t en una cadena significa un tabulador, en un símbolo será sólo la letra t. Por ejemplo:

(setq al\t 22)     ; ==> 22
alt                ; ==> 22

Tenemos que tener en cuenta alguna limitación. Si escribimos +1 Emacs lo interpretará como el número 1 entero; sin embargo, no habrá problema en utilizar como nombre 22+, por ejemplo.

Otro detalle a tener en cuenta es que en otros Lisp, como el common lisp los símbolos uno y UNO son equivalentes, sin embargo en elisp son dos símbolos diferentes.

En todo caso, siempre es recomendable utilizar nombres que sean fácilmente leídos, escritos y comprendidos; a poder ser, que el nombre sea consecuente con el contenido que le va a ser asignado.

4.7 Diccionarios o listas asociativas

En otro artículo de la serie ya hablé de las cons cells y las listas y vimos cómo se pueden crear. Pero hoy voy a hablar de otro de los usos que nos puede ser muy útil: los diccionarios o listas asociativas. Vemos un ejemplo para explicarlo un poco mejor.

(setq colores '((rosa . rojo)
        (margarita . amarillo)
        (lirio . blanco)
        (violeta . azul)))

(cdr (assoc 'rosa colores))            ; ==> rojo

(car (rassoc 'blanco colores))         ; ==> lirio

Como ya hablé de car y cdr no lo repito aquí, se puede uno ir a ese otro artículo o consultar la ayuda de Emacs (que será más rápido y exacto).

Como se puede ver, al principio creamos una variable que consiste en una lista de cons cell asignadas al símbolo colores. Dentro de la lista podemos buscar el par por el primer elemento o por el segundo. Si lo hacemos por el primer elemento (o car) utilizamos la función assoc que devolverá el primer elemento que coincida con el símbolo buscado. Si buscamos por el segundo elemento (el cdr) hará lo mismo.

Como (assoc 'rosa colores) devolverá (rosa . rojo), si a mí sólo me interesa el valor del color, que he almacenado en la posición cdr, lo que haré será pedirlo con la función cdr. Y al revés, si busco por colores con (rassoc 'blanco colores), devolverá (lirio . blanco); si a mí sólo me interesa el nombre de la flor de ese color, que hemos almacenado en la posición car del par, la pido con la instrucción car.

4.8 Listas de propiedades

Las listas de propiedades (property list o plist para acortar) es una lista de pares de elementos. Normalmente, el nombre de la propiedad es un símbolo que se asociará al valor correspondiente.

Lo vemos también con un ejemplo:

(setq contacto '(nombre "Fulanito"
         apellidos "de Tal y Tal"
         telefono "987654321"))

(plist-get contacto 'nombre)          ; ==> "Fulanito"
(plist-get contacto 'telefono)        ; ==> "987654321"
(plist-get contacto 'apellidos)       ; ==> "de Tal y Tal"

(plist-put contacto 'poblacion "Madrid")

(plist-get contacto 'poblacion)       ; ==> "Madrid"

Como vemos, una lista de propiedades también trabaja asociando valores de pares, pero lo hace sobre cualquier tipo de lista. En el ejemplo se utilizan símbolos para las propiedades y cadenas de texto para los contenidos, pero se pueden utilizar cualquier tipo de datos válidos en elisp, tanto para uno como para el otro.

4.9 Secuencias

En Lisp es habitual encontrar estructuras de datos que son secuencias. Las más habituales son las cadenas de texto (para abreviar cadenas a secas), los arrays y los vectores.

El siguiente diagrama muestra la relación entre distintos tipos de secuencia.

curs_elisp3.png

Acceder a cualquier elemento se puede hacer como sigue a continuación:

(elt '(1 2 3 4) 2)      ; ==> 3

Puede llamar la atención que pidiendo la posición 2, nos devuelva el 3. A estas alturas, los lectores programadores tienen la neurona acostumbrada a comenzar a contar índices por el 0. Si no eres programador y te cuesta imaginarte cómo, fíjate en el siguiente esquema:

curs_elisp4.png

Uno puede pensar en los índices como los indicadores que señalan no a las posiciones sino a la «imaginaria separación» entre ellas.

4.10 Arrays

Un array es un objeto de elisp que tiene una secuencia de espacios donde almacenar otros objetos Lisp. En un array, puesto que los elementos se almacenan de forma secuencial, el tiempo de acceso a cada uno de esos elementos es constante. Como podemos ver en el gráfico anterior, los vectores y las cadenas son dos tipos especiales de array.

Las funciones más socorridas en conjuntos de datos de tipo array son las de obtener o modificar un elemento:

(aref conjunto índice)
(aset conjunto índice valor)

4.11 Cadenas

Una cadena de texto es una secuencia de caracteres, como podríamos definir como array de caracteres. Podríamos definir una cadena por ejemplo como:

(setq cadena (string ?H ?o ?l ?a))       ; ==> "Hola"

(substring cadena 0 2)                   ; ==> "Ho"
(substring cadena -4 -2)                 ; ==> "Ho"

(string= "Hola" cadena)                  ; ==> t
(string= "hola" cadena)                  ; ==> nil

(aref cadena 3)                          ; ==>  97 -- ?a
(aset cadena 3 ?o)                       ; ==> 111 -- cadena == "Holo"

Como se ve en el ejemplo, se pueden acceder a los caracteres que forman una cadena por su posición. Si comenzamos a contar la posición desde el inicio, los números de índice son positivos. Si comenzamos por el final, los números serán negativos.

Otra forma muy socorrida de conseguir cadenas bien formadas son las cadenas con formato. Suelen utilizarse para mostrar mensajes de aviso dando formato legible a nuestros datos. Por ejemplo:

(format "El valor del márgen derecho es %d." fill-column)

Los valores que se pueden utilizar para ser sustituidos.

%s
    será reemplazada por una cadena.

%o
    lo sustituye un número entero en base octal.

%d
    lo sustituye un número entero en base decimal.

%x
    lo sustituye un número entero en base hexadecimal.

%e
    lo sustituye un número en notación exponencial.

%f
    lo sustituye un número decimal.

Hay más comodines para dar formatos a los datos, pero esos son los más habituales y los más usados, pues son los correspondientes a mostrar cadenas y números.

4.12 Vectores

Un vector es básicamente un array que puede contener cualquier tipo de objetos Lisp en su interior. Para distinguirlo claramente de las listas, se utilizan los corchetes en la notación. Por ejemplo:

(setq mi-vector [1 dos '(tres) "cuatro" [cinco]])

Como se puede observar, los vectores, como los números y las cadenas, se consideran datos estáticos para la evaluación, es decir, cuando Lisp los evalúa el valor devuelto es el mismo vector.

4.13 structs

Un struct es tipo de datos definido por el usuario. Mejor, lo vemos con un ejemplo. Voy a imaginar que quiero definir, a bote pronto y no con mucho arte, una estructura que albergue datos sobre cuentas:

(cl-defstruct cuenta id balance)       ; ==> cuenta

(setq a
  (make-cuenta :id 7 :balance 17.12))  ; ==> #s(cuenta 7 17.12)

(cuenta-id a)                          ; ==> 7

(setf (cuenta-balance a) 0)            ; ==> 0 -- cuenta == #s(cuenta 7 0)

En la definición he utilizado la macro cl-defstruct. Internamente, un struct es muy similar a un array, sin embargo, viene aderezado, como se puede ver en el ejemplo de funciones automágicas que nos permiten acceder a los campos del registro.

5 Condicionales, bucles e iteración

5.1 Condicionales, bucles e iteración

Un programa es un proceso que ejecuta la máquina con la instrucción que toca en ese momento ─obviando el procesamiento paralelo que puede ejecutar varias cosas a la vez─. Si todo, el 100% del código, se ejecutara a la vez, el programa no funcionaría y seguramente la máquina no sobreviviría a tamaño calentón. Eso ocurre también con nuestro cerebro, que está activado sobre el 10% o 15% y hay quien piensa que sólo usamos eso de él. En realidad lo usamos todo, el 100%, pero sólo cuando toca. También existen personas que tienen episodios de una activación mayor y pueden llegar hasta el 40%. A esa activación extraordinaria la llamamos epilepsia y los síntomas son los propios de activaciones de conjuntos neuronales funcionando cuando no toca.

Si nuestro programa ejecuta una instrucción tras otra, siempre en el mismo orden y siempre de la misma forma, nuestro programa será tan rígido que seguramente será poco útil. Necesitamos que nuestro código sea más flexible y según las condiciones que se den, realice unas acciones u otras, adaptándose a las diferentes circunstancias. Hoy toca hablar de esos pichorros que nos permiten hacer que nuestro programa haga cosas distintas tomando decisiones según las circunstancias, o las hagan varias veces.

5.2 Variables globales y locales

Antes de meternos con el tema del control de ejecución hay que hacer una puntualización sobre la definición de variables. Hasta ahora hemos utilizado en nuestros ejemplos el comando set, y su primo-hermano monocigótico setq, para definir y establecer las variables y sus valores.

Las variables ocupan espacio y hay ocasiones en las que necesitamos guardar un determinado valor sólo durante los momentos en que se realizan los cálculos, por lo que gastar el espacio que ocupa una variable durante toda la sesión cuando sólo la hemos necesitado unos pocos segundos es un derroche de medios que debemos evitar.

En otras ocasiones, efectivamente, necesitaremos valores durante más tiempo y eso merece que le hagamos un hueco de forma más permanente a esa variable. ¿Cuándo debemos emplear una u otra? Pues nos lo dirá el sentido común y la necesidad.

5.3 Globales

Para definir variables globales en nuestro código es recomendable utilizar la instrucción defvar, con el siguiente formato:

(defvar mi-variable 4
    "Esta variable se utiliza para almacenar un entero.")

En realidad, lo único necesario sería (defvar mi-variable). Eso solo ya reserva el espacio y nuestro programa ─y Emacs en general─, la reconocerá como válida. Sin embargo yo he utilizado la definición completa. ¿Por qué? La respuesta es obvia si después de evaluar el código anterior evaluamos (apropos "mi-variable"). ¿Lo ves?… ¡efectivamente! apropos nos mostrará la cadena de documentación que hayamos escrito.

Puedes seguir utilizando set, nada te lo impide, pero al escribir código será mucho más evidente que has querido crear una variable global ─si además documentas para qué la usas es ya de premio─ si utilizas la forma defvar.

También existe una forma defconst, que como su nombre indica implica el deseo de definir una constante, ─es decir, un valor que se mantendrá fijo durante la ejecución de nuestro programa─. Pero tiene un problema estructural básico: Sólo es un deseo, no es efectivo… sólo hay que comprobarlo con el siguiente código:

(defconst MI-CONSTANTE 3
  "Una constante cualquiera que quiero definir.")  ; ==> mi-constante = 3

(set 'MI-CONSTANTE 4)                              ; ==> mi-constante = 4

No hay ningún aviso de error ni nada que bloquee el que cambiemos el valor de una constante, lo cual no parece tener mucho sentido. En Emacs los símbolos mi-constante y MI-CONSTANTE son distintos. Yo suelo utilizar las constantes en mayúsculas para ver a simple vista que estoy intentando modificar algo que pensé como constante en el código y no se me crucen las cosas. También suelo utilizar defconst aún siendo de poca utilidad, porque cuando leo el código estoy seguro que quise definir algo como constante y mis motivos tenía ─los suelo escribir en la cadena de documentación de la definición─.

5.4 Variables locales

Para las ocasiones en las que sólo necesitamos las variables durante el tiempo que evaluamos el código y no lo necesitaremos fuera de él, podemos utilizar las formas let y let*, que como set y setq son primas-hermanas monocigóticas, para declarar variables. Tanto let como let* tienen una estructura similar:

(let (variables) (cuerpo))

Quizá con un ejemplo se vea más claro:

(let ((x 3)
      (y 5))    ; aquí se cierra el bloque de «variables»
  (+ x y))      ; aquí está el bloque «cuerpo» y se cierra la forma let

Si evaluamos el código anterior el resultado será 8. Vale, ¿y qué lo diferencia de let*? Vemos el siguiente ejemplo:

(let* ((x 3)
      (y (+ x 2)))
  (+ x y))

Prueba el código con el * y sin él. ¿Qué ocurre cuando no está? ¿Un error? ¿Qué error? … algo así como «la variable x no existe». ¿Por qué con el asterisco sí existe? Pues básicamente, porque Lisp va evaluando el código de la definición de las variables según se las va encontrando, así, ha podido definir y en base al valor de x. En el caso de no utilizar asterisco, simplemente x no está disponible hasta que no se cierre el bloque de las variables.

Si evaluamos el símbolo x o y fuera de la forma let, elisp nos dirá que esa variable no existe, porque sólo existen dentro de la forma let.

5.5 Formas

Lo he traducido como formas, en Lisp se llama form a todo lo que se mete entre paréntesis con el fin de ser evaluado. Lo bueno de estas formas es que se pueden meter unas dentro de otras, como se puede intuir de los ejemplos anteriores. Es muy habitual que veamos cosas como por ejemplo:

(defun simbolo-funcion ...
  (let (variables)
    (cuerpo)))

Se puede ver cómo se van acumulando paréntesis en algunos sitios. Al principio te preocupa tanto paréntesis junto, temes olvidar alguno, sin embargo, con la práctica verás que es fácil de entender: sólo son formas dentro de formas que están dentro de formas y tu pensamiento se centrará en esas formas y no en los paréntesis.

5.6 Condicionales

5.6.1 If

La forma if es la más sencilla de todos los condicionales. Su estructura es muy fácil:

(if (condición)
    (bloque evaluado si es «t»)
  (bloque evaluado si es nil))

Vemos un ejemplo para ver cómo funciona:

(if (y-or-n-p "¿Tú que dices?")
    (message "¡¡¡Has dicho que síiii!!!")
  (message-box "Pues no, pues vale."))

He utilizado dos formas interactivas para mostrar un mensaje dentro de una if. La forma y-or-no-p muestra un mensaje en el minibuffer de emacs, el que le pasamos como cadena de caracteres, y espera a que el usuario pulse y si está de acuerdo o n si no lo está. Devolverá t en el primer caso y nil en el segundo. La forma message simplemente muestra un mensaje en el minibuffer. La forma message-box muestra el mensaje en un diálogo de ventana con su botón y todo.

5.6.2 When y unless

Podríamos decir que when y unless son formas resumidas de if. La primera, when equivale a utilizar sólo el bloque cierto de la expresión. Dicho de otro modo, sólo se ejecutará el bloque de código si la condición es cierta. La forma unless, al contrario, sólo ejecutará el código si resulta la condición falsa.

Podríamos escribir when como:

(when (condición) (bloque de código))

; será completamente equivalente a

(if (condición)
    (bloque de código)
  nil)

Y también podríamos escribir unless como

(unless (condición) (bloque de código))

; será completamente equivalente a

(if (condición)
    nil
  (bloque de código))

5.6.3 Cond

Hemos visto que if evalúa sólo una condición para actuar en consecuencia. La forma cond lo que hace es evaluar una condición tras otra mientras y ejecuta sólo el cuerpo de la primera condición que se cumpla.

A ver, más despacio para no perdernos:

(cond (lista de condiciones))

donde cada condición tiene la forma:

(condición forma-código)

Vamos a poner un ejemplo a ver cómo funciona:

(cond ((numberp x) (message "x es un número"))
      ((stringp x) (message "x es una cadena"))
      (t           (message "x no es ni una cadena ni un número")))

Y para probarlo podemos ir evaluándolo con los siguientes valores:

(set 'x 4)
(set 'x "Hola")
(set 'x nil)

Bien, también aprovecho para contar que la tercera forma dentro de la lista de condiciones de cond que he puesto en el ejemplo, se evaluará siempre si ninguna de las anteriores es evaluada como cierta. Si no se pone, la forma cond devolverá un nil, que a veces no será un valor esperado.

5.7 Condiciones compuestas

Hay ocasiones en que las condiciones pueden ser más complejas que un sí o un no, sino una combinación de valores o cláusulas booleanas. Las funciones booleanas son and, or y not.

(not t)         ; ==> nil
(not nil)       ; ==> t

(and t   t)     ; ==> t
(and t   nil)   ; ==> nil
(and nil t)     ; ==> nil
(and nil nil)   ; ==> nil

(or t   t)      ; ==> t
(or t   nil)    ; ==> t
(or nil t)      ; ==> t
(or nil nil)    ; ==> nil

Vamos a evaluar un par de expresiones para ver cómo funcionan:

(and (message-box "Hola 1") (message-box "Hola 2") nil (message-box "Hola 3"))
(or (message-box "Hola 1") (message-box "Hola 2") nil (message-box "Hola 3"))

Si evaluamos la expresión and veremos que nos aparecen dos message-box y se detiene en tercer elemento, devolviendo nil. Sin embargo, en la expresión or sólo muestra el primero y devuelve la cadena "Hola 1" evaluando la expresión, por tanto como verdadera.

5.8 Bucles

Los bucles sirven para ejecutar código de forma repetitiva. El bucle más habitual es while. Pongo un ejemplo y lo destripamos:

(let ((num 0))
  (while (< num 4)
    (princ (format "Iteración %d.\n" num))
    (setq num (1+ num))))

Veremos que al evaluar el código escribe lo siguiente:

Iteración 0.
Iteración 1.
Iteración 2.
Iteración 3.
nil

En el ejemplo el bloque de código es muy simple, sólo utiliza la forma princ para escribir una cadena de formato para mostrarnos la vuelta en la que está. En general, es habitual encontrar una estructura del while de la siguiente manera:

(... establecer-contador
  (while (condición contador)
    (bloque-de-código)
    (actualizar-contador)))

En otros lenguajes podemos un bucle adicional, que primero ejecuta el código y luego evalúa la condición. A esos bucles se les suele llamar while-until y aunque en Lisp no hay una forma equivalente con nombre podemos simularla poniendo una forma progn justo dentro de la cláusula condición del while de esta manera:

(let ((num 0))
  (while
      (progn
       (princ (format "Iteración %d.\n" num))
       (set 'num (1+ num))
       (y-or-n-p "¿Repetir otra vez?"))))

Si lo evaluamos mostrará el mensaje mientras contestemos y a la pregunta de si repetimos otra vez.

Hay otras formas de iteración como dolist que repite el proceso a lo largo de los elementos de una lista o dotimes que lo repite el número de veces que se establezca. Los dejaré para más adelante cuando las necesitemos en algún ejemplo.

5.9 Conclusión

He dejado para otro artículo una forma de construir bucles más compleja, que se llama iteración. Son funciones que se llaman a sí mismas para hacer un determinado proceso. Pero como lo veremos en otra entrega no diré más aquí.

6 Funciones recursivas

6.1 Funciones recursivas: llamándose a sí mismas

En la entrega anterior hable sobre iteración, hoy hablaré sobre un mecanismo iterativo que se llama recursión. La recursión consiste en una función que se llama a sí misma para resolver la iteración. Normalmente la estructura recursiva implica que la función tenga una estructura similar a la siguiente:

(defun funcion-recursiva (lista-parámetros)
  (if (condición-de-parada)
      (devolver-valor definitivo)
    (llamar funcion-recursiva)))

Es muy importante la parte de condición-de-parada, porque si no, la función recursiva entrará en bucle infinito. Vemos un ejemplo que bastante habitual en los textos sobre Lisp, pero en general sobre cualquier lenguaje que soporte la recursión.

(defun factorial (num)
  "Calcula de forma recursiva el factorial de un número."
  (if (<= num 1)
      1
    (* num (factorial (1- num)))))

La función se llama factorial y admite un número num como argumento para calcular su factorial. En el bloque if la condición de parada es cuando num es igual a 1, en ese caso, la función devuelve 1. En otro caso, lo que se hace es multiplicar el valor de num por el factorial de num-1. Si probamos esa función con un num igual a 5, nos devolverá el valor de su factorial: 120.

6.2 Funciones recursivas aplicadas sobre listas

Es muy habitual encontrar en el código de Lisp una forma de iteración que utiliza las propiedades de las listas para la iteración. Consideremos el siguiente código:

(defvar lista-numeros
  "Una lista de cadenas con los primeros números enteros."
  '("uno" "dos" "tres" "cuatro"))

(defun imprimir (lista)
  (when lista
    (princ (concat (car lista) ".\n"))
    (imprimir (cdr lista))))

Se puede ver que la recursión se extiende por la lista aprovechando las propiedades car y cdr de la misma. El valor que condiciona la recursión es lista, es decir, mientras el argumento pasado a la función no sea una lista vacía '(), que sabemos es equivalente a nil. En el ejemplo se imprime el primer elemento obtenido mediante (car lista)6 y se llama recursivamente a imprimir con el resto de la misma (imprimir (cdr lista)).

6.3 Desenrollando la recursión

Si tomamos el ejemplo del cálculo de factorial anterior veamos cómo evoluciona la función:

(factorial 5)
(* 5 (factorial 4))
(* 5 (* 4 (factorial 3)))
(* 5 (* 4 (* 3 (factorial 2))))
(* 5 (* 4 (* 3 (* 2 (factorial 1)))))
(* 5 (* 4 (* 3 (* 2 1))))
(* 5 (* 4 (* 3 2)))
(* 5 (* 4 6))
(* 5 24)
120

Como vemos, la recursión se desenrolla expandiéndose a una expresión más simple que luego vuelve a plegarse realizando el cálculo.

Hay ocasiones en que tanto anidamiento no es lo que queremos. Porque en realidad lo que hace es ir «retrasando» el cálculo hasta que se ha desenrollado toda la expresión. Podemos evitar eso haciendo una función con un bucle while o do o loop, pero si queremos recursión, podemos hacerlo también con una función ayudante:

(defun factorial (num)
  "Devuelve el factorial del número expresado por NUM."
  (factorial-ayudante 1 1 num))

(defun factorial-ayudante (resul contador num)
  "Devuelve RESUL, utilizando CONTADOR, con el NUMERO incluido."
  (if (> contador num)
      resul
    (factorial-ayudante (* resul contador)
            (1+ contador)
            num)))

Si hacemos como con la función recursiva anterior, los pasos serían los siguientes:

(factorial 5)
(factorial-ayudante 1 1 5)
(factorial-ayudante 1 2 5)
(factorial-ayudante 2 3 5)
(factorial-ayudante 6 4 5)
(factorial-ayudante 24 5 5)
(factorial-ayudante 120 6 5)
120

Como se puede apreciar no hay anidamiento y el resultado se va acumulando de una llamada a otra de la misma función.

No te dejes despistar con la forma concat que sirve para concatenar cadenas de caracteres. En el ejemplo se añade ".\n", es decir, un punto y un salto de línea.

7 Comandos, módulos, marca, punto y otras zarandajas

7.1 Comandos, módulos, marca, punto y otras zarandajas

Hasta ahora hemos venido trabajando directamente en el buffer scratch y todo ha ido funcionando bien, aunque nuestras funciones y llamadas eran más bien efímeras. En este artículo vamos a guardar el código, por primera vez, en un módulo, librería, fichero, o como lo queráis llamar.

Tradicionalmente los ficheros que nos sirven de librerías o módulos, para marcarlos como elisp, llevan una extensión .el. Cargar uno de esos módulos o librerías se puede hacer de muchas maneras. Por ejemplo, Emacs cuando se inicia busca el código en el fichero especial init.el. Si queremos que nuestro código esté siempre accesible, ese es el lugar apropiado para configurarlo. No voy a entrar ahora a detallar cómo hacer un módulo para establecer un modo de Emacs, me quedaré en algo más básico: crear un par de funciones interactivas o comandos que nos permitan llamarlas desde el minibuffer con M-x.

7.2 Un poco de código en un fichero

Voy a poner un ejemplo sencillo, y por tanto inútil, de lo que puede representar un módulo o librería. Supongamos que quiero solucionar un problema, acelerando la inserción de etiquetas en mis ficheros html. Concretamente las etiquetas de negrita, que es muy cansado escribir <b></b> cada vez que toca. Lo suyo además es que me deje el cursor, lo que en emacs se llama punto, situado entre las etiquetas para escribir cómodamente lo que sea. Veamos el siguiente código:

(defun insertar-etiqueta-negrita ()
  "Inserta un par de etiquetas <b></b> en el punto del cursor."
  (interactive)
  (insert "<b></b>")
  (backward-char 4))

Definimos una función llamada insertar-etiqueta-negrita que hace justo lo que dice su cadena de documentación. Es una función sencilla y no necesitará que se le introduzcan datos a través de parámetros, por eso su lista de parámetros está vacía. Lo primero que vemos en el cuerpo de la función es interactive, una forma especial que hace que nuestra función se convierta en un comando7 al que se le puede llamar desde M-x. La siguiente forma insert insertará la cadena que queremos, en nuestro caso, la cadena contiene las etiquetas de apertura y cierre de negrita en html. Por último le decimos que retrase el punto cuatro caracteres para que se quede entre ambas. Bien, ese código lo vamos a guardar en un fichero que podemos llamar, por ejemplo, negrita.el.

Una vez guardado, sin haber evaluado el código si pulsamos M-x y llamamos a insertar-etiqueta-negrita nos dará un error. El sistema no encontrará dicha función. ¿Cómo puedo cargarla desde el fichero? Podríamos por ejemplo, abrir el fichero y evaluar el código, pero lo que nos facilita el proceso es que con load-file emacs hace las dos cosas sobre la marcha. Así si utilizamos M-x load-file y le decimos que cargue el fichero negrita.el, automágicamente tendremos disponible nuestra función.

Vamos a seguir con el ejemplo un poco… Vamos a suponer que se da el siguiente caso: ya he escrito todo el texto que debería ir en negrita y no he escrito las etiquetas, porque soy así de torpe. Lo puedo solucionar de dos maneras. La primera sería escribirlas manualmente al principio y al final. La segunda escribir las dos etiquetas juntas en un extremo y corto-pegar la otra en el otro… o una tercera, teniendo como tenemos el poder en la punta de los dedos, escribir una función que lo haga por mí. La forma debería escribir en el inicio la etiqueta <b> y en el final la etiqueta </b>. Veamos el siguiente código:

(defun marcar-negrita-region (inicio fin)
  "Inserta una marca <b></b> en torno a una región."
  (interactive "r")
  (save-excursion
    (goto-char fin)   (insert "</b>")
    (goto-char inicio) (insert "<b>")))

Vemos una función marcada como interactive, aunque es un poco distinta a la anterior, lleva aparejada una "r" para marcar que es de una clase especial. La forma interactive tiene varios códigos según lo que se quiera hacer8 con ella. Si miramos qué es lo que significa esa r en el enlace anterior nos dirá que se utilizarán el punto y la marca como parámetros para la función.

El punto he dicho ya que es la posición del cursor dentro del buffer, pero ¿qué es la marca? La marca es otro valor que está en el buffer, lo activamos con el comando set-mark-command, normalmente asociada a la combinación C-<SPC>, para marcar el texto que se sitúe entre los dos valores.

Bien, la siguiente forma que nos encontramos es save-excursion. Básicamente lo que hace es guardar los valores de posición del cursor ─o punto─ para poder recuperarlo y dejarlo como estaba aunque en su cuerpo se mueva el mismo, como hacemos con las llamadas a goto-char. En esta función, lo que se hace es saltar al final, proporcionado por el argumento fin para introducir la etiqueta de cierre de negrita y luego al inicio para introducir la etiqueta de comienzo de negrita. Cuanto save-excursion termina, tanto el punto como la marca están en el sitio que tenían antes de llamar a nuestra función.

Si hemos guardado el código en nuestro fichero, lo podemos recargar de nuevo llamando al comando load-file y nuestra nueva función estará disponible.

Alguien dirá que no hemos avanzado mucho: en lugar de escribir <p></p> tenemos que escribir M-x nombre-de-la-función, que es bastante más largo, así que no hemos ganado nada. Se olvidan que podemos establecer en nuestro código los atajos que queramos. Es habitual que en emacs se dejen las combinaciones de C-c para este tipo de cosas. Cada modo utiliza los suyos, y vemos que según el tipo de archivo que estemos editando C-c C-c, la combinación de teclas superocupada hace cosas distintas. En nuestro caso, podemos por ejemplo establecer los siguientes atajos en el código:

;; Asignación de combinaciones de teclas globales
(global-set-key (kbd "C-c b") 'insertar-etiqueta-negrita)
(global-set-key (kbd "C-c B") 'marcar-negrita-region)

7.3 Conclusión

Ha sido un repaso muy rápido a las funciones que vamos a emplear como comandos de nuestros programas. Pero los conceptos, ─dados así a grandes rasgos─, son sencillos de entender.

8 Interacción con el usuario y widgets

8.1 Interacción básica

Ya vimos cómo se podían hacer funciones que nos sirvieran de comandos y que se pudieran hacer de modo interactivo. Pero si la interacción se limita a llamar a una función, es posible que nos quedemos cortos, porque lo que necesitan los programas para funcionar son datos y la mayor fuente de datos es el propio usuario.

La interacción más sencilla es informar de algo al usuario. Para esos menesteres utilizamos la función message, que ya ha salido alguna vez en este minicurso. Sin embargo, message tiene algunas variantes que pueden servirnos para otras cosas. Por ejemplo, vamos a ver las diferencias en los siguientes mensajes:

(message "Esto es un mensaje que informa al usuario de que el código hace algo.")
(message-box "Esto también informa, pero casi salta a la vista.")
(message-or-box "Esto también lo hace pero además elige cómo hacerlo.")
(message-y-or-n-p "Con este mensaje podemos responder además." t)
(error "¡Se ha producido un error!")

Podemos ver cuatro formas de enviar un mensaje. La primera es la más sencilla, sólo informa de algo sin más. Además, como lo hace en el área de eco, desaparecerá a los pocos segundos y es posible que al usuario se le pase y no llegue a leerlo. Si estamos en un entorno gráfico, podemos emplear la segunda forma message-box, que mostrará el mensaje en un diálogo con un botón de aceptar. La tercera forma dependerá de nuestra configuración y entorno, puede mostrarse en forma de diálogo o en el área de eco. Y por último, la cuarta alternativa nos permite mostrar un mensaje y esperar una respuesta. Si el usuario pulsa y la forma devolverá t y si pulsa n, devolverá nil.

La forma error envía un mensaje y abre un buffer Backtrace y lo podemos utilizar como una forma de detener el programa informando al usuario de lo que ha pasado.

Para las peticiones de confirmación además, emacs nos proporciona dos formas más: y-or-n-p y yes-or-no-p. Básicamente hacen lo mismo que message-y-or-n-p pero con sutiles diferencias:

(y-or-n-p "¿Es mejor que te pregunten antes de hacer algo?")
(yes-or-no-p "¿Escribir más implica menos accidentes?")

La primera forma, se contesta como ya se ha explicado, pulsando y o n. Sin embargo, la segunda forma solicita que se escriba de modo completo yes o no. El manual recomienda utilizar esta función cuando se vaya a realizar alguna acción irreversible, como por ejemplo el borrado de un fichero o un texto.

Otras veces necesitamos que el usuario elija entre varias alternativas, por ejemplo:

(completing-read "Elige fruta: " '("manzana" "naranja" "pera" "plátano"))
(completing-read "Introduce un número: " nil)

Como vemos, completing-read abre más aún la interacción con el usuario. En la primera forma del ejemplo, vemos que podemos dar una lista de alternativas para seleccionar entre ellas. Esas alternativas podemos darlas en cualquier orden, la forma las ordenará alfabéticamente. Además podemos solicitar que el usuario proporcione cualquier entrada, en el ejemplo se solicita un número. Al introducirlo nos daremos cuenta que la función siempre devuelve una cadena. Así que si lo que deseamos es un número deberíamos convertir la entrada:

(string-to-number
  (completing-read "Introduce un número: " nil))

8.2 Widgets

widgets nos proporciona una forma de interacción más completa con el usuario: nos permite crear formularios. Los widgets son chismáticos que nos sirven para establecer datos en esos formularios.

Los tipos de widgets básicos que podemos trabajar son los siguientes:

link
    Es un área de texto asociado con una acción. Se muestran como hipertexto en el texto.
push-button
    Es un link dibujado como un botón.
editable-field
    Un campo de texto editable que puede tener un tamaño variable o fijo.
menu-choice
    Permite seleccionar una opción de un menú de opciones múltiples. Cada opción es a su
    ez un widget. Sólo es visible una opción en el buffer.
radio-button-choice
    Permite al usuario seleccionar una entre múltiples opciones activando los radio botones.
    Las opciones se implementan también como widgets.
item
    Un widget para utilizarlo en menu-choice y radio-button-choice.
toggle
    Un interruptor de tipo on-off.
checkbox
    Una caja para marcar, de forma [ ] - [X].
editable-list
    Una lista editable donde el usuario puede añadir y borrar ítems, cada uno de los cuales
    es también un widget.

¿Complicado? Vamos a hacer un ejemplo sencillo para que podamos hacernos una idea de su potencia.

(require 'widget) ; Declaramos que necesita «widget» para funcionar

(eval-when-compile
  (require 'wid-edit)) ; Y si lo compilamos, necesita también «wid-edit»

(defvar lista-campos '()
  "Una variable que utilizaremos para devolver el contenido del formulario.")

(defvar lista-resultados '()
  "Una variable en la que almacenaremos los resultados.")

(defun formulario-datos ()
  "Función que dibuja nuestro formulario en un buffer."
  (interactive)                                            ; Ya sabéis, para poder llamarla desde «M-x»
  (switch-to-buffer "*Formulario de datos*")               ; Nos creamos un buffer
  (kill-all-local-variables)                               ; Nos cargamos todas las variables locales que haya de antes.
  (make-local-variable 'lista-campos)                      ; hacemos local la variable donde guardaremos en contenido.
  (let ((inhibit-read-only t))                             ; Inhibir el «sólo lectura»
    (erase-buffer))                                        ; Poner el buffer en blanco para escribir en él.
  (widget-insert "Esto es un texto que podemos utilizar para explicar para qué es el formulario.\n\n")
  (setq lista-campos                                       ; Añadir el siguiente campo a la lista
    (cons (widget-create 'editable-field               ; Campo de edición para el nombre
                 :size 25                      ; Tamaño en caracteres del campo
                 :format "Nombre:     %v "     ; Dejar espacio para el texto tras el campo
                 "Mi nombre")                  ; Etiqueta del campo
          lista-campos))
  (setq lista-campos
    (cons (widget-create 'menu-choice                  ; Un menú para seleccionar género
                 :tag "Género"                 ; Etiqueta del campo
                 :value "Femenino"             ; Valor por defecto
                 '(item "No binario") '(item "Femenino") '(item "Masculino"))
          lista-campos))
  (setq lista-campos
    (cons (widget-create 'editable-field               ; Campo de edición para el primer apellido
                 :size 50
                 :format "Apellido 1: %v"
                 "Primer Apellido")
          lista-campos))
  (setq lista-campos
    (cons (widget-create 'editable-field               ; Campo de edición para el segundo apellido
                 :size 50
                 :format "\nApellido 2: %v \n"
                 "Segundo Apellido")
          lista-campos))
  (widget-insert "\n")                                     ; Separador de la línea de botones
  (widget-create 'push-button
         :notify (lambda (&rest ignore)
               (dolist (elemento lista-campos lista-resultados)
                 (message-box (widget-value elemento))))
         "Aceptar")
  (widget-insert " ")                                      ; Separador entre botones
  (widget-create 'push-button
         :notify (lambda (&rest ignore)
               (kill-buffer "*Formulario de datos*"))
         "Cerrar Formulario")
  (use-local-map widget-keymap)                            ; Activar la edición de los campos
  (widget-setup))

He puesto bastantes comentarios para saber qué hace cada cosa. Básicamente lo que hace el código es establecer un par de listas, una de campos y otra de resultados. Se crean los widgets y luego se muestra el formulario.

Es de destacar el diferente comportamiento de algunos widgets según se acceda desde teclado o con el ratón, por ejemplo en el menu-choice. En el primer caso se activará un buffer-popup con las alternativas mientas que en el segundo aparecerá un menú gráfico.

Además, los widgets básicos se pueden combinar en widgets más complejos, pero de momento no nos meteremos en esto.

8.3 Funciones lambda

A destacar también los :notify de los dos botones que hemos colocado al final del formulario. En general, las funciones se definen con defun tomando así un nombre con el que llamar a la forma. Sin embargo, en algunas situaciones las funciones no necesitan ser llamadas y se puede emplear una función anónima que se definen como se ve en el código:

(lambda (argumentos)
  "Documentación en forma de cadena."
  cuerpo-de-la-funcion)

En el modo en que se utilizan en el código, se lanzarán cada vez que se pulse en el botón.

Proyecto de ejemplo para seguir avanzando con elisp

Estoy en un punto muerto y no me decido en cómo continuar hablando sobre elisp. Creo que lo mejor sería hacer un pequeño ejemplo que implique un poco de programación y que sirva para repasar todo lo que hemos ido viendo hasta ahora.

No me resulta fácil encontrar un ejemplo, quizá porque estoy acostumbrado a programar por necesidad. Se me plantea un problema o dificultad que quiero solucionar y lo hago. Pero plantear algo que resolver, sin que sea realmente un problema el problema.

Así que me inclino más a preguntar a los que están siguiendo la serie de programación con elisp, si tienen algún problema que quieran solucionar o algo que les inquiete. Si no recibo sugerencias haré un ejemplo que consistirá en crear un modo menor de Emacs para gestionar datos personales desde un fichero org-mode. No será un ejemplo completo con todas las posibilidades, sino un modo en que con una combinación de teclas abra un formulario que rellenar para crear, editar y guardar los datos en concreto que nos interesan.

Recuerda, si estás siguiendo esta serie y tienes alguna sugerencia, aquí abajo están los comentarios, comenta qué te vendría bien tratar o hacer para solucionar tu problema, o al menos para encauzar esfuerzos para que lo soluciones.

9 Proyecto de ejemplo para seguir avanzando con elisp

https://notxor.nueva-actitud.org/blog/2019/02/08/proyecto-de-ejemplo-para-seguir-avanzando-con-elisp/

Estoy en un punto muerto y no me decido en cómo continuar hablando sobre elisp. Creo que lo mejor sería hacer un pequeño ejemplo que implique un poco de programación y que sirva para repasar todo lo que hemos ido viendo hasta ahora.

No me resulta fácil encontrar un ejemplo, quizá porque estoy acostumbrado a programar por necesidad. Se me plantea un problema o dificultad que quiero solucionar y lo hago. Pero plantear algo que resolver, sin que sea realmente un problema el problema.

Así que me inclino más a preguntar a los que están siguiendo la serie de programación con elisp, si tienen algún problema que quieran solucionar o algo que les inquiete. Si no recibo sugerencias haré un ejemplo que consistirá en crear un modo menor de Emacs para gestionar datos personales desde un fichero org-mode. No será un ejemplo completo con todas las posibilidades, sino un modo en que con una combinación de teclas abra un formulario que rellenar para crear, editar y guardar los datos en concreto que nos interesan.

Recuerda, si estás siguiendo esta serie y tienes alguna sugerencia, aquí abajo están los comentarios, comenta qué te vendría bien tratar o hacer para solucionar tu problema, o al menos para encauzar esfuerzos para que lo soluciones.

10 Creando un modo menor, sencillo y sin muchas pretensiones

https://notxor.nueva-actitud.org/blog/2019/02/15/creando-un-modo-menor-sencillo-y-sin-muchas-pretensiones/

Para seguir con el cursillo de elisp voy a crear un nuevo modo. Va a ser un minor mode, es decir, un modo que se puede activar como secundario a otros modos generales. Lo primero que necesito es un fichero que guarde el código en él y que se cargue desde la configuración de Emacs.

10.1 Crear el fichero de modo

Tengo la costumbre de ir metiendo todos mis proyectos en un directorio con ese nombre dentro de mi directorio home. Es decir, voy a crear un directorio de proyecto que me sirva para trabajar programando. Obvio aquí toda la configuración de git o cualquier otra herramienta de programación que se utilice de manera externa al propio Emacs. El resultado final es crear un fichero que se llamará ~/proyectos/datos-mode/datos-mode.el

Para que lo cargue Emacs cuando arranque, debemos definir algunas cosas en el fichero init.el:

;; Configuración de prueba de creación de datos-mode
(add-to-list 'load-path "~/proyectos/datos-mode")
(require 'datos-mode)

He añadido básicamente dos instrucciones: la primera añade a la lista de paths el lugar donde guardo el fichero; la segunda solicita cargar el modo datos-mode. Bien, muy bonito, pero de momento carga en vacío. ¿Qué código tengo que meter en el fichero datos-mode para que funcione?

De momento, cargo el siguiente código, que me sirve como plantilla de modos:

;; Definición del modo menor
(define-minor-mode datos-mode
  "Toggle Datos mode.
Interactivamente sin argumento, este comando des/activa el modo."
  ;; El valor inicial.
  nil
  ;; Indicador de modo en la línea.
  " Datos")

;; Informa que este fichero proporciona el modo datos-mode
(provide 'datos-mode)

La primera forma, define-minor-mode, es la que crea el modo. Sin entrar en todas las opciones que soporta, de momento vemos que le decimos que comience desactivado con nil y que el nombre que aparecerá en la línea de estado será " Datos". Hay que remarcar el espacio inicial para que no se pegue al modo que se liste antes.

La segunda forma, provide, es el espejo del require que hemos puesto en el init.el, le informa a Emacs que el modo requerido en la configuración lo proporciona nuestro fichero o paquete.

Podemos mejorarlo si en lugar de utilizar el modo posicional, utilizamos los nombres de los argumentos:

(define-minor-mode datos-mode
  "Toggle Datos mode.
Interactivamente sin argumento, este comando des/activa el modo."
  ;; El valor inicial.
  :init-value nil
  ;; Indicador de modo en la línea.
  :lighter " Datos")

10.2 Algo de contenido para probar

Vale, muy bonito todo, pero tengo un modo que no hace nada. Eso sí, si recargo Emacs resulta que puedo llamar hacer M-x datos-mode y me aparece el modo en la línea de estado.

Pero aún no hace nada y eso no sirve de mucho, vamos a hacer que tener instalado nuestro «modo» en el sistema sirva de algo. Algo que suelo necesitar bastante cuando trasteo con los datos es introducir el día de la fecha, normalmente en formato ISO. Entre las formas define-minor-mode y provide defino una función que me ayude a introducir la fecha con un comando:

(defun escribe-dia ()
  "Escribe el día de la fecha en el lugar del punto."
  (interactive)
  (insert (format-time-string "%Y-%m-%d")))

Si recargamos Emacs podemos llamar a M-x escribe-dia y en el lugar donde esté el cursor escribirá 2019-02-15, utilizando la forma insert después de llamar a format-time-string con una cadena de formato concreta, la ISO, se pueden consultar los formatos que vienen en el manual9 por si nos viniera mejor otro formato. Todo esto está muy bien, pero tampoco es una mejora sustancial… ¿qué tendría que hacer si necesito escribir la fecha en modo largo, o europeo, o americano? ¿Puedo hacer que la función me pregunte cómo quiero la salida en lugar de una función para cada formato de los que uso? La respuesta es sí, pongo el código y lo explico más despacio:

(defun escribe-dia (&opcional cadena-formato)
  "Escribe el día de la fecha en lugar del punto."
  (interactive "P\nsCadena de formato: ")
  (if (> (length cadena-formato) 0)
      (insert (format-time-string cadena-formato))
    (insert (format-time-string "%Y-%m-%d"))))

Analizando los cambios entre las dos versiones. Lo primero que puede llamar la atención es el uso de &opcional en los argumentos, lo que hace que cadena-formato pueda existir o no. Si no existe o es una cadena vacía "", se llama a la función con el formato.

Lo siguiente que llama la atención es cómo está escrita la forma interactive donde se añade una cadena que puede parecer un tanto extraña. Así que ahí va elemento a elemento: P indica que es interactivo y admite un argumento que proporcionará el usuario. \n equivale a pulsar <RET> cuando interactuamos. s especifica que el argumento será una cadena (string). Y por último una cadena que funciona como prompt de la función.

A continuación se evalúa si cadena-formato tiene contenido y si es así se lo pasará a la función; en caso contrario, se utiliza una cadena de formato que devuelve la cadena en formato ISO.

Si ahora recargamos Emacs y llamamos a la función escribe-dia nos aparecerá el mensaje Cadena de formato: en el buffer y podemos meter cualquier texto en él. Por ejemplo, si introduces a %d de %B de %Y devolverá una cadena como a 15 de febrero de 2019. Si no introducimos ninguna cadena, el prompt devolverá una vacía y la función utilizará la que hemos puesto por defecto.

10.3 Conclusión

Un pequeño paso hacia hacer un modo. Hay algunos problemas que nos podemos encontrar y muchas cosas por hacer aún. No sólo en el modo sino también en nuestra función de insertar fecha, si hay algo que puede fallar es la cadena que el usuario introduzca en nuestra función, aunque en realidad lo que hará insert será introducir «la mejor cadena posible.

11 Variables de modo, keymaps y menús

https://notxor.nueva-actitud.org/blog/2019/02/20/variables-de-modo-keymaps-y-menus/

Estamos acostumbrados a ajustar los paquetes que instalamos para que se comporten de una u otra forma y eso es lo que vamos a hacer ahora con nuestro modo de ejemplo datos-mode. El código de ejemplo utilizará dos variables para ello.

Imaginaos que estamos muy cansados de escribir el día de la fecha y el pie de firma en los documentos que escribo y quiero que lo haga Emacs por mí, porque soy así de vago y lo automatizo todo. Voy a crear una función que lo haga por mí. ¡Eso está «chupao»!¡Al lío! El pie de firma suele tener un formato más o menos fijo: Normalmente el lugar donde se firma, la fecha y a continuación quién lo firma.

(defun escribe-pie-firma ()
  "Escribe el pie de firma en el lugar del punto."
  (interactive)
  (insert (concat "En Macondo, a "
          (format-time-string "%d de %B de %Y")
          "\nFdo: Aureliano Buendía")))

La función es sencilla y se explica sola: inserta una cadena que ha formado concat juntando varias cadenas, el lugar, el día y la firma. Las he puesto en tres líneas para que se vean a simple vista y no se nos despisten.

11.1 Variables

Pero ¿qué ocurre si quiero que mi modo lo utilice también otra persona? ¿Y si tengo que firmar estando en otro lugar? Pues nada más fácil que poner eso en variables, de manera que cambiando el valor de la variable, cambia el texto que se introducirá.

;; Definir variables globales
(defvar datos-lugar
  "Macondo"
  "Lugar que se escribirá con la función de pie de firma.")

(defvar datos-usuario
  "Aureliano Buendía"
  "Usuario que es escribirá con la función de pie de firma.")

Hemos definido dos variables, datos-lugar y datos-usuario, para guardar aquellas cadenas que pueden variar. Se puede apreciar que las he inicializado con dos valores. Lo habitual es que se dejaran vacías o nil y luego en el código controlar qué comportamiento es el adecuado por defecto. Pero eso añadía complejidad a la función de ejemplo con varias formas if que podían confundir al novato pareciendo que la función definitiva es más compleja de lo que en realidad es:

(defun escribe-pie-firma ()
  "Escribe el pie de firma en el lugar del punto."
  (interactive)
  (insert (concat "En " datos-lugar ", a "
          (format-time-string "%d de %B de %Y")
          "\nFdo: " datos-usuario)))

De este modo, la función es idéntica a la anterior, pero colocando las variables en el lugar adecuado. Si la utilizamos directamente veremos que el resultado es equivalente… ¿pero qué ocurre si en nuestro init.el colocamos el siguiente código después de la importación del paquete?

(setq datos-lugar "Notxería")
(setq datos-usuario "Notxor de la Notxería")

¡Ahí va! !Así es como funcionan las configuraciones de Emacs¡ Algunos pensarán: ¡Qué tontería, eso ya lo sabíamos! Por contra otros acabarán de comprender que realmente init.el no es más que un fichero de elisp que funciona ajustando los distintos paquetes al comportamiento que queremos de ellos.

11.2 Keymap

También habrá quien piense que al final escribir M-x escribe-dia o M-x escribe-pie-firma no me ahorran mucho trabajo porque sigo teniendo mucho que escribir. Quizá se estén preguntado o incluso estén tentados a establecer combinaciones de teclas para las funciones. Eso lo podemos hacer también desde el mismo modo, pero además me facilitará que pueda explicar algunas cosas más adelante.

Definir keymap:

;; Definir las combinaciones de teclas del modo
(defvar datos-mode-map
  (let ((datos-mode-map (make-keymap)))
    (define-key datos-mode-map "\C-c\C-f" 'escribe-dia)
    (define-key datos-mode-map "\C-c\C-p" 'escribe-pie-firma)
    datos-mode-map)
  "Kaymap para datos-mode.")

Como se puede ver nuestro keymap lo llamamos datos-mode-map y lo metemos en una variable con ese nombre. Se utiliza la forma let para crear con make-keymap un keymap local al que añadir las combinaciones que necesito con la forma define-key. Esta forma necesita como primer argumento el keymap en el que definirá la combinación que se pasa en forma de cadena y por último la función que debe llamar. Cuando se han añadido todas las combinaciones que necesitamos, la forma let evalúa el keymap que se ha modificado para que se establezca en la variable… creo que es más complicado de explicar que de entender viendo el código.

Ahora hay que decirle al modo cuál es su keymap, por tanto modifico la definición para añadirlo. La definición quedará así:

;; Definición del modo menor
(define-minor-mode datos-mode
  "Toggle Datos mode.
Interactivamente sin argumento, este comando des/activa el modo."
  ;; El valor inicial.
  :init-value nil
  ;; Indicador de modo en la línea.
  :lighter " Datos"
  ;; Keymap para el modo
  :keymap datos-mode-map)

Como se puede apreciar, sólo he añadido :keymap datos-mode-map. Y ahora cuando recargo el mi modo ya puedo utilizar la combinación de teclas que está definida (y que puedo cambiar en la configuración de init.el si quiero).

11.3 Menú

Hace tiempo que dejé de utilizar los menús con profusión, aunque de vez en cuando los miro y los uso, más con el objeto de memorizar la combinación de teclas que por comodidad. Pero hay gente que se pierde con estas cosas de la tecnología y necesita un buen menú en el que pinchar. Para ellos es más claro y siempre mi modo parecerá mucho más «pofesioná» (a ojos del público común) si tiene un menú.

Para definir el menú hay varios modos, pero el que recomiendo para novatos como yo es el macro easy-menu-define. Pongo el código y lo explico:

(easy-menu-define datos-menu datos-mode-map
  "Menú para el modo datos-mode."
  '("Datos"
    ["Escribir día" escribe-dia]
    ["Escribir pie de firma" escribe-pie-firma]))

El macro define datos-menu como el menú para el modo y utiliza para ello datos-mode-map como keymap. Luego vemos que está el comentario para documentar qué es y una lista. La primera cadena "Datos" será la que se muestre en el menú superior de Emacs y luego están dos vectores para cada uno de los widgets de menú que aparecerán. Se define la cadena que mostrará el menú y la función a la que asociarlo.

11.4 Conclusión

Como vemos, con muy poco código tentemos un modo funcional y que da el pego, con chismáticos en los que pinchar quien sea más pusilánime y necesite llevar la flechita por toda la pantalla para asegurarse en su lentitud que lo está haciendo correctamente.

Pongo también el código completo tal y como ha quedado en el fichero datos-mode.el por si alguien se ha perdido desde las explicaciones de la anterior entrega y ésta.

;;; Inicio de datos-mode
(defvar datos-mode-hook nil)

;; Definir las combinaciones de teclas del modo
(defvar datos-mode-map
  (let ((datos-mode-map (make-keymap)))
    (define-key datos-mode-map "\C-c\C-f" 'escribe-dia)
    (define-key datos-mode-map "\C-c\C-p" 'escribe-pie-firma)
    datos-mode-map)
  "Kaymap para datos-mode.")

;; Definir variables globales
(defvar datos-lugar
  "Macondo"
  "Lugar que se escribirá con la función de pie de firma.")

(defvar datos-usuario
  "Aureliano Buendía"
  "Usuario que es escribirá con la función de pie de firma.")

(easy-menu-define datos-menu datos-mode-map
  "Menú para el modo datos-mode."
  '("Datos"
    ["Escribir día" escribe-dia]
    ["Escribir pie de firma" escribe-pie-firma]))

;; Definición del modo menor
(define-minor-mode datos-mode
  "Toggle Datos mode.
Interactivamente sin argumento, este comando des/activa el modo."
  ;; El valor inicial.
  :init-value nil
  ;; Indicador de modo en la línea.
  :lighter " Datos"
  ;; Keymap para el modo
  :keymap datos-mode-map)

(defun escribe-dia (&opcional cadena-formato)
  "Escribe el día de la fecha en lugar del punto."
  (interactive "P\nsCadena de formato: ")
  (if (> (length cadena-formato) 0)
      (insert (format-time-string cadena-formato))
    (insert (format-time-string "%Y-%m-%d"))))

(defun escribe-pie-firma ()
  "Escribe el pie de firma en el lugar del punto."
  (interactive)
  (insert (concat "En " datos-lugar ", a "
          (format-time-string "%d de %B de %Y")
          "\nFdo: " datos-usuario)))

;; Informa que este fichero proporciona el modo datos-mode
(provide 'datos-mode)
;;; Fin de datos-mode

Como se puede ver, contamos ya con una plantilla para hacer un modo muy sencilla que podemos abarcar aún de un vistazo.

12 Reorganizar las capturas de la agenda con plantillas

https://notxor.nueva-actitud.org/blog/2019/02/27/reorganizar-las-capturas-de-la-agenda-con-plantillas/

Llevo tiempo oyendo hablar de GTD10 como una forma de organizarse las tareas y flujo de trabajo que consigue aumentar la productividad y reducir el estrés. Sin embargo, no ha sido hasta hace unas semanas que me he propuesto empezar a aplicarlo a mi caso. Mi vida se ha ido complicando ─complicándomela yo sólo, se entiende─ y necesito un mejor control de las cosas que debo hacer. Entiendo que GTD me servirá para eso ─y algunos ya me han dicho que o me sirve o no habrá nada que me sirva─. Llevo muy poco estudiado sobre el tema de cómo funciona el sistema, pero lo poco que llevo hace énfasis en la captura de tareas.

Tengo que anotar tareas para varios aspectos de mi vida: las cosas del trabajo, las cosas de la asociación con la que colaboro, otras cosas que son personales y otras relacionadas con la Frateco de esperanto. Las tareas por tanto, llevarán las etiquetas correspondientes a esos aspectos y se anotarán todas en la agenda, para que estén juntas, pero no revueltas.

Ayer mismo, el amigo Rui Figueiredo me enlazaba un fichero11 de captura bastante interesante y completo que me ha hecho replantearme lo que tenía ya hecho, añadiendo algunas cosas y cambiando el cómo lo hacía también. El resultado completo, de cómo han quedado mis plantillas para hacer la captura, lo pongo al final del artículo.

La primera decisión ha sido sacar las plantillas de captura del init.el y las he puesto en un fichero, dentro del mismo directorio donde tengo la agenda. ¿Por qué? Pues básicamente porque de momento lo quiero compartir entre dispositivos, igual que comparto la agenda, para ir haciendo modificaciones según vaya aprendiendo cómo funciona el GTD. Además, ha quedado un listado bastante largo y hace más confuso el fichero de configuración de Emacs así que la decisión redunda también en mayor claridad del código.

Básicamente, las plantillas están divididas en tres grupos principales:

Tareas pendientes. Son las tareas que hay que hacer pero no tienen una fecha determinada
para hacerlas. Son ese tipo de cosas que me he responsabilizado a hacer (no hace falta
que me lo recuerdes cada seis meses).

Tareas a la espera. Son las tareas que hay que hacer pero no dependen de uno mismo, bien
porque están delegadas en otras personas, bien porque dependen de otra tarea anterior, o
por cualquier otro motivo, están esperando que se las complete.

Tareas con fecha límite. Algunas de las tareas tienen una fecha concreta en las que tienen
que estar realizadas, o bien un momento concreto en las que hacerlas. Por ejemplo, las
reuniones, las conferencias y algunas otras cosas que hago, como charlas en colegios o
sesiones clínicas, son las típicas que se encontrarán aquí.

Además, como he dicho antes, necesito saber cuándo una tarea pertenece a qué aspecto de mi vida. Y tengo varios:

Trabajo. Todas las acciones y tareas destinadas a ingresar dinero en mi cuenta corriente.

Asociación. Colaboro con una asociación que se llama Protección a la Infancia Contra el Abuso.
Les hecho una mano como psicólogo, aunque también con otras tareas.

Personal. Todas las demás acciones, que no son del trabajo ni de la asociación. Por ejemplo,
todas las cosas que hago relacionadas con el Esperanto.

12.1 Capturas que no son para la agenda

Teniendo ya la inercia de capturar cosas y copiándome un poco de los sistemas de captura que he encontrado por ahí, decidí tener también la posibilidad de capturar información que no tiene que ver con la agenda, o con las tareas. También puedo capturar anotaciones en un fichero especial que he llamado bitacora.org. En él tengo dos tipos de anotaciones:

Notas. En el diario se pueden hacer anotaciones que me gusta ordenar por fechas y por eso,
lo he metido de la siguiente forma:

  ("nd" "Anotar (d) diario" entry (file+headline "~/agenda/bitacora.org" "Diario")
  "** %U
  %?" :empty-lines 1)

Ideas. Hay veces que se me ocurre alguna idea que no puedo desarrollar en ese momento. Ideas,
por ejemplo, para artículos del blog, o para los sienes y sienes de proyectos, aventuras
conversacionales y otras historias que tengo.

  ("ni" "Anotar (i) idea" entry (file+headline "~/agenda/bitacora.org" "Ideas")
   "** Idea para %^{Tema}
    :PROPERTIES:
    :Tema:      %\\1
    :fecha:     %U
    :END:
%?" :empty-lines 1)

Al utilizar esta última plantilla, el sistema de captura me preguntará por el Tema y luego me dejará escribir lo que sea, marcándolo con la fecha correspondiente.

Sin embargo, el gran avance de lo que he copiado (adaptado) de otros sistemas de captura es la posibilidad de tomar datos personales. Aunque utilizo como base de datos el BBDB. Porque funciona, porque está completo, me permite importar y exportar a vcard, mientras que otros sistemas en org-mode sólo me permiten importar. Normalmente, cuando tomo notas de datos personales lo hago directamente a la base de datos, pero en ocasiones no la tengo a mano, porque está guardada en un disco externo encriptado y a veces no lo tengo montado. Así que he planteado la siguiente solución con las plantillas de captura:

("nc" "Anotar (c) contacto" entry (file "~/agenda/especiales/personal.org.gpg")
 "* %^{Nombre} %^{Apellidos}%?
  :PROPERTIES:
  :Nombre:     %\\1
  :Apellidos:  %\\2
  :F-nacim:    %^{F-nacim}u
  :Móvil:      %^{Móvil}
  :Email:      %^{Email}
  :Web:
  :Dirección:  %^{Dirección}
  :Ciudad:     %^{Ciudad}
  :Provincia:  %^{Provincia}
  :Cód.Pos:    %^{Código Postal}
  :Compañía:
  :Trab-Grupo:
  :Trab-puesto:
  :Trab-Tel.:
  :Trab-Email:
  :Trab-Web:
  :Trab-Dirección:
  :Trab-Oficina:
  :Trab-ciudad:
  :Trab-Provincia:
  :Trab-CP:
  :Notas:
  :END:" :empty-lines 1)

Como se puede ver, la captura va a un fichero especial que he llamado personal.org.gpg porque se encuentra cifrado con GPG. Los datos se guardan, por tanto a buen recaudo hasta que los pueda pasar a la base de datos junto con todos los demás.

Los códigos del estilo %Nombre lo que hacen es decirle a org-capture que debe preguntar por un dato Nombre que el usuario debe introducir en el buffer. Los códigos del estilo %\\1 lo que hace es sustituir ese código por el parámetro 1, en nuestro caso el nombre, que introduzca el usuario.

Y por último, el cursor se quedará, una vez introducidos todos los campos que se han marcado interactivamente, en la posición indicada por los caracteres %?. El fichero de plantillas completo

12.2 Este es el resultado:

;; Capturar cualquier cosa a formato org-mode
(setq org-capture-templates
          ; Es una lista de listas así que vamos por partes
      '(("t" "Tarea Pendiente")
    ("tt" "Tarea Simple    (t) trabajo" entry (file "~/agenda/agenda.org")
     "* PENDIENTE %? \t :trabajo:
   :PROPERTIES:
   :CREATE:      %U
   :END:
   :LOGBOOK:
   - State  \"PENDIENTE\"            from \"\"      %U
   :END:" :empty-lines 1)
    ("ta" "Tarea Simple    (a) asociación" entry (file+headline "~/agenda/agenda.org" "Asociación")
     "* PENDIENTE %? \t :asociación:
   :PROPERTIES:
   :CREATE:       %U
   :END:
   :LOGBOOK:
   - State  \"PENDIENTE\"            from \"\"      %U
   :END:" :empty-lines 1)
    ("tp" "Tarea Simple    (a) personal" entry (file+headline "~/agenda/agenda.org" "Personal")
     "* PENDIENTE %? \t :personal:
   :PROPERTIES:
   :CREATE:       %U
   :END:
   :LOGBOOK:
   - State  \"PENDIENTE\"            from \"\"      %U
   :END:" :empty-lines 1)
 ;;; Capturar tareas que pasan a estar a la espera
    ("e" "Tarea a la espera")
    ("et" "Tarea Simple    (t) trabajo" entry (file "~/agenda/agenda.org")
     "* ESPERANDO %? \t :trabajo:
   :PROPERTIES:
   :CREATE:      %U
   :END:
   :LOGBOOK:
   - State  \"ESPERANDO\"            from \"\"      %U
   :END:" :empty-lines 1)
    ("ea" "Tarea Simple    (a) asociación" entry (file+headline "~/agenda/agenda.org" "Asociación")
     "* ESPERANDO %? \t :asociación:
   :PROPERTIES:
   :CREATE:       %U
   :END:
   :LOGBOOK:
   - State  \"ESPERANDO\"            from \"\"      %U
   :END:" :empty-lines 1)
    ("ep" "Tarea Simple    (p) personal" entry (file+headline "~/agenda/agenda.org" "Personal")
     "* ESPERANDO %? \t :personal:
   :PROPERTIES:
   :CREATE:       %U
   :END:
   :LOGBOOK:
   - State  \"ESPERANDO\"            from \"\"      %U
   :END:" :empty-lines 1)
 ;;; Capturas que tienen «deadline» asociada
    ("l" "Tarea con fecha límite")
    ("lt" "Tarea    (t) trabajo" entry (file "~/agenda/agenda.org")
     "* PENDIENTE %? \t :trabajo:
   DEADLINE: %(substring (call-interactively 'org-deadline) 12)
   :PROPERTIES:
   :CREATE:      %U
   :END:
   :LOGBOOK:
   - State  \"PENDIENTE\"            from \"\"      %U
   :END:" :empty-lines 1)
    ("la" "Tarea    (a) asociación" entry (file+headline "~/agenda/agenda.org" "Asociación")
     "* PENDIENTE %? \t :asociación:
   DEADLINE: %(substring (call-interactively 'org-deadline) 12)
   :PROPERTIES:
   :CREATE:       %U
   :END:
   :LOGBOOK:
   - State  \"PENDIENTE\"            from \"\"      %U
   :END:" :empty-lines 1)
    ("lp" "Tarea    (p) personal" entry (file+headline "~/agenda/agenda.org" "Personal")
     "* PENDIENTE %? \t :personal:
   DEADLINE: %(substring (call-interactively 'org-deadline) 12)
   :PROPERTIES:
   :CREATE:       %U
   :END:
   :LOGBOOK:
   - State  \"PENDIENTE\"            from \"\"      %U
   :END:" :empty-lines 1)
 ;;; Templates de captura para cosas que no son para la agenda
    ("n" "Capturas no para agenda")
    ("nc" "Anotar (c) contacto" entry (file "~/agenda/especiales/personal.org.gpg")
     "* %^{Nombre} %^{Apellidos}%?
  :PROPERTIES:
  :Nombre:     %\\1
  :Apellidos:  %\\2
  :F-nacim:    %^{F-nacim}u
  :Móvil:      %^{Móvil}
  :Email:      %^{Email}
  :Web:
  :Dirección:  %^{Dirección}
  :Ciudad:     %^{Ciudad}
  :Provincia:  %^{Provincia}
  :Cód.Pos:    %^{Código Postal}
  :Compañía:
  :Trab-Grupo:
  :Trab-puesto:
  :Trab-Tel.:
  :Trab-Email:
  :Trab-Web:
  :Trab-Dirección:
  :Trab-Oficina:
  :Trab-ciudad:
  :Trab-Provincia:
  :Trab-CP:
  :Notas:
  :END:" :empty-lines 1)
    ("nd" "Anotar (d) diario" entry (file+headline "~/agenda/bitacora.org" "Diario")
     "** %U
%?" :empty-lines 1)
    ("ni" "Anotar (i) idea" entry (file+headline "~/agenda/bitacora.org" "Ideas")
     "** Idea para %^{Tema}
   :PROPERTIES:
   :Tema:      %\\1
   :fecha:     %U
   :END:
%?" :empty-lines 1)
;;; Lista de templates de captura para Frateco
    ("f" "Tareas para Frateco Esperanto")
    ("ff" "Tarea    (f) Frateco" entry (file+headline "~/agenda/agenda.org" "Esperanto")
     "* PENDIENTE %? \t :personal:
   DEADLINE: %(substring (call-interactively 'org-deadline) 12)
   :PROPERTIES:
   :CREATE:       %U
   :END:
   :LOGBOOK:
   - State  \"PENDIENTE\"            from \"\"      %U
   :END:" :empty-lines 1)
    ))

13 Creación de un modo mayor para emacs

Hacía que no me ponía a escribir en el blog, y no es por falta de ganas o por abandono, sino que he estado a otras cosas, que han acabado relacionándose también con el tema de la serie.

Resulta que hemos empezado otro vaporware juego, en forma de aventura conversacional y con la intención de probar otra herramienta para crearlas12 para web que de momento no había utilizado nunca: ngPAWS. Después de iniciar el proyecto con la versión estable (0.9), nos dimos cuenta que en el repositorio de github había algunos de los errores, que nos estábamos encontrando, corregidos. Así pues, descargamos la versión del repositorio y compilamos. En el paquete estable, en binario, se distribuye un IDE para manejar el fichero de base de datos en modo texto que luego se compilará en la aventura, pero las piezas fundamentales para hacerlo son otros dos binarios:

El txtPaws de baltasarq que básicamente es un preprocesador que toma la base de datos y cambia
los #define por sus valores, elimina comentarios y otras cosas que facilitan un poco las cosas
al programador. Por lo que programar con PAWS se convierte en algo más llevadero que tratar con
los códigos numéricos que utiliza.

El binario ngpc es un compilador que convertirá el fichero de texto generado por el txtPaws al
código javascript que será el juego.

Como decía, la versión estable cuenta con un IDE que facilita la modificación de la base de datos accediendo de manera directa a los distintos apartados y con unos «chismáticos» de los habituales en todo IDE para guardar, compilar y lanzar el juego. Sin embargo, al descargar los fuentes y compilar, vimos que el IDE es independiente, de hecho está programado en otro lenguaje: Pascal, mientras que el compilador lo está en C. No tengo compilador de Pascal, Lazarus ocuparía más sitio del que puedo permitirme en mi viejo disco duro. Algo habrá que hacer.

Resumiendo, no tenemos IDE, pero tenemos Emacs. Total, el fichero de base de datos es texto plano. Vale, pero queremos que nos facilite la vida: que salte a las secciones correspondientes, como el IDE, que resalte la sintaxis y que compile. ¿Podemos hacer que Emacs haga todas esas cosas para nosotros? Pues sí, pero necesitamos programar un modo mayor de Emacs.

Un modo mayor es algo más complicado que uno menor, que ya lo habíamos visto en anteriores entregas. Por eso, la forma recomendada en el manual de elisp ese hacerlo derivando el nuevo modo de otro modo primigenio. Quizá con el código se vea más claro:

;; Definición del modo
;;;###autoload
(define-derived-mode paws-mode
  prog-mode "Paws"
  "Toggle Paws mode.
Interactivamente sin argumento, este comando des/activa el modo."
  ;; Colorear sintaxis
  (setq font-lock-defaults '((paws-font-lock-keywords))))

Antes de explicar nada quiero llamar la atención sobre el comentario «automágico» ;;;###autoload. Autoload es un mecanismo por el cual Emacs conoce la existencia de, en este caso, paws-mode y lo puede cargar la primera vez que es llamado el modo, aún sin tenerlo cargado previamente en memoria. Es un poco de azúcar sintáctico para la forma (autoload …). Si tenéis curiosidad en el manual viene mejor explicado de lo que puedo hacer en cuatro líneas.

Pero vamos a lo que estamos: en este caso, el paws-mode se ha derivado del prog-mode, un modo básico que proporciona bastante comportamiento esperado por los programadores. Además, si utilizas algunos hooks, que yo utilizo en mi init.el, como por ejemplo:

(add-hook 'prog-mode-hook 'display-line-numbers-mode)
(add-hook 'prog-mode-hook 'auto-complete-mode)

para activar el autocompletado y los números de línea, al abrir cualquier archivo de cualquier lenguaje de programación, también funcionarán en el nuevo modo.

Se pueden derivar otros tipos de modo, pues hay varios modos básicos como, por ejemplo, text-mode, para distintos estilos de texto: de él se derivan modos para html o LaTeX. También el modo special-mode que está, principalmente, pensado para buffers generados por emacs más que para contener un fichero. Incluso hay otro modo tabulated-list-mode para contener información en forma de tablas, como pueden ser, por ejemplo, la lista de paquetes o varias de las listas que genera magit. Pero no vamos a entrar más allá de informar que existen y están ahí para ayudar.

13.1 Coloreado de sintaxis

En nuestro caso, como es modo sencillo lo derivamos como he dicho antes de prog-mode y en su definición lo único que necesitamos es activar el coloreado de sintaxis. Y para eso asignamos a font-lock-defaults una variable que hemos creado con los distintos tipos de palabras especiales del lenguaje. Esa variable paws-font-lock-keywords la definimos como sigue:

(setq paws-font-lock-keywords
      (let* (
        ;; definir algunas categorías de keywords
         (x-keywords '("define" "DONE" "GET" "DROP" "WEAR" "REMOVE" "CREATE" "DESTROY" "SWAP"
               "PLACE" "PUTO" "PUTIN" "TAKEOUT" "DROPALL" "AUTOG" "AUTOD" "AUTOW" "AUTOR"
               "AUTOP" "AUTOT" "BREAK" "COPYOO" "COPYOF" "COPYFO" "WHATO" "WEIGH" "SET" "BSET"
               "OSET" "CLEAR" "OCLEAR" "PLUS" "MINUS" "LET" "ADD" "SUB" "COPYFF" "RANDOM"
               "MOVE" "GOTO" "WEIGHT" "ABILITY" "MODE" "LINE" "GRAPHIC" "PROMPT" "INPUT"
               "TIME" "PROTECT" "PRINT" "TURNS" "SC0RE" "CLS" "NEWLINE" "MES" "MESSAGE"
               "SYSMESS" "PICTURE" "PAPEL" "TINTA" "BORDER" "CHARSET" "SAVEAT"
               "BACKAT" "PRINTAT" "LISTOBJ" "LISTAT" "INVEN" "DESC" "END" "DONE"
               "NOTDONE" "OK" "SAVE" "LOAD" "RAMSAVE" "RAMLOAD" "ANYKEY" "PAUSA"
               "PARSE" "NEWTEXT" "SYNONYM" "BEEP" "PROCESS" "DOALL" "RESET" "EXTERN"))
        (x-types '("noun" "verb" "adjective" "adverb" "preposition" "pronoun" "conjunction"))
        (x-constants '("const" "pic" "grf" "msc" "snd" "flg" "obj" "loc"))
        (x-events '("OREF" "ACTION" "WRITE" "WRITELN" "OBJECT" "ATTR" "LISTCONTENTS"))
        (x-functions '("AT" "NOTAT" "ATGT" "ATLT" "PRESENT" "ABSENT" "WORN" "NOTWORN" "CARRIED"
               "NOTCARR" "ISAT" "ISNOTAT" "ZERO" "OZERO" "NOTZERO" "ONOTZERO" "EQ" "NOTEQ"
               "GT" "LT" "SAME" "NOTSAME" "ADJECT1" "ADVERB" "PREP" "NOUN2" "ADJECT2" "CHANCE"
               "TIMEOUT" "QUIT" "ISNOTLIGHT" "OBJAT" "WHATOX" "WHATOX2" "BZERO" "BNOTZERO"))

        ;; generar las cadenas regex para cada categoría de keywords
        (x-keywords-regexp (regexp-opt x-keywords 'words))
        (x-types-regexp (regexp-opt x-types 'words))
        (x-constants-regexp (regexp-opt x-constants 'words))
        (x-events-regexp (regexp-opt x-events 'words))
        (x-functions-regexp (regexp-opt x-functions 'words)))

    `(
      (,x-types-regexp . font-lock-type-face)
      (,x-constants-regexp . font-lock-constant-face)
      (,x-events-regexp . font-lock-builtin-face)
      (,x-functions-regexp . font-lock-function-name-face)
      (,x-keywords-regexp . font-lock-keyword-face)
      )))

Aún tenemos que depurar en qué categoría meteremos cada una de las keywords, pero el código como se puede ver es muy fácil de entender y muy básico: se definen una serie de variables locales que representan las diferentes categorías, con let* y se rellenan con las palabras que queremos que se coloreen. Después esas listas se convierten con la función regexp-opt en las expresiones regulares (regexp), que espera el código y se almacenan luego en una lista de pares que asigna a cada uno su fuente correspondiente: (regexp . font).

Vale, la sintaxis de la lista de pares es rara y no la hemos visto hasta ahora en nuestro minicurso de programación con elisp, pero la explico en dos patadas:

Lo primero que llama la atención es que utilizamos el signo «`» en lugar del habitual
quote «'». Eso indica que dentro de la lista encontraremos elementos que no son constantes,
sino que se deben calcular (en nuestro caso sustituir la variable por la cadena que
contiene: por su valor).

Luego, cada elemento que se tenga que calcular va precedido de un carácter «,».

Con ese formato, por ejemplo, podemos hacer cosas como:

`(una lista de ,(+ 2 3) elementos)

que se convertirá en:

(una lista de 5 elementos)

En nuestro caso, como he dicho antes, se ha utilizado esta sintaxis para sustituir el nombre de cada variable, en cada uno de los elementos por su valor correspondiente, pero sólo del primer término de cada par.

13.2 Comentarios

Ya hemos visto cómo hacer que las palabras especiales de paws se coloreen, pero aún no hemos hecho nada con los comentarios. En este caso me volví un poco loco (es el primer modo mayor que hago), y no sabía que colorear los comentarios está separado del coloreado de sintaxis como tal. Se mete en otra lista o tabla de sintaxis. Al final también es sencillo de entender y fácil de hacer. El código es el siguiente:

(setq paws-mode-syntax-table
      (let ((tabla (make-syntax-table)))
    (modify-syntax-entry ?\; "<" tabla)
    (modify-syntax-entry ?\n ">" tabla)
    tabla))

Ese código lo que hace es crear una tabla de sintaxis mediante la forma make-syntax-table y una vez creada se modifican dos elementos con la forma modify-syntax-entry:

El carácter ; indica en paws el inicio de los comentarios y se marca con la expresión "<"
en la tabla para indicar que inicia el comentario.

En paws el comentario se extiende hasta el final de la línea, por tanto cuando se produce
un salto de línea, \n, es el final de un comentario, cuando acaba la línea, y se marca con
la expresión ">".

13.3 Haciéndolo funcionar

Tenemos ya establecido las sintaxis y los comentarios en sus correspondientes tablas, ahora necesitamos hacer que funcione:

(defun paws-mode-variables (&opcional syntax keywords-case-insensitive)
  (when syntax
    (set-syntax-table paws-mode-syntax-table)))

13.4 Buscar código en la base de datos

Una de las cosas que facilita esa búsqueda es que todos los apartados están marcados con una cabecera tipo /XXX; por ejemplo, por poner sólo tres: /CTL, /OTX u /OBJ. Al buscar esos textos la marca se moverá dentro del fichero con el siguiente código:

(defun buscar-apartado (apartado)
  "Busca el apartado correspondiente, dado por la cadena de texto."
  (goto-char 1)
  (search-forward apartado))

El sistema es sencillo, se va al primer carácter del búffer con goto-char y luego se hace un search-forward hasta el apartado correspondiente. Luego, llamaremos a esta función con algo tan simple como (buscar-apartado "/OBJ") y ya está desvelado todo el secreto.

13.5 Compilar

Para no irme por las ramas, pongo primero el código y luego lo explico. El código es el siguiente:

(defun compilar ()
  "Compila el fichero asociado con el buffer."
  (interactive)
  (message "Compilando: %s" (buffer-file-name))
  (if (eq 0 (shell-command (concat paws-directorio-txtpaws "/txtpaws " (buffer-file-name))))
      (shell-command (concat paws-directorio-ngpc "/ngpc " (file-name-sans-extension buffer-file-name) ".sce"))
    (message "Falló el preprocesador de %s" (buffer-file-name))))

Bueno, tampoco hay mucho que explicar: se lanzan los binarios con shell-command. Primero el preprocesador, comprobando si ha tenido éxito con eq 0. Como decíamos antes, el preprocesador es txtpaws y trabaja directamente con el fichero que editamos: buffer-file-name. Si tiene éxito, luego hay que lanzar el compilador ngpc; sin embargo, hay que hacerlo, no con el fichero que estamos editando, sino con el fichero generado por el paso anterior, que tiene el mismo nombre que el fichero del buffer (file-name-sans-extension) pero con la extensión .sce.

13.6 Otros pichorros y chismáticos

Pues eso, para facilitarnos la vida con esto de los modos es conveniente crear combinaciones de teclas y menús que llevarse al cursor del ratón. Esto es muy parecido, por no decir igual, que cuando lo vimos en el ejemplo del modo menor, pero lo repito aquí:

;; Definir las combinaciones de teclas del modo
(defvar paws-mode-map
  (let ((paws-mode-map (make-keymap)))
    (define-key paws-mode-map "\C-c\C-fd" 'ir-define)
    (define-key paws-mode-map "\C-c\C-fc" 'ir-ctl)
    (define-key paws-mode-map "\C-c\C-fv" 'ir-voc)
    (define-key paws-mode-map "\C-c\C-fs" 'ir-stx)
    (define-key paws-mode-map "\C-c\C-fm" 'ir-mtx)
    (define-key paws-mode-map "\C-C\C-ft" 'ir-otx)
    (define-key paws-mode-map "\C-C\C-fl" 'ir-ltx)
    (define-key paws-mode-map "\C-C\C-fn" 'ir-con)
    (define-key paws-mode-map "\C-C\C-fo" 'ir-obj)
    (define-key paws-mode-map "\C-C\C-fp" 'ir-pro)
    (define-key paws-mode-map "\C-C\C-j"  'compilar)
    paws-mode-map)
  "Kaymap para paws-mode.")

(easy-menu-define paws-menu paws-mode-map
  "Menú para el modo paws-mode."
  '("Paws"
    ("Buscar sección"
     ["Ir a defines" ir-define]
     ["Ir a /CTL" ir-ctl]
     ["Ir a /VOC" ir-voc]
     ["Ir a /STX" ir-stx]
     ["Ir a /MTX" ir-mtx]
     ["Ir a /OTX" ir-otx]
     ["Ir a /LTX" ir-ltx]
     ["Ir a /CON" ir-con]
     ["Ir a /OBJ" ir-obj]
     ["Ir a /PRO" ir-pro])
    ["--" nil]
    ["Compilar" compilar]
    ))

Básicamente, consiste en asociar las funciones que hemos definido con los pichorros y chismáticos correspondiente. Creo que el código es muy sencillo de entender y no voy a darle más vueltas.

13.7 Instalación

Sólo nos falta instalarlo y ajustarlo a nuestro sitio. Para funcionar, el modo necesita conocer dónde se encuentran los binarios a los que tiene que llamar. Para ello se han creado dos variables:

(defvar paws-directorio-txtpaws nil)
(defvar paws-directorio-ngpc    nil)

El tema es que están vacías, por lo que luego en nuestro init.el debemos establecer esos parámetros para que funcione la compilación. El código de configuración podría quedar de la siguiente manera:

(add-to-list 'load-path "~/proyectos/paws-mode")            ; Sitio donde está «paws-mode»
(require 'paws-mode)
(setq paws-directorio-ngpc "~/proyectos/ngpaws")            ; Sitio donde está el binario «ngpc»
(setq paws-directorio-txtpaws "~/proyectos/ngpaws")         ; Sitio donde está el binario «txtpaws»
(setq auto-mode-alist                                       ; Hacer que la extensión «.txp» enlace el modo paws
      (append '(("\\.txp\\'" . paws-mode)) auto-mode-alist))

Básicamente le decimos a Emacs dónde está instalado el modo y le pedimos que lo cargue. Luego le decimos dónde están los ejecutables y por último añadimos a la lista de modos automáticos la extensión de los ficheros de paws: .txp. Cada vez que abramos un archivo con extensión .txp, la que utiliza paws, se activará el modo paws-mode.

13.8 Conclusión

Ha sido una casualidad que necesitara un modo mayor. Recuerdo que dije que era algo más complicado y que no lo tocaríamos de momento, pero ya metido en el tema, tenía que explicarlo. Espero que se haya entendido todo.

Aparte de en el manual de elisp, podéis encontrar también más información sobre programar modos y coloreado de sintaxis13 en los sitios habituales de Internet.

Nota al pie de página:

1

Conste que acabo de reprimir las ganas de llamarlo pichorro o chismático.

2

Nunca se publicó la parte II.

3

Ese texto aparecerá una vez definida y cargada nuestra función si utilizamos el apropos de Elisp.

4

Si elisp evalúa esa lista, lanzará un error diciendo que no es una función válida y no la puede evaluar.

5

Hay otras estructuras opcionales en la definición de una función, pero ya las veremos más adelante, cuando toque.

6

No te dejes despistar con la forma concat que sirve para concatenar cadenas de caracteres. En el ejemplo se añade ".\n", es decir, un punto y un salto de línea.

10

Acrónimo de Getting Things Done.

Fecha: 2019

Autor: Notxor