Вы находитесь на странице: 1из 153

Aprendiendo Emacs Lisp

Apuntes de Notxor

Contents
1 Aprendiendo Emacs Lisp
2 Prefacio
Por qu . . . . . . . .
Leyendo esto . . . . .
Para quin est escrito
Historia de Lisp . . . .

9
.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

11
11
11
12
12

3 Procesamiento de listas
Listas . . . . . . . . . . . . . . . . . . . . . . . .
Nmeros, listas dentro de listas . . . . . . .
Lisp Atoms . . . . . . . . . . . . . . . . . .
Los espacios en las listas . . . . . . . . . . .
GNU Emacs te ayuda a teclear listas . . . .
Correr un programa . . . . . . . . . . . . . . . .
Generar un Error Message . . . . . . . . . . . . .
Nombres de smbolos y definicin de funciones .
El intrprete de Lisp . . . . . . . . . . . . . . . .
Complicaciones . . . . . . . . . . . . . . . .
Byte Compiling . . . . . . . . . . . . . . . .
Evaluacin . . . . . . . . . . . . . . . . . . . . .
Cmo acta el intrprete . . . . . . . . . . .
Evaluando listas internas . . . . . . . . . .
Variables . . . . . . . . . . . . . . . . . . . . . .
Ejemplo fill-column . . . . . . . . . . . .
Mensaje de error de un smbolo sin funcin
Mensaje de error de un smbolo sin valor . .
Argumentos . . . . . . . . . . . . . . . . . . . . .
Tipos de datos . . . . . . . . . . . . . . . .
Un argumento de valor de variable o lista .
Cantidad variable de argumentos . . . . . .
Utilizar el Tipo errneo como argumento . .
La funcin message . . . . . . . . . . . . .
Establecer un valor en una variable . . . . . . . .

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

13
13
13
14
14
15
15
16
17
17
17
18
18
18
18
19
19
19
20
21
21
21
22
22
23
24

. . .
. . .
esto
. . .

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

CONTENTS
Utilizando
Utilizando
Contar . .
Resumen . . .
Ejercicios . . .

set
setq
. . .
. . .
. . .

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

24
24
25
25
26

. . . .
. . . .
. . . .
. . . .
punto
. . . .

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

27
27
28
28
28
29
29

5 Cmo escribir definicin de funciones


Un aviso sobre funciones primitivas . . . . . . . . . . . . .
El macro defun . . . . . . . . . . . . . . . . . . . . . . . .
Instalar la definicin de funcin . . . . . . . . . . . . . . .
El efecto de la instalacin . . . . . . . . . . . . . . .
Cambiar la definicin de funcin . . . . . . . . . . .
Hacer una funcin interactiva . . . . . . . . . . . . . . . .
multiply-by-seven interactiva . . . . . . . . . . . .
Versin interactiva de multiply-by-seven en detalle
Diferentes opciones para interactive . . . . . . . . . . .
Instalar cdigo permanentemente . . . . . . . . . . . . . .
let . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
let previene la confusin . . . . . . . . . . . . . . .
Las partes de la expresin let . . . . . . . . . . . .
Ejemplo de expresin let . . . . . . . . . . . . . . .
Variables no inicializadas en una declaracin let . .
La forma especial if . . . . . . . . . . . . . . . . . . . . .
Ms detalles de if . . . . . . . . . . . . . . . . . . .
La funcin type-of-animal al detalle . . . . . . . .
Expresiones if-then-else . . . . . . . . . . . . . . . . .
Verdad y falsedad en Emacs Lisp . . . . . . . . . . . . . .
Una explicacin sobre nil . . . . . . . . . . . . . . .
save-excursion . . . . . . . . . . . . . . . . . . . . . . .
Punto y marca . . . . . . . . . . . . . . . . . . . . .
Plantilla para la expresin save-excursion . . . . .
Resumen . . . . . . . . . . . . . . . . . . . . . . . . . . .
Ejercicion . . . . . . . . . . . . . . . . . . . . . . . . . . .

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

31
31
31
33
33
34
34
34
35
35
36
36
36
37
37
38
38
38
39
40
40
40
41
41
42
42
45

4 Practicar la evaluacin
Cmo evaluar . . . . . . . . . . . .
Nombres de buffer . . . . . . . . .
Obteniendo Buffers . . . . . . . . .
Cambiando de Buffer . . . . . . .
Tamao de Buffer y la localizacin
Ejercicio . . . . . . . . . . . . . . .

.
.
.
.
.

.
.
.
.
.

. .
. .
. .
. .
del
. .

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

6 Unas pocas funciones relacionadas con los buffers


47
Buscando ms . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
Una definicin simplificada de beginning-of-buffer . . . . . . . . . 48
La definicin de mark-whole-buffer . . . . . . . . . . . . . . . . . . . 49

CONTENTS
Un vistazo a mark-whole-buffer . . . . . . . .
El cuerpo de mark-whole-buffer . . . . . . . .
La definicin de append-to-buffer . . . . . . . . .
Un vistazo a append-to-buffer . . . . . . . .
La expresin interactiva de append-to-buffer
El cuerpo de append-to-buffer . . . . . . . .
save-excursion en append-to-buffer . . . .
Resumen . . . . . . . . . . . . . . . . . . . . . . . .
Ejercicios . . . . . . . . . . . . . . . . . . . . . . . .

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

49
49
50
50
51
52
52
54
55

7 Algunas funciones ms complejas


La definicin de copy-to-buffer . . . . . . . .
Definicin completa de beginning-of-buffer
Argumentos opcionales . . . . . . . . . . .
beginning-of-buffer con un argumento
beginning-of-buffer completa . . . . .
Resumen . . . . . . . . . . . . . . . . . . . . .
Ejercicio de argumento optional . . . . . . . .

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

57
57
58
58
59
61
62
63

8 Narrowing y Widening
Ventajas del narrowing . . . . . . . . .
La forma especial save-restriction
what-line . . . . . . . . . . . . . . .
Ejercicio con Narrowing . . . . . . . .

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

65
65
65
66
67

9 Funciones fundamentales car, cdr, cons


Nombres raros . . . . . . . . . . . . . . . . .
car y cdr . . . . . . . . . . . . . . . . . . . .
cons . . . . . . . . . . . . . . . . . . . . . . .
Hacer una lista . . . . . . . . . . . . . .
Saber el largo de una lista: length . . .
nthcdr . . . . . . . . . . . . . . . . . . . . . .
nth . . . . . . . . . . . . . . . . . . . . . . . .
setcar . . . . . . . . . . . . . . . . . . . . . .
setcdr . . . . . . . . . . . . . . . . . . . . . .
Ejercicio . . . . . . . . . . . . . . . . . . . . .

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

69
69
69
70
70
71
71
72
73
73
74

. . . . . . . .
. . . . . . . .
zap-to-char
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

75
75
76
76
76
77
77
78
79

10 Cortar y almacenar texto


Almacenando texto en una lista . . .
zap-to-char . . . . . . . . . . . . .
La implementacin completa de
La expresin interactive . . .
El cuerpo de zap-to-char . . .
La funcin search-forward . .
La forma especial progn . . . .
Resumiendo zap-to-char . . .

.
.
.
.

.
.
.
.

.
.
.
.

CONTENTS
kill-region . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
La definicin completa de kill-region . . . . . . . . . . .
condition-case . . . . . . . . . . . . . . . . . . . . . . . .
Lisp macro . . . . . . . . . . . . . . . . . . . . . . . . . . .
copy-region-as-kill . . . . . . . . . . . . . . . . . . . . . . . .
La definicin de la funcin copy-region-as-kill completa
El cuerpo de copy-region-as-kill . . . . . . . . . . . . .
Digresin en C . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Inicializar una variable con defvar . . . . . . . . . . . . . . . . .
Ver el valor actual de una variable . . . . . . . . . . . . . .
defvar y un asterisco . . . . . . . . . . . . . . . . . . . . .
Resumen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Ejercicin de bsqueda . . . . . . . . . . . . . . . . . . . . . . . .

.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.

79
79
81
81
82
82
83
86
88
88
89
89
91

11 Cmo se implementan las listas


93
Diagrama de las listas . . . . . . . . . . . . . . . . . . . . . . . . . . . 93
Smbolos como una cajonera . . . . . . . . . . . . . . . . . . . . . . . . 96
Ejercicio . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97
12 Tironeando texto de vuelta
99
Un vistazo al anillo de matados . . . . . . . . . . . . . . . . . . . . . . 99
La variable kill-ring-yank-pointer . . . . . . . . . . . . . . . . . . 100
Ejercicios con yank y nthcdr . . . . . . . . . . . . . . . . . . . . . . . 100
13 Bucles y recursin
while . . . . . . . . . . . . . . . . . . . . . . .
Bucles con while . . . . . . . . . . . . . .
Un bucle while y una lista . . . . . . . .
Un ejemplo: print-elements-of-list .
Un bucle con un incremento de contador .
Bucle con decremento de contador . . . .
Ahorrando tu tiempo: dolist y dotimes . . .
El macro dolist . . . . . . . . . . . . . .
El macro dotimes . . . . . . . . . . . . .
Recursin . . . . . . . . . . . . . . . . . . . . .
Haciendo robots: extendiendo la metfora
Las partes de una definicin recursiva . .
Recursin con una lista . . . . . . . . . .
Recursin en lugar de un contador . . . .
Ejemplo de recursin utilizando cond . . .
Patrones recursivos . . . . . . . . . . . . .
Recursin sin aplazamientos . . . . . . . .
Solucin sin aplazamiento . . . . . . . . .
Ejercicios de bucles . . . . . . . . . . . . . . . .
14 Bsquedas de las expresiones regulares

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

101
101
102
102
103
104
106
108
108
109
110
110
111
112
112
114
115
118
119
121
123

CONTENTS
La expresin regular sentece-end . . . . . . . . . . . . . .
La funcin re-search-forward . . . . . . . . . . . . . . . .
forward-sentence . . . . . . . . . . . . . . . . . . . . . . .
La definicin de funcin forward-sentence completa
Los bucles while . . . . . . . . . . . . . . . . . . . . .
La expresin regular de bsqueda . . . . . . . . . . . .
forward-paragraph: una mina de oro de funciones . . . . .
La definicin de forward-paragraph resumida . . . .
La expresin let* . . . . . . . . . . . . . . . . . . . .
Bucle while para moverse hacia adelante . . . . . . .
Resumen . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Ejercicios con re-search-forward . . . . . . . . . . . . . .

.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.

123
124
125
125
126
127
128
128
129
130
133
134

15 Contar por repeticin y expresiones regulares


Contando palabras . . . . . . . . . . . . . . . . . . .
La funcin count-word-example . . . . . . . . . . .
Diseando count-words-example . . . . . . . .
El bug de espacio en count-words-example . .
Contar palabras recursivamente . . . . . . . . . . . .
Ejercicio: Contarndo signos de puntuacin . . . . . .

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

135
135
135
136
138
140
143

16 Contando palabras en un defun


Divide y vencers . . . . . . . . . . . . . . .
Qu contar? . . . . . . . . . . . . . . . . .
Qu constituye una palabra o un smbolo?
La funcin count-words-in-defun . . . . .
Contar varios defun en un fichero . . . . .
Buscar un fichero . . . . . . . . . . . . . . .
lengths-list-file en detalle . . . . . . .
Contar palabras en defun en varios ficheros
lengths-list-many-files . . . . . .

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

145
145
146
146
147
149
150
150
152
152

17 Footnotes

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

153

CONTENTS

Chapter 1

Aprendiendo Emacs Lisp


Esto est basado en el libro que viene con el propio emacs titulado An Introduction
to Programming in Emacs Lisp, que afirma que es una introduccin para gente
que no es programador.
As pues, aqu ir poniendo resmenes a modo de apuntes de los distintos puntos
que vaya haciendo. Adems del cdigo de los ejercicios que vaya haciendo y todo
aquello que vaya avanzando.
La idea es aadir al final algn tipo de apndice sobre la elaboracin de algn
proyecto personal que resuma lo aprendido.

10

CHAPTER 1. APRENDIENDO EMACS LISP

Chapter 2

Prefacio
La mayor parte de GNU emacs est escrito en un lenguaje de programacin
llamado Emacs Lisp. Por lo que he ledo por ah, algunas funciones bsicas
se escribieron en c, pero a esas funciones se le aade una completa capa de
funciones de ms alto nivel.
El tema es qu se puede hacer con este entorno?

Por qu
Normalmente se podra pensar que Emacs Lisp se restringe al uso del editor y
sus funciones. Pero parece que es un lenguaje completo y se puede hacer casi de
todo. Hay que darle una oportunidad.

Leyendo esto
Se encontrarn a lo largo de este documento pequeos ejemplos de cdigo. El
hecho de hacerlo en formato org es para poder ejecutar dentro de Emacs el
cdigo que se vaya encontrando y ver sus efectos dentro del editor, sin necesidad
de salir del entorno mientras se lee.
Creo que de ese modo puedo familiarizarme con cdigo que funciona y adems
con cmo funciona el propio editor. Tambin para familiarizarse con el uso de
Emacs y un conjunto de comandos que se utilizan para depurar, ejecutar cdigo,
etc. como por ejemplo, el uso de la combinacin de teclas M-. que llama al
comando find-tag.
11

12

CHAPTER 2. PREFACIO

Para quin est escrito esto


El texto original menciona a toda esa gente que no es programadora. Es decir, no
s si me centrar en este libro, porque ya programo en otros lenguajes. Conozco
aunque tengo olvidado un poco de Lisp. Pero creo que si tengo paciencia y me
dejo llevar mirndolo con un poco de ingenuidad puedo aprender sobre la visin
que da un experto en el lenguaje y el entorno.
Esto es otra razn para utilizar el propio Emacs como forma de tomar notas,
hacer los ejercicios y convertirlo tambin en un entorno de aprendizaje.
Adems, en el mismo sitio que he encontrado esta introduccin se encuentra el
Reference Manual para documentar de forma ms precisa y completa todo lo
que se vaya aprendiendo en esta introduccin.

Historia de Lisp
Lisp se desarroll a finales de la dcada de 1950 en el Massachusetts Institute of
Technology para investigar sobre Inteligencia Artificial.1
GNU Emacs Lisp se inspira en Maclisp que se escribi en el MIT en los 60.
Inspirado por el Common Lisp, que se hizo estndar en los 1980. Aunque, Emacs
Lisp es ms sencillo que Common Lisp.2

1 Un

campo que como psiclogo siempre me ha llamado la atencin.


distribucin estndar de Emacs tiene una extensin opcional cl.el, que aade muchas
funcionalidades de Common Lisp a Emacs Lisp.
2 La

Chapter 3

Procesamiento de listas
Para un ojo no entrenado, Lisp es un extrao lenguaje de programacin. En Lisp
el cdigo tiene parntesis por todos lados. Lisp proviene de LISt Processing y el
lenguaje de programacin maneja listas metidas entre parntesis. Los parntesis
marcan los lmites de la lista. Algunas veces una lista aparece precedida por un
apstrofe , que se llama single-quote en Lisp.1

Listas
En Lisp, una lista se parece a: \'(rose violet daisy buttercup). Esta lista
est precedida por un nico apstrofe. Tambin se puede escribir como:
'(rose
violet
daisy
buttercup)

Nmeros, listas dentro de listas


Las listas pueden tener tambin nmeros en ellas, como en la lista (+ 2 2). sta
lista tiene un smbolo de suma + seguido por dos doses separados por espacios.
En Lisp, tanto los datos como los programas se representan de la misma manera.
Esto es, tanto listas de palabras, nmeros u otras listas, separadas por espacios
y delimitadas por parntesis.
Otra lista, esta vez con una lista dentro de ella:
1 Un single-quote es una abreviatura para la forma especial quote. De momento no hay
que preocuparse por esto.

13

14

CHAPTER 3. PROCESAMIENTO DE LISTAS

'(this list has (a list inside of it))


Los componentes de esta lista son las palabras this, list, has y la lista (a
list inside of it). La lista interior esta formada por las palabras a, list,
inside, of y it.

Lisp Atoms
En Lisp, cuando decamos palabras diramos tomos. Este trmino proviene del
significado original de tomo: indivisible.
En una lista, los tomos estn separados de otros por espacios en blanco. Pero
pueden estas pegados a un parntesis.
Tcnicamente hablando, una lista en Lisp consiste en un parntesis delimitando
tomos separados por espacios o de otras listas tanto de tomos como de otras
listas. Una lista puede tener slo un tomo en ella o incluso nada. Una lista sin
nada puede aparecer como: () y se la llama lista vaca. A diferencia de lo
dems, una lista vaca es a la vez tanto un tomo como una lista.
La representacin escrita tanto de tomos como de listas se llaman symbolic
expressions (expresiones simblicas) o s-expressions. La palabra expresin
por s misma se puede referir tanto a la representacin como al tomo o a la lista
almacenados internamente en el ordenador. Frecuentemente, la gente utiliza
expresin indiscriminadamente. Tambin en muchos textos la palabra form
(forma) se utiliza como sinnimo de expresin.
En el mundo fsico hemos visto que los tomos se pueden dividir. Slo nos
precipitamos a ponerles nombres antes de averiguar su estructura interna. En
Lisp tambin hay tomos que se pueden dividir, como un array; pero el mecanismo
para hacerlo es distinto a los mecanismos para separar una lista. En lo que
concierne a las operaciones de las listas, los tomos son indivisibles.
El texto entre comillas tambin es un tomo. Por ejemplo:
'(this list includes "text between quotation marks.")
En Lisp todo el texto entrecomillado, incluyendo los signos de puntuacin y los
espacios en blanco, es un nico tomo de tipo cadena (string) y es la clase de
cosas que un ordenador puede mostrar para que un humano lo lea. Las cadenas,
por tanto, son un tipo distinto de tomo que los nmeros o los smbolos y se
usan de manera distinta.

Los espacios en las listas


La cantidad de espacios en una lista da igual. Desde el punto de vista de Lisp

CORRER UN PROGRAMA

15

'(this list
looks like this)
es exactamente la misma que:
'(this list looks like this)
Ambos ejemplos son la misma lista, la lista hecha con los smbolos this, list,
looks, like y this en ese orden.
Los espacios extra y los saltos de lnea se utilizan para hacer la lista ms legible
para humanos, pero hay que recordar que al menos debe haber uno.
Resumiendo: una lista est entre parntesis, una cadena entre comillas, un
smbolo parece una palabra y un nmero parece un nmero.

GNU Emacs te ayuda a teclear listas


Cuando tecleas una expresin en GNU Emacs utilizando el modo Lisp estn
accesibles varios comandos para hacer la expresin ms legible. Por ejemplo,
pulsando <TAB> se indenta la lnea del cursor hacia la derecha.
Adems, cuando se cierra el parntesis, Emacs muestra el parntesis de apertura
que concuerda con l. Algo muy necesario porque se pueden haber abierto varios
parntesis y se necesita cerrar todos.

Correr un programa
Una lista en Lisp cualquier lista es un programa listo para correr. Si lo lanzas
lo que en la jerga Lisp se llama evaluar, el ordenador puede hacer tres cosas:
no hacer nada excepto devolver la misma lista; lanzar un mensaje de error; o,
tratar el primer smbolo como un comando que hace algo. (Normalmente la
tercera es la que queremos).
El apstrofe, que se pone en algunas listas se llama quote; cuando precede
una lista le dice a Lisp que no haga nada con ella. Pero si no hay un quote
precediendo la lista el primer tem es especial y se considera que es un comando
que el ordenador debe obedecer. En Lisp estos comandos se llaman funciones.
La lista (+ 2 2) como no tiene un apstrofe delante hace que Lisp comprenda
que + es una instruccin para hacer algo con la lista: sumar los nmeros que
siguen. Colocando el cursor detrs de la expresin en Emacs y pulsando C-x
C-e, Aparecer en el rea de eco de la aplicacin el nmero 4. Probando lo
mismo con una lista con el apstrofe delante, por ejemplo: (this is a quoted
list), aparece el texto (this is a quoted list).
En ambos casos ests proporcionando un comando al programa interno de GNU
Emacs que se llama Lisp interpreter para que evale la expresin.

16

CHAPTER 3. PROCESAMIENTO DE LISTAS

Generar un Error Message


Lo primero es no ponerse nervioso si accidentalmente algn comando que hemos
proporcionado al intrprete genera un mensaje de error. Cuando se comprende
la jerga, los mensajes de error son informativos y una ayuda para arreglar el
problema.
El mensaje de error se genera en el GNU Emacs debugger interno. Entrando en
el debugger. Para salir del debugger hay que pulsar q.
Si evaluamos la siguiente lista poniendo el cursor justo tras el parntesis de cierre
y pulsamos C-x C-e:
(this is an unquoted list)
Emacs nos muestra una ventana *Backtrace* como sigue:
---------- Buffer: *Backtrace* ---------Debugger entered--Lisp error: (void-function this)
(this is an unquoted list)
eval((this is an unquoted list) nil)
elisp--eval-last-sexp(nil)
eval-last-sexp(nil)
funcall-interactively(eval-last-sexp nil)
call-interactively(eval-last-sexp nil nil)
command-execute(eval-last-sexp)
---------- Buffer: *Backtrace* ---------El cursor estar en esta ventana. Para salir del depurador y cerrar su ventana
hay que pulsar q.
Hay que leer el buffer del *Backtrace* de abajo hacia arriba. Cuando se ha pulsado C-x C-e, se ha hecho una llamada interactiva al comando eval-last-sexp.
eval es una abreviacin de evaluate y sexp de symbolic expression. El comando
significa, por tanto, evaluate last symbolic expression, que es justo la expresin
ante el cursor.
Cada lnea nos dice qu ha evaluado a continuacin. La accin ms reciente est
arriba. El buffer se llama *Backtrace* porque permite seguir la traza de Emacs
hacia atrs.
En lo alto del buffer *Backtrace* se puede ver la lnea:
Debugger entered--Lisp error: (void-function this)
El intrprete de Lisp ha intentado evaluar el primer tomo de la lista, la palabra
this. Esto ha generado un mensaje de error void-function this. Lo que nos
dice void-function es que this no tiene una funcin asignada y que por tanto
el sitio que debera contener las instrucciones est vaco.

NOMBRES DE SMBOLOS Y DEFINICIN DE FUNCIONES

17

Nombres de smbolos y definicin de funciones


Otra caracterstica de Lisp es que un smbolo, como +, por s mismo no es un
conjunto de instrucciones para el ordenador. En cambio, el smbolo se utiliza,
quiz temporalmente, como una forma de localizar la definicin o el conjunto
de instrucciones. Lo que vemos es el nombre por el cual podemos encontrar las
instrucciones. Mi nombre no soy yo, pero se puede utilizar para referirse a m.
En Lisp a un conjunto de instrucciones puede hacer referencia mediante varios
nombres. Por ejemplo, las instrucciones para sumar nmeros se pueden sealar
desde plus tanto como desde el smbolo +. Como pasa conmigo, me pueden
llamar Fernando tanto como Notxor.
Pero un smbolo slo puede tener una definicin de funcin.
Aunque Emacs Lisp es grande, es costumbre para los nombre de smbolos de
manera que sean explicativos de qu hace y a qu afecta.

El intrprete de Lisp
En lo que se ha visto hasta ahora podemos empezar a suponer qu hace el
intrprete Lisp. Primero mira si hay un quote delante de la lista; si est
devuelve la misma lista. Si no hay, el intrprete toma el primer elemento de la
lista y mira si tiene una definicin de funcin. Si la hay, el intrprete ejecuta las
instrucciones de la funcin. En otro caso muestra un mensaje de error.
As es como trabaja Lisp. Sencillo. Hay complicaciones adicionales, pero estos
son los fundamentos.

Complicaciones
Bueno, la primera complicacin. Adems de las listas, el intrprete de Lisp
puede evaluar un smbolo sin quoted y sin parntesis rodendolo. El intrprete
de Lisp intentar determinar el valor del smbolo como una variable.
La segunda complicacin ocurre porque algunas funciones son inusuales y no funcionan de la manera ordinaria. stas son las special forms o formas especiales.
Se utilizan para trabajos especiales, como definir una funcin. No son muchas.
Adems de las funciones especiales tambin hay macros. Un macro es una
construccin definida en Lisp que se diferencia de una uncin en que traduce una
expresin Lisp en otra expresin que ser la que se evale en lugar de la original.
Por el momento, no vamos a preocuparnos demasiado si es una funcin especial,
un macro o una funcin ordinaria. Por ejemplo, if es una funcin especial, pero

18

CHAPTER 3. PROCESAMIENTO DE LISTAS

when es una macro. En las primeras versiones de Emacs, defun era una funcin
especial, pero ahora es un macro.

Byte Compiling
Otro aspecto del intrprete es que permite interpretar dos tipos de entidades: el
cdigo legible por humanos u otro ms especfico de la mquina llamado cdigo
compilado o byte compiled code. El cdigo compilado corre ms rpido que el
legible.
Se puede trasformar el cdigo legible en cdigo compilado con comandos del tipo
byte-compile-file. El cdigo compilado normalmente se almacena en ficheros
con extensin .elc en lugar de .el.

Evaluacin
Cuando el intrprete Lisp trabaja con una expresin, decimos que lo est evaluando.

Cmo acta el intrprete


Despus de evaluar una expresin el intrprete Lisp devuelve (return) el valor
que el ordenador produce al alcanzar el final de las instrucciones que encontr
en la definicin de la funcin, o quiz se produzca en esa funcin un mensaje de
error.
A la vez que el intrprete devuelve un valor, puede hacer algo ms, como mover
el cursor o copiar un fichero; esto es un tipo de accin llamada efecto colateral
(side effect). Acciones que los humanos piensan que son importantes, como
mostrar resultados, son frecuentemente efectos colaterales para el intrprete. Es
muy fcil aprender a utilizar los efectos colaterales.
En resumen, evaluar una expresin simblica podr hacer que el intrprete
devuelva un valor y quiz realice un efecto colateral o quiz produzca un error.

Evaluando listas internas


Si una lista est dentro de otra lista, la lista exterior puede utilizar el valor
devuelto por la primera evaluacin de la lista interna como informacin para la
evaluacin de la segunda.
Por ejemplo:
(+ 2 (+ 3 3))

VARIABLES

19

El resultado que aparecer en el rea de eco es 8.


Lo que ocurre es que le intrprete de Lisp primero evala la expresin interna
(+ 3 3), cuyo valor de retorno es 6; despus evala la expresin externa como
(+ 2 6) que devuelve el valor 8. Y como no hay ms expresiones para evaluar,
el intrprete muestra el resultado en el rea de eco.
Ahora es fcil de entender que el comando invocado por la combinacin de teclas
C-x C-e, tiene el nombre de eval-last-sexp.
Se pueden evaluar las expresiones internas colocando el cursor en el justo tras el
cierre de su correspondiente parntesis y pulsando la combinacin de teclas.

Variables
En Emacs Lisp un smbolo puede tener asignado una definicin de funcin o un
valor. Las dos son diferentes. La primera es una serie de instrucciones que el
ordenador tiene que obedecer. La segunda es algo como un nmero o un nombre,
eso puede variar (por eso se llama variable). El valor de un smbolo puede ser
un nmero, una lista o una cadena.
Un smbolo puede tener asignado tanto una definicin de funcin como un valor,
a la vez. O puede tener lo uno o lo otro. Las dos cosas estn separadas.

Ejemplo fill-column
La variable fill-column ilustra lo que es un smbolo con un valor asignado en
l. Si lo ejecutamos pulsando C-x C-e:
fill-column
En el rea de eco, Emacs muestra el valor 70. Esto es el valor que contiene. El
valor puede ser diferente, dependiendo de la configuracin del editor.
Un smbolo puede contener o, utilizando la jerga, podemos atar la variable
a un valor: a un nmero como 70; a una cadena, como es esta; a una lista
como (picea pino roble).
Un smbolo puede ser amarrado a un valor de varias maneras.

Mensaje de error de un smbolo sin funcin


Cuando evaluamos fill-column para obtener su valor como variable, no hemos
puesto parntesis al smbolo. Esto es para que no se intente utilizar como el
nombre de una funcin.

20

CHAPTER 3. PROCESAMIENTO DE LISTAS

Si fill-column est como primer o nico elemento de una lista, el intrprete de


Lisp intentar encontrar la funcin asignada a l. Pero fill-column no tiene
definicin de funcin. Evala lo siguiente:
(fill-column)
Obtendremos un buffer *Backtrace* que dice:
---------- Buffer: *Backtrace* ---------Debugger entered--Lisp error: (void-function fill-column)
(fill-column)
eval((fill-column) nil)
elisp--eval-last-sexp(nil)
eval-last-sexp(nil)
funcall-interactively(eval-last-sexp nil)
call-interactively(eval-last-sexp nil nil)
command-execute(eval-last-sexp)
---------- Buffer: *Backtrace* ----------

Mensaje de error de un smbolo sin valor


