Scott Tiger Tech Blog

Blog technologiczny firmy Scott Tiger S.A.

VN:F [1.9.22_1171]
Rating: 5.0/5 (3 votes cast)

Emacs Lisp


      Piotr Karpiuk
Spis treści
1. Wstęp
2. Podstawy
    2.1. Wektory, tablice, sekwencje
3. Przypisania
4. Bufory
    4.1. Kilka funkcji i zmiennych
5. Definiowanie funkcji
    5.1. Aliasy, haki i wtręty
    5.2. Lambda
    5.3. Parametry opcjonalne i nadmiarowe
6. Zmienne
    6.1. Zmienne globalne, opcje użytkownika
    6.2. Zmienne lokalne
    6.3. Zmienne buforowe
7. Właściwości symbolu, tablica symboli
8. Znaczniki
9. Instrukcja warunkowa
10. save-excursion
11. Narrowing i widening
12. Listy, listy, listy
    12.1. Listy asocjacji
13. progn, prog1
14. Obsługa wyjątków
15. Pętle
16. Odwrotne cytowanie, makra, eval
    16.1. eval
17. Wyszukiwanie
18. Definiowanie małego trybu
19. Definiowanie głównego trybu
    19.1. Mapa skrótów klawiaturowych
        19.1.1. Obsługa myszki
        19.1.2. Menu
    19.2. Dziedziczenie trybu
10. Plik .emacs
21. Ładowanie i kompilacja plików ELispa
    21.1. Kompilacja
22. Debugger, profiler
    22.1. Standardowy debugger
    22.2. Edebug
    22.3. Profiler
23. Zewnętrzne procesy
24. Co dalej?
    24.1. Trochę funkcji
25. Podziel się z innymi
26. Literatura

Wstęp

Emacs jest – obok Vi – legendarnym, zaawansowanym, bizantyńsko rozbudowanym edytorem tekstu o przeszło 40-letniej historii, dobrze znanym starym wyjadaczom świata UNIXa. Większość kodu edytora jest napisana w języku Lisp (niskopoziomowe funkcje napisano w C). Bardziej zaawansowani użytkownicy zauważyli, że plik konfiguracyjny Emacsa jest plikiem źródłowym Lispu a na sam edytor można spojrzeć jak na przyzwoite środowisko programowania w Lispie (takie środowisko musi mieć dobry edytor, prawda?). Pamiętajmy, że kultura hakerów wyrosła na Lispie w czasach gdy o C nikt jeszcze nie słyszał.

Więcej na temat samego edytora w osobnym poście bloga: Edytor GNU Emacs z 25.08.2010.

Artykuł jest streszczeniem i kompilacją książek An Introduction to Programming in Emacs Lisp R.J.Chassella i Writing GNU Emacs Extensions B.Glicksteina, O'Reilly 1997. Jego celem jest nauka programowania w Elispie przynajmniej w takim stopniu aby użytkownik mógł bez problemu dostosowywać Emacsa do swoich potrzeb poprzez definiowanie własnych funkcji i edycję pliku konfiguracyjnego .emacs.

Obie wspomniane książki liczą sobie w sumie przeszło 500 stron i są przeznaczone dla osób które wcześniej nigdy nie programowały w żadnym języku, zawierają zatem dużo banałów, powtórzeń i z życia wziętych porównań. Dla odmiany tutaj staram się w dużo mniejszej objętości materiału przekazać zasadniczo tą samą wiedzę co wymienione pozycje przy założeniu że Czytelnik jest informatykiem mającym pojęcie o programowaniu, niekoniecznie w Lispie. Największy pożytek z lektury tej strony będą miały osoby dobrze zaznajomione z Emacsem i jego pojęciami, takimi jak np. wyrażenia regularne.

Przy odrobinie werwy po lekturze tego artykułu będziesz mógł rozbudowywać swój edytor, ale nade wszystko nauczysz się Lispa.

Podstawy

Podstawą Lispu jest pojęcie listy. Lista jest ujętym w okrągłe nawiasy ciągiem atomów lub list oddzielonych od siebie co najmniej jedną białą spacją. Atom może być symbolem (oznaczającym nazwę zmiennej lub funkcji), albo literałem (liczbowym, łańcuchowym (stringiem), znakowym itp.). Tekstowa reprezentacja symbolu jest analogiczna do reprezentacji identyfikatora w innych językach programowania. W ELispie przyjęło się do zapisu symboli używać małych liter, cyfr i znaku myślnika (odpowiednik znaku podkreślenia w identyfikatorach C/C++), np. my-variable, get-char itp.

Wyrażeniem symbolicznym (ang. symbolic expression, s-expression) nazywamy każdą listę lub atom. W dowolnym buforze
wyrażenie można wykonać ustawiając kursor tuż za nim i wklepując C-x C-e, albo używając polecenia M-: i podając treść wyrażenia w minibuforze.

Możesz także zażądać wykonania kodu w bieżącym regionie za pomocą M-x eval-region albo nawet w całym bieżącym buforze za pomocą M-x eval-current-buffer.

W każdej sesji Emacsa istnieje bufor o nazwie *scratch* przeznaczony do zabawy z ELispem. W tym buforze dowolne wyrażenie wykonujemy umieszczając za nim kursor i naciskając C-j – wynik wyrażenia pojawi się w następnym wierszu. Możemy również użyć polecenia C-M-x, które wykonuje najbliższe wyrażenie Lispu zaczynające się otwierającym nawiasem w pierwszej kolumnie bufora. W buforze *scratch* dostępna jest także operacja uzupełniania symboli (zmiennych, funkcji) o zadanym prefiksie za pomocą skrótu klawiaturowego M-TAB.

Polecenie C-h a pozwala wyszukać funkcję o nazwie pasującej do podanego wyrażenia regularnego – niestety ten mechanizm obejmuje tylko polecenia, czyli funkcje interaktywne. Do wyszukiwania wśród wszystkich symboli ELispu (funkcji – nie tylko interaktywnych, zmiennych, itd.) służy polecenie M-x apropos.

Gdy widzisz opis funkcji lub zmiennej, to znajduje się tam informacja o pakiecie w którym ten element został zdefiniowany. Gdy na nazwie tego pakietu ustawisz kursor i naciśniesz Enter, wówczas otworzy się bufor ze źródłami, a kursor ustawi się w miejscu definicji interesującej cię funkcji/zmiennej.

Lista pusta jest jednocześnie listą i atomem. Słowo kluczowe nil i lista pusta oznaczają to samo.

Lista jest zasadniczo traktowana jako wywołanie funkcji, przy czym pierwszy element listy jest nazwą funkcji, a pozostałe elementy są argumentami. Interpreter interpretuje listy zaczynając od najbardziej zagnieżdżonej, tzn. najpierw wyliczane są argumenty funkcji, a następnie wywoływana sama funkcja.

Przykład:

        (* (+ 2 3 4) 5 4)

Zwraca wynik wyrażenia (2+3+4)*5*4 czyli 180.

Konwencja nakazuje, aby nazwa funkcji będącej predykatem kończyła się sufiksem „p” lub „-p”. Elisp nie posiada wartości logicznych, przyjmuje się że fałsz to nil, a prawdą jest cokolwiek innego niż nil – zwykle (tradycja) symbol 't'.

Przykłady:

(zerop arg)
czy argument ma wartość 0
(listp arg)
czy argument jest listą
(number-or-marker-p arg)
czy argument jest liczbą lub markerem
(fboundp symbol)
czy pod wskazany symbol podpięta jest jakaś definicja funkcji
(stringp arg)
czy argument jest łańcuchem
(symbolp x)
czy x jest symbolem

W pewnych przypadkach tzw. form specjalnych (jest ich stosunkowo niewiele) pierwszy element listy jest słowem kluczowym LISPa a wówczas sposób i kolejność wyliczania podlist może być inny, np. w konstrukcji if-else tylko jej część się wykona.

Przykład:

        (if (> 4 5)
            (message "4 is greater than 5!")
          (message "4 is not greater than 5!"))

Symbol jest identyfikatorem (etykietką) z którą możemy powiązać (bind) jakąś wartość (atom, listę) lub definicję funkcji. Ten sam symbol może być jednocześnie nazwą zmiennej i funkcji – to się nie wyklucza. Znaczenie symbolu jest wyznaczane przez kontekst w jakim ów symbol występuje w wyrażeniu.

W rzeczy samej, symbol możemy sobie wyobrazić jako strukturę składającą się z czterech wskaźników:

  1. wskaźnika do tablicy znaków zawierającej nazwę symbolu,
  2. wskaźnika do funkcji (nil gdy dla tego symbolu nie zdefiniowano żadnej),
  3. wskaźnika do wartości zmiennej (nil gdy z symbolem nie skojarzono żadnej wartości),
  4. wskaźnika do listy właściwości (ang. property list), o której później.

Symbol nie ujęty w nawiasy okrągłe jest traktowany jako zmienna i ewaluuje do wartości tej zmiennej. Symbol zacytowany (ang. quoted), tj. poprzedzony pojedynczym cudzysłowem, ewaluuje do symbolu. Poprzedzenie listy pojedynczym cudzysłowem nakazuje językowi nie interpretować takiej listy.

Wartością łańcucha jest on sam (ang. self-evaluating), w związku z tym cytowanie go przy użyciu pojedynczego cudzysłowu, choć nieszkodliwe, jest zbędną redundancją. Innymi tego typu wartościami w ELispie są liczby, znaki i wektory.

Przykłady:

misio
Ewaluuje do wartości zmiennej misio.
(misio)
Ewaluuje do wyniku wywołania bezparametrowej funkcji misio.

(misio 20 misio)
Ewaluuje do wyniku wywołania funkcji misio z parametrami będącymi liczbą 20 oraz wartością zmiennej misio.
'(gruszka pietruszka)
Ewaluuje do listy symboli, które nie są interpretowane (zwróć uwagę na cudzysłów!).
'misio
Wartością tego wyrażenia jest (nie interpretowany) symbol misio.

ELisp wyszczególnia dwa rodzaje liczb: całkowite i zmiennoprzecinkowe.

(numberp x)
czy x jext liczbą
(integerp x)
czy x jest liczbą całkowitą

Literały łańcuchowe ujmujemy w podwójne cudzysłowy, podwójny cudzysłów wewnątrz łańcucha zapisujemy poprzedzając go backslashem.

(string= s1 s2)
czy łańcuchy są równe (mają tą samą zawartość)
(string-lessp s1 s2)
czy łańcuch s1 jest mniejszy leksykograficznie niż s2
(concat str1 str2)
łączy dwa łańcuchy
(substring str start-index [end-index])
zwraca podciąg łańcucha

Pojedynczy znak jest w ELispie reprezentowany przy użyciu znaku zapytania, np. ?a oznacza małą literkę „a”. Pewne znaki, zwłaszcza te które mogą być użyte na początku innych wyrażeń Lispu, muszą być zapisane z użyciem backslasha, np. ?", ?( czy ?). Podobnie jest w przypadku znaków specjalnych takich jak znak końca wiersza ?n czy tabulacji ?t.

