Princeton University
COS 217: Introduction to Programming Systems

Assignment 7: Supplementary Information

The following are implementation requirements and recommendations for your programs:


General

(Required) Your programs must be modular at the function level. Define small functions, each of which does a single well-defined job.

(Required) Your programs must be modular at the source code file level. Define interfaces and implementations, thus splitting your programs into multiple files. As noted in the primary assignment specification page, your ish must consists of a top-level module, a lexical analyzer module, a syntactic analyzer module, and a DynArray module; but it may -- and indeed must -- consist of additional modules.

(Required) Define stateless modules as appropriate. A stateless module is one that does not have any associated data. For example, your Str module from Assignment 2 is a stateless module; it consists of a group of related functions, but does not encapsulate any data. It is fine for a stateless module to declare only a single function in its interface.

(Required) Define abstract objects and abstract data types as appropriate, thus encapsulating data structures with functions.

(Required) Use the DynArray ADT (from early precepts). The source code is available through the course web pages, and also in the /u/cos217/Assignment7 directory on armlab.

(Required) Your programs must write the lines that it reads from stdin. That is, immediately after your programs read a line from stdin, they must write that line to stdout. If your programs do not do that, then it will be difficult for you and your grader to interpret the output of your programs when stdout and stderr are redirected to a file.

(Required) Your programs must make no assumptions about the maximum number of characters in each line read from stdin, the maximum length of the name of the given command, the maximum length of any of the command-line arguments of the given command, or the maximum number of command-line arguments of the given command.

(Recommended) Define the readLine function given in the dfa.c precept example program. Call that function to read each line from stdin.

(Required) ishlex must write each token to stdout on a distinct line. The token must be preceded by "Token: ". If the token is an ordinary token, then it must be followed by " (ordinary)". If the token is a special token, then it must be followed by " (special)".

(Required) ishsyn must write each command to stdout using this format:

Command name: commandname
Command arg: argument
Command arg: argument
...
Command stdin: filename
Command stdout: filename

The "Command name" line always must be present. There must be one "Command arg" line for each command-line argument. The "Command stdin" line must be present if and only if the command's stdin is redirected. Similarly the "Command stdout" line must be present if and only if the command's stdout is redirected.

(Required) Your programs must call fflush(stdout) immediately after writing the input line to stdout. More generally, your programs should call fflush(stdout) immediately after writing any data to stdout. Explanation: The stdout stream is buffered; the stderr stream is not. So if (1) both stdout and stderr are bound to the same destination, (2) your program writes X to stdout and then writes Y to stderr, and (3) your program does not call fflush(stdout) immediately after writing X to stdout, then under some circumstances X could appear in the destination after Y.


Error Handling

(Required) A programmer error is one that could not possibly be caused by user input. Informally, a programmer error is an error that should never happen. Your programs must detect each programmer error via an if statement, report the error via a descriptive error message written to stderr, and then exit with EXIT_FAILURE as the status code.

(Required) Invalid function parameter is a special-case programmer error. Your programs must call the assert macro to handle that error. Of course the assert macro detects the error, reports the error by writing a message to stderr, and exits.

(Required) A user error is one that could be caused by user input. Informally, a user error is one that could happen. Your programs must detect each user error via an if statement, and report the error via a descriptive error message written to stderr. Then your programs must reject the offending input line and continue.

(Required) In general heap exhaustion (that is, a failed call of malloc, calloc or realloc) could be caused by user input, and so must be treated as a user error. However in these particular programs (1) there is no reasonable user input that could cause heap exhaustion, and (2) if heap exhaustion does occur, then there is no reasonable way for your programs to recover from it and continue. So your programs must treat heap exhaustion as a programmer error. That is, your programs must detect heap exhaustion via an if statement, report the error via a descriptive error message written to stderr, and then exit with EXIT_FAILURE as the status code.

(Required) Your programs' error messages must begin with "programName: ", where programName is argv[0]. Note that the names of your programs are not necessarily ishlex, ishsyn, and ish; the user might have renamed the executable binary files to something else.