Si evaluamos un smbolo que no tiene atado un valor. Podemos experimentar
con la suma de dos ms dos. En la siguiente expresin vamos a colocar el cursor
despus del +, antes del primer 2:
(+ 2 2)
Nos aparecer un buffer *Backtrace* que dice:
---------- Buffer: *Backtrace* ---------Debugger entered--Lisp error: (void-variable +)
eval(+ nil)
elisp--eval-last-sexp(nil)
eval-last-sexp(nil)
funcall-interactively(eval-last-sexp nil)
call-interactively(eval-last-sexp nil nil)
command-execute(eval-last-sexp)
---------- Buffer: *Backtrace* ---------Este backtrace es diferente del primer mensaje de error que hemos visto, que deca
Debugger entered--Lisp error: (void-function this). En este caso dice
void-variable.
En este experimento con el + hemos hecho que el intrprete evale el + y lo mire
como si fuera una variable en lugar de la definicin de una funcin. Puesto que
+ no tiene un valor asignado en l, sino la definicin de una funcin, el mensaje
de error informa que el valor del smbolo es una variable vaca.

ARGUMENTOS

21

Argumentos
Para ver cmo se pasa la informacin a las funciones, vamos a ver nuestro viejo
ejemplo:
(+ 2 2)
Si evaluamos esa expresin aparecer el valor 4. Los nmeros sumados por + se
llaman argumentos de la funcin +.
En Lisp, los argumentos de una funcin son tomos o listas que siguen a la
funcin. Los valores devueltos al evaluar esos tomos o listas se pasan a la
funcin. Diferentes funciones necesitan diferentes cantidades de argumentos;
algunas funciones no necesitan ninguno.

Tipos de datos
El tipo de datos que se debe pasar a una funcin depende de qu clase de
informacin utiliza. Por ejemplo, la funcin + utiliza valores numricos, pero por
ejemplo, la funcin concat, que sirve para unir dos o ms cadenas para producir
otra ms larga, necesitas cadenas. Al evaluar la expresin:
(concat "abc" "def")
El valor producido ser abcdef.
Por ejemplo, si evaluamos lo siguiente:
(substring "The quick brown fox jumped." 16 19)
aparecer en el rea de eco la cadena fox. Hay que observar que la cadena que
se pasa a substring es un nico tomo aunque est formada por una cadena de
varias palabras separadas por espacios.

Un argumento de valor de variable o lista


Un argumento pude ser un smbolo que devuelva un valor cuando se evale. Por
ejemplo, el smbolo fill-column cuando se evala devuelve un nmero. Ese
nmero se puede utilizar en una suma, por ejemplo. Si evaluamos la expresin:
(+ 2 fill-column)
El valor ser el resultado de sumar 2 al valor de esa variable. En mi caso ser
72. 70 es el valor de fill-column. Por ejemplo, podemos evaluar la siguiente
expresin.
(concat "The " (number-to-string (+ 2 fill-column)) " red foxes.")

22

CHAPTER 3. PROCESAMIENTO DE LISTAS

Al evaluar esta expresin hay que notar que hay espacios en blanco despus de la
palabra The y antes de la palabra red. La funcin number-to-string convierte
un nmero en una cadena. El resultado ser The 72 red foxes.

Cantidad variable de argumentos


Algunas funciones como concat, + o *, toman una cantidad variable de argumentos.
El primer grupo de expresiones, las funciones no tienen argumentos:
(+) 0
(*) 1
En este grupo, las funciones tienen un argumento:
(+ 3) 3
(* 3) 3
En este conjunto, las funciones tienen tres argumentos:
(+ 3 4 5) 12
(* 3 4 5) 60

Utilizar el Tipo errneo como argumento