Wynikiem ewaluacji znaku jest jego kod ASCII. Liczba całkowita może być w rzeczywistości użyta wszędzie zamiast znaku.

(char-equal a b)
czy dwa znaki są równe
(char-to-string c)
tworzy jednoznakowy łańcuch zawierający znak c

Domyślnym rozszerzeniem nieskompilowanych programów Elispa jest .el, zaś skompilowanych – .elc. Wielkość liter w programach napisanych w ELispie ma znaczenie (w przeciwieństwie do wielu innych odmian Lispa).

Kilka funkcji Elispa:

(number-to-string number)
(int-to-string number)
konwertuje liczbę na łańcuch
(message str arg1 arg2 ...)
wyświetla sparametryzowany łańcuch w echo area (jak printf w C); %s oznacza łańcuch, %d liczbę całkowitą
(format str arg1 arg2 ...)
formatuje tekst podobnie jak message, ale nie wyświetla wynikowego łańcucha tylko zwraca go
(error "format" arg1 arg2 ... )
Sygnalizuje błąd, przekazując argumenty do funkcji format. Bieżąca funkcja jest przerywana i zwijany jest stos. Konwencja w Emacsie nakazuje rozpoczynanie komunikatu dużą literą i nie umieszczanie kropki na końcu komunikatu.
(print str)
wstawia do bufora wskazany łańcuch w osobnym wierszu
(insert str1 str2 ...)
wstawia konkatenację wskazanych łańcuchów do bufora, począwszy od pozycji kursora
(yes-or-no-p "prompt")
Wyświetla użytkownikowi wskazane pytanie typu Tak/Nie i zwraca wartość logiczną odzwierciedlającą wybór użytkownika.
(buffer-name)
zwraca nazwę bieżącego bufora
(buffer-file-name)
zwraca ścieżkę pliku skojarzonego z bieżącym buforem
(bufferp buffer)
czy argument jest buforem

Jak wiadomo, wykonanie w Emacsie dowolnego polecenia, czy to poprzez wklepanie stosownej sekwencji klawiszy czy w minibuforze za pomocą M-x powoduje wykonanie odpowiadającej mu funkcji Elispa. Nie każda funkcja Elispa może jednak być wywołana w ten sposób, a tylko ta która została zdefiniowana jako interaktywna. Funkcję interaktywną nazywamy poleceniem (ang. command).

Komentarze w Elispie są tylko jednowierszowe i zaczynają się od średnika. Przyjęło się, że jeżeli chcemy napisać komentarz wielowierszowy to wszystkie wiersze takiego komentarza powinny rozpoczynać się znakiem średnika powtórzonym trzykrotnie: ;;;.

Wektory, tablice, sekwencje

Wektor jest, podobnie jak lista, sekwencją dowolnej liczby podwyrażeń. Do jego zapisu w przeciwieństwie do listy używa się nawiasów kwadratowych zamiast okrągłych. Inaczej jak w liście, dostęp do poszczególnych elementów wektora jest bezpośredni, choć nie można zmienić rozmiaru wektora bez kopiowania elementów. Wartością wektora jest on sam.

Gdy zapisujesz wektor, jego podwyrażenia są automatycznie cytowane. Aby utworzyć wektor z elementów które są najpierw wyliczane, użyj funkcji vector.

(make-vector n init)
Tworzy n-elementowy wektor inicjując go wartościami init.
(vector a b c ...)
Tworzy nowy wektor z elementami będącymi wartościami argumentów
(vectorp x)
Czy x jest wektorem.

Niektóre typy danych Lispu są ze sobą ściśle powiązane. Łańcuchy i wektory są przykładami tablic (ang. arrays). Tablica jest ciągiem elementów o bezpośrednim dostępie do swych elementów.

Przykłady funkcji operujących na tablicach:

(aref a i)
zwraca i-ty znak tablicy a (licząc od 0)
(aset a i v)
zmienia i-ty znak tablicy a na v (licząc od 0)
(arrayp x)
czy x jest tablicą

Sekwencja (ang. sequence) jest jeszcze bardziej ogólnym typem danych, obejmującym zarówno tablice jak i listy. Sekwencja jest ciągiem elementów.

Przykłady funkcji operujących na wszystkich sekwencjach:

(length seq)
zwraca długość sekwencji
(sequencep x)
de>x jest sekwencją
(copy-sequence seq)
i zwraca kopię podanej sekwencji

Przypisania

Wyrażenie

        (set 'flowers '(rose violet daisy buttercup))

przypisuje symbolowi flowers wartość będącą listą symboli. Alternatywnym sposobem jest użycie formy specjalnej setq która pozwala nie cytować pierwszego argumentu, jak również zdefiniować jednocześnie więcej niż jedną zmienną.

