CS140 -- Lab 7

Brad Vander Zanden


This lab will give you practice:

  1. using doubly linked lists,
  2. using stacks,
  3. implementing information hiding using void *'s,
  4. writing programs to test library code, and
  5. figuring out potential error conditions


Materials

You will need the following files:


Part 1 -- Completing the Dllist Library

Copy the file /home/bvz/cs140/labs/lab7/dllist.c to your directory, and then as described in the class notes and in lecture, you need to write the following four functions to complete dllist.c:

  1. dll_insert_after
  2. dll_prepend
  3. dll_append
  4. dll_delete
If you forget what these functions do or want some hints on how to implement them, consult the dllist notes. If you want to test your dllist library, you can link it in with /home/bvz/cs140/labs/lab7/photo_sorter.o, which is described in the next section. In the next part of the lab you will write photo_sorter, but right now you can use my version to test your library. You will have to read the next section to figure out how to use photo_sorter.


Part 2 -- Testing Your Dllist Library

When you write a library, like dllist, that will be used in multiple applications, you should try to thoroughly test it before you release it. One way to test your library is to write a driver application that tests all the different functions in your library. You are going to write a driver program named photo_sorter.c to test your dllist library.

photo_sorter is going to mimic an imaginary photo album in which you are arranging the photos. Each photo is going to be identified by a single word, and your driver program is going to accept a number of commands for arranging the photos:

  1. append photo_name: appends the named photo to the end of the list.
  2. prepend photo_name: prepends the named photo to the beginning of the list.
  3. after photo1 photo2: inserts photo2 after photo1 in the list.
  4. before photo1 photo2: inserts photo2 before photo1 in the list.
  5. delete photo_name: delete the named photo from the list.
An example set of commands might be:
append brad
append smiley
append fred
prepend joe
prepend yifan
after yifan ebber
before brad huck
before yifan bounce
delete joe
delete fred
after yifan wolf
Do not worry about duplicate photo names. If a duplicate occurs, you should find the first photo with that name in the list and have the command act on that photo.

Output

valid command: After each valid command your program should print a list header and then the contents of the list, one photo per line. The list header should consist of:

  1. the command number
  2. a ":"
  3. a space
  4. the header "Photo List"
For example, after the first six commands above (ending with "after yifan ebber"), your output would be:
6: Photo List
  yifan
  ebber
  joe
  brad
  smiley
  fred
erroneous command: After an erroneous command you should print an appropriate error message, prefixed with the command number. If you experiment with my executable, you can see the error messages that I used. In trying to determine what is an erroneous command, consider this. Any command that is not one of the commands listed in the introduction to this section is an erroneous command. Further, any valid command that does not have the right number of arguments is an erroneous command. Doing this type of error checking can be tedious but it is important and you need to get used to doing it.

line spacing: There should be a single blank line after each list or error message.


Implementation

Your program should:

  1. create a dllist and an inputstruct to read from stdin,
  2. read commands from stdin until EOF is received. Your program should perform the appropriate action for each command. If the command is a valid one, it should then iterate through the list and print the names of the photos one per line. Remember to print the list header before you print the list. You can use the inputstruct's line field as the command number, since there is one command per line, and therefore the line number is the same as the command number. If the command is invalid, you should print an error message and continue.
  3. You will need to link in fields.o from /home/bvz/cs140/objs and dllist.o from your own directory. If you are uncertain about whether or not your dllist library is doing the right thing, you can also link in my dllist.o file from /home/bvz/cs140/objs for testing purposes. Ultimately, however you must link in your own dllist.o file.
  4. You must use the interface for my doubly-linked list library, dllist.h, for this lab.


Part 3 -- Completing the Stack Library

Complete the implementation for the stack library that was presented in class. stack.h contains the API for the stack library. Here are a few notes about your implementation:

  1. You should use your dllist library to implement your stacks.
  2. You will implement your stack library using information hiding and you will use void *'s, as discussed in class, to implement this information hiding. That means that that unlike the sllist and dllist libraries, which returns typed pointers to lists and to list nodes, the stack library returns void *'s. Similarly, while the sllist and dllist libraries accepted typed pointers to lists and to list nodes as parameters, the stack library accepts only void *'s.
  3. I would suggest writing a small program to test your implementation. It should consist of a few simple operations, such as a few pushes, a few pops, a few tops, etc. to make sure that your stack is working correctly.


Part 4 -- Implementing Call Stacks

Important: This program is worth only 15 points and the logic may take you some time to figure out. It is not worth delaying handing in your lab if you have not completed this part, because you will lose more points from not handing it in on time.

In this part of the program you are going to write an interpreter that reads a very simple program and executes it. An interpreter is a program that reads instructions and executes them. Your interpreter will be given input lines of the form:

