Running MIT-Scheme programs as Unix scripts and via CGI

As a long time Unix user, I expect my scripting languages to be, well, scriptable!

On Unix and Unix-like systems (like Linux), the standard way to invoke a script is with the #! (shebang) notation on the first line of the script. Most modern scripting languages like Perl, Python and PHP treat # as the start of a comment that ends with the new line. This choice of comment delimiter is not accidental. It is chosen so that the first line in a script is treated as comment, as shown below:

#!/path/to/script/interpreter
code

In a twist that probably reflects a heritage that pre-dates the advent of Unix, Scheme uses semi-colons to denote start of a single line comments. So the #! trick doesn't work. Some implementations like Chez Scheme interpret the #! on the first line as the interpreter invocation and ignore it.

MIT-Scheme however is more puritan. It will only accept input when it is streamed from the input as in:

scheme < program.scm

Making MIT-Scheme script friendly

To make MIT-Scheme play well with Unix scripting, and as an additional benefit allow itself to be invoked by a web server via CGI -- the Common Gateway Interface, I put together the following shell script hacks that wrap the Scheme interpreter in a way that makes it play well with Unix and web servers. Remember they are hacks, and as with all hacks come with significant limitations and uglinesses that I describe below.

#!/bin/sh
# Invokes the MIT-Scheme interpreter for each file 
# specified on the command line. Assumes that first
# line of each program is a Unix #! script invocation.
for program in $@
do
  # skip the first line of the script
  tail -n+2 $program | scheme
done

The code above bove simply wraps the call to the Scheme interpreter in a shell script that takes the file names specified on the command line and feeds them to the Scheme interpreter after stripping out the first line (that's the purpose of the tail -n+2 in the script above.) The for loop simply feeds the Scheme interpreter each file on the command line one after the other, allowing multiple programs to be run one after another with a single invocation.

This hack however comes with some significant limitations:

  • First, Scheme must be in one of the directories in the shell PATH
  • Second, the first line of every Scheme program written for this must contain the #!/path/to/scm or an env trampoline (as shown in the example below.)
  • I haven't figured out a way to supress those idiotic messages from the interprepter. Once it's funny. Twice, it's tolerable, but ad infinitum, it's just a pain.
  • It still isn't good enough to be invoked from a web server. See below.

If you're only playing around with MIT-Scheme, this is acceptable. But it is not a solution for real work.

Use with CGI — The Common Gateway Interface

While the shell wrapper above serves for command line use on a Unix machine. It still isn't good enough for invocation from a web server. The problem is that the scheme interpreter insists on prinitng some innaities upon startup. However, an invoking web server expects the first line of output to be part of a HTTP Response header. The script below solves this problem by inserting a content type header before the main Scheme program is executed.

#!/bin/sh
# scmweb: Invokes the MIT scheme interpreter for each file 
# specified on the command line. Assumes that first
# line of each program is a Unix #! script invocation.
for program in $@
do
  # Print an HTTP header to keep the webserver happy.
  echo "Content-type: text/plain; charset=iso-8859-1\n\n";
  # skip the first line of the script
  tail -n+2 $program | scheme
done

Okay. So this minimally puts the text output of a scheme program onto a web page. Can you do real web development with it? Unfortunately no. There are significant limitations with the whole setup and MIT-Scheme itself that make this hack unsuitable for real web development. We'll explore web development with scheme in subsequent posts.

Here's an example from SICP implemented as a CGI script that renders it's output on a web page. Observe the use of the Unix env command as a trampoline to remove the necessity of hardcoding the path to the scmweb wrapper. (See SRFI-22 for details on how to use a trampoline.)

#!/usr/bin/env scmweb
; Solution to exercise 1.3 SICP
; max2: Returns the second largest of three numbers
(define (max2 a b c)
  (cond
      ((> a (max b c))
          (max b c))
      (else
          (cond
              ((> a (min b c))
                  a)
              (else
                  (min b c))))))

; sumofmaxsq: Returns the sum of the squares of the 
;   largest two of three numbers
(define (sumofmaxsq a b c)
    (+
        (square (max a b c))
        (square (max2 a b c))))

; Test the functions to see if they work
(max2 1 2 3)
(max2 1 3 2)
(max2 2 3 1)
(max2 2 1 3)
(max2 3 1 2)
(max2 3 2 1)

(sumofmaxsq 1 2 3)
(sumofmaxsq 1 3 2)
(sumofmaxsq 2 3 1)
(sumofmaxsq 2 1 3)
(sumofmaxsq 3 1 2)
(sumofmaxsq 3 2 1)

I have a sneaking suspicion that there is a more elegant way to do this. If you know of a better way, do let me know @sumanthvepa.