sábado, 15 de octubre de 2016

Emacs Lisp - Función/comando para reemplazar coincidencias de texto.

Una de las grandes ventajas de la utilización del editor Gnu Emacs es su capacidad para extender las funciones del mismo editor a través de elisp.

 Para empezar a adentrarme en el lenguaje, decidí escribir una pequeña función que me ayudara a rápidamente cambiar todas las coincidencias de una cadena de texto en un buffer.  A continuación, explico como lograr esto de manera sencilla.

Funciones y sus argumentos.
Para definir una función en elisp, usamos la siguiente palabra (o símbolo, para ser más correcto según lisp):

defun 

Simplemente una abreviación de "define function". Este símbolo le dice a lisp, que el consecuente código dentro de la lista definida sea enlazado a otro símbolo: el nombre de nuestra función. Por ejemplo:

(defun suma (x y)
      (+ x y)
) 

¿Qué hace esta función? Es simple. Toma dos argumentos recibidos y los suma. En otras palabras, enlaza el símbolo "suma" con el código consecuente dentro de la función, el de la suma.
Ahora bien, observemos que después de la definición del nombre de la función viene otra lista. Esta es la lista de los parametros a usar en nuestro función y que deben ser pasados al momento de llamarla. Por ejemplo, evaluemos la siguiente función:

(suma 3 4)

Deberá imprimirse en nuestro mini-buffer el número 7.

La función interactive.

La función interactive hace que nuestra función pueda volverse un comando (pueda ser llamada con M-x/alt-x). Distintos argumentos dados a nuestra función, le dan distintos comportamientos a la llamada de nuestro comando/función.
Para nuestro caso, usaremos el argumento "s", como argumento de interactive. Este argumento, leerá del minibuffer y lo guardará en nuestra lista de argumentos dados a la función.

La función save-excursion.
Esta función nos permitirá guardar la posición actual del cursor (su punto, de acuerdo a la terminología de emacs). Esto debido a que mientras cuando se haga la búsqueda y reemplazo, el punto/point/cursor estará moviendose a través de todo el texto. Para evitar que, al usar la función, nos deje en donde no estabamos colocados usamos save-excursion.

La función beginning-of-buffer.
Retorna el cursor al principio del buffer.

re-search-forward, regexp-quote y replace-match.
Estas tres funciones son las que harán el trabajo fuerte de nuestro comando. 
re-search-forward nos moverá a través de las coincidencias del texto, como un puntero. Al ser llamada, regresará la ubicación de la coincidencia, en la siguiente iteración nos dará la siguiente coincidencia.
regexp-quote: Le daremos un string de argumento y nos retornará la forma de evaluar el string como si fuera una expresión regular.
replace-match: La llamamos para reemplazar la coincidencia que teníamos con re-search-forward dandole el string a reemplazar.

 Nuestra función: replace-all-ins-of.


(defun replace-all-inst-of (x y)
  ;; pregunta por los argumentos
  (interactive "sSearch (with regexp): \nsReplace: ")
  (save-excursion ;; guardar el actual punto
    (beginning-of-buffer) ;; moverse al principio del buffer 
    ;; Empezar la busqueda hacia adelante
    (while (re-search-forward (regexp-quote x) nil t)
      (replace-match y))
    )
  )

Evaluamos la función al ponernos al final de la misma y haciendo Ctrl-x CTRL-e. Esto también agregará nuestra función a los comandos de emacs.
Finalmente para probar la función, hacemos M-x replace-all-inst-of o ALT-x replace-all-inst-of.
Cabe destacar que para "instalar" de forma permanente nuestra función como un comando necesitamos guardarlo en nuestro archivo de inicio .emacs. De esta manera, siempre estará disponible en las siguientes sesiones de nuestro editor.

Interviewbit Question - Solución al problema de la potencia de dos enteros.

Este es un problema de tipo entrevista, encontrado en interviewbit.com. El problema nos menciona que debemos encontrar si dada una entr...