Emacs Lisp as a scripting language
April 8, 2008 at 5:54 PM by Dr. Drang
I don’t use Emacs as an editor—I subscribe to the belief that it’s a great operating system, lacking only a decent text editor—but I sometimes wish I had easy access to its libraries of functions. It turns out that you can run Elisp (the Lisp interpreter Emacs is built on) programs from outside Emacs by starting the program with this shebang line:
#!/usr/bin/emacs --script
This will make Emacs work like Perl or Python or Ruby or Bash—an interpreter that reads the rest of the program and executes the code. You will, of course, have to give the correct path to the Emacs binary; /usr/bin/emacs
is correct for OS X and for every Linux distribution I’ve ever used, so it’s probably right for you.
Lispers may be perplexed by that line, because, unlike Perl, Python, etc., Lisp doesn’t use the sharp symbol for comments. But since version 22, Emacs Lisp has been extended to handle the shebang (sharp-bang, #!) line the way all those other languages do. If you’re using an older version of Emacs, you’ll have use a more arcane top line, as described here
[The #! is magical for reasons that go way back in Unix history. Shebang lines even have their own Wikipedia page.]
One area where Emacs really excels is in the manipulation and conversion of dates. The Emacs calendar functions were written by Edward Reingold and Nachum Dershowitz, two computer scientists who literally wrote the book, as well as several papers, on Calendrical Calculations. I have an earlier edition of the book, which is now available at Google Books. You can also get a PostScript version of their original paper from Reingold’s site.
Reingold and Dershowitz are very good writers and if you read any of their calendrical stuff, you’ll learn not only a lot about calendars, but also how to build complex programs out of very simple pieces.
Here’s the source code of an Elisp program that takes a date in the Gregorian calendar and converts it into several other calendars. The script is very short because of the power of Emacs’ calendar functions.
1: #!/usr/bin/emacs --script
2:
3: (require 'calendar)
4:
5: ; Use current date if no date is given on the command line
6: (if (= 3 (length command-line-args-left))
7: (setq my-date (mapcar 'string-to-int command-line-args-left))
8: (setq my-date (calendar-current-date)))
9:
10: ; Make the conversions and print the results
11: (princ
12: (concat
13: "Gregorian: " (calendar-date-string my-date) "\n"
14: " ISO: " (calendar-iso-date-string my-date) "\n"
15: " Julian: " (calendar-julian-date-string my-date) "\n"
16: " Hebrew: " (calendar-hebrew-date-string my-date) "\n"
17: " Islamic: " (calendar-islamic-date-string my-date) "\n"
18: " Chinese: " (calendar-chinese-date-string my-date) "\n"
19: " Mayan: " (calendar-mayan-date-string my-date) "\n" ))
Lisp has a reputation for being hard to read, but it’s not so tough once you understand a few things:
- Function calls put parentheses in different places from Algol-based languages. You say
instead of(function argument)
function(argument)
- Arguments are separated by whitespace, not commas. So a function with three arguments is called like this:
(function arg1 arg2 arg3)
- Everything is a function, even things that you usually think of as operators. So you say
instead of(+ 2 2)
2 + 2
- Functions return a value, which may then be operated on by an enclosing function. It’s common to see things like this
where the functions are applied in 1-2-3 order.(func3 (func2 (func1 argument)))
Line 3 of my script imports the Reingold and Dershowitz calendar library. Lines 6-8 handle the command line arguments passed to the program (see below). The built-in Elisp variable command-line-args-left
is the list of arguments passed to the script as strings. Lines 14-19 actually do the conversions to the six listed calendars. Line 12 concatenates the date strings, and Line 11 prints them out.
I’ve called this program “date-convert,” and put it in my ~/bin
directory, which is in my $PATH
. If it’s called without any arguments, like this
date-convert
it will print out the current date in seven different calendars:
Gregorian: Tuesday, April 8, 2008
ISO: Day 2 of week 15 of 2008
Julian: March 26, 2008
Hebrew: Nisan 3, 5768
Islamic: Rabi II 1, 1429
Chinese: Cycle 78, year 25 (Wu-Zi), month 3 (Bing-Chen), day 3 (Wu-Yin)
Mayan: Long count = 12.19.15.4.2; tzolkin = 2 Ik; haab = 5 Pop
If it’s called with three numeric parameters—month, day, and year—it will print out that date in those same calendars. So
date-convert 6 6 1945
leads to
Gregorian: Wednesday, June 6, 1945
ISO: Day 3 of week 23 of 1945
Julian: May 24, 1945
Hebrew: Sivan 25, 5705
Islamic: Jumada II 24, 1364
Chinese: Cycle 77, year 22 (Yi-You), month 4 (Xin-Si), day 26 (Bing-Wu)
Mayan: Long count = 12.16.11.8.10; tzolkin = 8 Oc; haab = 8 Zip
If you give it more or fewer than three arguments, it will behave as if you gave it no arguments and print the current date. If you give it nonsensical arguments, like letters instead of numbers, it will print an error message from the calendar library. I don’t see much point in elaborate error handling in a script this simple. The world won’t come to an end if I give it bad input.
The script can be altered to give fewer or more conversions. The library can also handle the French Revolutionary, Persian, Coptic, and Ethiopian calendars.
Many very clever things have been written in Elisp. Using Emacs as an interpreter is a way of bringing those things to you even if you hate Emacs as a text editor.
Update
A later post adapts this into a CGI script.