Przykład:

        (setq carnivores '(lion tiger leopard))

        (setq trees '(pine fir oak maple)
              herbivores '(gazelle antelope zebra))

Bufory

Tworzenie nowego bufora jest dość kosztowną operacją. Bufory nie podlegają automatycznemu zarządzaniu pamięcią jak inne obiekty ELispa i należy je usuwać ręcznie za pomocą funkcji kill-buffer.

Bufory, które nie są związane z żadnym plikiem powinny mieć nazwę, której pierwszym i ostatnim znakiem jest gwiazdka (*).

Kilka funkcji i zmiennych

last-command
ostatnio wykonane polecenie Emacsa (symbol)
(current-buffer)
zwraca obiekt reprezentujący bieżący bufor
(other-buffer)
zwraca obiekt reprezentujący bufor w którym byłeś poprzednio
(switch-to-buffer buffer)
przełącza bufor i czyni go widocznym dla użytkownika
(set-buffer buffer)
przełącza bufor, bez czynienia go widocznym dla użytkownika (tylko na czas wykonywania bieżącego polecenia)
(display-buffer buffer)
wyświetla wskazany bufor użytkownikowi
(buffer-size)
zwraca liczbę znaków w bieżącym buforze

(point)
zwraca bieżącą pozycję kursora (w znakach od początku dokumentu)
(point-min)
zwraca minimalną wartość pozycji kursora (zwykle 1)
(point-max)
zwraca maksymalną wartość pozycji kursora (zwykle to samo co buffer-size)
(generate-new-buffer str)
tworzy i zwraca nowy bufor o zadanej nazwie (nie wyświetla go użytkownikowi)
(kill-buffer buffer)
usuwa bufor i zwalnia po nim pamięć

Definiowanie funkcji

Oto szablon definicji funkcji:

        (defun nazwa-funkcji (arg1 arg2 ...)
          "opcjonalny, być może wielowierszowy, komentarz..."
          (interactive argument-passing-info)                    ; opcjonalnie
          body... )

Lista argumentów jest obowiązkowa – może to być oczywiście lista pusta. Komentarz staje się opisem funkcji dostępnym tak jak dokumentacja wszystkich innych funkcji Emacsa przy użyciu C-h f. Pierwszy wiersz komentarza powinien być krótkim i zwięzłym opisem funkcji – to właśnie ten wiersz będzie widoczny w rezultatach polecenia apropos, C-h a. Nie należy wcinać kolejnych wierszy, bo będzie to paskudnie wyglądać w opisie.

Wewnątrz listy argumentów można użyć słowa kluczowego &optional (tak, z ampersandem!), co powoduje że wszystkie argumenty za nim są opcjonalne i jeżeli nie zostaną podane przy wywołaniu funkcji, to otrzymają wartość nil.

Element argument-passing-info w przypadku funkcji interaktywnej jest łańcuchem opcji wyznaczających parametry aktualne pobierane ze środowiska Emacsa. Opcje są rozdzielane znakiem nowego wiersza. I tak:

f
nazwa istniejącego pliku
p
wprowadzona przez użytkownika w postaci prefiksu liczba naturalna, 1 gdy użytkownik nie wprowadził jawnie prefiksu
P
surowy prefix (ang. raw prefix argument): jeśli użytkownik wprowadził C-u , liczba; jeśli wpisał C-u -, symbol minus; jeśli n-krotnie wpisał C-u, lista zawierająca liczbę 4^n; jeśli użytkownik nie podał żadnego prefiksu – nil
r
początek i koniec regionu (pozycje kursora i marka)
B<prompt>
nazwa bufora wprowadzona przez użytkownika; oznacza łańcuch wyświetlany użytkownikowi zachęcający do wprowadzenia danych; można podać nieistniejącą nazwę bufora (bufor zostanie utworzony)
b<prompt>
jak B, ale nie można podać nazwy nieistniejącego bufora
*
powoduje że funkcja zakończy się z błędem jeśli bufor jest tylko do odczytu
c<prompt>
znak wprowadzony przez użytkownika; <prompt> jest łańcuchem wyświetlanym użytkownikowi zachęcającym do wprowadzenia danych
N<prompt>
prefiks liczbowy wprowadzony przez użytkownika; jeśli nie poda, to zostanie zapytany o niego z promptem <prompt>
s<prompt>
dowolny łańcuch

Pełny opis kodów literowych można uzyskać za pomocą polecenia C-h f interactive.

Przykład:

        (defun show-params (number buffer region-start region-end)
          "Prosi użytkownika o podanie nazwy bufora, po czym wyświetla
          komunikat zawierający: prefiks liczbowy polecenia oraz początek i koniec
          regionu, następnie przełącza się do wskazanego bufora."
          (interactive "pnBPodaj nazwę bufora: nr")
          (message
            "Prefix: %d Region start: %d  Region end: %d"
            number region-start region-end)
          (switch-to-buffer buffer))

Użyteczna funkcja:

(prefix-numeric-value arg)
Jeżeli w funkcji interaktywnej użyto opcji P, wówczas surowy prefiks (dostępny na zmiennej current-prefix-arg) możemy przekształcić na rozsądną liczbę za pomocą prefix-numeric-value

Nawet jeśli funkcja modyfikuje zawartość aktualnie widocznego w oknie bufora, to zmiany ukażą się użytkownikowi dopiero po wyjściu z funkcji. Aby uczynić zmiany widocznymi natychmiast, należy użyć polecenia

        (sit-for 0)

Uwaga: W rozdziale Trochę funkcji znajduje się opis niektórych użytecznych funkcji Emacsa, których możesz użyć w swoich eksperymentach.

Gdy treść funkcji jest bardzo krótka a sama funkcja jest wykonywana w programie bardzo często (np. w pętli), warto skorzystać z mechanizmu inline będącego odpowiednikiem konstrukcji znanej z innych języków (funkcje inline w C++, makra w C). Wywołanie funkcji inline podczas kompilacji jest zastępowane treścią funkcji. Aby zdefiniować funkcję inline wystarczy zamiast słowa kluczowego defun użyć słowa defsubst.

W ELispie nie ma enkapsulacji. Przyjęło się, że programista chcąc zaznaczyć że dana funkcja jest prywatna powinien używać w jej nazwie podwójnego myślnika zamiast pojedynczego, np. my--private-function zamiast my-private-function.

Aliasy, haki i wtręty

Jedna funkcja może posiadać wiele nazw, tzn. możliwe jest tworzenie aliasów:

        (defalias 'function-name 'alias)

Rekomendowanym sposobem podczepiania funkcji hakującej do haka jest użycie funkcji add-hook:

        (add-hook 'hook-var 'my-hook-function)

Analogicznie do usuwania funkcji hakującej służy remove-hook:

        (remove-hook 'hook-var 'my-hook-function)

Ciekawym hakiem do eksperymentownia jest after-change-functions, który jest wywoływany przy każdej modyfikacji bufora. Funkcja podczepiona do takiego haka pobiera trzy argumenty: pozycja gdzie zmiana się rozpoczęła, pozycja gdzie zmiana się zakończyła i długość tekstu objętego zmianą. Taki hak może być wywołany wielokrotnie podczas wykonywania jednego polecenia użytkownika. Hak post-command-hook jest wywoływany na zakończenie wykonywania całego polecenia użytkownika.

Wtrętem (ang. advice) nazywamy kod podczepiony pod daną funkcję, wykonujący się przed nią (ang. before advice), po niej (ang. after advice) lub zamiast niej (ang. around advice). Można powiedzieć, że wtręt jest czymś w rodzaju haka z tą różnicą, że o ile Emacs definiuje tylko kilkadziesiąt haków na określone z góry okoliczności, programista ELispa może zrobić wtręt dowolnej funkcji.

Wtręt typu "przed" tworzymy według wzoru:

        (defadvice unquoted-advised-function-name
          (before unquoted-advice-name activate compile)
          "opcjonalny, być może wielowierszowy, komentarz..."
          body...

Lambda

Forma specjalna lambda pozwala na zdefiniowanie anonimowej funkcji. Przykładowo, poniżej definiujemy anonimową funkcję która zwraca swój argument pomnożony przez 7:

        (lambda (number) (* 7 number))

Wyrażenie:

        ((lambda (number) (* 7 number)) 3)

Zwróci wynik 21.

Po co nam taka lambda? Chociażby dla wygody. Parametrem wywołania jakiejś funkcji może być funkcja. Zamiast definiować i instalować jakąś funkcję tylko po to aby jej symbol przekazać jako parametr, możemy przekazać jako parametr anonimową funkcję.

Parametry opcjonalne i nadmiarowe

W specyfikacji parametrów funkcji możemy użyć słowa kluczowego &optional. Powoduje ono, że wszystkie dalsze parametry są opcjonalne – gdy wartość parametru opcjonalnego nie zostanie podana przy wywołaniu funkcji parametr ten otrzyma wartość nil.

Dodatkowo, lista parametrów definiowanej funkcji może na samym końcu zawierać słowo kluczowe &rest za którym następuje nazwa zmiennej. Przy wywołaniu funkcji na zmienną tą zostanie przypisana lista wszystkich nadmiarowych parametrów wywołania.

        (defun moja-funkcja (p1 p2 ... &optional px py ... &rest l)
          body...)

Zmienne

Zmienne globalne, opcje użytkownika

Do definiowania zmiennej globalnej służy forma specjalna defvar:

        (defvar name value
          "documentation string")

Zmiennej nadawana jest wartość tylko wtedy, gdy wcześniej zmienna nie miała wartości. Jeżeli pierwszym znakiem łańcucha dokumentacyjnego będzie gwiazdka, to taka zmienna stanie się opcją użytkownika (ang. user option) i zwykły użytkownik Emacsa będzie mógł dostosowywać jej wartość do swoich potrzeb za pomocą polecenia set-variable. Opcje użytkownika można modyfikować masowo przy użyciu polecenia edit-options.

Jeśli w łańcuchu dokumentacyjnym opcji umieścisz zapis: \[nazwa-polecenia], wówczas zostanie on zastąpiony skrótem klawiaturowym polecenia o podanej nazwie.

Zmienne globalne, które użytkownik może sobie edytować ("customizować") należy deklarować za pomocą konstrukcji defcustom.

        (defcustom name value
          "documentation string"
          :label1 value1
          :label2 value2
          ...

Pary label-value definiują rodzaj i strukturę zmiennej, dzięki czemu narzędzia do customizowania Emacsa wiedzą w jaki sposób użytkownikowi zaprezentować obecną wartość zmiennej i w jaki sposób użytkownik może ją modyfikować.

Przykładowo, zmienna text-mode-hook jest zdefiniowana następująco:

        (defcustom text-mode-hook nil
          "Normal hook run when entering Text mode and many related modes."
          :type 'hook
          :options '(turn-on-auto-fill flyspell-mode)
          :group 'data)

Niestety, nie ma tu miejsca na omówienie wszystkich możliwych etykietek – sięgnij do rozdziału Writing Customization Definitions w GNU Emacs Lisp Reference Manual.

Zmienne lokalne

Do definiowania zmiennych lokalnych służy konstrukcja (forma specjalna) let:

            (let ((var1 value1)
                  (var2 value2)
                  var3            ; to samo co (var3 nil)
                  (var4 value4))
             body...)

Listę postaci:

        ((var1 value)
         (var2 value2)
         var3
         (var4 value4)
         ...)

w języku angielskim określa się nazwą varlist.

W Elispie zasięg zmiennych jest dynamiczny, a nie leksykalny.

Jeżeli nie określisz jawnie wartości zmiennej, otrzyma ona wartość nil. Wartością konstrukcji let jest wartość ostatniego wyrażenia. Zmienne lokalne przestają być widoczne po wyjściu z konstrukcji let.

Rozważmy następujący przykład:

        (setq abc "misio")
        (let ((abc "pysio")
              (kulfon abc))
            kulfon)

Jaką wartość otrzyma zmienna lokalna kulfon, tzn. jaki będzie wynik wykonania konstrukcji let? Okazuje się, że "misio". Co więcej, kolejność obliczania wartości zmiennych w konstrukcji let jest niezdefiniowana. Jeżeli nadając wartość zmiennej lokalnej chcemy wykorzystać wartość zdefiniowanej wcześniej w obrębie tej samej konstrukcji let zmiennej, wówczas należy użyć bliźniaczej konstrukcji let*:

        (setq abc "misio")
        (let* ((abc "pysio")
              (kulfon abc))
            kulfon)

Teraz wynikiem konstrukcji let* będzie "pysio", a wartości zmiennych wyliczane będą po kolei z góry na dół.

Zmienne buforowe

Wywołanie postaci:

        (make-local-variable 'my-variable)

powoduje, że od tej pory zmienna my-variable będzie miała oddzielną wartość w bieżącym buforze, niezależną od globalnej wartości widzianej przez wszystkie pozostałe bufory. Lokalna wartość zmiennej jest inicjowana na wartość globalną.

Nieco odmienną semantykę ma wywołanie

        (make-variable-buffer-local 'my-variable)

Powoduje ono, że od tej pory jeśli jakikolwiek bufor zmodyfikuje tą zmienną, to nowa wartość stanie się lokalna dla tego bufora i wówczas taki bufor będzie miał do dyspozycji lokalną kopię zmiennej.

Właściwości symbolu, tablica symboli

Każdy symbol oprócz wartości zmiennej i definicji funkcji może posiadać listę właściwości (ang. property list). Można na nią spojrzeć jak na słownik, w którym kluczami są symbole, a wartościami dowolne wyrażenie ELispa. Masz pełną swobodę decydowania o tym co chcesz podczepić sobie do symbolu w postaci wpisu do listy jego właściwości.

Do wstawiania i przeglądania słownika służą funkcje put i get:

        (put 'a-symbol 'some-property 17)
        (get 'a-symbol 'some-property) ; zwraca 17

Funkcja get zwraca nil gdy nie ma odpowiedniego wpisu.

Symbole są zwykle przechowywane wewnętrznie w tablicy symboli aby zapobiec duplikowaniu przy tworzeniu. Można jednak ręcznie dodawać elementy do tablicy lub tworzyć symbole które nie będą umieszczane w tablicy symboli.

(intern str)
Zwraca symbol o nazwie zadanej łańcuchem z tablicy symboli, tworząc go jeśli nie istnieje jeszcze.
(make-symbol str)
Tworzy nowy symbol o zadanej łańcuchem nazwie. Nie jest on umieszczany w tablicy symboli i jest w związku z tym różny od innych symboli, w tym od innych symboli o tej samej nazwie.

Znaczniki

Znacznik (ang. marker) jest specjalnym obiektem używanym na elastyczne oznaczenie miejsca w tekście bufora. Słowo "elastyczne" oznacza tu, że znacznik raz ustawiony będzie wskazywał miejsce między tymi samymi dwoma znakami pomimo modyfikacji zawartości bufora.

Znaczniki tworzy się przy pomocy funkcji make-marker a do ustawienia pozycji znacznika w tekście należy użyć funkcji set-marker:

        (defvar my-marker (make-marker))
        (set-marker my-marker (point))

Aby zdezaktywować znacznik, należy ustawić go na wartość nil. Zdecydowanie zaleca się ze względów wydajnościowych unikać płodnego tworzenia nowych znaczników i wykorzystywać ponownie dotychczas stworzone (pozycja każdego znacznika bufora musi być przeliczana przy każdej zmianie tekstu bufora).

(point-marker)
zwraca obiekt znacznika postawionego w miejscu bieżącej pozycji kursora.

Instrukcja warunkowa

        (if true-or-false-test
            action-to-carry-out-if-test-is-true)

        (if true-or-false-test
            action-to-carry-out-if-test-is-true
          action-to-carry-out-if-test-is-false)

Wyrażenie true-or-false-test jest zwykle zapisywane w tym samym wierszu co słowo kluczowe if, natomiast poszczególne gałęzie w osobnych wierszach. Zwróć uwagę na wcięcia, które nie są konieczne, ale bardzo ułatwiają czytanie kodu. Trzeci i kolejne argumenty formy specjalnej if tworzą klauzulę "else".

Wyrażenie

        (if (not true-or-false-test)
            action-to-carry-out-if-test-is-false)

można zapisać również w postaci:

        (or true-or-false-test
          action-to-carry-out-if-test-is-false)

co wynika z faktu, że or jest również formą specjalną i wylicza się w sposób leniwy.

Analogicznie wyrażenie

        (if true-or-false-test
            action-to-carry-out-if-test-is-true)

można zapisać zamieniając słowo if słowem and.

Zauważ, że wyrażenie postaci (if a a b) jest w większości przypadków równoważne wyrażeniu (or a b).

Jeżeli instrukcja warunkowa nie ma klauzuli "else", wówczas słowo if możemy zastąpić słowem when, co ma tę zaletę że wewnątrz gałęzi możemy sobie pozwolić na więcej niż jedno wyrażenie.

        (when true-or-false-test
            action-to-carry-out-if-test-is-true1
            action-to-carry-out-if-test-is-true2
            ...)

Uzupełnieniem konstrukcji when jest konstrukcja unless:

        (unless true-or-false-test
            action-to-carry-out-if-test-is-false1
            action-to-carry-out-if-test-is-false2
            ...)

Odpowiednik znanej z C-podobnych języków konstrukcji switch ma postać:

        (cond
          (first-true-or-false-test first-consequent)
          (second-true-or-false-test second-consequent)
          (third-true-or-false-test third-consequent)
          ...
          (t last-consequent))

Pojedyncza klauzula będąca listą zawiera warunek i ciąg instrukcji. Poszczególne warunki wykonywane są po kolei i gdy któryś z nich okaże się prawdziwy wówczas wykonywana jest odpowiadająca mu część consequent (zero, jedna lub więcej instrukcji), a jej wynik jest wynikiem całej konstrukcji. Jeżeli consequent składa się z więcej niż jednej instrukcji to wynik jest wynikiem ostatniej z nich; jeżeli z kolei consequent nie występuje, to wynikiem konstrukcji jest (prawdziwy) wynik testu.

Gdy żaden test konstrukcji cond nie wyewoluuje do prawdy, wówczas wartością całej konstrukcji jest nil. Często jednak ostatnim testem jest po prostu t, co jest odpowiednikiem default w językach programowania takich jak Java czy C++.

(equal obj1 obj2)
sprawdza czy pierwszy i drugi argument mają taką samą wartość
(eq obj1 obj2)
sprawdza czy pierwszy i drugi argument są tym samym

save-excursion

Forma specjalna save-excursion jest powszechnie wykorzystywana w funkcjach ELispa i ma postać:

        (save-excursion
          body...)

Jej działanie jest następujące: zapamiętuje bieżący bufor oraz położenie kursora i marka, wykonuje ciało, następnie przywraca zapamiętane wartości – oszczędza to użytkownikowi zaskoczenia wynikającego z nieoczekiwanej zmiany pozycji kursora lub marka. Przywrócenie poprzednich wartości następuje nawet wtedy gdy któraś z instrukcji zawartych w ciele zakończy się z błędem. Wartością całego wyrażenia jest wartość ostatniej instrukcji ciała.

Narrowing i widening

Emacs umożliwia tymczasowe zawężenie edytowalnego obszaru, co pozwala na ograniczenie zasięgu takich operacji jak zamiana czy wyszukiwanie. Aby dokonać zawężenia należy zaznaczyć blok i użyć kombinacji C-x n n. Przywrócenie poprzedniego stanu uzyskujemy za pomocą C-x n w.

Forma specjalna save-restriction ma postać:

        (save-restriction
          body...)

Jej zadaniem jest wykonanie ciała, a następnie wycofanie wszystkich modyfikacji dotyczących zawężenia jakie zostały dokonane wewnątrz ciała.

Listy, listy, listy

W każdej odmianie Lispu istnieją trzy fundamentalne funkcje służące do obsługi list: cons, car i cdr (wymowa ang. could-er). Pierwsza z nich służy do tworzenia list, a dwie pozostałe do rozkładania listy na części.

Uwaga: O ile nazwa pierwszej funkcji jest skrótem od słowa construct, o tyle nazwy dwóch pozostałych trącą myszką – są akronimami dla odpowiednio: Contents of the Address part of the Register i Contents of the Decrement part of the Register odnoszącymi się do sprzętowych zagadnień architektury bardzo wczesnych komputerów na których Lisp był rozwijany. Wielu śmiałków usiłowało zmienić te nazwy np. na first i rest, ale siła tradycji okazała się być większa.

Lista jest przechowywana w pamięci jako zbiór par wskaźników. Pierwszy wskaźnik każdej pary wskazuje na atom lub inną parę, zaś drugi wskaźnik wskazuje na inną parę (następny element listy) lub jest wskaźnikiem pustym (nil) oznaczającym koniec listy.

Na poniższym rysunku widzimy stan pamięci po wykonaniu polecenia

        (setq bouquet '(rose violet buttercup))

                    +---+---+    +---+---+    +---+---+
        bouquet --->| + | --+--->| + | --+--->| + | --+---> nil
                    +-|-+---+    +-|-+---+    +-|-+---+
                      |            |            |
                      +--> rose    +--> violet  +--> buttercup

Do tworzenia pojedynczej pary służy funkcja cons. W języku angielskim taka para nosi nazwę cons cell albo dotted pair. Ta druga nazwa bierze się stąd, że gdy drugi element pary nie jest listą, np.

        (cons 'a 'b)

to wynikiem jest obiekt o tekstowej reprezentacji postaci

        (a . b)

(ang. dotted pair notation).

Jeśli drugi element pary jest listą, to otrzymamy listę, tzn. wyrażenie

        (cons 'pine '(fir oak maple))

zwróci (pine fir oak maple).

W rzeczy samej reprezentacja tekstowa listy jest skrótem notacyjnym, tzn.

        '(a . (b . nil)) == '(a b . nil) == '(a b)

Lista w Lispie jest łańcuchem par przy czym drugi element każdej pary jest albo kolejną parą albo nilem dla ostatniego elementu listy.

Listy których ostatnim elementem nie jest nil są nazywane listami niewłaściwymi (ang. improper lists) i są wykorzystywane jako elementy tzw. list asocjacji (ang. association list), o czym będzie mowa dalej.

Funkcja car zwraca pierwszy element listy, zaś cdr zwraca tą część listy, jaka powstałaby po usunięciu z niej pierwszego elementu. Należy podkreślić, że obie funkcje są niedestrukcyjne.

Jeśli wykonasz następujące wyrażenia:

    (car '(pine fir oak maple))
    (cdr '(pine fir oak maple))

to wynikiem pierwszego z nich będzie pine, a drugiego (fir oak maple). Warto wiedzieć, że (cdr nil) i (car nil) zwracają nil.

Zauważ, że w wyniku wykonania poleceń:

        (setq misio '(bim bam bom))
        (setq misio (cons misio misio))
        misio

zmienna misio będzie miała wartość ((bim bam bom) bim bam bom).

(nthcdr n list)
zwraca wynik będący n-krotnym zagnieżdżonym wykonaniem cdr, tzn. (nthcdr 3 '(pine fir oak mapple)) oznacza to samo co (cdr (cdr (cdr '(pine fir oak mapple)))), czyli zwróci (mapple).
(nth n list)
zwraca n-ty element listy (liczymy od 0) i oznacza dokładnie to samo co (car (nthcdr n list))
(reverse list)
zwraca kopię listy z odwróconą kolejnością elementów
(nreverse list)
odwraca kolejność elementów listy (modyfikuje listę)

Do modyfikacji listy służą funkcje setcar i setcdr.

(setcar list rep-element)
zastępuje pierwszy element listy argumentem rep-element; zwraca rep-element
(setcdr list rep-list)
zastępuje część cdr listy argumentem rep-list; zwraca rep-list

Spróbuj zrobić listę cykliczną!

Listy asocjacji

Listą asocjacji (ang. association list, assoc list) nazywamy listę par (klucz . wartość). Polecenia:

        (assoc 'green '((red . 5) (green . 10)))
        (assq 'green '((red . 5) (green . 10)))

zwracają parę o podanym kluczu, przy czym do porównywania kluczy funkcja assoc używa predykatu equal, a funkcja assq predykatu eq.

progn, prog1

Użycie formy specjalnej progn jest następujące:

        (progn
          body...)

Zadanie tej formy specjalnej jest banalne: wylicz po kolei argumenty i zwróć wynik będący ostatnim argumentem. Gdzie to się przydaje? Popatrzmy na przykład (funkcja która usuwa tekst pomiędzy kursorem a n-tym wystąpieniem podanego znaku):

        (defun zap-to-char (n char)
          "Kill up to and including N'th occurrence of CHAR.
        Goes backward if N is negative; error if CHAR not found."
          (interactive "*pncZap to char: ")
          (kill-region (point)
                       (progn
                         (search-forward
                          (char-to-string char) nil nil n)
                         (point))))

Wywołanie

        (search-forward string nil nil n)

powoduje wyszukanie n-tego wystąpienia łańcucha począwszy od bieżącej pozycji kursora. Drugi parametr to pozycja wyznaczająca koniec obszaru poszukiwań; nil oznacza "do końca bufora". Trzeci parametr jest wartością logiczną: fałsz oznacza że w przypadku niepowodzenia wyszukiwań podniesiony zostanie błąd który przerwie wykonywanie bieżącego polecenia; prawda spowoduje że funkcja wyszukująca zwróci nil. Gdy poszukiwania zakończą się sukcesem, kursor jest ustawiany bezpośrednio za znalezionym tekstem.

Jak widzimy, progn jest czymś w rodzaju wywołania anonimowej funkcji, czy też bloku, którego można użyć jako argumentu innej funkcji.

Konstrukcja prog1 pełni analogiczne zadanie wykonując wszystkie instrukcje, ale dla odmiany zwraca wynik pierwszej z nich, a nie ostatniej.

Zauważ, że w przypadku konstrukcji if, jej część wykonywana gdy warunek jest prawdziwy musi być jednym wyrażeniem; jeżeli chcemy tam umieścić więcej wyrażeń, można użyć konstrukcji progn lub prog1.

Obsługa wyjątków

Do obsługi sytuacji krytycznych w ELispie służy forma specjalna condition-case:

        (condition-case
          var
          bodyform
          error-handler1
          error-handler2
          ...)

condition-case wykonuje instrukcje zawarte w bodyform i jeżeli nie wystąpi błąd, wynikiem całej konstrukcji jest wartość zwracana przez bodyform.

Z wystąpieniem błędu (warunku) związane są dwie informacje: etykietka błędu (ang. error condition name) i dodatkowe informacje o błędzie.

Jeżeli w bodyform wystąpi błąd, wówczas dodatkowe informacje o błędzie zapisywane są w zmiennej var (jeżeli zamiast tej zmiennej jest nil to dodatkowe informacje są ignorowane), po czym wyszukiwany jest odpowiedni error-handler który obsłuży błąd.

Każdy error-handler jest listą, której pierwszym elementem jest etykietka lub lista etykietek błędów wychwytywanych przez handler, a kolejnymi elementami instrukcje obsługi błędu:

    ((label1 label2 ...) expr ...)

Przykładem etykietki jest error – błąd sygnalizowany w wyniku wykonania wspomnianej już wcześniej funkcji error.

Prostszą formą obsługi błędów jest konstrukcja:

        (unwind-protect
            normal
          cleanup1
          cleanup2
          ...
          cleanupN)

Wykonuje ona wyrażenie normal, po czym bez względu na to czy podczas jej wykonywania wystąpi błąd (np. użytkownik nacisnął C-g) czy nie, wykonywane są instrukcje cleanup. Gdy wyrażenie normal nie zakończyło się błędem, jego wynik jest wynikiem formy specjalnej unwind-protect.

Pętle

W Emacs Lispie realizuje się pętle na dwa sposoby: albo przy użyciu formy specjalnej while:

        (while true-or-false-test
          body...)

albo przy użyciu rekursji.

Przykład: iteracja elementów listy

        (defun print-list (list)
          "Print each element of LIST on a line of its own."
          (while list
            (print (car list))
            (setq list (cdr list))))

Przykład: sumowanie liczb naturalnych z podanego przedziału:

        (defun sum (first last)
          "Add up integers from FIRST to LAST."
          (let ((total 0)
                (counter first))
            (while (<= counter last)
              (setq total (+ total counter))
              (setq counter (1+ counter)))
            total)) ; tu zwracamy wynik

Konstrukcja:

        (dolist (element list value)
          body...)

jest makrem upraszczającym zadanie trawersowania listy: iteruje listę list w kolejnych iteracjach umieszczając kolejny element listy na zmiennej element. Wynikiem całej konstrukcji ma być wartość zmiennej value.

Analogicznie konstrukcja:

        (dotimes (number max-number value)
           body...)

wykonuje ciało dokładnie max-number razy, przy każdej iteracji podstawiając w zmiennej number kolejne wartości od 0 do max-number - 1; wynikiem całego wyrażenia jest wartość zmiennej value.

Na koniec obejrzyjmy przykłady dwóch rozwiązań rekurencyjnych zliczających liczby od 1 do n:

	(defun sum-1-to-n (number)
	  "Return the sum of the numbers 1 through NUMBER inclusive."
	  (if (= number 1)
	      1
	    (+ number (sum-1-to-n (1- number)))))

I drugi przykład:

	(defun sum-1-to-n (number)
	  "Return the sum of the numbers 1 through NUMBER inclusive."
          (sum-1-to-n-helper 0 0 number))

	(defun sum-1-to-n-helper (sum counter number)
	  (if (> counter number)
              sum
            (sum-1-to-n-helper (+ sum counter) (1+ counter) number)))

Oba rozwiązania tego samego problemu są rekurencyjne (bo procedura wywołuje samą siebie), ale w drugim rozwiązaniu proces będzie iteracyjny dzięki zastosowaniu tzw. rekursji ogonowej – do przechowywania stanu obliczeń nie potrzeba stosu lecz stałej pamięci zawierającej 3 wartości liczbowe; w ten sposób oszczędzamy pamięć.

Odwrotne cytowanie, makra, eval

Wyrażenie ELispa można zacytować przy użyciu pojedynczego cudzysłowu (ang. quote) albo odwróconego pojedynczego cudzysłowu (ang. backquote). O ile pierwszy sposób cytowania w przypadku cytowania listy cytuje również jej wszystkie podwyrażenia, o tyle używając drugiego sposobu można wymusić ewaluację wskazanych podwyrażeń, co ilustruje poniższy przykład:

	(setq b 5)
	`(a b c) => (a b c)
	`(a ,b c) => (a 5 c)

	(setq b '(x y z))
	`(a ,b c) => (a (x y z) c)
	`(a ,@b c) => (a x y z c)

Zwróć uwagę na przecinek który usuwa cytownanie z danego podwyrażenia, oraz ,@ który wkleja elementy listy podwyrażenia do listy nadrzędnej.

Jak wiadomo, wszystkie argumenty funkcji są wyliczane przed jej wywołaniem, tzn. treść funkcji ma dostęp jedynie do wyewaluowanych wartości argumentów. Czasami wolelibyśmy aby treść funkcji mogła decydować o tym czy, kiedy i które parametry funkcji mają być ewaluowane, dzięki czemu semantyka takiej funkcji mogłaby się upodobnić do działania form specjalnych.

Aby osiągnąć ten efekt, należy użyć mechanizmu makr. Gdy makro jest wywoływane, jego argumenty nie są wyliczane lecz przekazywane do treści makra w postaci zacytowanej. Mogą one być przez treść makra wykorzystane w swej oryginalnej postaci do skonstruowania wyrażenia ELispu, i dopiero to wyrażenie zostanie wykonane.

Makro definiujemy w następujący sposób:

        (defmacro nazwa-makra (arg1 arg2 ...)
	  ...)

Przykład makra pobierającego zmienną zwiększającego wartość zmiennej o 1:

	(defmacro incr (var)
	  "Add one to the named variable."
	  (list 'setq var (list '+ var 1)))

Przy użyciu odwrotnego cytowania makro zapisujemy prościej:

	(defmacro incr (var)
	  "Add one to the named variable."
	  `(setq ,var (+ ,var 1))

Możesz debugować makra przy użyciu funkcji macroexpand która pobiera wyrażenie Lispu będące wywołaniem makra i zwraca rozwinięcie tego makra, np. wynikiem wywołania

        (macroexpand '(incr x))

będzie

        (setq x (+ x 1))

eval

Funkcja o nazwie eval pozwala wykonać dowolne wyrażenie Lispu przekazane jako parametr. W poniższym przykładzie wynikiem wywołania eval będzie 8:

	(setq x '(+ 3 5))
	(eval x)

Przykład

Zaprezentowana wcześniej forma specjalna save-excursion jest dość droga w użyciu w tym sensie, że zapamiętuje i odtwarza sporą ilość informacji (pozycja kursora, znacznik, bieżący bufor), podczas gdy w wielu sytuacjach wystarczyłoby przechowywanie i odtwarzanie wyłącznie pozycji kursora.

Oto implementacja polecenia limited-save-excursion, które implementuje taką konstrukcję ukazując w praktyce kilka poznanych do tej pory właściwości języka:

	(defmacro limited-save-excursion (&rest subexprs)
	  "Like save-excursion, but only restores point."
	  (let ((orig-point-symbol (make-symbol "orig-point")))
	    `(let ((,orig-point-symbol (point-marker)))
	       (unwind-protect
		   (progn ,@subexprs)
		 (goto-char ,orig-point-symbol)))))

Aby uniknąć sytuacji gdy kod zawarty w subexprs używa zmiennej o tej samej nazwie co zmienna której użyliśmy do przechowywania pozycji kursora, zastosowaliśmy make-symbol. Zastosowanie unwind-protect pozwoliło nam się zabezpieczyć przed wpływem ewentualnych błędów na odtwarzanie pozycji kursora i zagwarantować, że wynikiem zwróconym przez makro będzie wynik wykonania wyrażeń subexprs.

Wyszukiwanie

Wyniki każdego wykonania operacji szukania (np. omawianą już funkcją search-forward) są umieszczane w zmiennych globalnych. Funkcja save-match-data pełni w odniesieniu do wyników wyszukiwania analogiczną rolę jak opisane wyżej save-excursion i save-restriction w odniesieniu do pozycji kursora i zawężenia edytowalnego obszaru.

(match-beginning n)
zwraca pozycję początku tekstu dopasowanego do n-tego wyrażenia nawiasowego (ang. parenthesized expression, submatch) w ostatnio wyszukiwanym wyrażeniu regularnym; n=0 oznacza cały dopasowany tekst
(regexp-quote string)
zwraca wyrażenie regularne dopasowujące dokładnie wskazany łańcuch, poprzez umieszczenie w nim stosownych znaków backslash znoszących działanie znaków specjalnych
(replace-match new-string nil t nil subexpr)
zastępuje część dopasowania z ostatniego szukania łańcuchem new-string; parametr subexpr oznacza numer podmienianego wyrażenia nawiasowego (0 oznacza całe dopasowanie)
(count-matches regexp)
zwraca łańcuch "N matches" który mówi ile razy wskazane wyrażenie można dopasować do tekstu w buforze począwszy od pozycji kursora
(string-match regexp string)
zwraca indeks pierwszego dopasowania wyrażenia regularnego w łańcuchu, albo nil gdy nie ma takiego

Funkcja re-search-forward jest odpowiednikiem funkcji search-forward używającym wyrażenia regularnego.

Definiowanie małego trybu

Aby zdefiniować własny mały tryb (ang. minor mode) o nazwie abc, należy wykonać co najmniej następujące czynności:

  • zdefiniować zmienną o nazwie abc-mode i uczynić ją lokalną dla bufora:
    	    (defvar abc-mode nil
    	      "Mode variable for abc minor mode.")
    	    (make-variable-buffer-local 'abc-mode)
    
  • zdefiniować funkcję o nazwie abc-mode pobierającą jeden opcjonalny argument informujący czy tryb ma być włączony czy wyłączony; klasyczna postać tej funkcji jest następująca:
    	    (defun abc-mode (&optional arg)
    	      "Abc minor mode."
    	      (interactive "P")
    	      (setq abc-mode
    		    (if (null arg)
    			(not abc-mode)
    		      (> (prefix-numeric-value arg) 0)))
    	      (if abc-mode
    		  
    		  ))
    
  • dodać nowy wpis do listy asocjacyjnej minor-mode-alist, w której kluczem jest symbol nazwy trybu, a wartością łańcuch pojawiający się w wierszu statusu gdy tryb będzie aktywny:
                (if (not (assq 'abc-mode minor-mode-alist))
                    (setq minor-mode-alist
                          (cons '(abc-mode " Abc")
                                minor-mode-alist)))
    
  • zdefiniować pozostałe elementy, np. mapę skrótów klawiaturowych (ang. keymap), tablicę składni (ang. syntax table), tablicę skrótów (ang. abbrev table), które tutaj pominiemy.

Definiowanie głównego trybu

Aby zdefiniować własny tryb główny (ang. major mode) o nazwie abc, należy wykonać co najmniej następujące czynności:

  • stworzyć plik źródłowy o nazwie abc.el przechowujący kod trybu,
  • zdefiniować zmienną abc-mode-hook przeznaczoną na haki użytkownika odpalane w chwili wejścia do trybu:

                (defvar abc-mode-hook nil
                  "*List of functions to call when entering Abc mode.")
    
  • jeśli jest to potrzebne, zdefiniować mapę skrótów klawiaturowych trybu:
                (defvar abc-mode-map nil
                  "Keymap for abc major mode.")
                (if abc-mode-map
                    nil
                  (setq abc-mode-map (make-keymap))
                  (define-key abc-mode-map  )
                  (define-key abc-mode-map  )
                  ...))
    
  • jeśli jest to potrzebne, zdefiniować tablicę składni dla trybu w zmiennej abc-mode-syntax-table i tablicę skrótów abc-mode-abbrev-table (nie będziemy tu poruszać tego wątku),
  • zdefiniować bezparametrowe polecenie abc-mode według szablonu:
                (defun abc-mode ()
                  "Major mode ..."
                  (interactive)
    
                  ;; usuwa definicje wszystkich lokalnych zmiennych bufora
                  ;; wyłączając w ten sposób poprzedni tryb główny
                  (kill-all-local-variables)
    
                  (xyz-mode) ; jeśli dziedziczymy po trybie xyz
    
                  (setq major-mode 'abc-mode)
    
                  ;; Łańcuch wyświetlany w wierszu statusu
                  (setq mode-name "Abc")
    
                  (use-local-map abc-mode-map)
    
                  ;; odpala haki użytkownika
                  (run-hooks 'abc-mode-hook)
    
                  ...)
    

Jeśli tryb abc jest przeznaczony wyłącznie do edycji specjalnie przygotowanego tekstu i nie jest przeznaczony jako tryb domyślny dla żadnych istniejących buforów, należy wykonać dodatkowo następujące polecenie:

        (put 'abc-mode 'mode-class 'special)

Mapa skrótów klawiaturowych

W powyższym przepisie do tworzenia nowej mapy użyliśmy funkcji make-keymap. Jeśli jednak mapa miałaby zawierać nie więcej niż kilkadziesiąt skrótów, wówczas zamiast make-keymap lepiej użyć make-sparse-keymap. Z kolei jeśli nasz tryb miałby dziedziczyć po trybie xyz, to zamiast

            (setq abc-mode-map (make-keymap))

można użyć

            (setq abc-mode-map (copy-keymap xyz-mode-map))

Mapa skrótów klawiaturowych jest strukturą danych która przyporządkowuje skróty klawiaturowe poleceniom. Skróty złożone (ang. key
sequences
) takie jak C-x C-w są zaimplementowane poprzez zagnieżdżanie map skrótów klawiaturowych. Przykładowo w globalnej mapie skrótów wartość dla skrótu C-x zawiera mapę skrótów zamiast polecenia. Funkcja define-key – wspomniana wyżej w tym rozdziale – służąca do dodawania wpisów do mapy, automatycznie generuje zagnieżdżone mapy w razie potrzeby.

Kilka map skrótów klawiaturowych może być aktywnych jednocześnie. Globalna mapa (zmienna current-global-map) jest aktywna zawsze. Jej dane są nadpisywane przez dane mapy lokalnej zawierającej skróty bieżącego trybu głównego. Ostatnie zdanie mają jednak mapy aktywnych małych trybów.

Jeśli użytkownik zechce sobie zmienić skróty klawiaturowe w Twoim trybie, może w swoim haku użyć funkcji local-set-key i local-unset-key:

        (add-hook 'abc-mode-hook
          (lambda ()
            (local-set-key "M-p" 'polecenie)
            (local-unset-key "C-x ;")))

Jeśli w komentarzu dokumentacyjnym funkcji uruchamiającej tryb, tj. abc-mode umieścisz zapis \{abc-mode-map}, to zostanie on zastąpiony przez opis wszystkich skrótów klawiaturowych dostępnych w tym trybie.

Polecenie:

        (substitute-key-definition 'beginning-of-line
                                   'abc-beginning-of-line
                                   abc-mode-map
                                   (current-global-map))

powoduje zdefiniowanie w mapie abc-mode-map takiego samego skrótu klawiaturowego dla polecenia abc-beginning-of-line jak skrót dla polecenia beginning-of-line w mapie globalnej.

Z kolei polecenie:

        (define-key abc-mode-map [t] 'undefined)

powoduje, że wszystkie skróty klawiaturowe niezdefiniowane do tej pory w mapie abc-mode-map mają być przez tą mapę blokowane (mapa bardziej ogólna nie będzie wówczas w stanie reagować na takie skróty).

Obsługa myszki

Choć to dziwnie brzmi, zdarzenia dotyczące myszki znajdują się pod kontrolą map skrótów klawiaturowych. Kluczem elementu odpowiadającego zdarzeniu myszki w takiej mapie jest symbol, np. down-mouse-1 odpowiada naciśnięciu pierwszego (zwykle lewego) klawisza myszki, mouse-1 zwolnieniu tego klawisza, C-down-mouse-2 naciśnięciu środkowego klawisza przy jednoczesnym przytrzymaniu klawisza Control, double-mouse-1, S-drag-mouse-3 itp.

Z każdym zdarzeniem myszki związana jest dodatkowa struktura danych zwana zdarzeniem wejściowym (ang. input event), przechowująca m.in. pozycję kursora myszki. Polecenie obsługujące zdarzenie myszki może dostać się do owej struktury albo wywołując funkcję last-input-event, albo używając kodu literowego e w swojej deklaracji interactive.

Poniższy przykład obsługuje zwolnienie pierwszego klawisza myszki z jednocześnie wciśniętymi klawiszami Control i Shift, umieszczając kursor w miejscu kliknięcia i pokazując w minibuforze strukturę zdarzenia wejściowego.

        (defun show-mouse-position (event)
          "Show mouse position in the minibuffer."
          (interactive "@e")
          (mouse-set-point event)
          (message "Mouse event: %S" event))

        (global-set-key [S-C-mouse-1] 'show-mouse-position)

Znak @ w interactive nakazuje Emacsowi uczynić bieżącym okno w którym nastąpiło zdarzenie. Funkcja mouse-set-point ustawia kursor klawiatury w miejscu wskazywanym przez kursor myszki.

Menu

Każda "karta" menu jest reprezentowana przez pojedynczą mapę skrótów klawiaturowych. Kluczem w tej mapie jest arbitralnie wybrany symbol, a wartością para: łańcuch który zostanie wyświetlony w menu i polecenie do wykonania w przypadku wyboru tej pozycji menu. Stwórzmy sobie dwa polecenia które wyświetlają coś w minibuforze i podepnijmy je pod menu w naszym trybie abc:

        (defun misio ()
          (interactive)
          (message "Misio"))

        (defun pysio ()
          (interactive)
          (message "Pysio"))

        (setq abc-menu-map (make-sparse-keymap))
        (define-key abc-menu-map [misio-command]
          '("Misio" . misio))
        (define-key abc-menu-map [pysio-command]
          '("Pysio" . pysio))

Na koniec należy jeszcze mapę abc-menu-map wpiąć do mapy skrótów klawiaturowych trybu abc:

        (define-key abc-menu-map [menu-bar mis]
          (cons "QL" abc-menu-map))

Symbol zdarzenia menu-bar reprezentuje pasek menu jako całość. Ciąg zdarzeń [menu-bar mis] przekazuje sterowanie do mapy abc-menu-map, z kolei [menu-bar mis pysio-command] powoduje wykonanie polecenia pysio.

Dziedziczenie trybu

Jeśli nasz tryb dziedziczy po innym trybie, to chcąc to zrobić porządnie należałoby oprócz wymienionych wyżej czynności wykonać całkiem sporo dodatkowej pracy, np. skopiować tablicę składni za pomocą copy-syntax-table czy tablicę skrótów. Dziedziczenie trybów jest jednak na tyle często spotykane, że stworzono pakiet ELispa o nazwie derived ułatwiający to zadanie. Jego główne makro o nazwie define-derived-mode tworzy odpowiednią funkcję abc-mode, a jej użycie w naszym przypadku przedstawia się następująco:

        (define-derived-mode abc-mode xyz-mode "Abc"
          "Major mode ..."
          (define-key abc-mode-map  )
          (define-key abc-mode-map  )
          ...)

Makro define-derived-mode gwarantuje, że zanim zacznie wykonywać wskazane przez Ciebie instrukcje, skopiowane i utworzone już będą takie byty jak mapa skrótów klawiaturowych, tablica składni i tablica skrótów, a po wykonaniu Twoich instrukcji umieści na końcu wygenerowanej funkcji abc-mode polecenie wykonania haków użytkownika.

Plik .emacs

Polecenie global-set-key związuje (ang. bind) interaktywną funkcję ze wskazaną kombinacją klawiszy. Oczywiście użytkownik może sobie zdefiniować dowolną kombinację, aliści aby uniknąć konfliktów przyjęło się, że kombinacje postaci

    C-c 

są zarezerwowane dla użytkownika i żadne wbudowane polecenie Emacsa nie powinno ich pokrywać. Poniżej przykłady mapowania:

        (global-set-key [f6] 'compare-windows)    ; klawisz funkcyjny F6
        (global-set-key "C-cw" 'compare-windows) ; C-c w

Ładowanie i kompilacja plików ELispa

Oczywiście w pliku .emacs możesz umieszczać dowolny kod rozszerzający możliwości edytora. Z paru powodów jednak warto zastanowić się nad umieszczeniem tego kodu w oddzielnych plikach:

  • im dłuższy plik konfiguracyjny tym mniej jest czytelny,
  • taki kod jest wykonywany za każdym razem, nawet jeśli nie będzie w bieżącej sesji w ogóle potrzebny, co niepotrzebnie wydłuża czas uruchamiania edytora i zużywa zasoby komputera; kod umieszczony w osobnych plikach może być ładowany na żądanie dopiero przy pierwszym użyciu,
  • kod umieszczony w osobnych plikach może zostać skompilowany, co znacząco przyspiesza jego wykonywanie.

ELisp nie udostępnia programistom udogodnień w postaci pakietów znanych z innych języków programowania. Przyjmuje się, że w jednym pliku źródłowym znajdować się powinien zbiór wzajemnie powiązanych bytów, udostępniających określoną funkcjonalność.

Ostrzeżenie: Pliki ELispu powinny być pisane w taki sposób, aby było możliwe ich (wielokrotne) ładowanie w dowolnych momentach bez niepożądanych skutków ubocznych.

Emacs może załadować plik ELispu na kilka sposobów: z wyszukiwaniem lub bez, interaktywnie/nieinteraktywnie, w końcu jawnie lub niejawnie.

Ładowanie z wyszukiwaniem oznacza że zamiast podawać pełną ścieżkę do pliku można ograniczyć się do podania nazwy pliku bez katalogu; Emacs będzie wówczas szukał pliku w katalogach wymienionych na liście load-path. Aby dodać do listy katalog ~/emacs ze swoimi plikami ELispa, możesz wykonać następujące polecenie:

        (setq load-path (cons "~/emacs" load-path))

Ostrzeżenie: W większości sposobów ładowania można podać nazwę pliku bez rozszerzenia – Emacs spróbuje uzupełnić ją o rozszerzenie *.elc, a w dalszej kolejności *.el. Przy ładowaniu pliku *.elc porównywana jest data jego ostatniej modyfikacji z datą ostatniej modyfikacji odpowiadającego mu pliku *.el. Gdy plik *.elc jest przestarzały, Emacs załaduje go wyświetlając ostrzeżenie.

Użytkownik może interaktywnie załadować plik ELispa bez wyszukiwania za pomocą polecenia

    M-x load-file

podając pełną ścieżkę do pliku (z rozszerzeniem), albo z wyszukiwaniem za pomocą polecenia

    M-x load-library

podając nazwę pliku bez ścieżki i rozszerzenia.

Z wnętrza kodu lispowego można jawnie bez wyszukiwania załadować plik poleceniem

        (load-file "/pelna/sciezka/do/pliku.elc")

albo z wyszukiwaniem za pomocą

        (load "plik-bez-rozszerzenia")

Z jawnego ładowania należy korzystać gdy plik musi być załadowany natychmiast i albo wiesz że nigdy wcześniej nie był ładowany, albo nie dbasz o to. W praktyce rzadko zdarza się konieczność jawnego ładowania pliku ELispa.

Gdy z wielu miejsc w kodzie ładowany jest dany plik ELispa, funkcje require i provide umożliwiają uczynienie tego w sposób zapewniający że plik ten zostanie załadowany dokładnie raz.

Plik Lispu zwykle implementuje określoną funkcjonalność, tzn. zawiera definicję zbioru powiązanych bytów, któremu nadajemy nazwę za pomocą polecenia

        (provide 'my-name)

umieszczonego na końcu pliku (aby mieć pewność że nazwa nie pojawi się w systemie gdy podczas wykonywania instrukcji wcześniejszych pojawią się jakieś błędy).

Kod, który potrzebuje dostępu do funkcjonalności o określonej nazwie, wykonuje polecenie

        (require 'my-name "nazwa_pliku")

gdzie "nazwa_pliku" (bez rozszerzenia) jest zbędna jeśli jest identyczna z identyfikatorem funkcjonalności. Wywołanie require ładuje wskazany plik jeśli jeszcze nie był załadowany i nie robi nic w przeciwnym wypadku. Przypomina to załączanie plików nagłówkowych w programach języka C.

Ciekawym pomysłem jest autoładowanie. Polecenie:

        (autoload 'html-helper-mode "my-html-mode" "Edit HTML documents" t)

umieszczone w pliku .emacs powoduje, że gdy użytkownik po raz pierwszy w danej sesji wykona polecenie M-x html-helper-mode, to definicja funkcji zostanie leniwie załadowana z pliku my-html-mode.elc (lub my-html-mode.el) znajdującego się w którymś z katalogów na ścieżce load-path. Dzięki temu, że definicja funkcji jest ładowana dopiero przy pierwszym jej użyciu, skraca się czas uruchamiania Emacsa i zmniejsza jego zasobożerność. Przedostatnim argumentem wywołania funkcji autoload jest dokumentacja funkcji – jest to oczywiście duplikat łańcucha zawartego w definicji (warto wiedzieć co to za funkcja bez potrzeby ładowania pliku z jej definicją), a ostatni parametr (logiczny) informuje czy funkcja jest interaktywna (czy jest poleceniem, ang. command).

Kompilacja

W wyniku kompilacji pliku źródłowego *.el ELispu powstaje nieczytelny dla człowieka plik wynikowy *.elc zawierający przenośny między platformami sprzętowymi kod bajtowy.

Istnieje kilka sposobów kompilowania pliku źródłowego.

W Emacsie można skompilować wskazany plik *.el za pomocą polecenia

    M-x byte-compile-file

lub wszystkie pliki we wskazanym katalogu za pomocą

    M-x byte-recompile-directory

Z poziomu UNIXowego shella można z kolei wydać polecenie

    emacs -batch -f batch-byte-compile 

Czasami zachodzi potrzeba wykonania kawałka kodu zaraz po tym jak zostanie załadowany dany plik ELispa – bez względu na to kiedy ten plik zostanie załadowany. Należy w tym celu użyć konstrukcji eval-after-load, np. w poniższym przykładzie:

        (eval-after-load "dired"
          body...)

ciąg instrukcji wykona się zaraz po załadowaniu pliku "dired.[el[c]]" z któregoś z katalogów na liście load-path, albo natychmiast gdy plik ten został załadowany przed wykonaniem eval-after-load.

Debugger, profiler

Standardowy debugger

Gdy podczas wykonywania jakiegoś wyrażenia wystąpi błąd a zmienna debug-on-error będzie ustawiona na t (zwykle jest), wówczas otworzy się bufor o nazwie *Backtrace* zawierający komunikat błędu i stan stosu. Okno zamknie się po naciśnięciu klawisza q.

Można również wykonywać polecenie interaktywnie krok po kroku. Powiedzmy że chcemy prześledzić działanie funkcji my-function. Wykonujemy:

    M-x debug-on-entry RET my-function RET

Następnie wykonujemy funkcję my-function, np.

    (my-function 4)

Otwiera się okienko debugera i czeka na interakcję programisty. Dostępne są wówczas następujące polecenia:

d
Kontynuacja wykonania kodu aż do napotkania kolejnego wywołania funkcji.
e
Wykonanie podanego w minibuforze wyrażenia ELispu w kontekście bieżącej ramki stosu.
b
Założenie pułapki przy wyjściu z bieżącej ramki stosu.
c
Wyjście z debuggera i kontynuowanie wykonania kodu (możliwe tylko wtedy gdy wejście do debuggera nastąpiło z innego powodu niż błąd).
q
Wyjście z debuggera i przerwanie obliczeń.
r
Przy wyjściu z funkcji prosi o podanie wyrażenia Lispu i zwraca jego wartość jako wynik funkcji.

Jeżeli chcemy zakończyć śledzenie wykonania funkcji, to po zamknięciu okna debuggera należy jeszcze wykonać polecenie:

    M-x cancel-debug-on-entry RET my-function RET

Innym sposobem przejścia do debuggera jest umieszczenie wewnątrz funkcji polecenia

    (debug)

Ponadto, jeżeli ustawisz zmienną debug-on-quit, to przejście do debuggera nastąpi za każdym razem gdy użyjesz kombinacji C-g – przydaje się gdy interpreter wszedł do pętli nieskończonej.

Edebug

Istnieje również bardziej zaawansowany debugger ELispu, o większej sile wyrazu. Aby go użyć, musisz wybrać funkcje których wykonanie masz zamiar prześledzić. Dokonuje się tego za pomocą polecenia M-x edebug-defun którego używa się w podobnym kontekście jak C-M-x wspomniane na początku tutoriala. Daną funkcję można łatwo usunąć z obszaru zainteresowania Edebuga poprzez ponowne skompilowanie jej w zwyły sposób.

Gdy wykonanie programu przejdzie do którejś z wybranych funkcji, debugger zostanie aktywowany, kursor przeskoczy do definicji funkcji, w aktualnie wykonywanym wierszu po lewej stronie pojawi się strzałka i można będzie wykonywać poniższe polecenia:

c
Kontynuuje wykonanie.
q
Przerywa obliczenia i opuszcza debugger.
SPC
Wykonuje jeden krok obliczeń (np. obliczenie kolejnego argumentu funkcji, wykonanie funkcji po obliczeniu argumentu) i pokazuje wynik.
n
Jak SPC, ale nie wchodzi do podfunkcji nawet jeśli są wybrane do debugowania.
e
Wykonuje wyrażenie podane w minibuforze w kontekście bieżącej ramki stosu.
h
Kontynuuje pracę programu do miejsca w którym stoi kursor.
d
Wyświetla stan stosu.
b
Stawia pułapkę w miejscu kursora.
u
Usuwa pułapkę z miejsca kursora.
x
Stawia pułapkę warunkową, w oparciu o podane w minibuforze wyrażenie logiczne. Za każdym razem gdy wykonanie programu osiągnie taką pułapkę a wyrażenie logiczne okaże się prawdziwe, program zatrzyma się.
?
Informacja o innych poleceniach.

Profiler

Jeśli chciałbyś zająć się optymalizacją wykonania swojego kodu, warto skorzystać z profilera o nazwie ELP. Wykonanie polecenia M-x elp-instrument-function pozwala wybrać funkcje do śledzenia (polecenie M-x elp-instrument-package zaznacza do śledzenia wszystkie funkcje o nazwach rozpoczynających się od zadanego prefiksu).

Po uruchomieniu programu zaczną być po cichu zbierane stosowne informacje, których podsumowanie możesz zobaczyć wykonując polecenie M-x elp-results, pokazujące dla każdej śledzonej funkcji ile razy została wykonana, całkowity czas spędzony na jej wykonywaniu i średni czas wykonywania przypadający na pojedyncze wywołanie.

Polecenia M-x elp-reset-function i M-x elp-reset-all zerują liczniki odpowiednio dla wskazanej funkcji i dla wszystkich funkcji.

Aby funkcja przestała być objęta śledzeniem, należy użyć polecenia M-x elp-restore-function (M-x elp-restore-all).

Zewnętrzne procesy

Aby wykonać dowolne polecenie shella, można się posłużyć funkcją:

        (call-process     arg1 arg2 ...)

gdzie znaczenie parametrów jest następujące:

name
nazwa programu/ścieżka dostępu
stdin
nil oznacza że program nie potrzebuje standardowego wejścia, łańcuch oznacza nazwę pliku z którego będzie pobierane standardowe wejście, a t oznacza że standardowym wejściem będzie zawartość bieżącego bufora
stdout
nil oznacza że standardowym wyjściem będzie /dev/null, 0 oznacza uruchomienie programu w tle, zignorowanie wyjścia i natychmiastowy powrót, t oznacza że wyjściem jest bieżący bufor, a obiekt bufora oznacza że wyjściem ma być wskazany bufor
redisplay
nil oznacza że wyjście programu zostanie ukazane użytkownikowi w buforze dopiero po zakończeniu działania programu, dowolna inna wartość oznacza odświeżanie zawartości bufora na bieżąco.

Większą kontrolę nad asynchronicznym wykonaniem programu oferuje funkcja start-process, która uruchamia proces i natychmiast kończy się, zwracając użytkownikowi obiekt reprezentujący proces. Można użyć tego obiektu aby przesłać procesowi coś na wejście, wysłać mu sygnał, dowiedzieć się o jego stanie lub skojarzyć proces z jakimś buforem Emacsa.

        (start-process    arg1 arg2 ...)

Parametry wywołania funkcji start-process mają następujące znaczenie:

name
łańcuch będący nazwą którą Emacs przypisze temu procesowi (nie musi to być koniecznie rzeczywista nazwa programu)
outbuffer
bufor w którym wyświetlane będzie standardowe wyjście programu, albo nil jeśli standardowe wyjście ma być pomijane
progpath
nazwa programu lub pełna ścieżka do niego

Dość typowym przypadkiem jest, gdy chcemy wykonać określoną akcję w momencie gdy asynchronicznie uruchomiony proces się zakończy. W tym celu należy zainstalować odpowiedniego wartownika (ang. sentinel), czyli funkcję wywoływaną gdy zmieni się stan procesu (proces się zakończy lub otrzyma sygnał).

        (set-process-sentinel process 'my-sentinel)

        (defun my-sentinel (process string)
          (if (eq (process-status process) 'exit
            (display-buffer (process-buffer process)))))

Tutaj zmienna process jest wynikiem wywołania funkcji start-process, funkcja process-status zwraca bieżący stan procesu, a process-buffer zwraca bufor skojarzony z procesem. Parametr string wartownika jest łańcuchem opisującym zmianę stanu.

Możemy również pośredniczyć pomiędzy standardowym wyjściem uruchomionego procesu, a wypisywaniem informacji do bufora. W tym celu należy zdefiniować stosowny filtr, czyli funkcję wykonywaną za każdym razem gdy nadejdą jakieś dane ze standardowego wyjścia procesu. Gdy zdefiniowany został filtr, wówczas to on jest odpowiedzialny za ostateczne umieszczenie łańcucha w buforze.

        (set-process-filter process 'my-filter)

        (defun my-filter (process string)
          (let ((buffer (process-buffer process)))
            (save-excursion
              (set-buffer buffer)
              (goto-char (point-max))
              (insert string))
            (display-buffer buffer)))

Parametr string filtra jest łańcuchem otrzymanym jako kawałek standardowego wyjścia procesu.

Ostrzeżenie: Ponieważ filtry i strażnicy mogą być wywoływane w nieprzewidywalnych momentach (asynchronicznie), należy uważać aby nie generować nieoczekiwanych efektów, w szczególności są rzeczy którymi trzeba się tu przejmować, a nie warto sobie nimi zawracać głowy w funkcjach wywoływanych synchronicznie. Przykładowo w asynchronicznie wywoływanych funkcjach nie należy liczyć na automatyczne odtwarzanie bufora zmienionego za pomocą set-buffer.

Gdy Emacs tworzy nowy proces, wówczas utrzymuje połączenie z nim (standardowe wejście i wyjście) przy użyciu albo UNIXowych łączy nienazwanych albo pseudoterminali, przy czym te pierwsze są bardziej stosowne w przypadku nieinteraktywnych procesów. Rodzaj tworzonych połączeń jest kontrolowany przez logiczną zmienną process-connection-type której wartość nil oznacza użycie łączy nienazwanych. Dość często w związku z tym spotyka się taką konstrukcję:

        (let ((process-connection-type nil))
          (start process ...))

Co dalej?

Niniejszy tutorialek obejmuje swym zasięgiem jedynie bardzo niewielki wycinek Emacs Lispa. Pełny opis języka znajdziesz w dostępnej on-line, liczącej sobie ponad 1000 stron książce pt. The GNU Emacs Lisp Reference Manual, bardzo prawdopodobne że już czekającej na Ciebie w Twoim Emacsie wśród dokumentów Info.

This is the end of the beginning.

Trochę funkcji

Kilka funkcji przydatnych przy tworzeniu własnego kodu:

(1+ arg)
(1- arg)
Zwraca wartość arg powiększoną/pomniejszoną o 1
(max arg1 arg2 ...)
(min arg1 arg2 ...)
Zwraca argument o największej/najmniejszej wartości
(apply symbol arg1 arg2 ... argN [lista])
Wywołuje funkcję etykietowaną wskazanym symbolem, przekazując jej jako argumenty arg1argN oraz zawartość listy (a nie samą listę jako taką). Przykładowo, jeżeli mamy w ręku listę zawierającą liczby całkowite i chcemy poznać maksymalną wartość na liście, to nie możemy wykonać polecenia (max lista), natomiast możemy (apply 'max lista).
(count-lines pos1 pos2)
Zwraca liczbę wierszy pomiędzy lokacjami pos1 i pos2
(string< str1 str2)
(string-lessp str1 str2)
Porównuje łańcuchy (string< jest aliasem dla string-lessp) – argumentem tej funkcji może być również symbol
(string= str1 str2)
(string-equal str1 str2)
Sprawdza czy dwa łańcuchy są równe – argumentem tej funkcji może być symbol
(symbol-name symbol)
Zwraca łańcuch będący nazwą podanego symbolu.
(push-mark pos)
Ustawia marka na wskazanej pozycji
(goto-char pos)
Ustawia kursor na wskazanej pozycji
(bobp)
Czy kursor znajduje się na początku bufora
(eolp)
Czy kursor znajduje się na końcu wiersza lub końcu bufora
(insert-buffer-substring buffer start end)
Kopiuje z bufora buffer obszar tekstu wyznaczony pozycjami start i end do bieżącego bufora
(mark-whole-buffer)
Zaznacza cały bufor
(get-buffer name)
(get-buffer-create name)
Zwraca bufor o podanej nazwie lub tworzy go gdy nie istnieje (pierwsza wersja w ostatnim przypadku zwraca nil)
(generate-new-buffer name)
Bezwarunkowo tworzy bufor o podanej nazwie (nawet jeśli bufor o takiej nazwie już istnieje!) i zwraca go
(erase-buffer)
Usuwa całą zawartość bufora
(forward-char)
Przesuwa kursor o jeden znak w prawo
(forward-line)
Przesuwa kursor o jeden wiersz w dół
(char-to-string char)
Konwersja znaku w łańcuch
(delete-and-extract-region beg end)
Usuwa z bufora i zwraca tekst wyznaczony przez pozycje beg i end
(buffer-substring beg end)
Zwraca tekst bufora wyznaczony przez pozycje beg i end
(char-after pos)
Zwraca znak bieżącego bufora na pozycji pos
(funcall var arg1 arg2 ...)
Wywoluje funkcję podpietą pod zmienną var z parametrami arg1, arg2 itd.
(regexp-quote string)
Zwraca wyrażenie regularne, które dopasowuje łańcuch string i tylko ten łańcuch
(beginning-of-line)
Ustawia kursor na początku wiersza
(start-of-paragraph-text)
(end-of-paragraph-text)
Ustawia kursor na początku/końcu paragrafu.
(eobp)
Wartość logiczna czy kursor jest na końcu bufora (EOBP – End Of Buffer Predicate)
(looking-at regexp)
Wartość logiczna – czy tekst bezpośrednio za kursorem da się dopasować do wyrażenia regularnego regexp
(match-beginning n)
Odnosi się do ostatniej operacji wyszukiwania wyrażenia regularnego i ustawia kursor na początku dopasowanej n-tej podgrupy zawartej w wyrażeniu regularnym; gdy n ma wartość 0 to ustawia kursor na początku dopasowanego tekstu
(global-set-key "C-c=" 'symbol)
Binduje kombinację klawiszy C-c= do polecenia o nazwie zadanej symbolem
(find-file-noselect filename)
Otwiera plik filename i zwraca reprezentujący go bufor – nie pokazuje bufora użytkownikowi
(kill-buffer buffer)
Zamyka bufor buffer
(setq buffer-read-only t)
Zabrania modyfikacji bieżącego bufora
(list arg1 arg2 arg3 ...)
Tworzy listę z podanych argumentów.
(append list1 list2 ... listN)
Tworzy nową listę będącą konkatenacją wskazanych list. Lista będąca ostatnim argumentem nie jest kopiowana, lecz dołączana jako składowa listy wynikowej.
(nconc list1 list2 ...)
Jak append, ale nie kopiuje list tylko modyfikuje ostatnie elementy każdej z nich (z wyjątkiem ostatniej) aby scalić je w jedną długą listę wynikową.
(consp arg)
Zwraca t wtw. gdy arg jest parą.
(atom arg)
Zwraca t wtw. gdy arg nie jest parą.
(null arg)
Zwraca t wtw. gdy arg jest nilem. Zauważ że not i null są synonimami.
(sort list predicate)
Sortuje listę stosując predykat do ustalenia porządku, np. (sort '(4 8 21 17 33 7 21 7) '<)
(mapcar funkcja-pobierajaca-1-parametr lista)
Wywołuje wskazaną funkcję na każdym elemencie sekwencji, i zwraca listę wyników wywołania wskazanej funkcji.
(current-time-string)
(format-time-string "format" (current-time))
Zwraca łańcuch z bieżącą datą, ew. w zadanym formacie (w stylu funkcji strftime języka C).
(buffer-modified-p)
True wtw. gdy bufor został zmodyfikowany.
(skip-syntax-forward )
Przesuwa kursor do przodu do napotkania pierwszego znaku należącego do innej kategorii syntaktycznej niż wyspecyfikowana. Kategorie syntaktyczne dla danego trybu głównego są wyspecyfikowane w tablicy składni – więcej możesz się dowiedzieć np. za pomocą polecenia C-h f modify-syntax-entry.
(current-column)
Bieżąca kolumna kursora.
(this-command-keys)
Tablica (ang. array) znaków (w przypadku użycia klawiatury) lub symboli (myszka) tworzących skrót odpalający bieżące polecenie
(erase-buffer)
Usuwa całą zawartość bieżącego bufora
(selected-window)
Zwraca obiekt reprezentujący aktualnie wyselekcjonowane okno
(delete-window window)
Zamyka wskazane okno

Wesołej zabawy (ang. happy hacking).

Podziel się z innymi

Jeśli stworzysz jakiś nowy ciekawy kawałek kodu w ELispie (tryb Emacsa, grę, etc.), byłoby miło gdybyś po gruntownym wytestowaniu tego kodu zechciał podzielić się nim z innymi wysyłając źródła na grupę dyskusyjną gnu.emacs.sources.

Na początku każdego pliku źródłowego należy umieścić komentarz opisujący plik, informujący o prawach autorskich, numerze wersji itp. Przykładowo:

        ;;; foretell.el -- predict what the user will do
        ;;; Copyright 1996 by Mortimer J. Hacker 
        ;;; Foretell is free software distributed under the terms
        ;;; of the GNU General Public License, version 2.  For details,
        ;;; see the file COPYING.
        ;;; This is version 1.7 of August 1996.
        ;;; Fo more information about Foretell, subscribe to the
        ;;; Foretell mailing list by sending a message to
        ;;; .

Na końcu pliku należy również umieścić stosowny komentarz:

        ;;; foretell.el ends here

(aby przy przesyłaniu pliku mailem było wiadomo gdzie się kończy).

Jeśli Twój pakiet zawiera więcej niż jeden plik, przyjęło się utworzyć dodatkowy plik README opisujący pakiet, jego pliki i sposób instalacji. Dodatkowo należy wówczas za pomocą programu shar połączyć pliki w jeden plik dystrybucyjny.

Jeśli masz duże ambicje, przygotuj również dokumentację do swojego pakietu w formacie Texinfo (za pomocą programu makeinfo).

Gdy chcesz rozpowszechniać swoje dzieło pod licencją GPL, konieczne jest umieszczenie w plikach źródłowych warunków umowy. Najczęściej warunki te umieszcza się w osobnym pliku COPYING, do którego odwołania znajdują się w komentarzach zawartych w poszczególnych plikach źródłowych (jak w powyższym przykładzie). Warunki umowy GPL możesz przeczytać wydając w Emacsie polecenie M-x describe-copying.

Literatura

Emacs Lisp, 5.0 out of 5 based on 3 ratings
Share and Enjoy:
  • del.icio.us
  • Facebook
  • Google Bookmarks
  • Śledzik
  • Blip
  • Blogger.com
  • Gadu-Gadu Live
  • LinkedIn
  • MySpace
  • Wykop

Zostaw komentarz

XHTML: Możesz użyć następujących tagów: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>