Sample Lab 8 Design Document
Your design document should also include pictures of data structures.
Error Checking
Your program should perform the following error checks:
- Ensure there are an appropriate number of command line arguments
- Ensure that a function has been defined before calling it. If a
function is not defined, print an error message and quit.
- Make sure that the user defined main. This will need to be
a special check just before main is pushed onto the call stack.
This check handles the special case of an empty file.
- Make sure that when a function is defined, that
the user has not previously defined the same function. For example,
if the user is defining function B, make sure that B has not been
previously defined.
Normal Test Cases
- main does not call any function (i.e., its definition is "main" or
"main print_stack").
- main calls functions but the functions do not in turn call any functions
(e.g., test1).
- functions call other functions (e.g., test2).
- functions call other functions multiple times (e.g., test3).
- functions gets called by multiple functions (e.g., A calls B and C also calls B).
Data Structures
Your program will need five data structures:
- A "function struct" to keep track of a function's information,
including its 1) name, 2) the list of functions it calls (which I
will henceforth refer to as its "call list"), 3) the size of the
call list, and 4) and the line number in the input
file on which this function was defined. The line number will be
needed for your error messages. Each
call list can be implemented as a dynamically allocated array
because the fields library tells you how many fields there are on a
line and therefore you can compute the number of functions on a
function's call list.
- A linked list to store the function structs. I will henceforth call
this list the "function list".
- A "stack frame" struct. The stack frame struct should keep track
of a currently executing function. It should maintain a pointer
to the function's "function struct" and an index that keeps track
of which function you are currently executing in its call list.
The pointer to the "function struct" will allow you to access important
information about the function, such as its name, call_list, and in
the case of an error, the line number it was defined on in the input
file.
- A call stack. When you call a function, you should create a
stack frame struct for it and push this stack frame
record onto the call stack.
- A print stack. It may not be immediately obviously why you need a
print stack but I am not permitting your stack library to contain
a function for printing the stack contents. Therefore your print_stack
routine is going to have to pull all the elements off the call_stack
in order to print its contents. You can use a second stack, a print_stack,
to save the call_stack elements and restore them once you're done
printing the call stack.
Program Steps
Here is one way you could incrementally develop your program. It is not the
only way, so feel free to do it your own way. However, you should use some
type of incremental development and like my solution, you should put off
error checking to the very end. You should initially assume that your input
is correct.
- Echo the input to the output: This will force you to allocate an
inputstruct and write a loop using getline to read the lines and
echo them. That will provide a basic framework for reading the input.
- Create the function struct that holds the information for each function (i.e.,
function name, array pointer for the functions that it calls,
size of the array, etc.).
- For each input line create a function struct and add it to a dllist.
Don't worry about potential duplicate function declarations
at this point.
- Write a struct for a stack frame. The stack frame will have a pointer
to the function struct of the function it is executing,
and an integer index into the function's cal list.
The function name pointed to by the index is the function
currently being called by that function.
- Write a function to print the contents of a stack. Test it
using a driver program that pushes integers onto the stack and
calls your function to print the stack. Once you have debugged
the function, you can copy the function to your program and change
the print statement so that it prints stack trace information. The
change will involve working with stack frame structs.
- Create a stack for the call stack,
create a stack frame for main and push it
onto the stack. To create a stack frame, you will need to write
a function that traverses your function list, finds the function
struct for that function, and makes the stack frame point to that
function.
- Write a loop that "executes" your toy program. Each iteration will
look at the top stack frame on the call stack. It will advance the
call list index
for that stack frame and find the function associated with that
index. One of three things will happen:
- The call index will be past the end of the function's call_list
(the array that holds the list of functions that this function
calls). In this case the function is done executing so pop
the frame off the call stack.
- The call index points to a "print_stack" call. Call your
print function to print the stack.
- The call index points to the name of a function. Look up that
function in your function list, create a stack frame struct
for that function, and push the stack frame onto the call stack.
This is the function that you are going to start "executing".
When the call stack is finally empty (i.e., main has called all of its
functions), your program can exit.
- Add error checking. Notice that my error messages reference the line
on which a function was defined. When a function is first defined,
you can use the "line" field from the fields library to record
its line number for later use by error messages.