Cuando a una funcin se le pasa un tipo equivocado, el intrprete de Lisp produce
un mensaje de error. Por ejemplo, la funcin + espera evaluar sus argumentos
como nmeros. Como experimento vamos a pasar el smbolo hello en lugar de
un nmero.
(+ 2 'hello)
Al evaluar la expresin obtenemos un *Backtrace* que dice:
---------- Buffer: *Backtrace* ---------Debugger entered--Lisp error: (wrong-type-argument number-or-marker-p hello)
+(2 hello)
eval((+ 2 (quote hello)) nil)
elisp--eval-last-sexp(nil)
eval-last-sexp(nil)
funcall-interactively(eval-last-sexp nil)
call-interactively(eval-last-sexp nil nil)
command-execute(eval-last-sexp)
---------- Buffer: *Backtrace* ---------La primera parte del error dice wrong-type-argument seguido de la jerga misteriosa de number-or-marker-p. La primera dice que se ha encontrado un tipo
de argumento errneo.

ARGUMENTOS

23

La p de number-or-marker-p es una costumbre que comenz en los primeros


das de la programacin Lisp y significa /predicado/2 . En la jerga de Lisp
un predicado es la referencia a una funcin que determina si una propiedad
es verdadera o falsa. Por ejemplo zerop es una funcin que determina si su
argumento es un valor cero y listp es una funcin que determina si su argumento
es una lista.
La expresin (quote hello) es la expresin que se encuentra bajo la abreviada
hello.

La funcin message
Como la funcin +, la funcin message toma un nmero variable de argumentos.
Se utiliza para enviar mensajes al usuario.
Un mensaje (message) es lo que aparece en el rea de eco. Por ejemplo, si
evaluamos lo sigiente:
(message "This message appears in the echo area!")
Aparecer en rea de eco la cadena entrecomillada completa.
(message "The name of this buffer is: %s." (buffer-name))
En este caso la funcin message toma dos argumentos una cadena y una expresin
que devuelve otra cadena: el nombre del buffer. Esa cadena se insertar en el
lugar donde la primera muestra el valor %s.
Para mostrar un valor como un entero, se utiliza %d igual que se utiliza %s. Por
ejemplo:
(message "The value of fill-column is %d." fill-column)
En este caso, el sistema mostrar al evaluar la lista The value of fill-column
is 70.
Tambin se pueden utilizar ms de un argumento en la funcin. Por ejemplo:
(message "There are %d %s in the office!"
(- fill-column 14) "pink elephants")
En la lnea de eco aparecer el mensaje There are 56 pink elephants in the
office!.
Igual que podemos calcular un nmero en lugar de pasrselo directamente,
tambin se puede sustituir la cadena por una expresin que al ser evaluada
produzca una cadena. Por ejemplo:
2 En la jerga de Lisp un predicado (predicate) es la referencia a una funcin para determinar
si una propiedad es verdadera o falsa. En este caso number-or-marker-p es el nombre de una
funcin que determina si el argumento que se ha pasado es un nmero o una marca.

24

CHAPTER 3. PROCESAMIENTO DE LISTAS

(message "He saw %d %s"


(- fill-column 32)
(concat "red "
(substring
"The quick brown foxes jumped." 16 21)
" leaping."))
El mensaje en este caso sera He saw 38 red foxes leaping.

Establecer un valor en una variable


Hay varias maneras de hacer que una variable tenga un determinado valor. Una
de las maneras es utilizar la funcin set o la funcin setq. Otro modo es utilizar
let. (La jerga para este proceso es atar una variable a un valor.

Utilizando set
Para establecer el valor del smbolo flowers a la lista (rose violet daisy
buttercup) se evaluara la siguiente expresin:
(set 'flowers '(rose violet daisy buttercup))
Despus de evaluar la expresin set podemos evaluar la expresin flowers y
nos devolver la lista que hemos fijado.
flowers
Si evaluamos flowers, la variable con una comilla delante de ella, lo que veremos
en el rea de eco el mismo smbolo flowers. Para probarlo, evaluar:
'flowers
Hay que observar que al utilizar set se necesita poner la comilla ambos argumentos de la funcin. Cuando utilizamos set sin la comilla, se evala el argumento
antes de hacer ninguna otra cosa. Si haces esto, se obtiene un mensaje de error
del tipo Symbol's value as variable is void; y por otra parte, flowers no
devuelve un valor que pueda ser evaluado despus.

Utilizando setq
Como un uso prctico, al menos debes utilizar siempre la comilla del primer
argumento, el que indica la variable, en la funcin set. Por ello es comn utilizar
la forma especial setq. Esta funciona como set excepto que el primer argumento
ser quoted automticamente, por ello no es necesario hacerlo nosotros.

25

RESUMEN

Por ejemplo, para establecer el valor de la variable carnivores a una lista (lion
tiger leopard) utilizando setq podemos utilizar la siguiente expresin:
(setq carnivores '(lion tiger leopard))
Si hubiramos utilizado la funcin set, la expresin sera:
(set 'carnivores '(lion tiger leopard))
Tambin, setq permite asignar diferentes valores a diferentes variables. El primer
argumento se ata al segundo argumento, el tercero se ata al cuarto y as en
adelante. Por ejemplo:
(setq trees '(pine fir oak maple)
herbivores '(gazelle antelope zebra))

Contar
Un ejemplo que muestra cmo se puede utilizar setq como un contador. Al
evaluar las siguientes expresiones podremos verlo.
(setq counter 0)
(setq counter (+ counter 1))
counter

; Let's call this the initializer.


; This is the incrementer.
; This is the counter.

Si se evala la primera expresin y despus la tercera, el valor que obtendremos


ser 0. Si evaluamos la segunda una vez y volvemos a evaluar la tercera, veremos
que el valor de counter ha cambiado a 1 y si repetimos el proceso, veremos que
cada vez que evaluamos la segunda expresin counter aumenta su valor en 1 y
cada vez que evaluemos la primera expresin, el valor de counter ser 0.
En el caso de la segunda expresin, setq lo que hace es evaluar la lista (+
counter 1) realizando la suma del valor actual de counter y 1. Ese resultado
se ata a continuacin con la variable counter de nuevo.

Resumen
Los programas Lisp estn compuestos de expresiones que son listas o
tomos.
Las listas estn compuestas de cero o ms tomos o listas internas separadas
por espacios en blanco encerradas entre parntesis. Una lista puede estar
vaca.
Los tomos son smbolos de varios caracteres, como forward-paragraph,
smbolos de un solo carcter +, cadenas de caracteres entre comillas o
nmeros.
Un nmero se evala como s mismo.
Una cadena entre comillas se evala tambin como s misma.

26

CHAPTER 3. PROCESAMIENTO DE LISTAS


Cuando se evala un smbolo, se devuelve su valor.
Cuando se evala una lista, el intrprete Lisp mira el primer smbolo de la
lista y toma la la definicin de funcin atada a ese smbolo. Esto es, las
instrucciones que definen la funcin.
Un apstrofe le dice al intrprete de Lisp que debe devolver la siguiente expresin como est escrito y no evaluarlo como hara si no hubiera
apstrofe.
Los argumentos pasan informacin a una funcin. Los argumentos a una
funcin se computan evaluando el resto de los elementos de la lista cuyo
primer elemento es la funcin.
Una funcin siempre devuelve un valor cuando se evala (a no se que se
genere un error). Adems puede acarrear algunas acciones como efectos
colaterales. En ocasiones, el propsito primario de una funcin es crear un
efecto colateral.

Ejercicios
Algunos ejercicios sencillos:
Genera un mensaje de error evaluando un smbolo correcto que no est
entre parntesis.
Genera un mensaje de error evaluando un smbolo correcto que est entre
parntesis.
Crea un contador que incremente por 2 en lugar de uno.
Escribe una expresin que muestre un mensaje en el rea de eco cuando se
evale.

Chapter 4

Practicar la evaluacin
Antes de meternos en definir funciones en Emacs Lisp, es mejor perder un poco
de tiempo en practicar la evaluacin de expresiones. Algunas funciones asociadas
con buffers son a la vez sencillas e interesantes.

Cmo evaluar
Cuando introduces un comando de edicin en Emacs Lisp, como puede ser mover
el cursor o deslizar la pantalla, ests evaluando una expresin.
Cuando tecleas algunas teclas haces que el intrprete de Lisp evale una expresin
y obtengas sus resultados. Incluso teclear texto plano implica evaluar una funcin
Emacs Lisp, en este caso una que usa self-insert-command, que nicamente
inserta el carcter que has pulsado.
Las funciones que evalas tecleando se llaman funciones interactivas o comandos.
Adems de teclear comandos por teclado, hay un segundo modo de evaluar una
expresin: colocar el cursor tras una lista y pulsar C-x C-e. Hay otras maneras
de evaluar una expresin, pero ya llegaremos a ellas.
En las siguientes secciones nos daremos cuenta de algunas funciones que nos
ayudarn a distinguir entre buffers y ficheros, como cambiar a un buffer y cmo
determinar la posicin en l.
27

28

CHAPTER 4. PRACTICAR LA EVALUACIN

Nombres de buffer
Las dos funciones buffer-name y buffer-file-name, muestra la diferencia entre
un fichero y un buffer. Cuando se evala la expresin (buffer-name) aparece
el nombre del buffer en el rea de eco. Si evaluamos (buffer-file-name)
aparecer el nombre del fichero. Cuando estamos editando un fichero la primera
devolver el nombre del fichero mientras que la segunda guarda el fichero con
todo el path.
(buffer-name)
(buffer-file-name)
Si utilizas C-u C-x C-e en lugar del habitual C-x C-e, el resultado aparece a
continuacin del cursor.

Obteniendo Buffers
La funcin buffer-name devuelve el nombre del buffer; para obtener el propio
buffer se necesita la funcin current-buffer. Al utilizar esta funcin en el
cdigo se obtiene el propio buffer.
(current-buffer)
Si evaluamos esa expresin de la manera habitual en Emacs, aparecer una
expresin como #<buffer nombre-buffer> en el rea de eco. Esto indica que
es el propio buffer lo que se ha obtenido.
Una funcin relacionada es other-buffer. Esto devuelve el buffer que se ha
utilizado recientemente.
(other-buffer)
Evaluando esto es posible que aparezca algo como #<buffer *scratch*> u otro
que se haya abierto el ltimo.

Cambiando de Buffer
La funcin switch-to-buffer cambia a otro buffer. Lo que hace la combinacin
de teclas C-x b es parecido:
(switch-to-buffer (other-buffer))
Si evaluamos esa expresin cambiaremos al ltimo buffer. En las siguientes
secciones se utilizar ms a menudo la funcin set-buffer. sta lo que hace es
cambiar el trabajo al buffer pero sin cambiarlo en pantalla. Cuando estamos
trabajando en un buffer y queremos ejecutar cdigo en otro, es mejor dejar la
ejecucin y los cambios fuera de la vista para no despistar al usuario.

TAMAO DE BUFFER Y LA LOCALIZACIN DEL PUNTO

29

Tamao de Buffer y la localizacin del punto


Algunas otras funciones sencillas pero muy utilizadas para moverse por los
buffers: buffer-size, point, point-min y point-max. La funcin buffer-size
devuelve el tamao del buffer.
(buffer-size)
La funcin point devuelve la posicin de carcter donde est situado el cursor.
(point)
La funcin point-min devuelve el valor mnimo permitido en el buffer actual.
Lo normal es que sea 1 a no ser que est activado el narrowing.1

Ejercicio
Carga un fichero con el que ests trabajando y muvete hacia el centro. Consigue
su nombre de buffer, nombre de fichero, tamao y posicin en el fichero.

1 /Narrowing/ es un mecanismo por el se puede restringirse el acceso a un programa u


operar slo con una parte de un buffer.

30

CHAPTER 4. PRACTICAR LA EVALUACIN

Chapter 5

Cmo escribir definicin de


funciones
Cuando el intrprete de Lisp evala una lista espera que el primer elemento es
una funcin. Un smbolo que contiene la definicin de una funcin se llama,
sencillamente, una funcin.

Un aviso sobre funciones primitivas


Todas las funciones estn definidas en base a otras funciones, excepto algunas
pocas funciones primitivas que estn escritas en lenguaje C. Las funciones
primitivas en C son idnticas a las que estn escritas en Emacs Lisp. Es decir,
cuando escribes cdigo en Emacs Lisp, no puedes distinguir las funciones que
estn escritas en C de las que estn escritas utilizando Emacs Lisp. La diferencia
es irrelevante, a no ser que quieras conocer en qu est escrita una funcin
concreta.

El macro defun
En Lisp, un smbolo como mark-whole-buffer tiene cdigo en l que le dice al
ordenador qu tiene que hacer cuando se llama a la funcin. Este cdigo se llama
definicin de la funcin y se crea evaluando una expresin Lisp que comienza
con el smbolo defun que es una abreviatura de define function.
Una definicin de funcin tiene cinco partes que siguen a la palabra defun:
1. El nombre del smbolo al que se debe atar la definicin de funcin.
31

32

CHAPTER 5. CMO ESCRIBIR DEFINICIN DE FUNCIONES


2. Una lista de los argumentos se hay que pasar a la funcin. Si no se pasan
argumentos a la funcin, esto tiene que ser una lista vaca ().
3. Documentacin describiendo la funcin. (Tcnicamente es opcional, pero
es muy recomendable).
4. Opcionalmente, una expresin para hacer la funcin interactiva y poder utilizarla tecleando M-x y el nombre de la funcin; o tecleando la combinacin
de teclas apropiadas.
5. El cdigo que da instrucciones al ordenador de qu hacer: el cuerpo (body)
de la definicin de funcin.

Ayuda a pensar que las cinco partes de la definicin de funcin estn ordenadas
como una plantilla como:
(defun FUNCTION-NAME (ARGUMENTS...)
"OPTIONAL-DOCUMENTATION..."
(interactive ARGUMENT-PASSING-INFO)
BODY...)

; optional

Un ejemplo. Cdigo para una funcin que multiplica su argumento por 7. (Este
ejemplo no es interactivo.)
(defun multiply-by-seven (number)
"Multiplica NUMBER por siete."
(* 7 number))
Esta definicin comienza con un parntesis y el smbolo defun seguido por el
nombre de la funcin: multiply-by-seven.
La lista de argumentos contiene slo number. Se podra haber utilizado otra
palabra como por ejemplo multiplicando. Sin embargo, number nos dice qu
tipo de valor se espera. Se podra haber utilizado foogle, pero sera una
mala opcin porque no le dira nada a quien leyera el cdigo. La eleccin del
nombre depende del programador, que debera elegir nombres que clarifiquen el
significado de la funcin.
Por otro lado, se puede utilizar cualquier nombre para un smbolo en la lista
de argumentos, porque esa lista es privada para esa funcin particular. Para
entenderlo, de una forma llana veamos un ejemplo. Vamos a suponer que en tu
familia te han puesto un mote o sobrenombre: Chiki. Cuando en tu familia
alguien se refiere a Chiki est aludiendo de forma inequvoca a ti. Sin embargo,
es posible que en otro sitio, por ejemplo, en una pelcula hagan referencia a un
personaje con el nombre de Chiki. An habiendo dos entidades con el mismo
nombre, no se genera ningn problema. En tu familia t eres Chiki.
A la lista de argumentos le sigue la cadena de documentacin que describe la
funcin. Esta es lo que obtienes cuando tecleas C-h f y el nombre de la funcin.
Si escribes cadenas de documentacin de varias lneas debes tener en cuenta
que algunos comandos como apropos slo muestran la primera lnea. Tambin
hay que recordar que no hay que indentar la segunda linea de la cadena, porque

INSTALAR LA DEFINICIN DE FUNCIN

33

puede obtenerse una mala visualizacin. La documentacin es opcional, pero es


tan utilizada que debera incluirla en toda funcin que escribas.
La tercera lnea del ejemplo consiste en el cuerpo de la definicin de la funcin.
En Emacs Lisp, * es la funcin de multiplicar, como + es la funcin de suma.
Cuando se utiliza la funcin multiply-by-seven, el argumento number se evala
al nmero que se pasa. Por ejemplo:
(multiply-by-seven 3)
En el ejemplo, se evala number a 3. Hay que observar que aunque en la definicin
de funcin number est entre parntesis, cuando utilizamos la funcin va sin
parntesis. Los parntesis se escriben en la definicin para decirle al ordenador
donde empieza y termina la lista de argumentos, y diferenciarla del resto de
partes.
Si evaluamos el ejemplo sin haber evaluado la definicin de funcin anterior
obtendremos un mensaje de error.

Instalar la definicin de funcin


Instalar la definicin de una funcin es bsicamente evaluar su definicin. Por
ejemplo, al evaluar
(defun multiply-by-seven (number)
"Multiplica NUMBER por siete."
(* 7 number))
se instala la funcin hasta que cierras Emacs.

El efecto de la instalacin
Puedes ver el efecto de instalar multiply-by-seven evaluando el siguiente
ejemplo pulsando C-x C-e. El nmero 21 aparecer en el rea de eco.
(multiply-by-seven 3)
Si quieres puedes leer la documentacin de la funcin tecleando C-h f
(describe-function) y el nombre de la funcin multiply-by-seven.
Aparecer una ventana *Help* que dice:
multiply-by-seven is a Lisp function.
(multiply-by-seven NUMBER)
Multiplica NUMBER por siete.

34

CHAPTER 5. CMO ESCRIBIR DEFINICIN DE FUNCIONES

Cambiar la definicin de funcin


Si quieres cambiar el cdigo de multiply-by-seven slo hay que reescribirla.
Para instalar la nueva versin en el sitio antiguo, evaluar la funcin de nuevo.
As es como se modifica cdigo en Emacs. Es muy simple.
Por ejemplo, vamos a cambiar multiply-by-seven por una versin que en lugar
de multiplicar suma siete veces el nmero dado. Produce la misma salida pero
de otra forma.
(defun multiply-by-seven (number)
; Second version.
"Multiplicar NUMBER por siete."
(+ number number number number number number number))
Los comentarios siguen al signo ;. En Lisp, todo lo que sigue en una lnea al
punto y coma, es un comentario.

Hacer una funcin interactiva


Puedes hacer una funcin interactiva colocando una lista que comience con la
forma especial interactive inmediatamente despus de la documentacin. Los
usuarios pueden invocar las funciones interactivas tecleando M-x y el nombre de
la funcin.
Cuando se llama a una funcin interactiva, el valor devuelto no se muestra
automticamente en el rea de eco. Esto es porque frecuentemente se llama a
una funcin interactiva por sus efectos colaterales, como mover el cursor adelante,
por ejemplo, y no por el valor devuelto.

multiply-by-seven interactiva
A continuacin se muestra tanto el uso especial de la forma interactive y una
manera de mostrar el valor en el rea de eco.
(defun multiply-by-seven (number)
; Interactive version.
"Multiply NUMBER by seven."
(interactive "p")
(message "The result is %d" (* 7 number)))
Al instalar el cdigo el nombre de la funcin aparecer en el rea de eco. Despus,
tecleando C-u y un nmero y despus M-x multiply-by-seven y pulsando
<RET>, aparecer el resultado en el rea de eco.
Hablando de forma general, puedes invocar una funcin como esta de dos
maneras:

DIFERENTES OPCIONES PARA INTERACTIVE

35

1. Tecleando un nmero como prefijo y despus teclear M-x y el nombre de la


funcin, como C-u 3 M-x nombre-funcin; o
2. Tecleando cualquier tecla o comando al que se ate la funcin, como por
ejemplo C-u 3 M-e.
Si utilizas C-u sin un nmero, por defecto ser 4. Otro modo es pulsar la
tecla Meta y el nmero deseado, como por ejemplo M-3 M-e o el largo M-x
nombre-funcin.

Versin interactiva de multiply-by-seven en detalle


Repasemos la funcin con un poco ms de detalle.
(defun multiply-by-seven (number)
; Interactive version.
"Multiply NUMBER by seven."
(interactive "p")
(message "The result is %d" (* 7 number)))
La expresin (interactive "p") es una lista de dos elementos. La p le dice a
Emacs que pase el prefijo como argumento para la funcin.
El argumento debe ser un nmero. Si por ejemplo el prefijo es un 5, el intrprete
de Lisp evala la ltima lnea como:
(message "The result is %d" (* 7 5))

Diferentes opciones para interactive


En el ejemplo, multiply-by-seven se utiliza p como un argumento para
interactive. Ese argumento le dice a Emacs que interprete lo que teclees,
tanto C-u y un nmero como <META> y un nmero, y que lo pase a la funcin
como argumento. Pero Emacs tiene ms de veinte caracteres predefinidos para
usar con interactive.
Consideremos la funcin zap-to-char. Su expresin interactiva es
(interactive "p\ncZap to char: ")
La primera parte del argumento de interactive es p, que ya nos es familiar.
Ese argumento le dice a Emacs que interprete un prefijo, como un nmero pasado
a la funcin. Puedes especificar un prefijo, como ya se ha visto con C-u y un
nmero.
Una funcin con dos o ms argumentos puede tener informacin pasada aadiendo partes a la cadena que sigue a interactive. Cuando se hace esto, cada
argumentos se pasa en el mismo orden especificado en la lista interactive. En
la cadena, cada parte est separada de la siguiente por \n, que es un salto de
lnea. Por ejemplo, se puede seguir p con un \n y un cZap to char: .

36

CHAPTER 5. CMO ESCRIBIR DEFINICIN DE FUNCIONES

Instalar cdigo permanentemente


Cuando instalas una definicin de funcin evalundola, permanecer instalada
hasta que se sale de Emacs. La prxima vez que se inicie una nueva sesin de
Emacs, la funcin no estar instalada a no ser que se evale la funcin de nuevo.
En algn punto, puedes querer tener cdigo instalado automticamente cada
vez que se inicie una nueva sesin de Emacs. Hay varias maneras de hacerlo:
El cdigo se puede poner la definicin de la funcin en el fichero de inicio
.emacs.
Tambin se pueden poner las definiciones de funciones que se quieran
instalar en uno o ms ficheros donde se quiera y utilizar la funcin load
que hace que Emacs evale e instale cada funcin de los ficheros.
Un tercer modo. Se puede poner en el fichero llamado site-init.el que
se carga cuando Emacs se hace. Esto hace que el cdigo est disponible
para todo el que use su mquina.
Finalmente, si tienes cdigo que cualquiera que utilice Emacs podra querer,
puedes ponerlo en una red o enviar una copia a la Free Software Foundation.

let
La expresin let es una forma especial en Lisp que necesitar utilizar en muchas
funciones.
let se utiliza para atar un smbolo a un valor de modo que el intrprete Lisp
no confunda la variable con una variable con el mismo nombre que no sea parte
de la funcin.
Para comprender por qu la forma especial let es necesaria: cuando estamos
en nuestra casa nos referimos a la casa, como en hay que pintar la casa. Si
ests visitando un amigo y l dice la casa se estar refiriendo a su casa, no a
la tuya, es decir, a una casa diferente.

let previene la confusin


La forma especial let previene la confusin. let crea un nombre para una
variable local que oculta cualquier uso del mismo nombre fuera de la expresin
let.
Las variables locales creadas por let retienen su valor solo con la expresin let
(y con expresiones llamadas en la expresin let); las varialbes locales no tienen
efectos fuera de la expresin let.

LET

37

Los valores fijados con let se descartan automticamente cuando acaba el let.
Por eso los efectos slo tienen efecto dentro de la expresin let.
let puede crear ms de una variable a la vez. Tambin proporciona un valor
inicial, tanto un valor especificado como nil. Despus de crear y asignar las
variables, let ejecuta el cdigo en el cuerpo y devuelve el valor de la ltima
expresin en el cuerpo.

Las partes de la expresin let


Una expresin let es una lista de tres partes. La primera parte es el smbolo
let. La segunda parte es una lista llamada varlist, cada elemento de la cual es
tanto un smbolo por s mismo o una lista de dos elementos, el primer elemento
es un smbolo. La tercera parte de la expresin let es el cuerpo. El cuerpo
normalmente consista en una o ms listas.
Una plantilla sera como esta:
(let VARLIST BODY...)
Una varlist puede parecerse a (thread (needles 3)). En este caso en una
expresin let, Emacs al smbolo thread lo ata con un valor nil y al smbolo
needles a un valor inicial de 3.
(let ((VARIABLE VALUE)
(VARIABLE VALUE)
...)
BODY...)

Ejemplo de expresin let


Por ejemplo:
(let ((zebra "stripes")
(tiger "fierce"))
(message "One kind of animal has %s and another is %s."
zebra tiger))
Aqu, la varlist es ((zebra "stripes") (tiger "fierce")). Cada variable,
zebra y tiger, es el primer elemento y cada valor es el segundo que cada par
de valores.
Si evaluamos el cdigo del ejemplo, obtenemos la siguiente frase en rea de eco:
"One kind of animal has stripes and another is fierce."

38

CHAPTER 5. CMO ESCRIBIR DEFINICIN DE FUNCIONES

Variables no inicializadas en una declaracin let


(let ((birch 3)
pine
fir
(oak 'some))
(message
"Here are %d variables with %s, %s, and %s value."
birch pine fir oak))
Si evaluamos esta expresin de la manera habitual, aparecer lo siguiente en el
rea de eco:
"Here are 3 variables with nil, nil, and some value."
En este ejemplo, Emacs asigna al smbolo birch el nmero 3, a pine y fir a
nil y a oak el valor de some.
Hay que observar que las variables pine y fir se encuentras solas como tomos
y no estas rodeadas de parntesis. Esto es porque se asignarn a nil, la lista
vaca. Pero oak se ata a some con la lista (oak 'some).

La forma especial if
La tercera forma especial, adems de defun y let, es la condicional if. Esta
forma se utiliza para dejar que el ordenador tome decisiones.
La idea bsica de if es que si una prueba es cierta, entonces se evala una
expresin. Si la prueba no es cierta la expresin no se evala.

Ms detalles de if
Una expresin if escrita en Lisp no utiliza la palabra then; la prueba y la
accin son el segundo y el tercer elemento de una lista cuyo primer elemento es
if.
(if PRUEBA-CIERTO-O FALSO
EJECUTAR-ACCION-SI-ES-CIERTO)
Un ejemplo que se puede evaluar de manera habitual. La prueba es si el nmero
5 es mayor que el nmero 4 y como es verdadero mostrar el mensaje 5 es
mayor que 4!.
(if (> 5 4)
(message "5 is greater than 4!"))

; parte-if
; parte-then

La funcin > comprueba si el primer argumento es mayor que su segundo


argumento y devuelve true si es as.

LA FORMA ESPECIAL IF

39

Por ejemplo, el valor puede ser pasado como un argumento a la definicin de


funcin. En la siguiente definicin de funcin, el carcter de animal es una valor
pasado a la funcin. Si el valor asignado a characteristic es fierce, entonces
se mostrar el mensaje It is a tiger!; de otro modo se devuelve nil.
(defun type-of-animal (characteristic)
"Print message in echo depending on CHARACTERISTIC.
If the CHARACTERISTIC is the string \"fierce\",
then warn of a tiger."
(if (equal characteristic "fierce")
(message "It is a tiger!")))
Un par de expresiones para evaluar y comprobar los resultados.
(type-of-animal "fierce")
(type-of-animal "striped")
Cuando evaluamos la primera debemos obtener It is a tiger! y si evaluamos
la segunda debemos obtener nil.

La funcin type-of-animal al detalle


La funcin type-of-animal est escrita rellenando las partes de dos plantillas.
Una es la definicin de la funcin y la otra es la de la expresin if.
La plantilla para toda funcin que no es interactiva es:
(defun NOMBRE-FUNCION (ARGUMENT-LIST)
"DOCUMENTACION..."
CUERPO...)
Las partes que coinciden con esta plantillas seran:
(defun type-of-animal (characteristic)
"Print message in echo area depending on CHARACTERISTIC.
If the CHARACTERISTIC is the string \"fierce\",
then warn of a tiger."
CUERPO: LA EXPRESION if)
La plantilla para una expresin if sera:
(if PRUEBA-CIERTO-O-FALSO
EJECUTAR-ACCION-SI-PRUEBA-DEVUELVE-TRUE)
En la funcin type-of-animal, la parte del cdigo if.
(if (equal characteristic "fierce")
(message "It is a tiger!"))
Aqu, la expresin de prueba de verdadero-falso es:
(equal characteristic "fierce")

40

CHAPTER 5. CMO ESCRIBIR DEFINICIN DE FUNCIONES

En Lisp equal es una funcin que determina si su primer argumento es igual a


su segundo argumento. El segundo argumento es la cadena fierce y el primer
argumento es el smbolo characteristic, el argumento pasado a esta funcin.

Expresiones if-then-else
Una expresin if puede tener un tercer argumento llamado else-part, para el
caso de que la prueba devuelva false.
La palabra else no se escribe en el cdigo Lisp; la else-part de una expresin
if aparece tras la then-part.
(if TRUE-OR-FALSE-TEST
EJECUTAR-ACCION-SI-PRUEBA-DEVUELVE-TRUE
EJECUTAR-ACCION-SI-PRUEBA-DEVUELVE-FALSE)
Por ejemplo, la siguiente expresin muestra un mensaje 4 no es mayor que 5!
cuando se evala:
(if (> 4 5)
(message "4 falsely greater than 5!")
(message "4 is not greater than 5!"))

; if-part
; then-part
; else-part

Verdad y falsedad en Emacs Lisp


Hay un importante aspecto de la prueba de una expresin if. Hemos hablado
de los valores true y false de los predicados como si fueran nuevos tipos de
objetos de Emacs Lisp. De hecho, false es nuestro viejo amigo nil. Cualquier
otro valor es true.
Cualquier expresin de prueba se interpreta como true si el resultado de su
evaluacin es un valor que no es nil. En otras palabras, el test considerado true
si devuelve un valor como 47, una cadena como hola o un smbolo (que no sea
nil) como flowers, o una lista (que no sea una lista vaca) o incluso un buffer!

Una explicacin sobre nil


En Emacs Lisp, el smbolo nil tiene dos significados. Primero, significa una lista
vaca. Segundo, significa false y se devuelve cuando una prueba se considera
falsa. nil se puede escribir como una lista vaca, () o como nil.
Por ejemplo, si se pone un nmero en el lugar de la prueba, cuando se evale se
devolver a s mismo, pues eso es lo que hacen los nmeros. En ese condicional,
la expresin if ser evaluada como true.

SAVE-EXCURSION

41

Por ejemplo:
(if 4
'true
'false)
(if nil
'true
'false)
Por ejemplo, la expresin (> 5 4) devuelve t cuando se evala.
(> 5 4)
Por otro lado, esta funcin devuelve nil si la prueba es falsa.
(> 4 5)

save-excursion
La funcin save-excursion es la tercera y ltima forma especial que discutiremos
en este captulo.
En programas Emacs Lisp utilizados para edicin, la funcin save-excursion
es bastante habitual. Guarda la localizacin del punto, ejecuta el cuerpo de la
funcin y devuelve el punto a su posicin anterior si la localizacin ha cambiado.
Su propsito primario es que el usuario no se sorprenda ante un movimiento
inesperado del punto.

Punto y marca
Antes de comentar save-excursion vamos a ver lo que son el punto y la marca
en GNU Emacs. El Punto es la posicin actual del cursor. Donde est el
cursor ah est el Punto. En los terminales donde el cursor aparece como un
recuadro, el punto est inmediatamente antes de ese cuadro. En Emacs Lisp, el
punto es un entero. El primer carcter en el buffer es el uno, el segundo es el
dos y as en adelante. La funcin point devuelve la posicin actual del cursor.
Cada buffer tiene su correspondiente valor de punto.
La marca es otra posicin en el buffer; su valor se fija con el comando C-<SPC>
(set-mark-command). Si se ha fijado la marca, se puede utilizar el comando C-x
C-x (exchange-point-and-mark) para hacer que el cursor salte a la marca y
ponga la marca en la posicin previa del punto. Adems, si se fija otra marca, la
posicin de la marca previa se guarda en el anillo de marcas. Se puede marcar
posiciones de marcas de esta manera. Puedes hacer saltar el cursor a una marca
guardada tecleando C-u C-<SPC> una o ms veces.

42

CHAPTER 5. CMO ESCRIBIR DEFINICIN DE FUNCIONES

La zona comprendida entre el punto y la marca se llama la regin.


Muchos comandos trabajan con la regin, incluyendo center-region,
count-lines-region, kill-region o print-region.
La forma especial save-excursion guarda la posicin del punto y restaura esta
posicin despus de que el cuerpo de la funcin especial se evale en el intrprete
Lisp.
Adems de guardar el valor del punto, save-excursion mantiene una huella del
buffer actual y lo restaura tambin.

Plantilla para la expresin save-excursion


La plantilla de cdigo utilizada por save-excursion es sencilla:
(save-excursion
CUERPO...)
El cuerpo de la funcin puede ser una o ms expresiones que se evaluarn secuencialmente por el intrprete de Lisp. Si hay ms de una expresin en el cuerpo, el
valor de la ltima se devolver como valor de la funcin save-excursion.
Con ms detalle, la plantilla ser:
(save-excursion
PRIMERA-EXPRESION-EN-CUERPO
SEGUNDA-EXPRESION-EN-CUERPO
TERCERA-EXPRESION-EN-CUERPO
...
ULTIMA-EXPRESION-EN-CUERPO)
Una expresin, por supuesto, puede ser un smbolo o una lista.
En el cdigo Emacs Lisp, una expresin save-excursion a menudo ocurre con
una expresin let. Como sigue:
(let VARLIST
(save-excursion
CUERPO...))

Resumen
eval-last-sexp Evala la ltima expresin simblica anterior a la posicin del
punto. El valor se muestra en el rea de eco a no ser que se invoque la
funcin con un argumento; en ese caso, la salida se imprime directamente
en el buffer. El comando normalmente est atado a C-x C-e.

RESUMEN

43

defun Definir funcin. Esta macro tiene cinco partes: el nombre, una plantilla
para los argumentos que se pasarn a la funcin, documentacin, una
declaracin interactiva opcional y el cuerpo de la definicin.
Por ejemplo, en Emacs la definicin de la funcin dired-unmark-all-marck
es como sigue.
(defun dired-unmark-all-marks ()
"Remove all marks from all files in the Dired buffer."
(interactive)
(dired-unmark-all-files ?\r))
interactive Declara al intrprete que la funcin se puede usar interactivamente.
Esta forma especial puede estar seguida de una cadena con una ms partes
que pasan informacin a los argumentos de la funcin en secuencia. Estas
partes pueden tambin decirle al intrprete que pregunte por informacin.
Las partes de la cadena se deben separar con saltos de lnea \n.
Caracteres de cdigo comunes:
b El nombre de un buffer existente.
f El nombre de un fichero existente.
p El argumento prefijo numrico. (Hay que observar que esta p es minscula.)
r El punto y la marca como dos argumentos numricos, el ms pequeo
primero. Esta es la nica letra cdigo que especifica dos argumentos
sucesivos en lugar de uno.
let Declara que una lista de variables es para usarse en el cuerpo de let y les da
un valor inicial, bien nil u otro valor; luego evala el resto de expresiones
en el cuerpo de let y devuelve el ltimo. Dentro del cuerpo de let, el
intrprete de Lisp no mira los valores de las variables con el mismo nombre
que se encuentren fuera del let.
Por ejemplo:
(let ((foo (buffer-name))
(bar (buffer-size)))
(message
"This buffer es %s and has %d characters."
foo bar))
save-excursion Guarda el valor del punto y el buffer actual antes de evaluar
el cuerpo de esta forma especial. Restaura el valor del punto y el buffer
despus.
Por ejemplo:
(message "We are %d characters into this buffer."
(- (point)

44

CHAPTER 5. CMO ESCRIBIR DEFINICIN DE FUNCIONES


(save-excursion
(goto-char (point-min)) (point))))

if Evala el primer argumento de la funcin; si es true evala el segundo


argumento; de otro modo evala el tercer argumento, si existe.
La forma especial if se llama condicional. Hay otros condicionales en
Emacs Lisp, pero if es quizs el ms usado normalmente.
(if (= 22 emacs-major-version)
(message "This is version 22 Emacs")
(message "This is not version 22 Emacs"))
<
>
<=
>= La funcin < prueba si el primer argumento es menor que el segundo. La
funcin > comprueba que el primer argumento es mayor que el segundo.
De forma parecida <= comprueba que el primer argumento es menor o
igual que el segundo y >= comprueba que el primer argumento es mayor
o igual que el segundo. En todos los casos los dos argumentos deben ser
nmeros o marcas (indicando posiciones en buffers).
= La funcin = comprueba que los dos argumentos, ambos nmeros o marcas,
son iguales.
equal
eq Comprueba que dos objetos son iguales. equal devuelve true si dos objetos
tienen una estructura y contenidos similares, como dos copias del mismo
libro. Por otro lado, eq devuelve true si ambos argumentos son el mismo
objeto.
string<
string-lessp
string=
string-equal La funcin string-lessp comprueba cuando su primer argumento es ms pequeo que el segundo. Un nombre alternativo ms corto
es string<.
Los argumentos de string-lessp deben ser cadenas o smbolos; el orden
es lexicogrfico, por eso las maysculas son significativas.
Una cadena vaca ==, es ms pequea que cualquier cadena de caracteres.
string-equal proporciona el correspondiente test de igualdad. Su alternativa ms corta es string=. No hay funciones equivalentes para cadenas
que correspondan a >, >= o <=.

EJERCICION

45

message Muestra un mensaje en el rea de eco. El primer argumento es una


cadena que puede contener %s, %d o %c para imprimir el valor de los
argumentos que siguen a la cadena. El argumento usado por %s debe
ser una cadena o un smbolo; el argumento utilizado por %d debe ser un
nmero. El argumento utilizado por %c debe ser un cdigo numrico ASCII;
se imprimir como el carcter con el cdigo ASCII.
setq
set La funcin setq fija el valor de su primer argumento al valor del segundo
argumento. El primer argumento es automticamente comillado (quoted)
por setq. Hace lo mismo para sucesivos pares de arguemntos. La otra
funcin, set toma slo dos argumentos y evala ambos antes de fijar el
valor devuelto por su primer argumento al valor devuelto por su segundo
argumento.
buffer-name Sin argumento, devuelve el nombre del buffer, como cadena.
buffer-file-name Sin argumento, devuelve en nombre del fichero del buffer
visitado.
current-buffer Devuelve el buffer que est activo en Emacs; puede no ser el
buffer que est visible en la pantalla.
other-buffer Devuelve el buffer ms recientemente seleccionado.
switch-to-buffer Selecciona un buffer de Emacs para activarlo y mostrarlo
en ventana para que el usuario lo pueda ver. Normalmente atado a C-x b.
set-buffer Cambia la atencin de Emacs a un buffer en el que el programa
correr. No altera lo que est mostrando la ventana.
buffer-size Devuelve el nmero de caracteres en el buffer actual.
point Devuelve la posicin actual del cursor, como un entero contando el nmero
de caracteres desde el inicio del buffer.
point-min Devuelve el valor mnimo permisible para el punto en el buffer actual.
Ser 1, a no ser que est activado el narrowing.
point-max Devuelve el valor mximo permisible del punto en el buffer actual.
Ser el final del buffer, a no ser que est activado el narrowing.

Ejercicion
Escribe una funcin no interactiva que duplique el valor de su argumento,
un nmero. Haz esta funcin interactiva.

46

CHAPTER 5. CMO ESCRIBIR DEFINICIN DE FUNCIONES


Escribe una funcin que compruebe si el valor actual de fill-column es
mayor que el argumento pasado a la funcin, y si es as que imprima un
mensaje apropiado.

Chapter 6

Unas pocas funciones


relacionadas con los buffers
En este captulo veremos en detalle varias funciones utilizadas en GNU Emacs.
Estas funciones se utilizarn como ejemplos de cdigo Lisp, pero no son ejemplos
imaginarios. Estas funciones son cdigo utilizado actualmente en GNU Emacs.

Buscando ms
En este paseo se describirn las funciones a veces en detalle y a veces brevemente.
Si te interesa obtener una documentacin completa de cualquier funcin de
Emacs Lisp lo puedes hacer tecleando C-h f y luego el nombre de la funcin.
Tambin, describe-function te dir el lugar de la definicin de la funcin.
De manera ms general, si quieres ver una funcin en su fichero fuente original,
puedes utilizar la funcin xref-find-definitions para saltar a ella.
Para utilizar el comando xref-find-definitions, teclee M-. (o pulsa <ESC>
y luego el punto) y luego en el prompt teclea el nombre de la funcin de la que
quieres ver el cdigo fuente, como por ejemplo mark-whole-buffer y despus
pulsa <RET>.
Los ficheros que contienen cdigo Lisp son convencionalmente llamados libreras. En el Manual de GNU Emacs se puede encontrar que el comando C-h p
te permite buscar en las libreras estndar de Emacs Lisp por los nombres clave.
47

48CHAPTER 6. UNAS POCAS FUNCIONES RELACIONADAS CON LOS BUFFERS

Una definicin simplificada de beginning-of-buffer


El comando beginning-of-buffer es una buena funcin para comenzar puesto
que es algo fcil de entender a estas alturas. beginning-of-buffer mueve el
cursor al inicio del buffer, dejando la marca en la posicin anterior. Generalmente
est atada a M-<.
Antes de mirar el cdigo, vamos a considerar qu debe contener la definicin:
debe incluir una expresin que haga la funcin interactiva para poder llamarla
tecleando M-x beginning-of-buffer o tecleando la combinacin M-<; debe
contener cdigo que deje la marca en la posicin original en el buffer; y debe
incluir el cdigo para mover el cursor al inicio del buffer.
He aqu una versin completa abreviada de la funcin:
(defun simplified-beginning-of-buffer ()
"Move point to the beginning of the buffer;
leave mark at previous position."
(interactive)
(push-mark)
(goto-char (point-min)))
Como todas las definiciones de fucin, esta definicin tiene cinco partes siguiendo
al macro defun:
1.
2.
3.
4.
5.

El nombre: en este ejemplo, simplified-beginning-of-buffer.


Una lista de argumentos: en este ejemplo, una lista vaca, ().
La cadena de documentacin.
La expresin interactive.
El cuerpo.

La expresin interactiva le dice a Emacs que la funcin se puede utilizar interactivamente. En este ejemplo, interactive no tiene argumentos porque
simplified-beginning-of-buffer no requiere ninguno.
El cuerpo de la funcin consiste en dos lneas:
(push-mark)
(goto-char (point-min))
La primera de estas lneas es la expresin (push-mark). Cuando se evala,
establece la marca en la posicin actual del cursor. La posicin de esta marca se
guarda en el anillo de marcas.
La siguiente lnea es (goto-char (point-min)). Esta expresin hace saltar el
cursor al punto mnimo del buffer, es decir, al inicio del mismo.
Cuando ests leyendo cdigo y llegas a una funcin no familiar, como goto-char,
puedes encontrar el comando describe-function. Para usar este comando
teclea C-h f y despus el nombre de la funcin. Por ejemplo, la documentacin
para goto-char es:

LA DEFINICIN DE MARK-WHOLE-BUFFER

49

Set point to POSITION, a number or marker.


Beginning of buffer is position (point-min), end is (point-max).
La definicin de funcin end-of-buffer est escrita de la misma manera que
beginning-of-buffer, excepto que en el cuerpo de la funcin se encuentra la
expresin (goto-char (point-max)) en lugar de (goto-char (point-min)).

La definicin de mark-whole-buffer
La funcin mark-whole-buffer no es ms complicada de entender que la funcin
simplified-beginning-of-buffer. En este caso, sin embargo, veremos la
funcin completa, no la versin simplificada.

Un vistazo a mark-whole-buffer
En GNU Emacs 22, el cdigo completo de lafuncin es como sigue:
(defun mark-whole-buffer ()
"Put point at beginning and mark at end of buffer.
You probably should not use this function to use any subroutine
that uses or sets the mark."
(interactive)
(push-mark (point))
(push-mark (point-max) nil t)
(goto-char (point-min)))
Al nombre de la funcin (mark-whole-buffer) lo sigue una lista de argumentos
vaca (), que significa que la funcin no necesita argumentos. A continuacin va
la cadena de documentacin. La siguiente lnea indica a Emacs que se puede
utilizar interactivamente y luego est el cuerpo, que lo veremos en el siguiente
punto.

El cuerpo de mark-whole-buffer
El cuerpo de la funcin mark-whole-buffer consiste en tres lneas de cdigo:
(push-mark (point))
(push-mark (point-max) nil t)
(goto-char (point-min))
La primera lnea es la expresin (push-mark (point)), que como ya vimos
antes hace que el intrprete de Lisp fije una marca en la posicin actual del
cursor. Aunque en la anterior ocasin apareca push-mark sin argumento. Quiz
sea porque en esta ocasin el que escribi la funcin pens que el argumento no
era opcional o porque quera guardar la misma estructura que las otras lneas.

50CHAPTER 6. UNAS POCAS FUNCIONES RELACIONADAS CON LOS BUFFERS


En cualquier caso, la lnea hace que Emacs determine la posicin del punto y la
establezca como una marca.
En la lnea (push-mark (point-max) nil t), la funcin push-mark tiene dos
argumentos ms. El segundo argumento (nil) le dice a la funcin que debe
mostrar el mensaje =Mark set= cuando empuja (pushes) la marca. El tercer
argumento es t. Esto le dice a push-mark que active la marca al activarse el
Transient Mark mode. Este modo ilumina la regin activa actual.
Finalmente, la ltima lnea (goto-char (point-min)) mueve el cursor la punto
mnimo del buffer, es decir, al principio del buffer (o al principio de la parte
accesible del buffer).
Como resultado, el punto se sita al inicio del buffer y la marca al final, por lo
que el buffer completo es la regin.

La definicin de append-to-buffer
El comando append-to-buffer es ms complejo que el anterior. Lo que hace
es copiar la regin desde el buffer actual a un buffer especificado.

Un vistazo a append-to-buffer
El comando append-to-buffer utiliza la insert-buffer-substring que copia
la regin y la inserta en otro buffer. La mayor parte de append-to-buffer se
dedica a establecer las condiciones para que trabaje insert-buffer-substring.
(defun append-to-buffer (buffer start end)
"Append to specified buffer the text of the region.
It is inserted into that buffer before its point.
When calling from a program, give three arguments:
BUFFER (or buffer name), START and END.
START and END specify the portion of the current buffer to be copied."
(interactive
(list (read-buffer "Append to buffer: " (other-buffer
(current-buffer) t))
(region-beginning) (region-end)))
(let ((oldbuf (current-buffer)))
(save-excursion
(let* ((append-to (get-buffer-create buffer))
(windows (get-buffer-window-list append-to t t))
point)
(set-buffer append-to)
(setq point (point))

LA DEFINICIN DE APPEND-TO-BUFFER

51

(barf-if-buffer-read-only)
(insert-buffer-substring oldbuf start end)
(dolist (window windows)
(when (= (window-point window) point)
(set-window-point window (point))))))))
La primera lnea de la funcin incluye su nombre y tres argumentos. Los
argumentos son el buffer en el que se copiar el texto y en inicio (start) y el final
(end) de la regin que se copiar del buffer actual.
La siguiente parte de la funcin es la documentacin que es clara y completa.
Es una convencin escribir los nombres de los argumentos en maysculas para
verlos rpidamente. Adems siempre es mejor citarlos en el mismo orden que
aparecen en la lista de argumentos.
Hay que hacer notar que la documentacin distingue entre un buffer y su nombre.

La expresin interactiva de append-to-buffer


Puesto que la funcin append-to-buffer se utilizar de forma interactiva, debe
llevar una expresin interactive. La expresin es:
(interactive
(list (read-buffer
"Append to buffer: "
(other-buffer (current-buffer) t))
(region-beginning)
(region-end)))
Esta expresin no tiene ninguna letra marcando partes, como se dijo antes. En
su lugar, comienza una lista con estas partes:
La primera parte de la lista es una expresin para leer el nombre de un buffer
y devolverlo como una cadena. Esto es read-buffer. La funcin requiere
un prompt como primer argumento ("Append to buffer: "). Su segundo
argumento le dice qu valor pasarle si no se especifica ninguno. En este caso
el segundo argumento utiliza la funcin other-buffer, una excepcin y una t
(por true).
(other-buffer (current-buffer) t)
El primer argumento de other-buffer, la excepcin, es otra funcin
current-buffer. Que no ser devuelto. El true dice que other-buffer
puede mostrar los buffers visibles, excepto en este caso, el buffer actual
(current-buffer).
La segunda y tercera expresiones de list son (region-beginning) y
(region-end). Estas dos funciones especifican el inicio y el final del texto que
se aadir.

52CHAPTER 6. UNAS POCAS FUNCIONES RELACIONADAS CON LOS BUFFERS


Originalmente, el comando utilizado utiliza las letras B y r y la expresin
interactive completa apareca como sta:
(interactive "BAppend to buffer: \nr")
Pero esto haca que el valor por defecto del cambio de buffer fuera invisible. Lo
que no es deseable.

El cuerpo de append-to-buffer
El cuerpo de append-to-buffer comienza con let.
La plantilla de
append-of-buffer con la expresin let hace que la funcin quede as:
(defun append-to-buffer (buffer start end)
"DOCUMENTATION..."
(interactive...)
(let ((VARIABLE VALOR))
CUERPO...))
La expresin let tiene tres elementos:
1. El smbolo let.
2. Una lista de variables conteniendo en este caso una simple lista de dos
valores (VARIABLE VALOR).
3. El cuerpo de la expresin let.
En la funcin append-to-buffer la lista de variables aparece como:
(oldbuf (current-buffer))
En esta parte, la nica variable oldbuf se ata al valor devuelto por la expresin
(current-buffer). La variable oldbuf se utiliza para manejar el buffer en el
que ests trabajando y desde el que se copiar.
El elemento o elementos de la lista de variables se encerrarn entre parntesis
para que el intrprete de Lisp pueda distinguir entre la lista de variables y el
cuerpo de let. La lnea se ver as:
(let ((oldbuf (current-buffer)))
... )

save-excursion en append-to-buffer
El cuerpo de la expresin let en append-to-buffer consiste en una expresin
save-excursion.
La funcin save-excursion guarda la localizacin del punto y lo restaura
despus de que el cuerpo de save-excursion se ejecute por completo. Adems

LA DEFINICIN DE APPEND-TO-BUFFER

53

save-excursion mantiene referencia el buffer original y lo restaura. Por eso se


utiliza en esta funcin.
En la convencin de formato es fcil ver que las lneas de save-excursion en el
cuerpo se encierran entre parntesis asociados con save-excursion, igual que
save-excursion se encuentra encerrado entre los parntesis asociados con let:
(let ((oldbuf (current-buffer)))
(save-excursion
...
(set-buffer ...)
(insert-buffer-substring oldbuf start end)
...))
El uso de la funcin save-excursion se puede ver como un proceso de completar
los huecos de una plantilla:
(save-excursion
PRIMERA-EXPRESION-EN-CUERPO
SEGUNDA-EXPRESION-EN-CUERPO
...
ULTIMA-EXPRESION-EN-CUERPO)
En esta funcin, save-excursion contiene slo una expresin let*, que es un
poco diferente de let. Tiene un * en su nombre. Permite a Emacs establecer
cada variable en su lista en secuencia, una tras la otra. Lo veremos ms delante.
De momento nos saltaremos let* y nos centraremos en dos funciones ms
set-buffer e insert-buffer-substring.
En los viejos das, la expresin set-buffer era simplemente
(set-buffer (get-buffer-create buffer))
pero ahora es
(set-buffer append-to)
La definicin de la funcin append-to-buffer inserta texto desde el buffer
actual a otro buffer por nombre. Ocurre que insert-buffer-substring copia
texto desde otro buffer al buffer actual, justo al revs que append-to-buffer.
La expresin insert-buffer-substring aparece como:
(insert-buffer-substring oldbuf start end)
La funcin insert-buffer-substring copia una cadena desde el buffer especificado en su primer argumento y la inserta en el buffer actual. En este caso, el
argumento para insert-buffer-substring es el valor de la variable creada y
atada en let, el valor de oldbuf, que contendr el buffer actual cuando se tome
el comando append-to-buffer.
Despus de que insert-buffer-substring haya hecho su trabajo,
save-excursion restaurar el buffer original y append-to-buffer habr hecho

54CHAPTER 6. UNAS POCAS FUNCIONES RELACIONADAS CON LOS BUFFERS


su trabajo.
Escrito de forma esquemtica, el trabajo del cuerpo sera como ste:
(let (ATAR-oldbuf-AL-VALOR-DE-current-buffer)
(save-excursion
; Keep track of buffer.
CAMBIAR-BUFFER
INSERTAR-CADENA-DESDE-oldbuf-EN-BUFFER)
VOLVER-AL-BUFFER-ORIGINAL-CUANDO-TERMINE
ENTONCES-EL-SIGNIFICADO-LOCAL-DE-oldbuf-DESAPARECE-CUANDO-TERMINA
En resumen, append-to-buffer funciona como sigue: guarda el buffer actual en
la variable llamada oldbuf. Obtiene un buffer nuevo (creando uno si es necesario)
y cambia la atencin de Emacs a l. Utiliza el valor de oldbuf para insertar
la regin de texto del buffer anterior al nuevo; y utilizando save-excursion
vuelve a su buffer original.
Mirando en append-to-buffer, se ha explorado una bonita y compleja funcin.
Muestra el uso de let y save-excursion y como regresar desde otro buffer.
Muchas funciones utilizan let, save-excursion y set-buffer de este modo.

Resumen
describe-function
describe-variable Muestra la documentacin de una funcin o variable. Convencionalmente atados a C-h f y C-h v.
find-tag Busca el fichero que contiene el cdigo fuente de una funcin o variable
y cambia los buffers a l, posicionando el punto al comiendo del tem.
Convencionalmente M-..
save-excursion Guarda la posicin del punto y restaura su valor despus de
que se hayan evaluado sus argumentos. Tambin recuerda el buffer actual
y regresa a l.
push-mark Pone marca en una posicin y graba el valor de la marca previa en
el anillo de marcas. La marca es una posicin en el buffer que mantendr
su posicin relativa incluso si se aade o borra texto del buffer.
goto-char Pone el punto en la posicin especificada por el valor del argumento,
que debe ser un nmero, una marca, o una expresin que devuelva el
nmero de una posicin, como puede ser point-min.
insert-buffer-substring Copia una regin de texto desde un buffer que se
pasa a la funcin como argumento e inserta la regin en el buffer actual.

EJERCICIOS

55

mark-whole-buffer Marca como regin el buffer completo. Normalmente atado


a C-x h.
set-buffer Cambia la atencin de Emacs a otro buffer, pero no cambia lo que
se est mostrando por ventana. Utilizado cuando el programa en lugar de
un humano est trabajando en un buffer diferente.
get-buffer-create
get-buffer Busca un buffer por el nombre o crea uno si no existe ese nombre
de buffer. La funcin get-buffer devuelve nil si no existe el nombre del
buffer.

Ejercicios
Escribe tu propia definicin de funcin simplified-end-of-buffer y
luego prueba si funciona.
Utiliza if y get-buffer para escribir una funcin que muestre un mensaje
dicindote si existe un buffer.
Utiliza find-tag, busca el cdigo fuente para la funcin copy-to-buffer.

56CHAPTER 6. UNAS POCAS FUNCIONES RELACIONADAS CON LOS BUFFERS

Chapter 7

Algunas funciones ms
complejas
En este captulo aprenderemos mirando en algunas funciones complejas. La
funcin copy-to-buffer ilustra el uso de dos expresiones save-excursion en
una definicin mientras la funcin insert-buffer ilustra el uso de un asterisco
en una expresin interactive, uso de or y la importante diferencia entre un
nombre y el objeto al que se refiere el nombre.

La definicin de copy-to-buffer
Despus de comprender cmo trabaja append-to-buffer es fcil comprender
copy-to-buffer. Esta funcin copia texto en un buffer pero en lugar de aadirlo
al segundo buffer, reemplaza todo el texto anterior del segundo buffer.
El cuerpo de copy-to-buffer aparece sigue,
...
(interactive "BCopy to buffer: \nr")
(let ((oldbuf (current-buffer)))
(with-current-buffer (get-buffer-create buffer)
(barf-if-buffer-read-only)
(erase-buffer)
(save-excursion
(insert-buffer-substring oldbuf start end)))))
La funcin copy-to-buffer tiene una expresin interactive ms sencilla que
append-to-buffer.
La definicin entonces dice
57

58

CHAPTER 7. ALGUNAS FUNCIONES MS COMPLEJAS

(with-current-buffer (get-buffer-create buffer) ...


Primero mira a la primera expresin interna, que se evaluar primero. Esta
expresin comienza con get-buffer-create buffer. La funcin le dice al
ordenador que use el buffer con el nombre especificado en el que se copiar, o si
ese buffer no existe, lo crea. Entonces, la funcin with-current-buffer evala
su cuerpo con ese buffer temporalmente.
La funcin barf-if-buffer-read-only te enva un mensaje de error diciendo
que el buffer es de slo lectura si t no puedes modificarlo.
La siguiente lnea tiene la funcin erase-buffer como nico contenido. Esta
funcin borra el buffer.
Finalmente, las ltimas dos lneas contienen la expresin save-excursion con su
cuerpo con insert-buffer-substring. La expresin insert-buffer-substring
copia el texto desde el buffer en el que ests.
Para reemplazar texto, Emacs borra el texto previo y despus inserta el texto
nuevo.
En resumen, el cuerpo de copy-to-buffer sera esto:
(let (ATA-oldbuf-AL-VALOR-DE-buffer-actual)
(CON-EL-BUFFER-AL-QUE-ESTS-COPIANDO
(PERO-NO-BORRAR-O-COPIAR-EN-UN-BUFFER-DE-SOLO-LECTURA)
(erase-buffer)
(save-excursion
INSERTAR-SUBCADENA-DESDE-oldbuf-EN-BUFFER)))

Definicin completa de beginning-of-buffer


Como se ha dicho antes cuando se invoca beginning-of-buffer mueve el cursor
al comienzo del buffer (bueno, en realidad al principio de la parte editable del
buffer). Se puede llamar a esta funcin con el comando M-<, que mueve el cursor
al comienzo del buffer, o con un comando como puede ser C-u 7 M-< que mueve
el cursor a un punto en el 70% del buffer. Si se pasa un nmero mayor de 10 se
mueve al final del buffer.
La funcin beginning-of-buffer se puede llamar con o sin argumento. El uso
de argumento es opcional.

Argumentos opcionales
Lisp espera que una funcin con un argumento en la definicin se llame con un
valor para ese argumento. Si eso no ocurre se obtendr un error que dice Wrong
number of arguments.

DEFINICIN COMPLETA DE BEGINNING-OF-BUFFER

59

Sin embargo, una de las propiedades de Lisp son los argumentos opcionales: se
utiliza una palabra clave para indicarle al intrprete de Lisp que un argumento
es opcional. Esa palabra clave es &opcional. El & delante es parte de la palabra
clave. En la definicin de una funcin si el argumento sigue a la palabra clave
&opcional no se necesita pasar un valor cuando se llama a esa funcin.
La primera lnea de la funcin sera:
(defun beginning-of-buffer (&optional arg)
Un esquema de la funcin completa sera como:
(defun beginning-of-buffer (&optional arg)
"DOCUMENTACION..."
(interactive "P")
(or (EL-ARGUMENTO-ES-UNA-CONS-CELL arg)
(and ESTAN-ACTIVOS-TRANSIENT-MARK-MODE-Y-MARK-ACTIVE-TRUE)
(push-mark))
(let (DETERMINAR-TAMAO-Y-FIJARLO)
(goto-char
(SI-HAY-UN-ARGUMENTO
CALCULAR-DONDE-IR
ELSE-IR
(point-min))))
DO-NICETY
La funcin es similar a simplified-beginning-of-buffer excepto que la expresin interactive tiene un argumento P y la funcin goto-char est seguida
por una expresin if-then-else que calcula donde poner el cursor si hay un
argumento que no es una cons cell.
La P en la expresin interactive le dice a Emacs que pase un argumento, si
hay uno, a la funcin.
La prueba de verdadero o falso en la expresin if parece compleja, pero no lo
es: comprueba cuando arg tiene un valor que no sea nil ni una cons cell. Si
no hay argumento el punto sencillamente ser point-min que se pasar y la
expresin quedara como (goto-char (point-min)) que es como aparece en su
modo simplificado.

beginning-of-buffer con un argumento


Cuando se llama a beginning-of-buffer con un argumento, se evala una
expresin que calcula qu valor pasar a goto-char. Esta expresin es menos
complicada de lo que parece. Incluye una expresin if y un poco de aritmtica.
Se parece a esto:
(if (> (buffer-size) 10000)
;; Avoid overflow for large buffer sizes!

60

CHAPTER 7. ALGUNAS FUNCIONES MS COMPLEJAS


(* (prefix-numeric-value arg)
(/ size 10))

(/
(+ 10
(*
size (prefix-numeric-value arg))) 10)))
Desentraar beginning-of-buffer

(if (BUFFER-ES-GRANDE
DIVIDIR-TAMAO-BUFFER-POR-10-Y-MULTIPLICAR-POR-ARG
DE-OTRO-MODO-UTILIZAR-CALCULO-ALTERNATIVO
La razn para esto es que las versiones de Emacs anteriores a las 18 utiliza
nmeros no superiores a los ocho millones. Por esto el programador previniendo
el overflow.
Hay dos casos: el buffer es grande o no lo es.
Qu ocurre con un buffer grande
En beginning-of-buffer, el interior de la expresin if comprueba si el tamao
del buffer es mayor que 10.000 caracteres. Para hacer esto, utiliza la funcin > y
el clculo de size.
(if (> size 10000)
Cuando el buffer es grande la parte de la expresin que se evala es
(*

(prefix-numeric-value arg)
(/ size 10))

La expresin es una multiplicacin, con dos argumentos para la funcin *. Que


podramos resumir as:
(goto-char (* (prefix-numeric-value arg)
(/ size 10)))
Esto pone el cursor donde se queremos.
Qu ocurre en un buffer pequeo
Si el buffer contiene menos de 10.000 caracteres se realiza un clculo significativamente distinto. En un buffer pequeo el primer mtodo puede no poner el
cursor exactamente en la lnea deseada.
El cdigo sera el siguiente:

DEFINICIN COMPLETA DE BEGINNING-OF-BUFFER

61

(/ (+ 10 (* size (prefix-numeric-value arg))) 10))


Es ms fcil de leer si indentamos cada expresin ms profundamente que la que
la rodea:
(/
(+ 10
(*
size
(prefix-numeric-value arg)))
10))
Mirando los parntesis la operacin ms interna es prefix-numeric-value arg
que convierte el argumento en un nmero. En la siguiente expresin, se multiplica
por el tamao de la parte accesible del buffer:
(* size (prefix-numeric-value arg))
El nmero que resulta de todo este proceso se pasa a goto-char y el cursor se
mueve a ese punto.

beginning-of-buffer completa
Aqu la funcin completa:
(defun beginning-of-buffer (&optional arg)
"Move point to the beginning of the buffer;
leave mark at previous position.
With \\[universal-argument] prefix,
do not set mark at previous position.
With numeric arg N,
put point N/10 of the way from the beginning.
If the buffer is narrowed,
this command uses the beginning and size
of the accessible part of the buffer.
Don't use this command in Lisp programs!
\(goto-char (point-min)) is faster
and avoids clobbering the mark."
(interactive "P")
(or (consp arg)
(and transient-mark-mode mark-active)
(push-mark))
(let ((size (- (point-max) (point-min))))
(goto-char (if (and arg (not (consp arg)))
(+ (point-min)
(if (> size 10000)

62

CHAPTER 7. ALGUNAS FUNCIONES MS COMPLEJAS


;; Avoid overflow for large buffer sizes!
(* (prefix-numeric-value arg)
(/ size 10))
(/ (+ 10 (* size (prefix-numeric-value arg)))
10)))
(point-min))))
(if (and arg (not (consp arg))) (forward-line 1)))

Excepto por dos pequeos detalles, ya hemos visto cmo funciona la funcin.
El primer detalle es la cadena rara en la documentacin:
\\[universal-argument]
Se utiliza \\ antes del primer corchete. Le dice al intrprete de Lisp la clave
que est entre los corchetes [...]. En este caso universal-argument que
normalmente es C-u pero puede ser diferente.
Finalmente, la ltima lnea que dice que mueva el punto al comienzo la siguiente
lnea si el comando se invoca con un argumento:
(if (and arg (not (consp arg))) (forward-line 1))
La parte (not (consp arg)) es para cuando se especifica el comando con C-u,
pero sin un nmero, el comando no colocar el cursor al comienzo de la segunda
lnea.

Resumen
or Evala cada argumento secuencialmente y devuelve el valor del primer
argumento que no sea nil. Si ningn valor es distinto de nil, devolver
nil. En resumen, devuelve un valor true si uno o ms elementos son
true.
and Evala cada argumento secuencialmente y si cualquiera es nil devolver
nil; si no hay ninguno nil devolver el valor del ltimo argumento. En
resumen, devolver un valor true slo si todos los argumentos son true.
&optional Una palabra clave que indica que un argumento en una funcin es
opcional; esto significa que la funcin se puede evaluar sin el argumento, si
se quiere.
prefix-numeric-value Convierte un argumento prefijo crudo producido por
(interactive "P") en un valor numrico.
forward-line Mueve el punto hacia adelante al principio de la lnea siguiente.
erase-buffer Borra todo el contenido del buffer actual.
bufferp Devuelve t si su argumento es un buffer; si no devolver nil.

EJERCICIO DE ARGUMENTO OPTIONAL

63

Ejercicio de argumento optional


Escribe una funcin interactiva con un argumento opcional si su argumento
(un nmero) es mayor, igual o menor del valor fill-solumn y lo diga con un
mensaje. Si no se pasa argumento, la funcin utilizar 56 como valor por defecto.

64

CHAPTER 7. ALGUNAS FUNCIONES MS COMPLEJAS

Chapter 8

Narrowing y Widening
Narrowing (estrechamiento) es una caracterstica de Emacs que hace posible que
centrarte en una parte especfica de un buffer y trabajar sin cambiar accidentalmente otras partes. Normalmente est desactivada porque puede ser confuso
para novatos.

Ventajas del narrowing


Con narrowing el resto del buffer se hace invisible, como si no estuviera ah.
Esto es una ventaja si, por ejemplo, quieres reemplazar una palabra en una parte
del buffer y no en otras. Las bsquedas slo funcionarn dentro de una regin
estrechada. La tecla atada para narrow-to-region es C-x n n.
Puesto que el narrowing oculta el resto del buffer, puede asustar a la gente que
sin querer invoque el estrechamiento y piense que ha borrado parte de su fichero.
Adems, el comando undo (normalmente C-x u) no desactiva el narrowing (ni
debera hacerlo). Para volver a hacer visible el resto del buffer se utiliza el
comando widen, normalmente atada a la combinacin C-x n w.

La forma especial save-restriction


En Emacs Lisp puedes utilizar la forma especial save-restriction mantener
bajo control cundo est el narrowing activo. Cuando el intrprete de Lisp se
encuentra con save-restriction, ejecuta el cdigo en el cuerpo de la expresin
save-restriction y entonces deshace cualquier cambio que cause el cdigo en
el estrechamiento.
La plantilla de la expresin save-restriction es simple:
65

66

CHAPTER 8. NARROWING Y WIDENING

(save-restriction
BODY... )
El cuerpo de save-restriction es una o ms expresiones que el intrprete Lisp
evaluar secuencialmente.
Cuando utilizas save-excursion y save-restriction, una despus de la otra,
deberas utilizar save-excursion en el exterior. Si las escribes en el orden
inverso, puede fallar el registro del narrowing. La plantilla para utilizar las dos
sera:
(save-excursion
(save-restriction
BODY...))
En otras circunstancias, cuando no las escribes juntas, se pueden escribir en el
orden apropiado a la funcin. Por ejemplo:
(save-restriction
(widen)
(save-excursion
BODY...))

what-line
El comando what-line te dice el nmero de la lnea en la que se sita el cursor.
La funcin ilustra el uso de los comandos save-restriction y save-excrusion.
El cdigo original de la funcin:
(defun what-line ()
"Print the current line number (in the buffer) of point."
(interactive)
(save-restriction
(widen)
(save-excursion
(beginning-of-line)
(message "Line %d"
(1+ (count-lines 1 (point)))))))
En versiones recientes de GNU Emacs la funcin se ha expandido un poco y es
ligeramente distinta.
La funcin what-line muestra una lnea de documentacin y es interactiva. Las
siguientes dos lneas utilizan las funciones save-restriction y widen.
La forma especial save-restriction anota si est activo el narrowing en el
buffer y restaura ese estrechamiento despus de que se evale el cdigo en el
cuerpo de save-restriction.

EJERCICIO CON NARROWING

67

La forma especial widen deshace cualquier narrowing en el buffer actual cuando


se llama a what-line. Esto hace posible que se puedan contar las lneas
desde el principio del buffer. Hay que observar que al situarse widen entre
save-restriction y save-excursion se pueden utilizar en ese orden, mientras
que si no estuviera sera recomendable que save-restriction fuera interna a
save-excursion.
Despus se llama a la funcin save-excursion que guarda la posicin del cursor
y lo restaura despus de la ejecucin.
Las dos lneas del final cuenta el nmero de lneas en el buffer y muestra en
nmero en el rea de eco.
(message "Line %d"
(1+ (count-lines 1 (point)))))))
Lo que hace es contar las lneas desde la primera posicin del buffer, indicado
por el 1 hasta el punto (point) y luego aade uno a ese nmero. La funcin 1+
aade uno a sus argumentos. Se aade uno porque la lnea 2, por ejemplo, slo
tiene un lnea anterior y count-lines cuenta slo las lneas anteriores a la lnea
actual.

Ejercicio con Narrowing


Escribir una funcin que mostrar los primeros 60 caracteres del buffer actual, incluso si se ha estrechado el buffer. Restaurando el punto, la marca y
el estrechamiento. Para este ejercicio se necesitar el popurr completo de funciones, incluyendo save-restriction, widen, goto-char, point-min, message
y buffer-substring.

68

CHAPTER 8. NARROWING Y WIDENING

Chapter 9

Funciones fundamentales
car, cdr, cons
En Lisp, las funciones car, cdr y cons son fundamentales. La funcin cons se
utiliza para construir listas y las funciones car y cdr se utilizan para tomar
partes de ellas.

Nombres raros
El nombre de cons es una abreviatura de construct (construccin). El origen
de los nombres car y cdr es ms esotrico: car es un acrnimo de la frase
/Contents of the Address part of the Register/ y cdr de la frase /Contents
of the Decrement part of the Register/. Estas frases se refieren a partes del
hardware de los inicios de la computacin de cuando se desarroll el Lisp original.
Aunque estn obsoletas, las frases se mantienen en uso.

car y cdr
El CAR de una lista es muy simple, el primer tem de la lista. As el CAR de la
lista (rose violet daisy buttercup) es rose.
Claramente, un nombre ms razonable para la funcin car sera first como se
ha sugerido frecuentemente.
car no quita el primer elemento de la lista, slo informa cul es. Despus de
que se aplique car a una lista, la lista contina siendo la misma. En la jerga,
car es no destructiva. Esta caracterstica ser importante.
69

70

CHAPTER 9. FUNCIONES FUNDAMENTALES CAR, CDR, CONS

El CDR de una lista es el resto de la lista despus del primer elemento. Esto
quiere decir, con el mismo ejemplo anterior que si el CAR de la lista (rose
violet daisy buttercup) era rose el CDR es (violet daisy buttercup).
Claramente, un nombre ms razonable para cdr sera rest.
Cuando car y cdr se aplican a la lista de smbolos, como la lista (pine fir
oak maple), el elemento de la lista devuelto por la funcin car es el smbolo
pine sin ningn parntesis. Sin embargo, el CDR de la lista es una lista tambin
(fir oak maple).
Por otro lado, en una lista de listas, el primer elemento es una lista. car devolver
el primer elemento como una lista. Por ejemplo:
(car '((lion tiger cheetah)
(gazelle antelope zebra)
(whale dolphin seal)))
Puesto que car y cdr no son destructivas, no modifican las listas a las que se
aplican. Esto es muy importante para cmo se usan.
Las funciones car y cdr se utilizan para separar listas y se consideran fundamentales en Lisp. Aunque no pueden separar o tener acceso a las partes de un
array; un array se considera un tomo. La otra funcin cons puede construir
una lista, pero no un array.

cons
La funcin cons construye listas, sera la inversa de car y cdr. Por ejemplo,
cons puede utilizarse para hacer una lista de cuatro elementos desde una lista
de tres elementos (fir oak maple):
(cons 'pine '(fir oak maple))
Como car y cdr, cons es no destructiva.

Hacer una lista


cons necesita una lista a la que aadir. No puede comenzar absolutamente de
cero. Si ests haciendo una lista, necesitas proporcionar por lo menos una lista
vaca al principio. Lo que aparece tras es lo que aparecer en el rea de eco si
se evala la expresin de arriba.
(cons 'buttercup ())
(buttercup)
(cons 'daisy '(buttercup))
(daisy buttercup)

NTHCDR

71

(cons 'violet '(daisy buttercup))


(violet daisy buttercup)
(cons 'rose '(violet daisy buttercup))
(rose violet daisy buttercup)
Como se puede ver, una lista vaca no se muestra en la lista que se construir.
Todo lo que se obtiene es (buttercup). La lista vaca no cuenta como elemento
de una lista porque no hay nada en una lista vaca. De forma general, una lista
vaca es invisible.

Saber el largo de una lista: length


Se puede saber cuntos elementos hay en la lista usando la funcin de Lisp
length como en los siguientes ejemplos:
(length '(buttercup))
1
(length '(daisy buttercup))
2
(length '(cons 'violet '(daisy buttercup)))
3
En el tercer ejemplo a length se le pasa el resultado de evaluar la expresin
cons.
El tamao de una lista vaca ser cero:
(length ())
0
La funcin length necesita un argumento. Si se la evala sin argumentos se
lanzar un mensaje de error:
(length )
Lisp error: (wrong-number-of-arguments length 0)

nthcdr
La funcin nthcdr est asociada con la funcin cdr. Lo que hace es tomar el
CDR de una lista repetidamente.

72

CHAPTER 9. FUNCIONES FUNDAMENTALES CAR, CDR, CONS

Si tomas el CDR de la lista (pine fir oak maple), obtendrs la lista (fir
oak maple). Si lo repites otra vez obtienes (oak maple). Si continas llegars
a obtener () o dicho de otro modo nil.
Por ejemplo:
(cdr (cdr '(pine fir oak maple)))
(oak maple)
La funcin nthcdr hace lo mismo repitiendo la llamada a cdr. En el siguiente
ejemplo, se pasa el argumento 2 antes de la lista y el valor devuelto es la listas
sin los dos primeros tems, que es lo mismo que repetir dos veces cdr en la lista:
(nthcdr 2 '(pine fir oak maple))
(oak maple)
Utilizando la lista de cuatros elementos original podemos ver los siguientes
resultados para entender cmo funciona.
;; Dejar la lista como es
(nthcdr 0 '(pine fir oak maple))
(pine fir oak maple)
;; Devolver una copia sin el primer elemento
(nthcdr 1 '(pine fir oak maple))
(fir oak maple)
;; Devolver una copia sin los primeros tres elementos
(nthcdr 3 '(pine fir oak maple))
(maple)
;; Devolver una lista sin los cuatro elementos
(nthcdr 4 '(pine fir oak maple))
nil
;; Devolver una lista sin ningn elemento
(nthcdr 5 '(pine fir oak maple))
nil

nth
La funcin nth devuelve un determinado elemento de la lista.
Esta funcin est definida en C por cuestin de velocidad, pero si no lo estuviera,
su cdigo sera algo as:
(defun nth (n list)
"Returns the Nts element of LIST.

SETCAR

73

N counts from zero. If LIST is not that long, nil is returned."


(car (nthcdr n list)))
La funcin nth devuelve un nico elemento de la lista. Y hay que observar que
se numera desde cero, no desde uno. Esto quiere decir que el primer elemento
de una lista es el 0.
Por ejemplo:
(nth 0 '("one" "two" "three"))
"one"
(nth 1 '("one" "two" "three"))
"two"
Hay que fijarse en que tanto nth, como nthcdr y cdr no hacen cambios en la
lista origial, son funciones no destructivas.

setcar
Tal como se puede sospechar por los nombres, setcar y setcdr son funciones
que establecen el CAR y el CDR de una lista a un nuevo valor. Al contrario que
car y cdr s modifican la lista original.
Vamos a verlo con un ejemplo que se pueda evaluar en Emacs.
(setq animals '(antelope giraffe lion tiger))
Si evaluamos la lnea anterior el intrprete atar la lista pasada a la variable
animals. As si evaluamos
animals
(antelope giraffe lion tiger)
A continuacin utilizaremos la funcin setcar con dos argumentos, la variable
animals y el smbolo hippopotamus con apstrofe.
(setcar animals 'hippopotamus)
Si evaluamos ahora la variable animals obtendremos (hippopotamus giraffe
lion tiger). La funcin setcar ha sustituido antelope con hippopotamus:
ha cambiado la lista.

setcdr
La funcin setcdr es similar a la funcin setcar excepto que la funcin reemplaza
desde el segundo al ltimo elemento. Por ejemplo:

74

CHAPTER 9. FUNCIONES FUNDAMENTALES CAR, CDR, CONS

(setq domesticated-animals '(horse cow sheep goat))


si evaluamos la expresin domesticated-animals nos devolver la lista (horse
cow sheep goat).
Vamos a cambiar todos los valores, excepto el primero por la lista (cat dog).
(setcdr domesticated-animals '(cat dog))
Si evaluamos ahora la variable domesticated-animals nos devolver la lista
(horse cat dog). Es decir, el CDR de la lista se ha cambiado de (cow sheep
goat) a (cat doc).

Ejercicio
Construye una lista de cuatro pjaros, evaluando algunas expresiones con cons.
Mira a ver qu ocurre cuando pasas cons a una lista consigo misma. Reemplaza
el primer elemento de la lista de cuatro pjaros con un pez. Reemplaza el resto
de esa lista con otro pez.

Chapter 10

Cortar y almacenar texto


Cuando cortas texto de un buffer con un comando kill en GNU Emacs, se
almacena en una lista que puedes retornar con un comando yank.

Almacenando texto en una lista


Cuando se corta texto de un buffer, se almacena en una lista. Sucesivos trozos
de texto se almacenan en la lista sucesivamente, as la lista puede parecer como
sigue:
("a piece of text" "previous piece")
La funcin cons se podra utilizar para crear una nueva lista desde un trozo de
texto (un tomo, para utilizar la jerga) y una lista existente, como esto:
(cons "another piece"
'("a piece of text" "previous piece"))
Y podemos obtener una cadena cualquiera de esa lista con, por ejemplo, el
siguiente cdigo:
(car (nthcdr 1 '("another piece"
"a piece of text"
"previous piece")))
"a piece of text"
Las actuales funciones de Emacs son ms complejas que esto, por supuesto. Por
ejemplo, cuando se alcanza el final de la lista de textos borrados, Emacs vuelve
al primer elemento en lugar de no devolver nada.
La lista que almacena los trozos de texto se llama kill ring (anillo de borrado).
La utilizaremos para trazar cmo trabaja la funcin zap-to-char. Esta funcin
75

76

CHAPTER 10. CORTAR Y ALMACENAR TEXTO

llama a una funcin que invoca una funcin que manipula el kill ring.

zap-to-char
Vamos a echar un vista a la funcin interactiva zap-to-char.

La implementacin completa de zap-to-char


La funcin zap-to-char borra el texto en la regin entre el cursor (es decir, el
punto) y la siguiente ocurrencia de un determinado carcter. El texto borrado
se sita en el kill ring y se puede recuperar tecleando C-y (yank).
Si el carcter especificado no se encuentra zap-to-char mostrar un mensaje
de Search failed.
Veamos la implementacin completa de la funcin en la versin 22.
(defun zap-to-char (arg char)
"Kill up to and including ARG'th occurrence of CHAR.
Case is ignored if `case-fold-search' is non-nil in the current buffer.
Goes backward if ARG is negative; error if CHAR not found."
(interactive "p\ncZap to char: ")
(if (char-table-p translation-table-for-input)
(setq char (or (aref translation-table-for-input char) char)))
(kill-region (point) (progn
(search-forward (char-to-string char)
nil nil arg)
(point))))
La cadena de documentacin utiliza el acento grave y el apstrofe para poner
comilla simple a un smbolo. Emacs, normalmente los cambia por .

La expresin interactive
La expresin interactive en el comando zap-to-char aparece como:
(interactive "p\ncZap to char: ")
La parte entre comillas especificar dos cosas diferentes. Primero, y ms simple,
es la p. Esta parte separada de la siguiente por una nueva lnea \n. La p significa
que el primer argumento a la funcin pasar un valor a processed prefix. El
argumento prefijo se pasar tecleando C-u y un nmero o M- y un nmero. Si la
funcin se llama interactivamente sin un prefijo, se pasar uno en ese argumento.

ZAP-TO-CHAR

77

El resto de la expresin, cZap to char: indica con c que se espera un carcter


mediante un prompt Zap to char: con un espacio despus de los dos punto
para que se vea mejor.
En los buffers de slo lectura, zap-to-char copia el texto al kill ring pero no
lo borra. Se muestra un mensaje diciendo que el buffer es de slo lectura.

El cuerpo de zap-to-char
El cuerpo de la funcin zap-to-char contiene cdigo que mata (esto es, borra)
el texto en la regin entre la posicin del cursor y el caracter especificado.
La primera parte del cdigo sera:
(if (char-table-p translation-table-for-input)
(setq char (or (aref translation-table-for-input char) char)))
(kill-region (point) (progn
(search-forward (char-to-string char) nil nil arg)
(point)))
char-table-p es una funcin que no hemos utilizado hasta ahora. Determina si
su argumento es un carcter de la tabla.
(point) es la posicin actual del cursor.
La siguiente parte del cdigo es una expresin utilizando progn. El cuerpo de
progn consiste en llamar a search-forward y point. Es ms fcil comprender
cmo funciona progn despus de aprender algo sobre search-forward, por eso
veremos sta ltima primero.

La funcin search-forward
La funcin search-forward se utiliza para localizar el carcter hasta el que
borrar en zap-to-char. Si la bsqueda tiene xito, search-forward deja el
punto inmediatamente despus del en la cadena objetivo.
En zap-to-char, la funcin search-forward aparece as:
(search-forward (char-to-string char) nil nil arg)
La funcin search-forward toma cuatro argumentos:
1. El primer argumento es el objetivo que se buscar.
Ocurre que el argumento pasado a zap-to-char es un slo carcter. Dentro
del ordenador, un carcter tiene una estructura diferente a una cadena de
un slo carcter. Sin embargo, la funcin espera una cadena, esa conversin
la hace char-to-string.

78

CHAPTER 10. CORTAR Y ALMACENAR TEXTO


2. El segundo argumento se especifica como una posicin en el buffer. En este
caso, la bsqueda puede ir al final del buffer, por eso no se limita poniendo
el segundo argumento a nil.
3. El tercer argumento le dice a la funcin qu debe hacer si la bsqueda falla.
Puede sealar un error (y mostrar un mensaje) o puede devolver nil. Un
nil como tercer argumento hace que la funcin seale un error cuando la
bsqueda falla.
4. El cuarto argumento es las veces que debe buscar la cadena. Este argumento
es opcional y si se llama la funcin sin l, el valor por defecto es 1. Si el
argumento es negativo, la bsqueda se realizar hacia atrs.

Una plantilla para la expresin search-forward sera:


(search-forward "CADENA-OBJETIVO"
LMITE-DE-BSQUEDA
QU-HACER-SI-BSQUEDA-FALLA
REPETIR-VECES)

La forma especial progn


progn es una forma especial que hace que sus argumentos se evalen secuencialmente y luego devuelva el valor del ltimo. Las expresiones precedentes
se evalan solo por los efectos colaterales que realicen. Los valores pueden
descartarse.
La plantilla de una expresin progn es muy simple:
(progn
BODY...)
En zap-to-char la expresin progn tiene que hacer dos cosas: poner el punto
exactamente en la posicin correcta y devolver el lugar del punto para que
kill-region sepa hasta dnde borrar.
El primer argumento de progn es search-forward. Cuando search-forward
busca la cadena de un carcter, sita el punto inmediatamente despus del
carcter encontrado. Si la bsqueda es hacia atrs, seach-forward deja el punto
antes. El movimiento del punto es un efecto colateral.
El segundo (y ltimo) argumento es la expresin (point). Esta expresin
devuelve el valor del punto, que en este caso ser la posicin a la que se ha
movido por search-forward. El valor de point que devuelve la expresin progn
se pasa a kill-region como segundo argumento.

KILL-REGION

79

Resumiendo zap-to-char
Ahora que ya est claro cmo funcionan search-forward y progn podemos
comprender cmo funciona zap-to-char.
El primer argumento a kill-region es la posicin del cursor cuando se llama
la funcin zap-to-char. Despus se realiza la bsqueda y el valor del punto
cambia. La nueva posicin se pasa como segundo argumento y la funcin borrar
la regin.
La forma especial progn es necesaria porque el comando kill-region toma dos
argumentos y lo que hace es agrupar en uno slo, la bsqueda de search-forward
y la expresin del resultado point, que se pasar como segundo argumento.

kill-region
Esta funcin corta texto de una regin y copia ese texto en el kill ring, desde
el que se puede recuperar.
En esencia, la funcin kill-region llama a condition-case que toma tres
argumentos. En esta funcin el primer argumento no hace nada. El segundo
argumento contiene el cdigo que hace el trabajo cuando todo va bien. El tercero
contienen el cdigo que se llama si ocurre un error.

La definicin completa de kill-region


Un vistazo a todo el cdigo y sus comentarios:
(defun kill-region (beg end)
"Kill (\"cut\") text between point and mark.
This deletes the text from the buffer and saves it in the kill ring.
The command \\[yank] can retrieve it from there. ... "
;; * Since order matters, pass point first.
(interactive (list (point) (mark)))
;; * And tell us if we cannot cut the text.
;; 'unless' is an 'if' without a then-part.
(unless (and beg end)
(error "The mark is not set now, so there is no region"))
;; * 'condition-case' takes three arguments.
;;
If the first argument is nil, as it is here,
;;
information about the error signal is not
;;
stored for use by another function.
(condition-case nil

80

CHAPTER 10. CORTAR Y ALMACENAR TEXTO

;; * The second argument to 'condition-case' tells the


;;
Lisp interpreter what to do when all goes well.
;;
;;
;;
;;
;;
;;
;;

It starts with a 'let' function that extracts the string


and tests whether it exists. If so (that is what the
'when' checks), it calls an 'if' function that determines
whether the previous command was another call to
'kill-region'; if it was, then the new text is appended to
the previous text; if not, then a different function,
'kill-new', is called.

;;
;;
;;

The 'kill-append' function concatenates the new string and


the old. The 'kill-new' function inserts text into a new
item in the kill ring.

;;
'when' is an 'if' without an else-part. The second 'when'
;;
again checks whether the current string exists; in
;;
addition, it checks whether the previous command was
;;
another call to 'kill-region'. If one or the other
;;
condition is true, then it sets the current command to
;;
be 'kill-region'.
(let ((string (filter-buffer-substring beg end t)))
(when string
;STRING is nil if BEG = END
;; Add that string to the kill ring, one way or another.
(if (eq last-command 'kill-region)
;; - 'yank-handler' is an optional argument to
;;
'kill-region' that tells the 'kill-append' and
;;
'kill-new' functions how deal with properties
;;
added to the text, such as 'bold' or 'italics'.
(kill-append string (< end beg) yank-handler)
(kill-new string nil yank-handler)))
(when (or string (eq last-command 'kill-region))
(setq this-command 'kill-region))
nil)
;; * The third argument to 'condition-case' tells the interpreter
;;
what to do with an error.
;;
The third argument has a conditions part and a body part.
;;
If the conditions are met (in this case,
;;
if text or buffer are read-only)
;;
then the body is executed.
;;
The first part of the third argument is the following:
((buffer-read-only text-read-only) ;; the if-part
;; ... the then-part
(copy-region-as-kill beg end)

KILL-REGION

81

;;
Next, also as part of the then-part, set this-command, so
;;
it will be set in an error
(setq this-command 'kill-region)
;;
Finally, in the then-part, send a message if you may copy
;;
the text to the kill ring without signaling an error, but
;;
don't if you may not.
(if kill-read-only-ok
(progn (message "Read only text copied to kill ring") nil)
(barf-if-buffer-read-only)
;; If the buffer isn't read-only, the text is.
(signal 'text-read-only (list (current-buffer)))))

condition-case
Normalmente cuando Emacs Lisp falla evaluando una expresin para el programa
y muestra un mensaje. Sin embargo, algunos programas cuando emprenden
acciones complicadas, no pueden simplemente devolver un error.
Con la funcin kill-region puede ocurrir que se intente borrar texto en un
buffer de solo lectura y no se pueda. Por eso kill-region tiene cdigo que
maneja esa circunstancia. Ese cdigo, se encuentra dentro de la forma especial
condition-case.
La plantilla de condition-cases es:
(condition-case
VAR
BODYFORM
ERROR-HANDLER...)
El segundo argumento, BODYFORM, es claro.
La forma especial
condition-case hace que el intrprete de Lisp evale el cdigo de BODYFORM.
Dicho de otro modo, la parte BODYFORM de una expresin condition-case
determina qu ocurrir cuando todo funciona correctamente.
Sin embargo, si ocurre un error, la funcin genera una seal de error que definir
una o ms nombres de condiciones de error.
El tercer argumento es un manejador de errores. Un manejador de errores tiene
dos partes un NOMBRE-CONDICION y un CUERPO.

Lisp macro
La parte del cdigo de la expresin condition-case que se evala cuando todo
va bien tiene un when. El cdigo utiliza when para determinar si la variable
string apunta a texto que existe.

82

CHAPTER 10. CORTAR Y ALMACENAR TEXTO

Una expresin when simplemente es una conveniencia del programador. Es un


if sin la posibilidad de una clusula else. Se puede reemplazar mentalmente
when con if y comprender lo que hace. Eso es lo que hace el intrprete de Lisp.
Tcnicamente hablando, =when" es un macro de Lisp. Un macro de Lisp permite
definir nuevos operadores de control y otras caractersticas del lenguaje. Le dice
al intrprete como debe procesar otra expresin Lisp cuando se evala. En este
caso la otra expresin es un if.
La definicin de la funcin kill-region tambin tiene un macro unless; que
es la complementaria de when. El macro unless es un if sin la clusula then.
Si miramos el macro when en la expresin condition-case, cuando una cadena
tiene contenido, se evala otra expresin condicional. Esta es un if con las dos
partes (then y else).
(if (eq last-command 'kill-region)
(kill-append string (< end beg) yank-handler)
(kill-new string nil yank-handler))
La parte then se evala si el comando anterior es una llamada a kill-region;
si no, se evala la parte else.
last-command es una variable de Emacs que no hemos visto antes. Normalmente,
cuando se ejecuta una funcin, Emacs establece el valor de last-command al
comando previo.
yank-handler es un argumento opcional que dice a las funciones kill-append y
kill-new cmo tratar con propiedades aadidas al texto, como negrita o cursiva.
Si el comando anterior fue kill-region. Lo que se hace es concatenar una copia
del texto cortado de nuevo al que ya estaba en el kill ring.
(kill-append string (< end beg) yank-handler)

copy-region-as-kill
La funcin copy-region-as-kill copia una regin de texto de un buffer y la
guarda en el kill-ring.
Si llamas a copy-region-as-kill inmediatamente tras un comando
kill-region, Emacs aade el texto copiado al anterior.

La definicin de la funcin copy-region-as-kill completa


El cdigo completo de la versin 22 de la funcin copy-region-as-kill:

COPY-REGION-AS-KILL

83

(defun copy-region-as-kill (beg end)


"Save the region as if killed, but don't kill it.
In Transient Mark mode, deactivate the mark.
If `interprogram-cut-function' is non-nil, also save the text for a window
system cut and paste."
(interactive "r")
(if (eq last-command 'kill-region)
(kill-append (filter-buffer-substring beg end) (< end beg))
(kill-new (filter-buffer-substring beg end)))
(if transient-mark-mode
(setq deactivate-mark t))
nil)
Como es normal, esta funcin se puede dividir en sus partes componentes:
(defun copy-region-as-kill (LISTA-ARGUMENTOS)
"DOCUMENTACIN..."
(interactive "r")
CUERPO...)
Los argumentos son beg y end y la funcin es interactiva con r, as los dos
argumentos se refieren al comienzo y al final de la regin.
Tras establecer una marca, un buffer siempre contiene una regin. Si quieres se
puede utilizar el modo Transient Mark para iluminarla.
El cuerpo de la funcin comienza con una clusula if que comprueba si el ltimo
comando fue un kill-region. Si es as se aade el texto marcado al anterior
texto copiado. De otro modo, se inserta en el anillo de borrado como un trozo
separado.
Las dos ltimas lneas de la funcin evitan que se ilumine la regin si el modo
Transient Mark est activado.
El cuerpo de copy-region-as-kill merece una discusin detallada.

El cuerpo de copy-region-as-kill
La funcin copy-region-as-kill funciona del mismo modo que lo hace la
funcin kill-region. Ambas estn escritas de modo que dos o ms matados se
combinan en una sola entrada. Si tiras del texto desde el anillo de matados, se
obtiene todo en una pieza.
last-command y this-command
Normalmente, cuando se ejecuta una funcin, Emacs establece el valor
this-command a la funcin que se est ejecutando (que en este caso ser

84

CHAPTER 10. CORTAR Y ALMACENAR TEXTO

copy-region-as-kill). A la vez, Emacs establece el valor de last-command al


valor de el ltimo this-command.
En la expresin if se utiliza eq:
(if (eq last-command 'kill-region)
;; then-part
(kill-append (filter-buffer-substring beg end) (< end beg))
;; else-part
(kill-new (filter-buffer-substring beg end)))
La funcin eq comprueba que el primer argumento es el mismo objeto que el
segundo argumento. La funcin eq es similar a la funcin equal para comprobar
la igualdad pero difiere en que determina que dos representaciones son actualmente el mismo objeto en el ordenador, pero con diferentes nombres. equal
determina si la estructura y contenidos de dos expresiones son lo mismo.
Si el comando anterior es kill-region, el intrprete llama a la funcin
kill-append.

La funcin kill-append
La funcin kill-append sera como sigue:
(defun kill-append (string before-p &optional yank-handler)
"Append STRING to the end of the latest kill in the kill ring.
If BEFORE-P is non-nil, prepend STRING to the kill.
..."
(let* ((cur (car kill-ring)))
(kill-new (if before-p (concat string cur) (concat cur string))
(or (= (length cur) 0)
(equal yank-handler
(get-text-property 0 'yand-handler cur)))
yand-handler)))
Utiliza la funcin let* para establecer el valor del primer elemento del anillo de
matados el cur.
Hay que considerar el primer argumento a kill-new es un condicional. Utiliza
concat para concatenar el texto nuevo al CAR del anillo de matados aadindolo
antes o despus dependiendo del valor de before-p.
(if before-p
(concat string cur)
(concat cur string))

; if-part
; then-part
; else-part

El smbolo before-p es el nombre de uno de los argumentos de kill-append.


Cuando se evala la funcin kill-append. Cuando se evala la expresin (<

COPY-REGION-AS-KILL

85

end beg), y resulta true el texto se colocar delante del texto anterior. Segn
su valor se evaluar (concat string cur) o (concat cur string).
Para comprender como trabaja vamos a ver algunos ejemplos:
(concat "abc" "def")
"abcdef"
(concat "new "
(car '("first element" "second element")))
"new first element"
(concat (car
'("first element" "second element")) " modified")
"first element modified"
La funcin kill-new
La funcin kill-new funciona como sigue:
(defun kill-new (string &optional replace yank-handler)
"Make STRING the latest kill in the kill ring.
Set `kill-ring-yank-pointer' to point to it.
If `interprogram-cut-function' is non-nil, apply it to STRING.
Opcional second argument REPLACE non-nil means that STRING will replace
the front of the kill ring, rather than being added to the list.
..."
(if (> (length string) 0)
(if yank-handler
(put-text-property 0 (length string)
'yank-handler yank-handler string))
(if yank-handler
(signal 'args-out-of-range
(list string "yank-handler specified for empty string"))))
(if (fboundp 'menu-bar-update-yank-menu)
(menu-bar-update-yank-menu string (and replace (car kill-ring))))
(if (and replace kill-ring)
(setcar kill-ring string)
(push string kill-ring)
(if (> (length kill-ring) (kill-ring-max)
(setcdr (nthcdr (1- kill-ring-max) kill-ring) nil)))
(setq kill-ring-yank-pointer kill-ring)
(if interprogram-cut-function
(funcall interprogram-cut-function string (not replace)))))
Hay que observar que la funcin no es interactiva.

86

CHAPTER 10. CORTAR Y ALMACENAR TEXTO

(if (and replace kill-ring)


;; then
(setcar kill-ring string)
;; else
(push string kill-ring)
(if (> (length kill-ring) kill-ring-max)
;; avoid overly long kill ring
(setcdr (nthcdr (1- kill-ring-max) kill-ring) nil)))
(setq kill-ring-yank-pointer kill-ring)
(if interprogram-cut-function
(funcall interprogram-cut-function string (not replace))))
La prueba condicional es (and replace kill-ring). Ser cierta cuando el
anillo de matados contenga algo y la variable replace sea cierta.
Si es as se ejecutar:
(setcar kill-ring string)
La funcin setcar cambia el primer elemento de la lista kill-ring por el valor
string. Lo reemplaza.
De otra manera, si el anillo de matados est vaco o replace es falso, se ejecuta:
(push string kill-ring)
push empuja el primer argumento dentro del segundo. Es similar al viejo
(setq kill-ring (cons string kill-ring))
o el ms nuevo
(add-to-list kill-ring string)

Digresin en C
La funcin copy-region-as-kill utiliza la funcin filter-buffer-substring,
que a su vez utiliza la funcin delete-and-extract-region.
Al contrario que otro cdigo que se discute aqu, la ltima funcin no est escrita
en Emacs Lisp. Est escrita en C y es una de las primitivas del sistema de GNU
Emacs.
Como muchas de otras primitivas de Emacs, delete-and-extract-region est
escrita como una instancia de un macro C. El macro completo sera as:
DEFUN ("delete-and-extract-region", Fdelete_and_extract_region,
Sdelete_and_extract_region, 2, 2, 0,
doc: /* Delete the text between START and END and return it.
{

*/ )

DIGRESIN EN C

87

validate_region (&start, &end);


if (XINT (start) == XINT (end))
return empty_unibyte_string;
return del_range_1 (XINT (start), XINT (end), 1, 1);

Sin entrar en los detalles del proceso de escribir macros, vamos a explicarlo un
poco. Lo primero es que el macro comienza con la palabra DEFUN. Esta palabra
se utiliza con el mismo propsito que defun en Lisp.
La palabra DEFUN est seguida de siete partes dentro de parntesis:
La primera parte es el nombre de la funcin en Lisp, delete-and-extract-region.
La segunda parte es el nombre de la funcin en C, Fdelete_and_extract_region.
Por convencin, comienza con F. Puesto que C no utiliza guiones en los
nombres, se utilizan subrayados en su lugar.
La tercera parte es el nombre de la estructura constante en C que guarda
informacin en esta funcin para uso interno. Es el mismo nombre de la
funcin en C pero comienza con S en lugar de F.
La cuarta y quinta especifican el mnimo y mximo nmero de argumentos que puede tener la funcin. Esta funcin requiere exactamente 2
argumentos.
La sexta parte es como el argumento que sigue a la declaracin interactive
en una funcin escrita en Lisp: una letra seguida, quiz, por un prompt.
La nica diferencia con Lisp es cuando el macro se llama sin argumentos.
Entonces se escribe un 0, como en este macro.
Si se especifican argumentos, hay que ponerlos entre comillas.
La sptima parte es una cadena de documentacin como una escrita en
Emacs Lisp. Esta est escrita como un comentario de C.
En un macro C, los parmetros formales vienen a continuacin, con una
declaracin de qu tipo de objeto son, seguidos por el cuerpo del macro. Para
delete-and-extract-region el cuerpo consiste en las siguientes cuatro lneas:
validate_region (&start, &end);
if (XINT (start) == XINT (end))
return empty_unibyte_string;
return del_range_1 (XINT (start), XINT (end), 1, 1)
La funcin validate_region comprueba si los valores pasados como principio
y final de la regin son tipos apropiados y estn en rango. Si las posiciones de
principio y final son las misma, devuelve una cadena vaca.
La funcin del_range_1 borra el texto. Es una funcin compleja y no miraremos
dentro. Actualiza el buffer y hace otras cosas.

88

CHAPTER 10. CORTAR Y ALMACENAR TEXTO

Para lo concerniente al lenguaje C, start y end son dos enteros que marcan el
comiendo y el final de la regin borrada.1
El tamao del entero depende de la mquina (32 64 bits).
XINT es una macro C que extrae el nmero relevante de una larga coleccin de
bits.
Desde el punto de vista de una persona escribiendo Lisp, Emacs es muy simple;
pero oculta bajo ella existe una complejidad que hace todo el trabajo.

Inicializar una variable con defvar


La funcin copy-region-as-kill est escrita en Emacs Lisp. Dos funciones
que vienen en ella, kill-append y kill-new, copian una regin en un buffer
y lo guarda en una variable llamada kill-ring. Esta seccin describe cmo
se crea la variable kill-ring y cmo se inicializa utilizando la forma especial
defvar.
El nombre de la forma especial defvar viene de define variable.
La forma especial defvar es similar a setq en que se establece el valor de una
variable. Pero difiere de setq de dos maneras: primero, slo establece el valor
de una variable si la variable no tiene todava ningn valor, si tiene un valor no
lo sobrescribe. Segundo, defvar tiene cadena de documentacin.

Ver el valor actual de una variable


Se puede ver el valor actual de una variable, cualquier variable, utilizando
la funcin describe-variable, que se puede invocar mediante C-h v y luego
kill-ring. Tambin se muestra la documentacin de la siguiente manera:
Documentation:
List of killed text sequences.
Since the kill ring is supposed to interact nicely with cut-and-paste
facilities offered by window systems, use of this variable should
interact nicely with interprogram-cut-function and
interprogram-paste-function. The functions kill-new,
kill-append, and current-kill are supposed to implement this
interaction; you may want to use them instead of manipulating the kill
ring directly.
El anillo de matados se define por una defvar de la siguiente manera:
1 Para ser ms precisos, los dos enteros son de tipo Lisp_Object, que puede ser una union
C en lugar de un entero.

RESUMEN

89

(defvar kill-ring nil


"List of killed text sequences.
...")
En la definicin de esta variable se proporciona un valor inicial nil. La cadena
de documentacin est escrita como se escribira la cadena de documentacin
de una defun, la primera lnea de la documentacin debera ser una sentencia
completa, ya que algunos comandos como apropos muestran slo la primera
lnea de la documentacin. Las lneas sucesivas no se deben indentar, de otro
forma se mostrarn mal cuando se utiliza C-h v.

defvar y un asterisco
En el pasado, Emacs utilizaba defvar para variables internas que no se espera
que el usuario cambie y para variables que se espera que cambie el usuario.
Aunque puedes utilizar defvar para tus variables, se recomienda que utilices
defcustom en su lugar.
Cuando se especifica una variable utilizando defvar, puedes diferenciar la variable
que un usuario puede modificar de otras poniendo un * en la primera columna
de la cadena de documentacin. Por ejemplo:
(defvar shell-command-default-error-buffer nil
"*Buffer name for `shell-command' ... error output.
... ")
Puedes (y debes) utilizar set-variable para cambiar temporalmente el valor
de shell-command-default-error-buffer. Sin embargo, las opciones establecidas mediante set-variable slo duran durante una sesin de edicin. Los
nuevos valores no se guardan entre sesiones. A no ser que cambies el valor en el
fichero .emacs.
Para el autor, el principal uso del comando set-variable es que pulsando
M-x set-variable se muestra una lista completa de variables que se pueden
modificar.

Resumen
car
cdr car devuelve el primer elemento de una lista; cdr devuelve el segundo y
siguientes elementos de una lista.
Por ejemplo:
(car '(1 2 3 4 5 6 7))
1

90

CHAPTER 10. CORTAR Y ALMACENAR TEXTO


(cdr '(1 2 3 4 5 6 7))
(2 3 4 5 6 7)

cons construye una lista poniendo el primer argumento delante del segundo
elemento.
Por ejemplo:
(cons 1 '(2 3 4))
(1 2 3 4)
funcall funcall evala su primer argumento como una funcin. Pasa el resto
de argumentos a su primer argumento.
nthcdr Devuelve el resultado de tomar el CDR N veces en una lista. Se obtiene
el resto del resto.
Por ejemplo:
(nthcdr 3 '(1 2 3 4 5 6 7))
(4 5 6 7)
setcar
setcdr setcar cambia el primer elemento de una lista; setcdr cambia el resto
de la lista.
Por ejemplo:
(set triple '(1 2 3))
(setcar triple '37)
triple
(37 2 3)
(setcdr triple '("foo" "bar"))
triple
(37 "foo" "bar")
progn Evala cada argumento en una secuencia y luego devuelve el valor del
ltimo.
Por ejemplo:
(progn 1 2 3 4)
4
save-restriction Almacena cuando est activo el narrowing en el buffer actual,
si hay, y restaura el narrowing despus de evaluar los argumentos.
search-forward Busca una cadena y si se encuentra mueve el punto. Con una
expresin regular, utilizar la similar re-search-forward.

EJERCICIN DE BSQUEDA

91

search-forward y re-search-forward timan cuatro argumentos:


1. La cadena o expresin regular que buscar.
2. Opcionalmente, el lmite de la bsqueda.
3. Opcionalmente, qu hacer si la bsqueda falla, devolver nil o un
mensaje de error.
4. Opcionalmente, cuantas veces repetir la bsqueda; si es negativo, la
bsqueda se realiza hacia atrs.
kill-region
delete-and-extract-region
copy-region-as-kill kill-region corta el texto entre el punto y la marca
de un buffer y lo almacena en el anillo de matados, as se puede recuperar
tironeando (yanking).
copy-region-as-kill copia el texto entre el punto y la marca en el anillo
de matados, de donde se puede obtener tironeando. La funcin no corta o
borra el texto del buffer.
delete-and-extract-region borra el texto entre el punto y la marca de
un buffer definitivamente. No se puede recuperar. (Este comando no es
interactivo).

Ejercicin de bsqueda
Escribe una funcin interactiva que busque una cadena. Si se encuentra
la cadena, lleve el punto tras ella y muestre un mensaje Encontrado!.
(Sin utilizar search-forward para el nombre de esta funcin; si lo haces,
sobreescribirs la versin existente de search-forward que viene con
Emacs. Utiliza un nombre como test-search en su lugar).
Escribe una funcin que muestre el tercer elemento del anillo de matados
en el rea de eco, si hay alguno; si el anillo de matados no contiene un
tercer elemento, que muestre un mensaje apropiado.

92

CHAPTER 10. CORTAR Y ALMACENAR TEXTO

Chapter 11

Cmo se implementan las


listas
En Lisp, los tomos se registran de forma directa; si la implementacin no es
directa en la prctica, sin embargo, en teora es directa. El tomo rose por
ejemplo, est registrado como cuatro letras contiguas r, o, s, e. Una lista es
ligeramente distinto. El mecanismo es igualmente sencillo, pero lleva un momento
hacerse a la idea. Una lista utiliza una serie de pares de punteros. En las series,
el primer puntero de cada par apunta a un tomo o a otra lista, y el segundo
puntero en cada par apunta al siguiente par, o al smbolo nil, que marca el fin
de la lista.
Un puntero es simplemente una direccin electrnica a la que se apunta. De esta
manera, una lista se una serie de direcciones electrnicas.

Diagrama de las listas


Por ejemplo, la lista (rose violet buttercup) tiene tres elementos, rose,
violet y bottercup. En el ordenador, la direccin de rose se almacena en un
segmento de la memoria con la direccin donde est el tomo violet. Y ste se
guarda con la direccin que tiene el tomo buttercup.
93

94

CHAPTER 11. CMO SE IMPLEMENTAN LAS LISTAS

En el diagrama cada caja representa un word de memoria que almacena un


objeto Lisp. Las cajas, las direcciones, van por pares. Cada flecha apunta a
la direccin de un tomo o a otro par de direcciones. La primera caja es la
direccin electrnica de rose y la segunda caja es la direccin de la siguiente
par de direcciones. Cuya primera direccin apunta a violet y la segunda al
siguiente par. Cuyo ltimo puntero seala a nil, que marca el final de la lista.
Cuando una variable se fija con una lista con setq almacena la direccin de la
primera caja en la variable. As, al evaluar la expresin
(setq bouquet '(rose violet buttercup))
crea una situacin como esta:

En este ejemplo, el smbolo bouquet almacena la direccin del primer par de


cajas.
Podemos hacer el mismo grfico de una manera diferente como esta:

DIAGRAMA DE LAS LISTAS

95

Si un smbolo est establecido al CDR de una lista, la lista no se cambia, el


smbolo smplemente tiene una direccin a la lista. (En la jerga, CAR y CDR
son no destructivos). Cuando se evala la siguiente expresin:
(setq flowers (cdr bouquet))
Se obtiene esto:

El valor de flowers es (violet buttercup), se puede decir que el smbolo


flowers guarda la direccin del par de cajas-direccin, la primera apunta a
violet y la segunda a la que guarda la direccin de buttercup.
Un par de cajas-direccin se llama una cons cell o dotted pair.
La funcin cons aade un nuevo par de direcciones al frente de la serie de
direcciones como se mostr antes. Por ejemplo, evaluando la expresin
(setq bouquet (cons 'lily bouquet))
produce:

96

CHAPTER 11. CMO SE IMPLEMENTAN LAS LISTAS

Sin embargo, eso no cambia el valor del smbolo flowers como puedes ver
evaluando lo siguiente,
(eq (cdr (cdr bouquet)) flowers)
que devolver t por true.
Hasta que se resetee, flowers mantendr el valor (violet buttercup).
De este modo, en Lisp, al obtener el CDR de una lista, lo que se obtiene la
direccin de la siguiente celda (cell) de la serie; obtener el CAR de una lista, se
obtiene la direccin del primer elemento de la lista; con cons y un nuevo elemento,
aades una nueva cons cell delante de la lista. La estructura subyacente de Lisp
es brillantemente simple!

Smbolos como una cajonera


En la seccin anterior, se sugiri que se poda imaginar un smbolo como una
cajonera. La definicin de funcin se mete en un cajn, el valor en otro y as
los otros valores. Cuando abres un cajn puedes cambiar su valor sin afectar al
contenido de los otros cajones, y vice versa.
Una representacin:

EJERCICIO

97

Ejercicio
Establecer flowers a violet y buttercup. Hacer cons con dos flores ms en
la lista y establecer esta nueva lista a more-flowers. Establecer el CAR de
flowers a un pez. Qu contiene ahora more-flowers?

98

CHAPTER 11. CMO SE IMPLEMENTAN LAS LISTAS

Chapter 12

Tironeando texto de vuelta


El texto que se ha cortado de un buffer se coloca en el anillo de matados y los
comandos de tironeo (yank) inserta los contenidos en el buffer desde el anillo de
matados (no necesariamente en el buffer original).
Un simple comando C-y (yank) inserta el primer tem del anillo de matados en el
buffer actual. Si despus del comando C-y se utiliza M-y, el primer elemento se
reemplaza por el segundo. Los sucesivos comandos M-y reemplazan el texto por
los sucesivos textos matados. Cuando se alcanza el ltimo elemento se regresa al
primero (por eso se llama anillo).

Un vistazo al anillo de matados


El anillo de matados es una lista de cadenas. Puede parecer algo as:
("some text" "a different piece of text" "yet more text")
Si esos fueran los contenidos del anillo de matados y pulso C-y, la cadena de
caracteres que dice some text se insertar en el buffer donde est situado el
cursor.
El comando yank tambin se utiliza para duplicar texto copindolo. El texto
copiado no se quita del buffer, pero se almacena en el anillo de matados.
Se utilizan tres funciones para devolver el texto del anillo de borrados: yank,
C-y; yank-pop, M-y; y rotate-yank-pointer, que es utilizada por las otras dos.
Esas funciones se refieren al anillo de matado a travs una variable llamadas
kill-ring-yank-pointer. El cdigo de insercin para ambas funciones, yank
y yank-pop:
(insert (car kill-ring-yank-pointer))
99

100

CHAPTER 12. TIRONEANDO TEXTO DE VUELTA

Para comprender como funcionan yank y yank-pop, primero es necesario echar


un vistazo a la variable kill-ring-yank-pointer.

La variable kill-ring-yank-pointer
kill-ring-yank-pointer es una variable, igual que kill-ring.
Pongamos que el valor del anillo de matados es:
("some text" "a different piece of text" "yet more text")
y kill-ring-yank-pointer apunta a la segunda posicin:
("a different piece of text" "yet more text")
Un diagrama:

Ambas variables, kill-ring y kill-ring-yank-pointer son punteros.


El anillo de matados es una estructura completa de datos que almacena
informacin de lo que se ha cortado los buffers de Emacs. Por otro lado,
kill-ring-yank-pointer sirve para indicar con un puntero el CAR del anillo
de borrados.

Ejercicios con yank y nthcdr


Utilizando C-h v (describe-variable) mira al valor de tu anillo de matados. Aade algn tem al anillo de matados y mira ese valor de nuevo.
Utilizando M-y (yank-pop), muvete por todo el anillo de borrados. Cuntos tem tiene el anillo de matados? Encuentra el valor de kill-ring-max.
Est lleno el anillo de matados o puede contener algunos bloques de texto
ms?
Utilizando nthcdr y car construye una serie de expresiones que devuelva
el primer, segundo, tercero y cuarto elementos de una lista.

Chapter 13

Bucles y recursin
Emacs Lisp tiene dos maneras principales para hacer que una expresin, o una
serie de expresiones, se evale repetidamente: una es utilizar un bucle while y
la otra utilizar recursin.
La repeticin es muy valiosa. La gente escribe principalmente funciones Emacs
Lisp utilizando bucles while; pero puedes utilizar recursin, que proporciona
una manera verdaderamente potente de pensar y resolver problemas1 .

while
La forma especial while comprueba cuando el valor devuelto por la evaluacin
de su primer argumento es true o false. Es similar a como el intrprete Lisp
hace con if.
En una expresin while, si el valor devuelto evaluando el primer argumento es
falsa, el intrprete Lisp salta el resto de la expresin (el cuerpo de la expresin)
y no lo evala. Sin embargo, si la expresin es cierta (true), el intrprete
Lisp evala el cuerpo de la expresin y despus comprueba de nuevo el primer
elemento.
La plantilla while es:
(while TRUE-OR-FALSE-TEST
BODY...)
1 Emacs se ha diseado para correr en mquinas que se consideran limitadas y por defecto su configuracin es conservadora. Es posible que quieras incrementar los valores de
max-specpdl-size y max-lisp-eval-depth. Se podran aumentar en el fichero .emacs a 15 y
30, por ejemplo.

101

102

CHAPTER 13. BUCLES Y RECURSIN

Bucles con while


Mientras la expresin true-or-false-test devuelva true, el cuerpo se evaluar
repetidamente. Este proceso se llama un bucle porque el intrprete Lisp repite lo
mismo una y otra vez. Cuando el resultado es evaluado como false, el intrprete
Lisp no evala el resto de la expresin while y sale del bucle.
Claramente, si el valor devuelto de la evaluacin while siempre es true, el
cuerpo ser evaluado una y otra vez para siempre. Al contrario, si el valor
devuelto nunca es true, la expresin del cuerpo nunca se evaluar. El punto
clave de escribir un bucle while consiste en escoger un mecanismo que haga
que la expresin true-or-false-test devuelva true justo el nmero de veces
necesarias para que se evale la siguiente expresin.

Un bucle while y una lista


Una manera habitual de controlar un bucle while es cuntos elementos tiene
una lista.
Una manera sencilla de probar si una lista tiene elementos es evaluar la lista: si
no tiene elementos, es una lista vaca y devolver () que es sinnimo de nil o
false. Por otro lado, una lista que tenga elementos devolver esos elementos
cuando sea evaluado. Puesto que Emacs Lisp considera como true cualquier
valor que no sea nil, una lista que devuelva elementos ser evaluada como ture
en un bucle while.
Por ejemplo, puedes evaluar la variable empty-list a nil con la siguiente
expresin setq:
(setq empty-list ())
Despus de evaluar la expresin setq puedes evaluar la variable empty-list de
la manera habitual y aparecer nil en el rea de eco.
Por otro lado, si se establece una variable a una lista con elementos, la lista
aparecer al evaluar la variable, como se puede apreciar evaluando las siguientes
dos expresiones:
(setq animals '(gazelle giraffe lion tiger))
animals
Entonces, para crear un bucle while que compruebe cada tem de la lista
animals, la primera parte del bucle se escribir como sta:
(while animals
...

WHILE

103

Cuando se evala la variable animals en el primer argumento de while. Devuelve


una lista. Tan larga como elementos tiene la lista. El while considera que es
true, pero cuando la lista est vaca considera que la prueba es false.
Para prevenir que el bucle while se convierta en bucle infinito, se necesita algn
mecanismo que eventualmente proporcione la lista vaca. Una tcnica utilizada
frecuentemente es ir proporcionando sucesivas listas con el CDR de la lista. Cada
vez que se evala la funcin cdr la lista se acorta hasta proporcionar una lista
vaca.
Por ejemplo, la lista de animales se puede ir acortando como en la siguiente
expresin:
(setq animals (cdr animals))
Si evalas la expresin anterior, aparecer en el rea de eco (giraffe lion
tiger). Si la vuelves a evaluar se obtiene (lion tiger), si se evala de nuevo
se obtendr (tiger) y despus, si se vuelve a evaluar, aparecer nil.
Una plantilla para un bucle while que utiliza la funcin cdr repetidamente para
la prueba true-or-false-test podra ser as:
(while PROBAR-SI-LA-LISTA-ESTA-VACA
CUERPO...
ESTABLECER-LISTA-AL-CDR-DE-LISTA)

Un ejemplo: print-elements-of-list
La funcin print-elements-of-list ilustra un bucle while con una lista.
La funcin necesita varias lneas para su salida. Puedes hacer la prueba en el
buffer *scratch*, por ejemplo.
(setq animals '(gazelle giraffe lion tiger))
(defun print-elements-of-list (list)
"Print each element of LIST on a line of its own."
(while list
(print (car list))
(setq list (cdr list))))
(print-elements-of-list animals)
Cada elemento se escribe en una lnea (que es lo que la funcin print hace). En
la ltima expresin de la funcin del bucle while se evala un nil y es lo que se
imprime despus del ltimo elemento de la lista.

104

CHAPTER 13. BUCLES Y RECURSIN

Un bucle con un incremento de contador


Adems de controlar un bucle con una lista, otra forma comn de pararlo es
escribir el primer argumento como una prueba que devuelva falso cuando se
hayan repetido las veces necesarias. Esto quiere decir que el bucle debe tener
una expresin que cuente cuntas veces se repite.
Puede ser una expresin del tipo (< count desired-number) que devolver t
si el valor count es menor que desired-number. La expresin que incrementa la
cuenta puede ser un sencillo setq, como por ejemplo (setq count (1+ count)),
donde 1+ es una funcin interna en Emacs Lisp que aade 1 a su argumento.
Sera lo mismo que (+ count 1), pero quiz sea ms fcil de leer por un humano.
Una plantilla para un bucle while controlado por un contador incrementado
sera como esta:
ESTABLECER-CUENTA-AL-VALOR-INICIAL
(while (< count desired-number)
CUERPO...
(setq count (1+ count)))

; true-or-false-test
; incremento

Hay que observar que se debe establecer el valor inicial de count; normalmente
a 1.
ejemplo con un contador incrementado
Supn que ests jugando en la playa y decides hacer un tringulo de guijarros,
poniendo una piedrecita en la primera fila, dos en la segunda, tres en la tercera:
*
* *
* * *
* * * *
Cuntas piedras necesitas para hacer un tringulo de 7 filas?
Claramente, lo que necesitas es sumar los nmeros de 1 a 7. Lo que se puede
hacer fcilmente con un bucle while con contador.
Partes de la definicin de funcin
El anlisis anterior se llega a la conclusin que se necesita una variable total
para guardar el nmero total de piedras que ser lo que devuelva la funcin.
Segundo, sabemos que la funcin necesita un parmetro que diga cuntas filas
tendr el tringulo. Se puede llamar number-of-rows.
Finalmente, necesitamos una variable que acte como contador. Podemos utilizar
counter, pero un nombre mejor sera row-number.

105

WHILE

Tanto total como row-number se utilizan slo dentro de la funcin, se pueden


declarar como variables locales con let y proporcionar valores iniciales. El
comando let se mostrar as:
(let ((total 0)
(row-number 1))
CUERPO...)
Lisp proporciona la funcin <= que devuelve true si el valor de su primer
argumento es menor o igual al valor de su segundo argumento y falso en otro
caso. As, la expresin while debera evaluar la prueba como:
(<= row-number number-of-rows)
El nmero total de piedras se hallar sumando repetidamente el nmero de
piedras de una fila.
(setq total (+ total row-number))
Lo que hace es establecer un nuevo valor de total que es la suma del nmero
de piedras de la fila y las piedras del anterior total.
Despus de establecer el nuevo valor de total, se necesita establecer la
nueva condicin para la repeticin. Esto se hace incrementando el contador
row-counter. La funcin interna de Emacs Lisp 1+ aade un nmero:
(setq row-number (1+ row-number))

Poniendo la funcin junta


Hasta ahora se han visto las partes de la funcin, ahora vamos a ponerlas todas
juntas.
Primero, los contenidos de la expresin while:
(while (<= row-number number-of-rows)
(setq total (+ total row-number))
(setq row-number (1+ row-number)))

; true-or-false-test
; incrementar

Esto se complementara con la expresin let que establecera los valores de las
variables. Sin embargo, necesitar un elemento final que devuelva el valor de
total.
El toque final es poner la variable total en una lnea, sola tras la expresin
while. El pseudocdigo de la funcin sera:
(defun NOMBRE-DE-FUNCIN (LISTA-ARGUMENTOS)
"DOCUMENTACIN..."
(let (VARLIST)
(while (TRUE-OR-FALSE-TEST)

106

CHAPTER 13. BUCLES Y RECURSIN


CUERPO-DEL-WHILE... )
... ))

; Se necesita expresin aqu.

El resultado de evaluar el let es el que devolver defun como un todo.


Ponindolo todo junto; la funcin triangle sera as:
(defun triangle (number-of-rows)
; Versin con incremento de contador
"Add up the number of pebbles in a triangle.
The first row has one pebble, the second row two pebbles,
the third row three pebbles, and so on.
The argument is NUMBER-OF-ROWS."
(let ((total 0)
(row-number 1))
(while (<= row-number number-of-rows)
(setq total (+ total row-number))
(setq row-number (1+ row-number)))
total))

Bucle con decremento de contador


Otra forma comn de escribir un bucle while es utilizar un contador que se
incremente. La prueba ser una expresin como (> counter 0) que devuelve
t si el valor counter es mayor que cero y nil o falso si el valor de counter es
igual o menor que cero.
La plantilla para un bucle while decrementando, ser:
(while (> counter 0)
CUERPO...
(setq counter (1- counter)))

; true-or-false-test
; decrementar

Ejemplo decrementando contador


Para ilustrar un bucle con un contador decrementado, reescribimos la funcin
triangle con un contador que se decrementa hasta cero.
En este caso debemos darle la vuelta a la funcin. Como en el ejemplo anterior
vamos a ir sumando las piedras de cada fila. Por ejemplo, la fila 7 tiene 7 piedras
y hay que sumarlas a las 6 de la fila 6, las 5 de la fila 5, etc.
Sabemos que el nmero de piedras en una fila coincide con el orden de su fila y
cada fila anterior tiene una piedra menos.

WHILE

107

Las partes de la definicin de la funcin.


Comenzamos con tres variables: El nmero total de filas en el tringulo, el nmero
de piedras en una fila y el nmero total de piedras, que es lo que se quiere calcular.
Estas variables se pueden llamar number-of-rows, number-of-pebbles-in-row
y total, respectivamente.
Tanto total como number-of-pebbles-in-row se utilizan slo dentro de la
funcin y se declararn con let. El valor inicial de total ser cero. Sin embargo,
el valor inicial de number-of-pebbles-in-row debe ser igual al nmero de filas
en el tringulo.
La expresin let quedar como sigue:
(let ((total 0)
(number-of-pebbles-in-row number-of-rows))
CUERPO...)
El nmero total de piedras se puede encontrar sumando repetidamente el nmero
de piedras de cada fila. Eso lo haremos con la siguiente expresin:
(setq total (+ total number-of-pebbles-in-row))
Despus de sumar el nmero de piedras en esa fila, hay que decrementar el
nmero de piedras para el siguiente bucle:
(setq number-of-pebbles-in-row
(1- number-of-pebbles-in-row))
Finalmente, sabemos que el bucle while debera parar si no hay piedras en una
fila. La prueba del bucle while es simplemente:
(while (> number-of-pebbles-in-row 0)

Poniendo la definicin de la funcin junta


Podemos crear la funcin como sigue:
;;;; Primera versin restando
(defun triangle (number-of-rows)
"Add up the number of pebbles in a triangle."
(let ((total 0)
(number-of-pebbles-in-row number-of-rows))
(while (> number-of-pebbles-in-row 0)
(setq total (+ total number-of-pebbles-in-row))
(setq number-of-pebbles-in-row
(1- number-of-pebbles-in-row)))
total))

108

CHAPTER 13. BUCLES Y RECURSIN

As escrita esta funcin sirve. Sin embargo, no se necesita la variable


number-of-pebbles-in-row. Bsicamente, ya sabemos que el nmero de
piedras en una fila coincide con su nmero de orden.
He aqu una segunda versin escrita ms claramente:
(defun triangle (number)
; Segunda versin
"Return sum of numbers 1 through NUMBER inclusive."
(let ((total 0))
(while (> number 0)
(setq total (+ total number))
(setq number (1- number)))
total))
En resumen, propiamente la escritura de un bucle loop consiste en tres partes:
1. Una prueba que devolver false despus de que el bucle se repita el
nmero correcto de veces.
2. Una expresin que evale lo que devolver el valor deseado despus de
evaluarlo repetidamente.
3. Una expresin para cambiar el valor pasado a la prueba del bucle para que
se repita el nmero correcto de veces.

Ahorrando tu tiempo: dolist y dotimes


Adems de while, tanto dolist como dotimes proporcionan bucles. Ambos
son macros de Lisp.
dolist trabaja como un bucle while controlado con una lista y haciendo el
bucle con el CDR. dolist automticamente decrementa cada vez el CDR de la
lista.
dotimes hace el bucle el nmero de veces que le digamos.

El macro dolist
Para darle la vuelta a una lista, por ejemplo que primero segundo tercero
se convierta en tercero segundo primero.
En la practica se utiliza la funcin reverse as:
(setq animals '(gazelle giraffe lion tiger))
(reverse animals)
As podramos revertir una lista utilizando un bucle while:

AHORRANDO TU TIEMPO: DOLIST Y DOTIMES

109

(setq animmals '(gazelle giraffe lion tiger))


(defun reverse-list-with-while (list)
"Using while, reverse the order of LIST."
(let (value)
; asegurarse que la lista comienza vaca
(while list
(setq value (cons (car list) value))
(setq list (cdr list)))
value))
(reverse-list-with-while animals)
Y as como se usar un macro dolist:
(setq animmals '(gazelle giraffe lion tiger))
(defun reverse-list-with-dolist (list)
"Using dolist, reverse the order of LIST."
(let (value)
; asegurarnos que la lista empieza vaca
(dolist (element list value)
(setq value (cons element value)))))
(reverse-list-with-dolist animals)
Para este ejemplo, la funcin reverse ser mejor.
La expresin dolist hace lo mismo que la expresin while, excepto que el macro
dolist hace algo de trabajo que tienes que escribir t en la expresin while.
La diferencia es que dolist cada bucle acorta automticamente la lista. En el
ejemplo, el CAR de cada versin acortada de la lista se la refiere utilizando el
smbolo element y la lista se llama list y el valor devuelto se llama value. El
resto de la expresin dolist es el cuerpo.
La expresin dolist pone el CAR de cada versin acortada en la lista con
element y luego evala el cuerpo de la expresin; y repite el bucle. El resultado
se devuelve en value.

El macro dotimes
El macro dotimes es similar a dolist, excepto que hace el bucle un nmero
especfico de veces.
El primer argumento de dotimes es una variable que contendr el nmero. Un
ejemplo:
(let (value)
; otherwise a value is a void variable
(dotimes (number 3 value)
(setq value (cons number value))))

110

CHAPTER 13. BUCLES Y RECURSIN

(2 1 0)
dotimes devuelve value, as la manera de utilizar dotimes es operar en alguna
expresin con el NUMBER de veces y devolver el resultado.
Aqu un ejemplo de defun utilizando dotimes para sumar el nmero de piedras
en un tringulo.
(defun triangle-using-dotimes (number-of-rows)
"Using `dotimes', add up the number of pebbles in a triangle."
(let ((total 0)) ; otherwise a total is a void variable
(dotimes (number number-of-rows total)
(setq total (+ total (1+ number))))))
(triangle-using-dotimes 4)

Recursin
Una funcin rescursiva contiene cdigo que le dice al intrprete Lisp que al
mismo programa que est corriendo pero con argumentos ligeramente diferentes.
Sin embargo, no es la misma instancia. En la jerga es una instancia diferente.
Si el programa est correctamente escrito, las diferencias de los argumentos son
suficientes para que desde el primer argumento llegue hasta la instancia final y
pare.

Haciendo robots: extendiendo la metfora


En ocasiones es socorrido pensar que correr un programa es tener un robot que
hace el trabajo. Haciendo el trabajo, una funcin recursiva llama a un segundo
robot que lo ayuda. El segundo robot es idntico al primero excepto por los
argumentos que se le pasan desde el primero.
En una funcin recursiva, el segundo robot puede llamar a un tercero y el tercero
a un cuarto, y as en adelante. Cada uno de ellos tiene una entidad diferente:
son clones. El ltimo robot debera saber cundo parar.
Vamos a expandir un poco la metfora de que un programa de ordenador es un
robot:
Una definicin de funcin proporciona los planos de un robot. Cuando instalas
la definicin de funcin, es decir, cuando evalas el macro defun ests instalando
el equipamiento necesario para hacer robots. Como en una factora establecer
una lnea de montaje. Los robots con el mismo nombre se hacen con los mismos
planos. As tienes el mismo modelo, pero con nmeros de serie distinto.

RECURSIN

111

Frecuentemente se dice que una funcin recursiva se llama a s misma. Cuando


lo que se quiere decir es que las instrucciones en una funcin recursiva hace que
el intrprete Lisp corra una funcin diferente con el mismo nombre que hace el
mismo trabajo que la primera, pero con argumentos diferentes.
Es importante que el argumento difiera de una llamada a la siguiente; de otro
modo, el proceso no se detendr nunca.

Las partes de una definicin recursiva


Una funcin recursiva normalmente contiene una expresin condicional que tiene
tres partes:
1. Una prueba true-or-false-test que determina cuando se llama a la
funcin de nuevo, lo llamaremos do-again-test.
2. El nombre de la funcin. Cuando se llama a este nombre, se crea una
nueva instancia del robot.
3. Una expresin que devuelve un valor diferente cada vez que se llama a la
funcin. Aqu la llamaremos next-step-expression. As, los argumentos
pasados a la nueva instancia sern diferentes de los recibidos. Esto causa que
la expresin condicional do-again-test sea evaluada como falsa despus
del nmero correcto de repeticiones.
Las funciones recursivas pueden ser ms simples que otro tipos de funcin. Sin
embargo, cuando la gente comienza a usarlas, a menudo parece algo misteriosamente simple pero incomprensible. Como montar en bicicleta, leer una definicin
de funcin recursiva tiene un poco de dificultad al principio pero luego se hace
muy simple.
Hay muchas plantillas recursivas diferentes. La ms simple sera:
(defun (NOMBRE-DE-FUNCIN-RECURSIVA (LISTA-ARGUMENTOS)
"DOCUMENTACIN..."
(if DO-AGAIN-TEST
CUERPO...
(NOMBRE-DE-FUNCIN-RECURSIVA
NEXT-STEP-EXPRESSION)))
Cada vez que se evala una funcin recursiva, se crea una nueva instancia. Los
argumentos le dicen qu hacer. El valor en next-step-expression se utiliza
en do-again-test. El valor devuelto por next-step-expression se pasa a la
nueva instancia de la funcin, que lo evala para determinar si contina o para.
La expresin next-step-expression est diseada para que do-again-test
devuelva false cuando la funcin no debe continuar.
En ocasiones, do-again-test se llama tambin la condicin de parada.

112

CHAPTER 13. BUCLES Y RECURSIN

Recursin con una lista


Vamos a ver un ejemplo:
(setq animals '(gazelle giraffe lion tiger))
(defun print-elements-recursively (list)
"Print each elemnt of LIST on a line of
Uses recursion."
(when list
;
(print (car list))
;
(print-elements-recursively
;
(cdr list))))
;

its own.
do-again-test
body
recursive call
next-step-expression

(print-elements-recursively animals)
La funcin print-elements-recursively primero prueba cundo hay algn
contenido en la lista. Si hay algo, la funcin escribe el primer elemento de la
lista (el CAR). Despus, la funcin se invoca a s misma pero proporcionando
como argumento el CDR de la lista que le pasaron a esa instancia.
Dicho de otro modo, si la lista no est vaca, la funcin invoca otra instancia
con diferentes argumentos.
De otra manera: si la lista no est vaca, el primer robot monta un segundo
robot dicindole qu tiene que hacer. El segundo robot es un individuo distinto,
pero del mismo modelo.
Cuando la segunda evaluacin ocurre, la expresin when se evala. Si es true
se escribe el primer elemento de la lista que recibe en su argumento (que es el
segundo de la lista original). Luego se llama de nuevo con el CDR de la lista
que recibi (que ser el CDR del CDR de la lista original).
Eventualmente, la funcin se llamar con una lista vaca. Crear una nueva
instancia con un argumento nil. La expresin when evaluar list y la encontrar
false, de ese modo no ejecutar la clusula y devolver nil como un todo. No
habr ms repeticiones.

Recursin en lugar de un contador


La funcin triangle descrita en la seccin anterior se puede escribir tambin
recursivamente. Sera as:
(defun triangle-recursively (number)
"Return the sum of the numbers 1 through NUMBER inclusive.
Uses recursion."
(if (= number 1)
; do-again-test
1
; then-part

113

RECURSIN
(+ number
(triangle-recursively
(1- number)))))

; else-part
; recursive call
; next-step-expression

(triangle-recursively 7)

Un argumento de 1 2
Primero, qu ocurre cuando el valor del argumento es 1?
La funcin tiene una expresin if despus de la cadena de documentacin.
Prueba cuando el valor de number es igual a 1; si lo es se evala la then-part
que es igual a 1 (devuelve 1 sin ms recursin).
La else-part consiste en una suma, que llama recursivamente a
triangle-recursively y decrementa number.
(+ number (triangle-recursively (1- number)))
Cuando Emacs evala esta expresin, la expresin ms interna se evala primero;
luego las otras partes en secuencia.
Paso 1 Evala la expresin ms interna.
La expresin ms interna es (1- number) por lo que Emacs decrementa el
valor de number de 2 a 1.
Paso 2 Evaluacin de la funcin triangle-recursively.
Le intrprete de Lisp crea una instancia individual de triangle-recursively.
Emacs pasa el resultado del Paso 1 como argumento para esta instancia
de la funcin.
En este caso, Emacs evala triangle-recursively con un argumento de
1. Esto significa que la evaluacin devolver 1.
Paso 3 Evaluar el valor de number.
La variable number es el segundo elemento de la lista que comienza con +;
su valor es 2.
Paso 4 Evaluar la expresin +.
La expresin + recibe dos argumentos, el primero la evaluacin de number
(Paso 3) y el segundo la evaluacin de triangle-recursively (Paso 2).
El resultado de la suma es la suma de 2 ms 1 y se devuelve 3, lo que es
correcto. Un tringulo con dos filas tiene tres piedras en l.

114

CHAPTER 13. BUCLES Y RECURSIN

Un argumento de 3 4
Supongamos que se llama triangle-recursively con un argumento de 3.
Paso 1 Evaluar do-again-test.
La expresin if se evala primero. La prueba do-again devuelve false,
por lo que se evaluar la else-part.
Paso 2 Evaluar la expresin ms interna de la else-part.
La expresin ms interna se evaluar decrementando 3 a 2. Estas es la
next-step-expression.
Paso 3 Evala la funcin triangle-recursively.
Se pasa 2 a la funcin trinagle-recursively.
Ya hemos visto en el punto anterior que cuando se llama a
triangle-recursively con un argumento de 2. Despus de toda
esa secuencia, devolver un 3. Que es lo que suceder aqu.
Paso 4 Evaluar la suma.
El 3 recibido como argumento se pasar a la suma que ser aadido a lo
que devuelve la llamada recursiva, que es 3.
El valor devuelto por la funcin al final, ser 6.
Ahora que sabemos qu suceder cuando se llama triangle-recursively con
un argumento 3, es evidente lo que ocurrir si la llamamos con un argumento 4:
En la llamada recursiva, la evaluacin de
(triangle-recursively (1- 4))
devolver el valor evaluando
(triangle-recursively 3)
que ser 6 y a ese valor se sumar 4.

Ejemplo de recursin utilizando cond


La versin de triangle-recursively descrita antes est escrita con la forma
especial if. Tambin se podra haber escrito con otra forma llamada cond. El
nombre de la forma especial cond es una abreviatura de la palabra conditional.
Aunque cond no se usa tan frecuentemente en Emacs Lisp como if, es lo
suficientemente utilizado para justificar su explicacin.
La plantilla de la expresin cond ser como esta:

RECURSIN

115

(cond
CUERPO...)
Donde el CUERPO es una serie de listas. Escrita de forma ms completa, la
plantilla sera:
(cond
(PRIMERA-PRUEBA-CIERTO-FALSO PRIMERA-CONSECUENCIA)
(SEGUNDA-PRUEBA-CIERTO-FALSO SEGUNDA-CONSECUENCIA)
(TERCERA-PRUEBA-CIERTO-FALSO TERCERA-CONSECUENCIA)
...)
Cuando el intrprete evala la expresin cond, evala el primer elemento de la
primera expresin. Si se evala como nil se salta a la siguiente expresin. Si
ninguna de las condiciones se evalan como true, la expresin cond devuelve
nil.
Escribir la funcin triangle utilizando cond sera algo as:
(defun tirangle-using-cond (number)
(cond ((<= number 0) 0=
((= number 1) 1)
((> number 1)
(+ number (triangle-using-cond (1- number)))))))
En este ejemplo, cond revuelve 0 si el nmero es menor o igual a 0, devuelve 1
si el nmero es 1 y evala (+number (triangle-using-cond (1- number)))
si el nmero es mayo que 1.

Patrones recursivos
Aqu veremos tres patrones recursivos. Cada uno implica las listas. La recursin
no necesita implicar listas, pero Lisp est diseado para las listas y estas dan
sentido a sus capacidades intrnsecas.
Patrn recursivo: every
El patrn recursivo every (para cada) se disea una accin para cada elemento
de una lista.
El patrn bsico es:
Si la lista est vaca, devuelve nil.
Si no, comienza la accin con el comienzo de la lista (el CAR de la lista).
a travs de llamadas recursivas a la funcin con el resto (el CDR) de
la lista,
y, opcionalmente, combina la actuacin sobre el elemento, utilizando
cons con los resultados de actuar sobre el resto.

116

CHAPTER 13. BUCLES Y RECURSIN

Un ejemplo:
(defun square-each (numbers-list)
"Square each of a NUMBERS LIST, recursively."
(if (not numbers-list)
; do-again-test
nil
(cons
(* (car numbers-list) (car numbers-list))
(square-each (cdr numbers-list)))))
; next-step-expression
(square-each '(1 2 3))
(1 4 9)
Si numbers-list est vaca no hace nada. Pero si tiene contenido, construye una
lista combinada con los cuadrados de cada nmero formada a base de llamadas
recursivas.
La funcin print-elements-recursively es otro ejemplo del patrn every,
excepto que en este caso en lugar de utilizar cons para juntar los resultados, se
imprime cada elemento.
(setq animals '(gazelle giraffe lion tiger))
(defun print-elements-recursively (list)
"Print each element of LIST on a line of its own.
Uses recursion."
(when list
; do-again-test
(print (car list))
; body
(print-elements-recursively
; recursive call
(cdr list))))
(print-elements-recursively animals)
El patrn para print-elements-recursively es:
Cuando la lista est vaca no hace nada.
Cuando la lista tiene al menos un elemento,
Acta sobre el principio de la lista (el CAR).
Hace una llamada recursiva sobre el resto (el CDR) de la lista.
Patrn recursivo: accumulate
Otro patrn recursivo es el patrn accumulate. Este consiste en que la accin
que se realiza en cada elemento de la lista, se acumula produciendo un nico
valor para la accin completa.
El patrn es:
Si una lista est vaca, devuelve cero u otro valor constante.

RECURSIN

117

Si no, se acta sobre el comienzo de la lista (el CAR),


y combinar la accin de cada elemento, utilizando + o cualquier otra
funcin de combinacin, con
una llamada recursiva con el resto (el CDR) de la lista.
Por ejemplo:
(defun add-elements (numbers-list)
"Add the elements of NUMBERS-LIST together."
(if (not numbers-list)
0
(+ (car numbers-list) (add-elements (cdr numbers-list)))))
(add-elements '(1 2 3 4))
10
Patrn recursivo: keep
El tercer patrn recursivo se llama keep. En el patrn recursivo, se prueba cada
elemento de la lista; se acta sobre cada elemento y se mantiene el resultaso si
el elemento cumple algn criterio.
Se parece mucho al patrn every excepto que el elementos se salta a no ser que
cumpla algn criterio.
El patrn tiene tres partes:
Una lista vaca revuelve nil.
Si no, si la cabecera (el CAR) de la lista pasa la prueba
se acta con ese elemento, combinndolo, utilizando cons con
una llamada recursiva a la funcin con el resto (el CDR) de la lista.
De otro modo, si la cabecera (el CAR) no pasa la prueba
se salta ese elemento,
y recursivamente se llama a la funcin con el resto (el CDR) de la
lista.
Aqu un ejemplo que utiliza cond:
(defun keep-three-letter-words (word-list)
"Keep three letter words in WORD-LIST."
(cond
;; First do-again-test: stop-condition
((not word-list) nil)
;; Second do-again-test: when to act
((eq 3 (length (symbol-name (car word-list))))
;; combine acted-on element with recursive call on shorter list
(cons (car word-list) (keep-three-letter-words (cdr word-list))))

118

CHAPTER 13. BUCLES Y RECURSIN

;; Third do-again-test: when to skip element;


;;
recursively call shorter list with next-step expression
(t (keep-three-letter-words (cdr word-list)))))
(keep-three-letter-words '(one two three four five six))
(one two six)
No es necesario decir que no necesitas utilizar nil como prueba para parar; y
adems se pueden combinar estos patrones.

Recursin sin aplazamientos


Consideremos de nuevo lo que ocurre con la funcin triangle-recursively.
Esta era la definicin de la funcin:
(defun triangle-recursively (number)
"Return the sum of the numbers 1 through NUMBER inclusive.
Uses recursion."
(if (= number 1)
; do-again-test
1
; then-part
(+ number
; else-part
(triangle-recursively
; recursive call
(1- number)))))
; next-step-expression
Qu ocurre cuando se llama a esa funcin con un argumento 7?
La primera instancia de la funcin triangle-recursively suma 7 al valor
devuelto por la segunda instancia de triangle-recursively que se la llamar
con 6 de argumento. Es decir, el primer clculo es:
(+ 7 (triangle-recursively 6))
Y qu hace (triangle-recursively 6)? Pues devuelve el 6 sumado a lo que
devuelve =triangle-recursively llamado con 5 de argumento.
Ahora el total ser:
(+ 7 6 (triangle-recursively 5))
Y qu har despus?
(+ 7 6 5 (triangle-recursively 4))
Finalmente, el proceso completo ser:
(+ 7 6 5 4 3 2 1)
El diseo de esta funcin aplaza el clculo del primer paso hasta que se haya
hecho el segundo, y aplaza ste hasta que se haya hecho el tercero y as en
adelante. Cada aplazamiento significa que el ordenador debe recordar qu est

RECURSIN

119

esperando. Esto no es un problema cuando hay unos pocos pasos, como en este
ejemplo. Pero puede ser un problema cuando hay muchos ms pasos.

Solucin sin aplazamiento


La solucin al problema de las operaciones aplazadas es escribirlas de manera
que los clculos no se aplacen. Esto requiere que utilicemos otro patrn diferente
para escribirlas, frecuentemente se necesita escribir dos funciones distintas, una
funcin de inicializacin y una funcin de ayuda.
La funcin de iniciacin establece el trabajo y una funcin ayudante que hace el
trabajo. Este ejemplo es muy sencillo, pero est hecho para comprender cmo
funciona.
(defun triangle-initialization (number)
"Return the sum of the numbers 1 through NUMBER inclusive.
This is the innitialization component of a two function
duo that uses recursion."
(triangle-recursive-helper 0 0 number))
(defun triangle-recursive-helper (sum counter number)
"Return SUM, using COUNTER, through NUMBER inclusive.
This is the helper component of a two function duo
that uses recursion."
(if (> counter number)
sum
(triangle-recursive-helper (+ sum counter)
; sum
(1+ counter)
; counter
number)))
; number
Si instalamos ambas definiciones evalundolas y luego llamamos a
triangle-innitialization con dos filas:
(triangle-initialization 2)
3
La funcin de inicializacin llama a la primera instancia de la funcin ayudante
con tres argumentos: cero, cero y un nmero que son las filas del tringulo.
Los dos primeros valores que se pasan a la funcin ayudante son valores de
inicializacin. Estos valores se cambian cuando se invocan nuevas instancias de
triangle-recursive-helper.
Vamos a ver qu ocurre:
triangle-initialization llamar a su ayudante con los argumentos 0 0 1.
Esta funcin correr el condicional (> counter number), que ser (> 0 1).
Encontrar que es falso y por tanto invocar la else-part de la clusula if:

120

CHAPTER 13. BUCLES Y RECURSIN

(triangle-recursive-helper
(+ sum counter)
; sum ms counter sum
(1+ counter)
; incrementar counter counter
number)
; number contina igual.
En la primera computacin:
(triangle-recursive-helper (+ 0 0)
(1+ 0)
1)

; sum
; counter
; number

que ser:
(triangle-recursive-helper 0 1 1)
Todava (> counter number) ser falso, por eso de nuevo el intrprete de Lisp
evaluar triangle-recursive-helper, creando una nueva instancia con nuevos
argumentos.
(triangle-recursive-helper
(+ sum counter)
; sum ms counter sum
(1+ counter)
; incrementar counter counter
number)
; number contina igual.
que ser:
(triangle-recursive-helper 1 2 1)
En este caso (> counter number) ser true. As la instancia devolver el valor
de sum, que ser 1, como se esperaba.
Ahora, veamos triangle-initialization con un argumento de 2, para encontrar cuantas piedras hay en un tringulo con dos filas.
Esta funcin llama (triangle-recursive-helper 0 0 2).
Por pasos, las instancias llamadas sern:
sum counter number
(triangle-recursive-helper 0
1
2)
(triangle-recursive-helper 1
2
2)
(triangle-recursive-helper 3
3
2)
Cuando se llama a la ltima instancia, la prueba (> counter number) ser
evaluado como true, as la instancia devolver sum que ser 3.
Este patrn ayudar a escribir funciones que salven muchos recursos del ordenador.

EJERCICIOS DE BUCLES

121

Ejercicios de bucles
Escribe una funcin similar a triangle en la que cada fila tenga el cuadrado
del nmero de fila. Utiliza un bucle while.
Escribe una funcin similar a triangle que multiplique en lugar de sumar
los valores.
Reescribe estas dos funciones recursivamente. Utiliza cond para hacerlo.
Escribe una funcin para el modo Texinfo que cree una entrada de ndice
al principio de un prrafo para cada @dfn en el prrafo. (En un fichero
Texinfo, @dfn marca una definicin.)

122

CHAPTER 13. BUCLES Y RECURSIN

Chapter 14

Bsquedas de las
expresiones regulares
Las bsquedas con expresiones regulares se utilizan extensivamente en GNU
emacs. Las dos funciones, forward-sentence y forward-paragraph ilustran
bien esas bsquedas. La frase expresin regular frecuentemente se escribe
como regexp.

La expresin regular sentece-end


El smbolo sentence-end se enmarca en el patrn que marca el final en una
sentencia.
Una sentencia puede finalizar con un punto, un interrogante o una exclamacin.
En ingls slo las clusulas que terminan con uno de estos caracteres se pueden
considerar con el fin de sentencia. Esto significa que el patrn debera incluir un
carcter de los siguientes:
[.?!]
Sin embargo, no se puede utilizar simplemente forward-sentence para saltar
a un punto, una exclamacin o un interrogante, porque algunas veces esos
caracteres aparecen en medio de una frase. Por ejemplo, el punto se utiliza
despus de las abreviaturas.
Otro grupo de alternativas podra ser ste:
\\($\\| \\| \\)
^
^^
TAB ESPAC
123

124

CHAPTER 14. BSQUEDAS DE LAS EXPRESIONES REGULARES

Aqu, $ indica el fin de la linea y se han sealado un tabulador y dos espacios en


la expresin.
Dos barras invertidas \\ son necesarias delante de los parntesis y las barras
verticales: la primera indica comilla la siguiente barra invertida en Emacs; y
la segunda indica que el siguiente carcter, el parntesis o la barra vertical, es
especial.
Tambin, una sentencia puede estar seguida por uno o ms retornos de carro,
como esto:
[
]*
Como los tabuladores y los espacios, un retorno de carro se inserta en una
expresin regular insertndolo directamente. El asterisco indica que el <RET>
se repite cero o ms veces.
Se requerira una expresin que se parecera a esta:
[]\"')}]*
Todo esto sugiere que la expresin regular para encontrar el final de una sentencia
debera ser
sentence-end
"[.?!][]\"')}]*\\($\\|
]*"

\\|

\\)[

La funcin re-search-forward
La funcin re-search-forward es muy similar a la funcin search-forward.
re-search-forward busca una expresin regular. Si la bsqueda tiene xito,
deja el punto inmediatamente despus del ltimo carcter del objetivo. Si la
bsqueda es hacia atrs, deja el punto inmediatamente antes del primer carcter
del objetivo.
Como search-forward, la funcin re-search-forward toma tres argumentos:
1. El primer argumento es la expresin regular que utilizar la funcin para
buscar. La expresin regular debe ser una cadena entre comillas.
2. El segundo argumento es opcional y limita cunto de lejos debe buscar; es
un lmite que se especifica como una posicin en el buffer.
3. El tercer argumento es opcional y especifica como responde la funcin al
fallo: nil como tercer argumento causa que la funcin lance una seal de
error cuando la bsqueda falla; cualquier otro valor hace que devuelva nil
si falla la bsqueda y t si tiene xito.

FORWARD-SENTENCE

125

4. El cuarto argumento es opcional y el nmero de repeticiones. Una repeticin


negativa hace que re-search=forward busque hacia atrs.
La plantilla para re-search-forward es:
(re-search-forward "EXPRESIN REGULAR"
LIMITE-DE-BSQUEDA
QU-HACER-SI-FALLA-LA-BSQUEDA
REPETIR-VECES)

forward-sentence
El comando para mover el cursor hacia adelante una sentencia es una forma
de ilustrar una bsqueda con expresiones regulares en Emacs Lisp. La funcin
parece ms larga y complicada de lo que debera, esto es porque est diseada
para ir hacia adelante tanto como hacia atrs y opcionalmente ms de una
sentencia. La funcin normalmente est asociada a la combinacin M-e.

La definicin de funcin forward-sentence completa


Aqu est el cdigo completo
(defun forward-sentence (&optional arg)
"Move forward to next end of sentence. With argument, repeat.
With negative argument, move backward repeatedly to start of sentence.
The variable `sentence-end' is a regular expression that matches ends of
sentences. Also, every paragraph boundary terminates sentences as well."
(interactive "p")
(or arg (setq arg 1))
(let ((opoint (point))
(sentence-end (sentence-end)))
(while (< arg 0)
(let ((pos (point))
(par-beg (save-excursion (start-of-paragraph-text) (point))))
(if (and (re-search-backward sentence-end par-beg t)
(or (< (match-end 0) pos)
(re-search-backward sentence-end par-beg t)))
(goto-char (match-end 0))
(goto-char par-beg)))
(setq arg (1+ arg)))
(while (> arg 0)
(let ((par-end (save-excursion (end-of-paragraph-text) (point))))
(if (re-search-forward sentence-end par-end t)
(skip-chars-backward " \t\n")

126

CHAPTER 14. BSQUEDAS DE LAS EXPRESIONES REGULARES


(goto-char par-end)))
(setq arg (1- arg)))
(constrain-to-field nil opoint t)))

El esqueleto:
(defun forward-sentence (&optional arg)
"DOCUMENTACIN..."
(interactive "p")
(or arg (setq arg 1))
(let ((opoint (point)) (sentence-end (sentence-end)))
(while (< arg 0)
(let ((pos (point))
(par-beg (save-excursion (start-of-paragraph-text) (point))))
RESTO-DE-CUERPO-DEL-BUCLE-WHILE-CUANDO-TRABAJA-HACIA-ATRS
(while (> arg 0)
(let ((par-end (save-excursion (end-of-paragraph-text) (point))))
RESTO-DE-CUERPO-DEL-BUCLE-WHILE-CUANDO-TRABAJA-HACIA-DELANTE
FORMAS-MANEJADORAS-Y-EQUIVALENTES
Esto parece ms simple. La definicin de la funcin consiste en documentacin,
expresin interactiva, una expresin or, una expresin let y un par de bucles
while.
La funcin tiene una declaracin interactive "p". Esto significa que el proceso
puede tener un argumento prefijo, que debe ser un nmero. La expresin or
comprueba si se ha pasado argumento y si no lo hay, lo pone a 1. Cuando se
pasa sin argumento arg est establecido a nil.
Lo siguiente es un let que establece los valores de dos variables locales, opoint
y sentence-end. La primera es establece al valor actual del punto antes de la
bsqueda. La segunda se establece a la funcin sentence-end.

Los bucles while


Le siguen dos bucles while. El primer while tiene una prueba que ser verdadera
si el argumento para forward-sentence es negativo. El cuerpo de este bucle es
similar al cuerpo del segundo pero no exactamente igual. Nos centraremos en el
segundo.
El segundo bucle while es para mover el punto adelante. Su esqueleto es como
sigue:
(while (> arg 0)
; true-or-false-test
(let VARLIST
(if (TRUE-OR-FALSE-TEST)
THEN-PART

127

FORWARD-SENTENCE
ELSE-PART
(setq arg (1- arg))))

; while loop decrementer

El bucle while es de tipo de decremento. En este caso la variable arg es mayor


que cero y el decremento quita 1 cada vez que se repite el bucle.
El cuerpo del bucle while sera as:
(let ((par-end
(save-excursion (end-of-paragraph-text) (point))))
(if (re-search-forward sentence-end par-end t)
(skip-chars-backward " \t\n")
(goto-char par-end)))
La expresin let crea y ata la variable local par-end est ah para establecer
un lmite a la bsqueda. Si la bsqueda falla y no encuentra una sentencia
apropiada en el prrafo, parar la bsqueda al final del prrafo.
El valor de par-end se establece a
(save-excursion (end-of-paragraph-text) (point))
En esta expresin end-of-paragraph-text mueve el puntero al final del prrafo,
(point) devuelve el valor del punto y luego save-excursion restaura el punto
a posicin original. As, el let ata par-end al valor devuelto por la expresin
save-excursion, que es la posicin del final del prrafo.
A continuacin Emacs evala el cuerpo de let que es una expresin if como
sta:
(if (re-search-forward sentence-end par-end t) ; if-part
(skip-chars-backward " \t\n")
; then-part
(goto-char par-end)))
; else-part
El if comprueba si el primer argumento es true y si lo es evala su then-part,
de otro modo, el intrprete de Emacs Lisp evala la else-part.

La expresin regular de bsqueda


La funcin re-search-forward busca el final de una sentencia. Si el patrn se
encuentra la funcin hace dos cosas:
1. La funcin re-search-forward acarrea un efecto colateral que es mover
el punto al final de la ocurrencia encontrada.
2. La funcin re-search-forward devuelve un valor de true. Este es el valor
recibido por el if y significa que la bsqueda ha tenido xito.
El efecto colateral, el movimiento del puntero, se completa antes que la funcin
if maneje el valor retornado.

128

CHAPTER 14. BSQUEDAS DE LAS EXPRESIONES REGULARES

Cuando la funcin if recibe el valor de true de una llamada exitosa


a re-search-forward, el if evala la then-part que es la expresin
(skip-chars-backward " \t\n"). Esta expresin se mueve hacia atrs sobre
cualquier nmero de espacios, tabuladores o retornos de carro hasta el primer
carcter encontrado y luego deja el punto despus del carcter.
Por otro lado, si la funcin re-search-forward falla en encontrar un patrn
devolver false. En ese caso, if evaluar el tercer argumento que es (goto-char
par-end): mueve el punto al final del prrafo.

forward-paragraph: una mina de oro de funciones


La funcin forward-paragraph mueve el puntero hacia adelante hasta el final
del prrafo. Normalmente est asociada a la combinacin M-} y hace uso
de un nmero importante de funciones, includa let*, match-beginning y
looking-at.
La definicin de la funcin forward-paragraph es considerablemente ms larga
que la de la funcin forward-sentence porque trabaja con un prrafo, cada
lnea del cual puede comenzar con un prefijo de rellenado.
Un prefijo de rellenado consiste en una cadena de caracteres que se repiten
al principio de cada lnea. Por ejemplo, en el cdigo Lisp, es una convencin
comenzar cada lnea de un prrafo de comentario largo con ;;; . En modo
texto, cuatro espacios en blanco pueden ser un prefijo de relleno comn cuando
se escriben prrafos indentados.

La definicin de forward-paragraph resumida


Antes de mostrar toda la funcin vamos a ver sus partes. Leer la funcin sin
preparacin puede ser descorazonador.
La funcin se parece a:
(defun forward-paragraph (&optional arg)
"DOCUMENTACIN..."
(interactive "p")
(or arg (setq arg 1))
(let*
VARLIST
(while (and (< arg 0) (not (bobp)))
...
(while (and (> arg 0) (not (eobp)))
...

; backward-moving-code
; forward-moving-code

FORWARD-PARAGRAPH: UNA MINA DE ORO DE FUNCIONES

129

Las primeras partes de la funcin son rutinarias: los argumentos (uno opcional),
la documentacin, la declaracin interactive con p (que procesa un argumento
numrico). La expresin or en la siguiente lnea maneja el caso en que no se
pase argumento a la funcin.

La expresin let*
La siguiente lnea comienza con la expresin let*, que es diferente de let. Es
similar a la versin sin asterisco excepto que Emacs establece cada variable
en secuencia una despus de otra y las variables en las siguientes partes de la
varlist pueden hacer uso de los valores definidos antes.
En la expresin let* Emacs asigna un total de siete variables: opoint,
fill-prefix-regexp, parstart, parsep, sp-parstart, start y found-start.
La variable parsep aparece dos veces, primero para eliminar instancias de y
despus para manejar los prefijos de relleno.
La variable opoint se establece al valor de point.
La variable fill-prefix-regexp se establece al valor devuelto por la evaluacin
de la siguiente lista:
(and fill-prefix
(not (equal fill-prefix ""))
(not paragraph-ignore-fill-prefix)
(regexp-quote fill-prefix))
Esta expresin utiliza la forma especial and, que evala cada uno de sus argumentos hasta que encuentra un valor nil, en cuyo caso la expresin devolver
nil; por contra, si ninguno de los argumentos devuelve el valor nil, el valor
resultante de la evaluacin ser el ltimo argumento evaluado. En otras palabras,
una expresin and devolver true slo si todos sus argumentos son true.
En este caso, la variable fill-prefix-regexp se establecer a un valor no
nil slo si las siguientes cuatro expresiones producen un valor true cuando se
evalen.
fill-prefix Cuando se evala esta variable, se devuelve el valor de la misma,
si tiene alguno. Si no hay prefijo de relleno se devolver nil.
(not (equal fill-previx "") Esta expresin comprueba cundo un prefijo
de relleno es una cadena vaca. Una cadena vaca no es un prefijo de relleno
usable.
(not paragraph-ignore-fill-prefix) Esta expresin devolver nil si se ha
activado la variable paragraph-ignore-fill-prefix o se le ha dado un
valor t.
(regexp-quote fill-prefix) Este es el ltimo argumento de la forma especial
and. Si todos los anteriores son true el valor resultante de esta expre-

130

CHAPTER 14. BSQUEDAS DE LAS EXPRESIONES REGULARES


sin ser el que devuelva la expresin and y se establecer a la variable
fill-prefix-regexp.

Por tanto, fill-prefix-regexp se establecer al valor que coincida con el prefijo


de relleno, si hay alguno, o a nil si no hay.
En esta definicin se utiliza let* en lugar de let. El true-or-false-test de
la expresin if depende de si la variable fill-prefix-regexp se evala a nil
o a otro valor.
Si no tiene valor, Emacs evala la else-part y establece la expresin parsep a
su variable local. Pero si Emacs evala la then-part establecer parsep a una
expresin regular que incluir fill-prefix-regexp como parte del patrn.
Especficamente, parsep se establece al valor original del separador de prrafos
en una expresin regular concatenada con una expresin alternativa que consiste
en fill-prefix-regexp seguido por espacios opcionales al final de la lnea. El
espacio (en blanco) se define por =[ ]*$=.
La siguiente variable local sp-parstart se utiliza para buscar y luego las dos
finales start y found-start se establecen a nil.
Luego llegamos al cuerpo de let*. La primera parte del cuerpo let* se evala
en caso de que la funcin sea llamada con una argumento negativo y en ese caso
se mover hacia atrs. Saltaremos esta seccin.

Bucle while para moverse hacia adelante


La segunda parte del cuerpo de let* es un bucle while que se repite mientras el
valor de arg sea mayor que cero. En el caso ms habitual el valor del argumento
es 1, por eso el bucle while se evala una sola vez y el cursor se mueve hacia
adelante una sola vez.
El bucle while es as:
;; going forwards and not at the end of the buffer
(while (and (> arg 0) (not (eobp)))
;; between paragraphs
;; Move forward over separator lines...
(while (and (not (eobp))
(progn (move-to-left-margin) (not (eobp)))
(lookiing-at parsep))
(forward-line 1))
;; This decrements the loop
(unless (eobp) (setq arg (1- arg)))
;; ... and one more line.
(forward-line 1)

FORWARD-PARAGRAPH: UNA MINA DE ORO DE FUNCIONES

131

(if fill-prefix-regexp
;; There is a fill prefix; it overrides parstart;
;; we go forward line by line
(while (and (not (eobp))
(progn (move-to-left-margin) (not (eobp)))
(not (looking-at parsep))
(looking-at fill-prefix-regexp))
(forward-line 1))
;; There is no fill prefix;
;; we go forward character by character
(while (and (re-search-forward sp-parstart nil 1)
(progn (setq start (match-beginning 0))
(goto-char start)
(not (eobp)))
(progn (move-to-left-margin)
(not (looking-at parstart))
(or (not (looking-at parstart))
(and use-hard-newlines
(not (get-text-property (1- start) 'hard)))))
(forward-char 1))
;; and if there is no fill prefix and if we are not at the end,
;;
go to whatever was found in the regular expression search
;;
for sp-parstart
(if (< (point) (point-max))
(goto-char start))))
Se puede ver que es un bucle de contador decreciente, utilizando la expresin
(setq arg (1- arg)). Esta expresin no est muy alejada del while, pero se
esconde en otro macro de Lisp: unless. A no ser (unless) que estemos en el
final del buffer (que es lo que evala la funcin eobp) que es una abreviacin de
End Of Buffer P, el valor de arg se decrementa en 1.
Nos interesa que el contador de bucle no se decrementar hasta que deje el espacio
entre prrafos, a no ser que estemos al final del buffer donde no encontraremos
separador de prrafos.
El segundo while tambin tiene una expresin (move-to-left-margin). La
funcin se explica sola. Lleva una expresin progn y no es el ltimo elemento de
su cuerpo, por lo que slo se invocar por sus efectos colaterales, que es mover
el puntero al margen izquierdo de la lnea actual.
La funcin looking-at devuelve true si el texto tras el puntero coincide con la
expresin regular dada en su argumento.
El resto del cupero del bucle parece difcil, pero es sencillo comprenderlo.
Primero vamos a considerar qu ocurre con el prefijo de relleno:

132

CHAPTER 14. BSQUEDAS DE LAS EXPRESIONES REGULARES

(if fill-prefix-regexp
;; There is a fill prefix; it overrides parstart;
;; we go forward line by line
(while (and (not (eobp))
(progn (move-to-left-margin) (not (eobp)))
(not (looking-at parsep))
(looking-at fill-prefix-regexp))
(forward-line 1))
Esta expresin mueve el puntero lnea a lnea mientras estas cuatro condiciones
son true:
1. El punto no est al final del buffer.
2. Podemos movernos al margen izquierdo del texto y no estamos al final del
buffer.
3. El texto donde est el punto no es un separador de prrafos.
4. El patrn que sigue al punto es el patrn de relleno.
La ltima condicin puede ser confusa, hasta que recuerdas que el punto se ha
movido al principio de la lnea en la funcin forward-paragraph. Esto significa
que el texto tiene un prefijo de relleno, que es lo que mira la funcin looking-at.
Consideremos qu ocurre cuando no hay prefijo de relleno.
(while (and (re-search-forward sp-parstart nil 1)
(progn (setq start (match-beginning 0))
(goto-char start)
(not (eobp)))
(progn (move-to-left-margin)
(not (looking-at parsep)))
(or (not (looking-at parstart))
(and use-hard-newlines
(not (get-text-property (1- start) 'hard)))))
(forward-char 1))
El bucle while busca hacia adelante una sp-parstart que es una combinacin
de posibles espacios en blanco con el valor local de inicio de un prrafo o de un
separador de prrafos.
Las dos expresiones,
(setq start (match-beginning 0))
(goto-char start)
significan que el inicio del texto coincide por la expresin regular buscada.
La expresin (match-beginning 0) es nueva. Devuelve un nmero que especifica
el lugar del inicio del texto que se ha encontrado con la ltima bsqueda.
La funcin match-beginning se utiliza aqu porque es una bsqueda hacia
adelante caracterstica. Una bsqueda que mueve el punto al final del texto que

RESUMEN

133

ha encontrado. En este caso, el xito de la bsqueda mueve el punto al final de


un patrn para sp-parstart.
Cuando se le pasa un argumento de 0, match-beginning devuelve la posicin
que es el inicio del texto que coincide con la bsqueda ms reciente. En este caso,
la bsqueda ms reciente es sp-parstart. Por lo tanto, =(match-beginning 0)
devuelve la posicin donde comienza el patrn en lugar de donde acaba.
La ltima expresin cuando no hay prefijo de relleno es
(if (< (point) (point-max))
(goto-char start))))
Esto dice que si no hay prefijo de relleno y si no est al final, el punto debera
moverse al principio de donde ha encontrado la expresin regular buscada para
sp-parstart.

Resumen
Un breve resumen de las funciones que se acaban de presentar.
while Evala repetidamente el cuerpo de la expresin mientras el primer elemento sea evaluado como true. Luego devuelve nil. (La expresin se
evala slo por sus efectos colaterales.)
Por ejemplo:
(let ((foo 2))
(while (> foo 0)
(insert (format "foo is %d.\n" foo))
(setq foo (1- foo))))
re-search-forward Bsca un patrn y si lo encuentra mueve el puntero all.
Toma cuatro argumentos, como search-forward:
1. Una expresin regular que especifica el patrn de bsqueda. (Recuerda
poner marca de quotation para este argumento)
2. Opcionalmente, el lmite de la bsqueda.
3. Opcionalmente, qu hacer si la bsqueda falla, devuelve nil o un
mensaje de error.
4. Opcionalmente, cuntas veces repetir la bsqueda; si es negativa, la
bsqueda va hacia atrs.
let* Establece valores a variables locales y luego evala los restantes argumentos,
devolviendo el valor del ltimo. Mientras establece las variables locales,
usa los valores locales establecisas antes, si hay alguna.
Por ejemplo:

134

CHAPTER 14. BSQUEDAS DE LAS EXPRESIONES REGULARES


(let* ((foo 7)
(bar (* 3 foo)))
(message "`bar' is %d." bar))

match-beginning Devuelve la posicin del comienzo del texto encontrado por


la ltima bsqueda con expresin regular.
looking-at Devuelve t si el texto tras el punto coincide con el argumento, que
sera una expresin regular.
eobp Devuelve t (por true) si el punto est al final de la parte accesible de
un buffer. El final de la parte accesible es el final del buffer si no esta
estrechado; si lo est, el final ser el final del narrowed.

Ejercicios con re-search-forward


Escribe una funcin para buscar una expresin regular que coincida con
dos o ms lneas en blanco seguidas.
Escribir una expresin de bsqueda para palabras duplicadas, como por
ejemplo el el. (Puedes mirar el anexo sobre el tema).

Chapter 15

Contar por repeticin y


expresiones regulares
La repeticin y las bsquedas de expresiones regulares son herramientas poderosas
que se usan frecuentemente para escribir cdigo en Emacs Lisp. Este captulo
ilustra el uso de bsquedas por expresiones regulares a travs de comandos de
conteo utilizando bucles while y recursin.

Contando palabras
La distribucin estndar de Emacs contiene funciones de conteo de nmero de
lneas y palabras en una regin.
Ciertos tipos de escritura preguntan por el nmero de palabras. Por ejemplo,
al escribir un ensayo se pueden limitar las palabras a 800; si escribes una
novela, puedes imponerte la disciplina de escribir 1000 palabras diarias. La
gente normalmente utiliza Emacs para programar o escribir documentacin que
no suele requerer contar palabras. As se puede utilizar el comando wc del
sistema operativo. Alternativamente, la gente podra seguir la convencin de las
editoriales de dividir el nmero de caracteres por cinco.

La funcin count-word-example
Un comando de contar palabras en una lnea, prrafo, regin o buffer. Vamos a
disear una funcin de este tipo.
Claramente, contando palabras es una acto repetitivo: comenzar desde el inicio
de la regin, contar la primera palabra, luego la segunda, despus la tercera y
135

136CHAPTER 15. CONTAR POR REPETICIN Y EXPRESIONES REGULARES


as en adelante, hasta encontrar el final de la regin. Esto parece que el contador
de palabras se adapta a la recursin de un bucle while.

Diseando count-words-example
Primero, necesitamos implementar el contador de palabras en un bucle while.
El comando ser, por supuesto, interactivo.
La plantilla para una funcin interactiva es, como siempre:
(defun NOMBRE-DE-FUNCIN (LISTA-ARGUMENTOS)
"DOCUMENTACIN..."
(EXPRESIN-INTERACTIVA...)
CUERPO...)
El nombre de la funcin debera ser suficientemente descriptiva y similar al
nombre existente de count-lines-region. Eso har el nombre fcil de recordar. count-words-region es la opcin obvia. Pero ese nombre lo utiliza el
comando estndar de Emacs que cuenta palabras. As que el nombre ser
count-words-example.
La funcin contar palabras en una regin. Esto significa que la lista de argumentos debera contener smbolos que limiten las dos posiciones de inicio y final
de la regin. Esas dos posiciones se pueden llamar respectivamente beginning
y end. La primera lnea de la documentacin debe ser una sla frase, que ser
la que se muestre con el comando apropos. La expresin interactiva tendr la
forma (interactive "r"), porque eso har que Emacs pase el inicio y el final
de la regin a la lista de argumentos. El resto es rutina.
El cuerpo de la funcin necesita escribirse de tres tareas: primero, fijar las
condiciones del bucle while para contar palabras, segundo correr el bucle y
tercero, enviar un mensaje al usuario.
Cuando el usuario llame a count-words-example, el punto se mover al principio
o el final de la regin. Por esa razn, el cuerpo debe estar encerrado en una
expresin save-excursion.
La parte central del cuerpo de la funcin consiste en un bucle while en cuya
primera expresin salta el punto palabra a palabra y otra expresin que contar
esos saltos. La prueba true-or-false-test del bucle while debera ser true
mientras se pueda saltar hacia adelante, y falso cuando se encuentre el final de
la regin.
Usaremos (forward-word 1) como la expresin para mover el puntero palabra
a palabra, pero es fcil ver que Emacs identifica word utilizando un bsqueda
con una expresin regular.
Una expresin regular que busca un patrn deja el punto tras el ltimo carcter
coincidente. Esto significa una sucesin de bsquedas exitosas que movern el

LA FUNCIN COUNT-WORD-EXAMPLE

137

punto hacia adelante palabra a plabra. La expresin segular ser:


\w+\W*
La expresin de bsqueda, por tanto ser como sta:
(re-search-forward "\\w+\\W*")
Teniendo la expresin que produce los saltos, necesitaremos la que los cuente.
Para ello necesitamos un contador, que se pondr inicialmente a 0 y se incrementar en cada vuelta del bucle while. La expresin de incremento es
simplemente:
(setq count (1+ count))
Nos queda la expresin que muestre la informacin con la funcin message. Nos
encontraremos con los problemas habituales entre plural y singular y mensajes
extraos del tipo hay 1 palabras en la regin. Hay tres posibilidades: no hay
palabras en la regin, hay una palabra o hay ms de una palabra. Esto significa
que la forma especial cond es apropiada.
Si juntamos todo lo anterior haremos una definicin de funcin como sigue:
;;; First version; has bugs!
(defun count-words-example (beginning end)
"Print number of words in the region.
Words are defined as at least one word-costituent
character followed by at least one character that
is not a word-constituent. The buffer's syntax
table determines which characters these are."
(interactive "r")
(message "Counting words in region ... ")
;;; 1. Set up appropiate conditions.
(save-excursion
(goto-char beginning)
(let ((count 0))
;;; 2. Run the while loop.
(while (< (point) end)
(re-search-forward "\\w+\\W*")
(setq count (1+ count)))
;;; 3. Send a message to the user.
(cond ((zerop count)
(message
"The region does NOT have any words."))
((= 1 count)
(message
"The region has 1 word."))

138CHAPTER 15. CONTAR POR REPETICIN Y EXPRESIONES REGULARES


(t
(message
"The region has %d words." count))))))
As escrita, la funcin trabaja, pero no en todas las circunstancias.

El bug de espacio en count-words-example


El comando count-words-example de la seccin anterior tiene dos bugs, o un
bug con dos manifestaciones. Primero, si marcas una regin conteniendo slo
espacios en blanco en medio de algn texto, el comando te dice que slo hay
una palabra. Segundo, si marcas una regin conteniendo slo espacios al final
del buffer o a la parte accesible de un narrowed buffer, el comando muestra un
mensaje de error como:
Search failed: "\\w+\\W*"
Los dos bugs vienen del mismo problema.
Considera la primera manifestacin del bug, en cual el comando dice que espacio
en blanco contiene una palabra. Que ocurre cuando se pueve el punto al principio
de la region. El bucle while comprueba si el valor del punto es ms pequeo
que el valor end, lo que ocurre. Por consiguiente, la expresin regular busca y
encuentra la primera palabra. Deja el puntero tras la primera palabra. count se
pone a 1. El bucle while se repite; pero esta vez el punto es mayor que el valor
end y el bucle sale y la funcin muestra el mensaje de que hay una palabra en la
regin.
En la segunda manifestacin del bug, la regin es espacio en blanco al final del
buffer. Emacs muestra Search failed.
En ambas manifestaciones del bug, la bsqueda se extiende o intenta extenderse
fuera de la regin.
La solucin es limitar la bsqueda a la regin. Es una accin simple pero no tan
sencillo por se podra pensar.
Como hemos visto, la funcin re-search-forward toma un patrn de bsqueda
como primer argumento. Pero adems de este argumento obligatorio, puede
aceptar tres argumentos opcionales. El segundo argumento opcional pone lmites
a la bsqueda.
En la definicin de count-words-example el valor de final de la regin se maneja
por la variable end que se pasa como un argumento a la funcin. Entonces,
podemos aadir end como argumento a la expresin regular de bsqueda:
(re-search-forward "\\w+\\W*" end)
Sin embargo, si slo se hace ese cambio en la definicin cuando se pruebe la
nueva versin en una regin de espacios en blaco, obtendremos un error original

LA FUNCIN COUNT-WORD-EXAMPLE

139

diciendo Search failed.


Lo que ocurre aqu es que la bsqueda se limita a la regin y falla, como se
espera al no haber palabras en la regin. Sin embargo, se recibe un mensaje de
error cuando lo que queramos era recibir un mensaje de The region does NOT
have any words.
La solucin a este problema es proporcionar a re-search-forward un tercer
argumento de t, que har que la funcin devuelva nil en lugar de una seal de
error si la bsqueda falla.
Sin embargo, si haces ese cambio y lo pruebas, obtendrs el mensaje Counting
words in region . . . y . . . hasta que pulses C-g. Lo que ocurre es que al
devolver nil la expresin re-search-forward no hace nada, en particular, no
mueve el punto, que es uno de los efectos colaterales buscados con la bsqueda y
el bucle se repite.
Por lo tanto, la definicin de count-words-example requiere otra modificacin
que controle los efectos en el bucle cuando falla la bsqueda.
Puesto hay que repetir el bucle hasta que se alcanza el final de la regin o la
bsqueda falla. La prueba del true-or-false-test, debera ser:
(and (< (point) end) (re-search-forward "\\w+\\W*" end t))
Despus de incoroporar estos cambios, la versin de count-words-example sin
bugs sera como:
;;; Final version: while
(defun count-words-example (beginning end)
"Print number of words in the region."
(interactive "r")
(message "Counting words in region ... ")
;;; 1. Set up appropriate conditions.
(save-excursion
(let ((count 0))
(goto-char beginning)
;;; 2. Run the while loop.
(while (and (< (point) end)
(re-search-forward "\\w+\\W*" end t))
(setq count (1+ count)))
;;; 3. Send a message to the user.
(cond ((zerop count)
(message
"The region does NOT have any words."))
((= 1 count)
(message

140CHAPTER 15. CONTAR POR REPETICIN Y EXPRESIONES REGULARES


"The region has 1 word."))
(t
(message
"The region has %d words." count))))))

Contar palabras recursivamente


Tambin se puede escribir una funcin para contar palabras recursivamente.
Primero necesitas darte cuenta que la funcin count-words-example realiza
tres trabajos: establece las condiciones para el contador; cuenta las palabras en
la regin; y escribe un mensaje diciendo el resultado.
Si queremos escribir una funcin recursiva que haga todo, recibiremos un mensaje
con cada llamada recursiva. Si la regin contiene 13 palabras, se recibirn 13
mensajes uno detrs de otro. En lugar de hacerlo en una sla funcin tenemos
que escribir dos funciones para el trabajo. Una que ser la recursiva y que se
utilizar dentro de otra. Una funcin puede establecer la condiciones y mostrar
el mensaje; la otra devolver el nmero de palabras.
El esquema sera:
;; Recursive version; uses regular expression search
(defun count-words-example (beginning end)
"DOCUMENTACIN... "
(EXPRESIN-INTERACTIVA...)
;;; 1. Set up appropiate conditions
(MENSAJE EXPLICATIVO)
(FUNCIONES DE SET-UP...
;;; 2. Contar las palabras
RECURSIVE CALL
;;; 3. Send a message to the user.
(MENSAJE CON PALABRAS CONTADAS))
La definicin resulta familiar. Utilizando let la definicin de la funcin sera as:
(defun count-words-example (beginning end)
"Print number of words in the region."
(interactive "r")
;;; 1. Set up appropriate conditions.
(message "Counting words in region ... ")
(save-excursion
(goto-char beginning)

CONTAR PALABRAS RECURSIVAMENTE

141

;;; 2. Count the words.


(let ((count (recursive-count-words end)))
;;; 3. Send a message to the user.
(cond ((zerop count)
(message
"The region does NOT have any words."))
((= 1 count)
(message
"The region has 1 word."))
(t
(message
"The region has %d words." count))))))
Lo siguiente es escribir la funcin recursiva que cuenta palabras.
Una funcin recursiva tiene al menos tres partes: do-again-test, la
next-step-expression y la llamada recursiva. Tiene esta estructura.
(defun recursive-count-words (region-end)
"DOCUMENTACIN..."
DO-AGAIN-TEST
NEXT-STEP-EXPRESSION
RECURSIVE CALL)
Vamos a rellenar las partes. La parte de la prueba do-again-test sera as:
(and (< (point) region-end)
(re-search-forward "\\w+\\W*" region-end t))
Hay que observar que se pasa t a la funcin de bsqueda para que devuelva nil
si falla.
El pseudocdigo del cuerpo de recursive-count-words sera as:
(if DO-AGAIN-TEST-AND-NEXT-STEP-COMBINED
;; then
RECURSIVE-CALL-RETURNING-COUNT
;; else
RETURN-ZERO)
Cmo incorporar el mecanismo que cuenta? Esta es una pregunta molesta
cuando se utiliza esta aproximacin sistemticamente. Vamos a considerar
algunos casos:
Si hay dos palabras en la regin, la funcin debera devolver el valor
sumando uno al valor que devuelva la cuenta de esta primera palabra ms
el nmero devuelto por la cuenta de las restantes palabras en la regin,
que en este caso es uno.

142CHAPTER 15. CONTAR POR REPETICIN Y EXPRESIONES REGULARES


Si hay una palabra en la regin, la funcin debera devolver el resultado
de sumar uno al valor devuelto cuando cuenta el resto de palabras, que en
este caso es cero.
Si no hay palabras en la regin, la funcin debera devolver cero.
La expresin se parecera a sta:
(1+ (recursive-count-words region-end))
La funcin recursive-count-words completa se parecera a:
(defun recursive-count-words (region-end)
"DOCUMENTACIN..."
;;; 1. do-again-test
(if (and (< (point) region-end)
(re-search-forward "\\w+\\W*" region-end t))
;;; 2. then-part: the recursive call
(1+ (recursive-count-words region-end))
;;; 3. else-part
0))
Con toda la documentacin las dos funciones seran as:
La funcin recursiva:
(defun recursive-count-words (region-end)
"Number of words between point and REGION-END."
;;; 1. do-again-test
(if (and (< (point) region-end)
(re-search-forward "\\w+\\W*" region-end t))
;;; 2. then-part: the recursive call
(1+ (recursive-count-words region-end))
;;; 3. else-part
0))
El envoltorio:
;;; Recursive version
(defun count-words-example (beginning end)
"Print number of words in the region.
Words are defined as at least one word-constituent
character followed by at least one character that is
not a word-constituent. The buffer's syntax table

EJERCICIO: CONTARNDO SIGNOS DE PUNTUACIN

143

determines which characters these are."


(interactive "r")
(message "Counting words in region ... ")
(save-excursion
(goto-char beginning)
(let ((count (recursive-count-words end)))
(cond ((zerop count)
(message
"The region does NOT have any words."))
((= 1 count)
(message "The region has 1 word."))
(t
(message
"The region has %d words." count))))))

Ejercicio: Contarndo signos de puntuacin


Utiliza un bucle loop para escribir una funcin que cuente el nmero de signos
de puntuacin en una regin: punto, coma, punto y coma, dos puntos, signos de
exclamacin y de interrogacin. Haz lo mismo utilizando recursin.

144CHAPTER 15. CONTAR POR REPETICIN Y EXPRESIONES REGULARES

Chapter 16

Contando palabras en un
defun
Nuestro siguiente proyecto es contar el nmero de palabras en una definicin de
funcin.

Divide y vencers
Descrito en una frase, el proyecto histograma es terrorfico; pero vamos a dividirlo
en muchos pequeos pasos, cada uno de los cuales haremos de una vez y el
proyecto se har ms llevadero. Vamos a considerar que los pasos podran ser:
Primero, escribir una funcin para contar las palabras en una definicin.
Esto incluye el problema de manejar smbolos tambin como palabras.
Segundo, escribir una funcin que liste el nmero de palabras en
cada funcin de un fichero. Esta funcin har uso de la anterior
count-words-in-defun.
Tercero, escribir una funcin que liste el nmero de palabras de cada funcin
en cada uno de varios ficheros. Esto incluye buscar automticamente en
varios ficheros, cambiar a ellos y contar las palabras en las definiciones que
contengan.
Cuarto, escribir una funcin que convierta la lista de nmeros que se ha
creado en el paso tres a un formulario que muestre un grfico.
Quinto, escribir una funcin que imprima los resultados como un grfico.
Es un gran proyecto, pero tomando cada paso despacio, no ser tan dificultoso.
145

146

CHAPTER 16. CONTANDO PALABRAS EN UN DEFUN

Qu contar?
Cuando empezamos a pensar sobre cmo contar palabras en la definicin de una
funcin, la primera cuestin es (o debera ser) qu vamos a contar? Cuando
decimos palabras con respecto a una funcin en Lisp, estamos hablando, en su
mayora, de smbolos. Por ejemplo:
(defun multiply-by-seven (number)
"Multiply NUMBER by seven."
(* 7 number))
En este ejemplo, la funcin contiene los smbolos defun, multiply-by-seven,
number, * y 7. Adems, en la cadena de documentacin encontramos cuatro
palabras Multiply, NUMBER, by y seven. El smbolo number est repetido.
Sin embargo, si llamamos a la funcin que creamos en el punto anterior
count-words-example sobre ella, veremos que la definicin tiene once palabras
y no diez. Algo no funciona.
El problema es doble: count-words-example no contar * como una palabra
y sin embargo contar el smbolo multiply-by-seven como tres palabras. Los
guiones sern tratados como espacios entre palabras y no conectores intrapalabras.
La causa de esta confusin es la expresin regular que se utiliza para separar
palabras:
"\\w+\\W*"

Qu constituye una palabra o un smbolo?


Emacs trata a diferentes caracteres en diferentes categoras sintcticas. Por
ejemplo, la expresin segular \\w+ es un patrn que especifica uno o ms caracteres constitutivos de palabras. Otra categora sintctica incluye los caracteres
de puntuacin, como pueden ser el punto y la coma. Otra clase son los espacios
en blanco que incluiran no slo los espacios, tambin los tabuladores o los saltos
de lnea.
Hay dos maneras, principalmente, de hacer que Emacs cuente multiply-by-seven
como un nico smbolo: modificar las tablas de sintaxis o modificar la expresin
regular.
La primera es compleja y podra afectar al funcionamiento de Emacs. Modificar
la expresin regular es ms factible. La primera parte de la expresin regular
debera encontrar al menos un caracter que sea constituyente de caracter o de
smbolo. Esto es:
"\\(\\w\\|\\s_\\)+"

LA FUNCIN COUNT-WORDS-IN-DEFUN

147

El \\( es la primera parte del grupo que incluye las partes \\w y \\s_ como
alternativas separadas por \\|. La parte \\w para caracteres que coinciden con
los que forman palabras y la \\s_ para los forman parte de un smbolo. El signo
+ indica que debe haber al menos un caracter que coincida.
Sin embargo, la segunda parte de la expresin regular es ms difcil de disear.
Al principio puede pensarse en definirla as:
"\\(\\W\\|\\S_\\)*"
Las W y S maysculas buscaran caracteres que no son partes de smbolos o
palabras. Desafortunadamente, esta expresin coincide con cualquier carcter.
Finalmente, necesitamos un patrn en que cada palabra o smbolo pueda estar
seguido por caracteres que no son espacios y luego por espacios. La expresin
regular sera:
"\\(\\w\\|\\s_\\)+[ \t\n]*[ \t\n]*"

La funcin count-words-in-defun
Como hemos visto hay varias formas de escribir la funcin, slo necesitamos
adaptar una de esas versiones a lo que queremos hacer.
La versin que utiliza el bucle while es quizs la ms clara y por eso es la que
vamos a utilizar. Esta sera su plantilla:
(defun count-words-in-defun ()
"DOCUMENTACIN..."
(SET UP...
(WHILE BUCLE ...)
DEVOLVER COUNT)
Vamos a rellenar las partes. Primero el set up.
Asumimos que la funcin se llamar en un buffer que contenga definiciones de
funciones. El punto puede estar en la definicin de una funcin o no. Para
que funcione count-words-in-defun el punto se debe mover al comienzo de la
definicin, establecer un contador a cero y el bucle debe parar cuando el punto
alcance el final de la definicin.
La funcin beginning-of-defun busca hacia atrs un delimitador ( al comienzo
de una lnea y mueve el punto a esa posicin, o al comienzo del lmite de la
bsqueda, si es el caso. In la prctica, significa que begining-of-defun mueve
el punto al comienzo de una definicin de funcin, por lo que podemos utilizarla
para empezar.
El bucle while necesita un contador que guarde las veces que se cuentan smbolos
o palabras. Una expresin let podra ayudarnos a crear una variable local pare

148

CHAPTER 16. CONTANDO PALABRAS EN UN DEFUN

este propsito y guardar en ella un valor inicial de cero.


La funcin end-of-defun funciona como beginning-of-defun excepto que
mueve el punto al final de la definicin. Se podra utilizar end-of-defun como
parte de una expresin que determine la posicin del final de la definicin.
El cdigo del set up podra ser algo as:
(beginning-of-defun)
(let ((count 0)
(end (save-excursion (end-of-defun) (point))))
El cdigo es simple. Lo nico un poco ms complicado es el end: est atado a la
posicin del final de la definicin mediante una expresin save-excursion que
retorna el valor del punto tras el end-of-defun al principio de la definicin.
La segunda parte de count-words-in-defun sera el bucle while.
El bucle debe contener una expresin que haga saltar al punto hacia adelante de
palabra y/o smbolo en palabra y/o smbolo. La prueba true-or-false-test
del bucle while debera ser true mientras el punto no alcance el valor end.
(while (and (< (point) end)
(re-search-forward
"\\(\\w\\|\\s_\\)+[^ \t\n]*[ \t\n]*" end t))
(setq count (1+ count)))
La tercera parte de la definicin sera devolver el valor de count. As que
juntando todas las partes, la definicin de la funcin debera ser esta:
(defun count-words-in-defun ()
"Return the number of words and symbols in a defun."
(beginning-of-defun)
(let ((count o)
(end (save-excursion (end-of-defun) (point))))
(while
(and (< (point) end)
(re-search-forward
"\\(\\w\\|\\s_\\)+[^ \t\n]*[ \t\n]*"
end t))
(setq count (1+ count)))
count))
Cmo se puede probar? La funcin no es interactiva, pero es fcil escribir
un envoltorio para poder llamarla de forma interactiva. Sera muy parecido al
envoltorio de la versin recursiva de count-words-example:
;;; Interactive version.
(defun count-words-defun ()
"Number of words and symbols in a function definition."
(interactive)

CONTAR VARIOS DEFUN EN UN FICHERO

149

(message
"Counting words and symbols in function definition ... ")
(let ((count (count-words-in-defun)))
(cond
((zerop count)
(message
"The definition does NOT have any words or symbols."))
((= 1 count)
(message
"The definition has 1 word or symbol."))
(t
(message
"The definition has %d words or symbols." count)))))
Ahora, si evaluamos el cdigo de las dos funciones y lo probamos en la conocida
multiply-by-seven:
(defun multiply-by-seven (number)
"Multiply NUMBER by seven."
(* 7 number))
El valor devuelto ser 10, que es el valor buscado.

Contar varios defun en un fichero


Un fichero como simple.el puede contener cientos de funciones. El objetivo
final es obtener estadsticas de varios ficheros, pero el primer paso sera conseguir
esas estadsticas de uno.
La informacin podran ser series de nmeros con el largo de cada definicin
almacenadas en una lista.
El contador de palabras inclua una expresin que haca saltar el punto palabra
a palabra y otra que contaba los saltos. La funcin que devuelve el largo de una
definicin se puede disear de la misma manera.
Deberamos empezar por el principio del fichero, as por el primer comando
podra ser (goto-char (point-min)). Luego comenzara un bucle while que
debera saltar de definicin en definicin. En el cuerpo de la expresin se debera
crear una lista que vaya guardando los valores con cons.
Un fragmento de este cdigo podra ser:
(goto-char (point-min))
(while (re-search-forward "^defun" nil t)
(setq lengths-list
(cons (count-words-in-defun) lengths-list)))

150

CHAPTER 16. CONTANDO PALABRAS EN UN DEFUN

Esto sera el mecanismo para buscar en un fichero que contenga definiciones de


funciones.

Buscar un fichero
Para buscar un fichero en Emacs, utilizas el comando C-x C-f (find-file).
Este comando no es el adecuado para el problema de los tamaos.
Si miramos en el cdigo de find-file:
(defun find-file (filename)
"Edit file FILENAME.
Switch to a buffer visiting file FILENAME,
creating one if none already exists."
(interactive "FFind file: ")
(switch-to-buffer (find-file-noselect filename)))
La definicin te muestra una corta pero completa documentacin y una especificacin interactiva que pregunta por un nombre de fichero cuando se usa
interactivamente el comando. El cuerpo de la definicin contiene dos funciones,
find-files-noselect y switch-to-buffer.
De acuerdo con su documentacin lo que hace es leer el nombre de un fichero,
cargarlo en un buffer y cambiar a l (En versiones ms modernas se permiten
el uso de caracteres especiales que permiten abrir varios a la vez, pero estos
argumentos opcionales son irrelevantes aqu).
Sin embargo, la funcin find-file-noselect no salta al buffer donde pone el
fichero. En el proyecto histograma, no necesitamos que se muestre por pantalla
cada fichero analizado. En lugar de utilizar switch-to-buffer utilizaremos
set-buffer que dirige la atencin del programa a un buffer diferente al que se
muestra por pantalla. Por eso, en lugar de llamar a find-file para hacer el
trabajo, debemos escribir nuestra propia expresin.
La tarea es fcil utilizando find-file-noselect y set-buffer.

lengths-list-file en detalle
El corazn de la funcin lengths-list-file es un bucle while que contiene
una funcin para mover el punto de defun en defun e ir llamando a funciones
auxiliares que cuenten el largo de cada una. La funcin debera ser:
(defun lengths-list-file (filename)
"Return list of definitions' lengths within FILE.
The returned list is a list of numbers.
Each number is the number of words or

LENGTHS-LIST-FILE EN DETALLE

151

symbols in one function definition."


(message "Working on `%s' ... " filename)
(save-excursion
(let ((buffer (find-file-noselect filename))
(lengths-list))
(set-buffer buffer)
(setq buffer-read-only t)
(widen)
(goto-char point-min))
(while (re-search-forward "^(defun" nil t)
(setq lengths-list
(cons (count-words-in-defun) lengths-list)))
(kill-buffer buffer)
lengths-list))
A la funcin se le pasa un argumento, el nombre del fichero sobre el que se
trabajar. Tiene cuatro lneas de documentacin, pero no una especificacin
interactiva.
La siguiente lnea contiene una expresin save-excursion que devuelve la
atencin de Emacs al buffer actual cuando se complete la accin.
En la lista de variable de la expresin let, Emacs busca el fichero y lo ata a una
variable local buffer que ser el buffer que contenga el fichero. A la vez, Emacs
crea una variable local lengths-list.
Lo siguiente es que Emacs fija su atencin en el buffer.
En la siguiente lnea, Emacs hace que el buffer sea de slo lectura. Idealmente,
esta lnea no sera necesario, ninguna de las funciones que cuentan palabras y
smbolos en una definicin de funcin hacen ninguna modificacin. Adems, el
buffer no se guardar despus, se haya cambiado o no. Esta lnea es slo consecuencia de una gran, quiz excesiva, precaucin. La razn para esa precaucin es
que esta funcin se puede llamar sobre fuentes de Emacs y sera un inconveniente
modificarlas inadvertidamente.
Despus viene una llamada para widen por si el buffer est estrechado. Esta
funcin no se necesitar normalmente cuando Emacs crea el buffer, pero es posible
que el buffer ya exista y se debe saltar cualquier restriccin de localizacin o
posicin del punto.
La expresin (goto-char (point-min)) mueve el puntero al inicio del buffer.
Luego viene el bucle while que hace el trabajo de la funcin. En el bucle,
Emacs determina el largo de cada definicin y construye una lista que contiene
la informacin de los largos.
Emacs destruye el buffer despus de haber hecho el trabajo. Esto es para
conservar espacio en Emacs.

152

CHAPTER 16. CONTANDO PALABRAS EN UN DEFUN

Se puede comprobar el funcionamiento de esa funcin evalundola y luego


evaluando la expresin:
(lengths-list-file
"/usr/local/share/emacs/22.1/lisp/emacs-lisp/debug.el")
Si ni tienes la versin 22.1 de Emacs tendrs que cambiar el path.
Tambin puedes ver el largo completo de la lista. Se puede evaluar lo siguiente:
(custom-set-variables '(eval-expression-print-length nil))
La lista de largos de debug.el toma menos de un segundo producirla:
(83 113 105 144 289 22 30 97 48 89 25 52 52 88 28 29 77 49 43 290 232 587)
Utilizando una versin ms antigua, la de la versin 19, es:
(75 41 80 62 20 45 44 68 45 12 34 235)
La versin ms nueva de debug.el contiene ms definiciones de funcin que la
ms antigua.

Contar palabras en defun en varios ficheros


Ahora vamos a definir la funcin que devuelva una lista maestra de los largos de
las definiciones en una lista de ficheros.
Trabajando cada uno de los ficheros de una lista como un acto de repeticin,
podemos, por tanto, utilizar un bucle while o recursin.

lengths-list-many-files
El diseo utilizando bucle while es rutinario.

Chapter 17

Footnotes

153

Вам также может понравиться