Emacs Lisp as a scripting language

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:

  1. Function calls put parentheses in different places from Algol-based languages. You say
    
    (function argument)
    
    instead of
    
    function(argument)
    
  2. Arguments are separated by whitespace, not commas. So a function with three arguments is called like this:
    
    (function arg1 arg2 arg3)
    
  3. Everything is a function, even things that you usually think of as operators. So you say
    
    (+ 2 2)
    
    instead of
    
    2 + 2
    
  4. Functions return a value, which may then be operated on by an enclosing function. It’s common to see things like this
    
    (func3 (func2 (func1 argument)))
    
    where the functions are applied in 1-2-3 order.

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.

Tags: