The "Hello Command Line" Example
The previous section explained how to define functions in Scheme. This section demonstrates how to access command-line arguments and generate a test standalone executable. The following code snippet uses the function
print-arg-list, which contains at least two surprises if this is your first time using Scheme:
- The function calls itself recursively (as did the function fact you saw in the last section) to implement a loop over a list of argument values.
- It uses another function that is defined inside its definition.
(define (print-arg-list arg-list)
(define (print-arg arg) ; define the inner utility function print-arg
(display "Next command-line-argument: ")
(display (car arg-list))
(newline))
;; this is the start of the function print-arg-list:
(if (pair? arg-list) ; check that arg-list is a list with at least one argument
(let ()
(print-arg (car arg-list))
(print-arg-list (cdr arg-list))))) ; make a recursive call
The only reason that I split out the separate inner function print-arg was to show you an example of nested functions. Nesting function definitions like this is a good technique when a small utility function is used by only one other function. The inner function is not visible outside the scope of the outer function. Using a nested function makes sense when it is used more than once in an outer function definition.
When I am interactively coding, I write and debug small functions as I write them to catch mistakes earlier. In the case of nested functions, I usually write and debug them before copying them into the definition of another function that uses them.
You can test the function print-arg-list using gsi:
> (print-arg-list '("-i" "input.dat" "-o" "output.dat" 1 2 3))
Next command-line-argument: -i
Next command-line-argument: input.dat
Next command-line-argument: -o
Next command-line-argument: output.dat
Next command-line-argument: 1
Next command-line-argument: 2
Next command-line-argument: 3
>
You have seen that top-level Scheme expressions typed in gsi are evaluated immediately:
> (display "Hello\n")
Hello
>
If you put top-level Scheme expressions in a Scheme source file and compile it to a native application, then these top-level expressions are also immediately executed when the application runs.
The gsc compiler generates a separate C file ending with _.c. The generated .c files contain a main C function. For the "Hello Command Line Arguments" test program, I use the print-arg-list function and the following expression:
(let* ((args (command-line))
(in-file (member "-i" args))
(out-file (member "-o" args))
(arg-hash-table (make-table)))
;; let's start by printing the command line arguments:
(print-arg-list args)
;; check to see if an argument is "-help":
(if (member "-help" args) (print-help))
;; now print out input and output file names, if they are supplied:
(if (and
in-file
(> (length in-file) 1))
(let ()
(display "Input file: ") (display (cadr in-file)) (newline)))
(if (and
out-file
(> (length out-file) 1))
(let ()
(display "Output file: ") (display (cadr out-file)) (newline))))
This expression uses the built-in Gambit-C specific function command-line, which returns command-line arguments as a list of strings. It also uses the Scheme function member, which takes two arguments: an expression and a list of expressions. The expression searches the list for an element equal to the first argument.
Here are some examples of member:
> (define test-list '(1 2 3 4 5 "cat" 3.14159 100 101))
> (member 3 test-list)
(3 4 5 "cat" 3.14159 100 101)
> (member "cat" test-list)
("cat" 3.14159 100 101)
> (member "not in list" test-list)
#f
>
Note that member returns a sub-list starting with the matching list element. The function length returns the number of elements in a list:
> (length test-list)
9
An example program you will develop later requires you to process command arguments.
Creating an Executable File
When creating standalone executables, be aware that the Gambit-C
gsc compiler compiles Scheme code to C; the generated C code is compiled and linked against the Gambit-C libraries. The location of these files is different for OS X, Linux, and Windows. When I started writing this article, using version 4.4.4 of Gambit-C, compiling and linking Scheme code into a native executable took several steps and was platform dependent because you had to know the location of the include files and library files for Gambit-C. With the release of Version 4.5.0, the compiler has a new option,
-exe, that removes the platform dependencies and allows you to compile to C, compile the generated C code, and link it all with one command.
The code directory hello-command-line-args in the source code download contains both the source file hello-command-line.scm and a makefile. Here is a listing of the makefile:
clean:
rm -f *~
rm -f */*~
rm -f \#*
rm -f */\#*
rm -f *.c
rm -f hello-command-line
app:
gsc -exe hello-command-line.scm
The make target clean removes all files from the working directory except for the Scheme source file and the makefile. The make target app builds a standalone executable with no other dependencies. Here is an attempt at running the hello command-line executable:
markw$ ./hello -help
Next command-line-argument: ./hello
Next command-line-argument: -help
Hello Command Line (native) command line arguments:
-help -- to print help message
-i -- to define the input file name
-o -- to specify the output file name
markw$ ./hello "this is a test" 1 2 3
Next command-line-argument: ./hello
Next command-line-argument: this is a test
Next command-line-argument: 1
Next command-line-argument: 2
Next command-line-argument: 3
markw$ ./hello -i input.dat -o output.dat
Next command-line-argument: ./hello
Next command-line-argument: -i
Next command-line-argument: input.dat
Next command-line-argument: -o
Next command-line-argument: output.dat
Input file: input.dat
Input file: output.dat
The next section demonstrates another example that processes input text files.