fct-name fct-call fct-call ... fct-call
For example:
B C print_stack D
will define a function named B that calls the functions C, print_stack and D. There are only two types of instructions:

  1. user defined function call: This instruction should cause your interpreter to push a record for that function onto the call stack and start "executing" that function. When the function finishes executing, its record will be popped off the call stack and the function that called it will resume its execution. For example, suppose your input has four functions defined as follows:
    main B
    B C D
    C D
    D
    
    Initially you will place a record for main on the call stack:
    main
    
    main calls B, thus placing a record of B on the stack, and B begins execution:
    B
    main
    
    B then calls C, which in turn calls D, resulting in the following stack:
    D
    C
    B
    main
    
    D makes no function calls so it exits immediately, and pops itself off the stack:
    C
    B
    main
    
    C is also finished with calling functions so it exits and pops itself off the stack:
    B
    main
    
    B now calls D and pushes it on the stack:
    D
    B
    main
    
    Hopefully you get the picture of how things work.

  2. print_stack: This instruction should cause your interpreter to print the call stack. Your interpreter should first print a header of the form:
    **** stack %d ****
    
    where %d will be replaced with a count of the number of times you have printed the stack thus far. Your interpreter will then print the call stack from top to bottom. For each function on the call stack your interpreter will print the function's name in a 10 character wide, left justified field. Your interpreter will then print a space separated list of the functions it calls. Finally your interpreter should place an asterisk after the function that it is currently calling. For example, given the input:
    main A C D
    A B
    B C print_stack D
    C D
    D print_stack
    
    your program should produce the output:
    **** stack 0 ****
    D          print_stack*
    C          D*
    B          C* print_stack D
    A          B*
    main       A* C D
    **** stack 1 ****
    B          C print_stack* D
    A          B*
    main       A* C D
    **** stack 2 ****
    D          print_stack*
    B          C print_stack D*
    A          B*
    main       A* C D
    **** stack 3 ****
    D          print_stack*
    C          D*
    main       A C* D
    **** stack 4 ****
    D          print_stack*
    main       A C D*
    

Program Execution

Your program will begin by reading the function definitions and storing them. Once it has read all the function definitions, it will push main onto the call stack and start executing the "program" that the user just entered.

Implementation Hints

This program is the most complicated program that you have written thus far in the course. You will need two structs, one which will hold information about a function and one which will hold information about a stack frame. The struct that holds information about a function will need to keep track of the function's name and the list of functions that that function calls. The struct that holds information about a stack frame will need to keep track of the function's name and the function that it is currently calling.

You will need a couple data structures to implement this program:

  1. A list that keeps track of function information. The list nodes will point to the structs that contain information about a function.
  2. A stack that maintains the call stack. The stack elements will point to stack frame structs.

Finally here are a few implementation tips:

  1. To print a stack, you can pop each element off the stack, print it, and push it onto a second stack. Once your stack is empty (i.e., you have printed all the stack elements), you can restore the stack by popping all the elements off the second stack and pushing them back onto your stack. The pseudo code looks something like:
    // print the stack
    while (!empty(stack)) {
      stackElement = pop(stack);
      print stackElement;
      push(saveStack, stackElement);
    }
    // restore the stack
    while (!empty(saveStack)) {
      stackElement = pop(saveStack)
      push(stack, stackElement);
    }
    
    Try working through the pseudo-code with a small concrete stack, such as one that contains the elements (3, 6, 5, 4) and convince yourself that it works.

  2. Each stack frame will need to keep track of where it is in the list of functions called by the current function. For example, in the above example, main calls the functions A, C, and D. You need to keep track of which function main is calling, so that when that function returns, you can move to the next function in the list. For example, if I am currently calling C, then when C returns, I next need to call D. I am leaving it up to you to decide how to keep track of your function lists. Since you know the exact number of functions on the list, you may decide to malloc an array to store the function names. Alternatively you may decide to use a linked list. If you use an array, then your stack frame should keep an index into the array. In the above example, when main calls C, the stack frame for main would store the index 1. If you use a linked list, the stack frame would keep a pointer to the C's node in main's function list.

Input Assumptions and Program Format

  1. Place your code in a file named call_stack.c and your structs/typedefs in a file named call_stack.h.
  2. You must use the stack library, stack.h that I discussed in class. You can find the .h file in /home/bvz/cs140/include.
  3. Your program will take one command line argument, which is the name of the input file. You must use the fields library to read and process the input file.
  4. You will need to link in fields.o from /home/bvz/cs140/objs and stack.o from your own directory. If you are uncertain about whether or not your stack library is doing the right thing, you can also link in my stack.o file from /home/bvz/cs140/objs for testing purposes. Ultimately, however you must link in your own stack.o file.
  5. The input should always contain a function named main, which will be your starting function.
  6. You can find an executable at /home/bvz/cs140/labs/lab7/call_stack. If you have questions about how your program should behave or how it should format its output, check the executable.
  7. There are a number of test files in /home/bvz/cs140/labs/lab7, all starting with the prefix "stackTest". Do not assume that these files represent an exhaustive list of tests.

What to Submit

You will submit your design document in the lab when the TA calls for it. Submit the following source files:

  1. dllist.c
  2. photo_sorter.c
  3. stack.c
  4. call_stack.c and call_stack.h