(Required) After each failed call of a function that sets the errno variable, your programs must call the perror or strerror function to write an appropriate error message to stderr.


Lexical Analysis

(Recommended) Implement your lexical analyzer as a deterministic finite state automaton.

(Required) Your lexical analyzer must call the isspace function to identify white-space characters.

(Required) Your lexical analyzer must represent tokens so that the difference between special and ordinary tokens is adequately captured. For example, these two commands are very different, and that difference must be captured at the lexical analysis phase:

echo one > two
echo one ">" two

(Recommended) Test your lexical analyzer by making sure that your ishlex program handles these example test cases properly:

INPUT OUTPUT
echo
Token: echo (ordinary)
echo 123
Token: echo (ordinary)
Token: 123 (ordinary)
echo one123
Token: echo (ordinary)
Token: one123 (ordinary)
echo 123one
Token: echo (ordinary)
Token: 123one (ordinary)
echo @#$%^&*()
Token: echo (ordinary)
Token: @#$%^&*() (ordinary)
echo '
Token: echo (ordinary)
Token: ' (ordinary)
echo one   two
Token: echo (ordinary)
Token: one (ordinary)
Token: two (ordinary)
    echo one two
Token: echo (ordinary)
Token: one (ordinary)
Token: two (ordinary)
echo one >
Token: echo (ordinary)
Token: one (ordinary)
Token: > (special)
echo one>
Token: echo (ordinary)
Token: one (ordinary)
Token: > (special)
echo>one
Token: echo (ordinary)
Token: > (special)
Token: one (ordinary)
rm one
Token: rm (ordinary)
Token: one (ordinary)
echo "one"
Token: echo (ordinary)
Token: one (ordinary)
echo ">"
Token: echo (ordinary)
Token: > (ordinary)
echo "one   two"
Token: echo (ordinary)
Token: one   two (ordinary)
echo one"two"
Token: echo (ordinary)
Token: onetwo (ordinary)
echo "one"two
Token: echo (ordinary)
Token: onetwo (ordinary)
echo "one
./ishlex: unmatched quote
echo one"two
./ishlex: unmatched quote

Syntactic Analysis

(Recommended) Test your syntactic analyzer by making sure that your ishsyn program handles these example test cases properly:

INPUT OUTPUT
pwd
Command name: pwd
pwd > file1
Command name: pwd
Command stdout: file1
cat file1
Command name: cat
Command arg: file1
cat < file1
Command name: cat
Command stdin: file1
cat file1 > file2
Command name: cat
Command arg: file1
Command stdout: file2
cat < file1 > file2
Command name: cat
Command stdin: file1
Command stdout: file2
cat > file1 < file2
Command name: cat
Command stdin: file2
Command stdout: file1
cat > file2 file1
Command name: cat
Command arg: file1
Command stdout: file2
< file1
./ishsyn: missing command name
cat file1 <
./ishsyn: standard input redirection without file name
cat file1 >
./ishsyn: standard output redirection without file name
cat file1 > file2 > file3
./ishsyn: multiple redirection of standard output
cat < file1 < file2
./ishsyn: multiple redirection of standard input
rm file1 file2
Command name: rm
Command arg: file1
Command arg: file2

Execution

(Required) Your ish must call fflush before each call of fork to flush the buffers of all open streams. That is, your ish must call fflush(stdin) and fflush(stdout) before each call of fork.

(Required) Your ish must call the setenv function to implement the setenv shell built-in command.

(Required) Your ish must call the unsetenv function to implement the unsetenv shell built-in command.

(Required) Your ish must call the chdir function to implement the cd shell built-in command.

(Required) Your ish must call exit(0) to implement the exit shell built-in command. Or, your ish must implement the exit command by returning 0 from the program's main function.

(Required) Your ish must not call the system function.


Signal Handling

(Required) Your ish must call the signal function to install signal handlers.

(Required) Your ish must call the alarm function when handling signals, as appropriate.

(Required) Your ish must use the SIG_IGN and/or SIG_DFL arguments to the signal function, as appropriate.