For this assignment, you will implement a small scripting language using Scheme's macro system. Like most Unix shells, we will use streams to represent the output of system processes; below, we provide Scheme primitives for streams. Do this assignment in the Pretty Big language level in DrScheme.
Since this task relies heavily on support libraries (for systems calls, I/O, etc), you will need to learn some new features of PLT Scheme as you go. A good resource is the Help Desk, which contains extensive documentation on all of the libraries.
Your language should include at least the following expressions:
(files re)
This expression produces a stream containing the names of all files in the current directory that match the regular expression re. The re argument should be a string, not a Scheme reg-exp object.
(lines re filename)
This expression produces a stream containing all lines in the file filename that match the regular expression re. The filename should be a string naming a file in the current directory.
(run cmd arg1 arg2 ... argn)
This expression produces a stream containing all lines output by the program cmd with arguments arg1, arg2, ..., argn. The subexpressions (cmd, etc.) can be either symbols or strings, and should be implicitly quasiquoted (which allows them to contain arbitrary Scheme code). For example, the following expressions are legal:
(run /usr/bin/yes) (run /bin/ls -l -a) (run "/bin/ps" u) (run finger ,(string-append "k" "fisler"))
(for stream-expr with ([var init-expr] ...) do
body-expr
then return-expr)
This expression iterates over the elements in stream-expr, evaluating body-expr each time, and returning return-expr when the stream is empty. The variables var ... are initially bound to init-expr ... and are updated each iteration by the special expression (loop next-expr ...). Also, the identifier it is bound in body-expr to the current stream element.
The for-expression is best illustrated with an example. The following expression prints all logins and the total number at the end:
(for (run who) with ([n 0]) do
(begin
(printf "~a~n" it)
(loop (+ n 1)))
then (printf "total: ~a~n" n))
There are also two shorter forms of for. The following form is useful when only binding one variable:
(for stream-expr with (var init-expr) do
body-expr
then return-expr)
The above example thus could be written as:
(for (run who) with (n 0) do
(begin
(printf "~a~n" it)
(loop (+ n 1)))
then (printf "total: ~a~n" n))
This form binds no variables:
(for stream-expr do
body-expr
then return-expr)
Since the body of the for-expression includes the implicitly bound identifiers loop and it, your macro must produce an expression where these identifier are not automatically renamed; in parlance, you must break hygiene. If you took CS1102 or CS2135, you learned how to write hygienic macros using syntax-rules. Breaking hygiene requires a more sophisticated macro construct called syntax-case. Chapter 36 of the course text discusses hygiene, syntax-rules and syntax-case.
(define-syntax stream-cons
(lambda (stx)
(syntax-case stx ()
[(_ f r) (syntax (cons f (delay r)))])))
(define stream-empty
empty)
(define (stream-empty? s)
(empty? s))
(define (stream-first s)
(first s))
(define (stream-rest s)
(force (rest s)))
(define (stream-display s)
(unless (stream-empty? s)
(display (stream-first s))
(newline)
(stream-display (stream-rest s))))
If the evaluation of a for expression's body does not use loop, the value of the body should be returned (i.e. you should not continue iterating over stream elements, and you should not evaluate the then expression).
Just stdout.
Look up the Scheme command subprocess, an example
use of which appears below (see the documentation for a description
of the inputs and outputs to this function); let-values
supports functions with multiple outputs.
(let-values ([(proc p-out p-in p-err) (subprocess #f #f #f "/usr/bin/finger" "kfisler")])
...)
No.
Just evaluate the then clause.
You can assume you are given a full path.