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:
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
#!/path/to/scmor 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
implemented as a CGI
script that renders it's output on a web page. Observe the use of the
env command as a trampoline to remove the necessity
of hardcoding the path to the scmweb wrapper. (See
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.