Lab Assignment -- WSU Tri-Cities CptS 121 (Spring, 2017)

Lab 11: Large Scale Software Development

This lab will introduce you to multi-file software development and the use of header files and libraries.

Files referred to here are available in this on-line directory:

http://www.tricity.wsu.edu/~bobl/cpts121/lab11_large_scale

One of those files, writeup.html, is this document in HTML. We'll download other files from this directory below.

Plug your thumbdrive into your workstation. Give it a few seconds to be recognized by Windows and then create a MinGW terminal emulator. In that emulator, create a new directory for this lab and cd to it:

$ cd /e/cpts121
$ mkdir lab11
$ cd lab11

As always, keep all your work for this lab in this directory.

The monsterdb Library

Just like Dr. Frankenstein, your assignment will be built out of separate parts of already-existing entities, in this case from Labs #7, #8, and Homework #6. The monsterdb library has a header monsterdb.h and an accompanying source file monsterdb.c. To simplify things, our "library" will consist of a single object file monsterdb.o. Typically libraries are made of multiple object files and put in files with names that end in .so, .a, .dll, or .dylib suffixes.

Much of this code has already been extracted into template files, but you'll make a few changes to these to turn them into a working library, then compile and run refactored versions of those labs and homework to show the usefulness of libraries.

The monsterdb.h Header File

Any library must come with at least one header file.

  1. Download the file monsterdb_tplt.h to your lab directory.

  2. Rename it:

    $ mv monsterdb_tplt.h monsterdb.h
    

    Here's what it looks like:

#ifndef _INCLUDED_MONSTERDB_H /* effectively prevents multiple #includes */

/*
 * This header file describes the "Monster" struct and all accessible
 * functions in the "monsterdb2" library.
 */

/*
 * To design the database, we #define sizes of every string attribute
 * in the database.  This is its maximum length plus one (and its
 * minimum width for table printing purposes).
 */
#define NAME_SIZE 15
#define FILM_SIZE 50
#define WEAKNESS_SIZE 25
#define DEFEATED_BY_SIZE 30

/*
 * This "struct" declares all information we want to maintain in the
 * "monster" database. All strings have the #define'd sizes given
 * above.
 */
struct Monster {
    char name[NAME_SIZE];
    char film[FILM_SIZE];
    int year;
    char weakness[WEAKNESS_SIZE];
    char defeatedBy[DEFEATED_BY_SIZE];
    double rating;
};

/*
 * ASSIGNMENT
 *
 * Add "extern" declarations for all data structures and functions in
 * "monster.c" that you want to export (for applications to use). (The
 * lab writeup will tell you when to do this.) That includes
 * monsters[] itself and every function whose name begins with
 * "monsterdb_". (Note this convention.)
 */


#define _INCLUDED_MONSTERDB_H
#endif

This contains the Monster struct as you probably implemented it in Part 1 of Lab #7. Note the #include _INCLUDED_MONSTER_H ... #define _INCLUDED_MONSTER_H at the beginning and the #endif at the end. This is a standard trick to prevent the contents of this file being read more than once by the same compile.

There's nothing else to do with this file now, but you'll be adding the externally-visible parts of the library here (as per the ASSIGNMENT comment) as you come across them in monsterdb.c.

The monsterdb.c Library Source File

monsterdb is implemented in a single source file monsterdb.c. Larger libraries can use hundreds of source files, but the principle is the same. Here's what you do:

  1. Download the file monsterdb_tplt.c to your lab directory.

  2. Rename it:

    $ mv monsterdb_tplt.c monsterdb.c
    

    Here's what it looks like:

#include <stdio.h>
#include <string.h> /* for strcmp() prototype */
#include <stdlib.h> /* for qsort(), atoi(), and atof() prototypes */

/*
 * ASSIGNMENT
 *
 * "#include" the header that corresponds to this library file. You
 * should do this in the source for every library you write.
 */

/*
 * Declaring storage for the database.
 */
/*
 * ASSIGNMENT
 *
 * As in Homework #6, make a #define of MAX_MONSTERS to 1024 and
 * declare a (global) array of that many `Monster` structs,
 * `monsters[]`. Note that we are choosing to make the maximum
 * allowable number of monsters (MAX_MONSTERS) internal to
 * "monsterdb". This makes it easier for us to increase it later as
 * needed.
 */


/*
 * A recommended practice when programming in the large is to put
 * function declarations in alphabetical order. There is no problem
 * doing this with functions that you've already declared "extern" in
 * the header file, but private (i.e. "static") functions that are
 * referenced before they are defined need "forward" prototypes, and
 * the best place to put those prototypes is here at the start of the
 * file.
 *
 * Here's how to do it for scanCsvHeader():
 */
static int scanCsvHeader(FILE *f_p);
/*
 * ASSIGNMENT
 *
 * Put other "static forward" prototypes here as needed.
 */


static int foundColumnName(FILE *f_p, char columnName[], char followedBy)
/* helper function used to read an expected column name */
{
    /*
     * This is originally from Homework #6, but it has been modified
     * as you should have for Homework #7. We have also made it
     * "static", as it is purely internal to the library.
     */
}


int monsterdb_getCount(void)
{
    /*
     * ASSIGNMENT
     *
     * Add code here to count and return the number of monsters in the
     * database.  Remember that the last monster in the database has a
     * zero-length name.
     */
}


int monsterdb_load(char *fileName)
/*
 * reads a CSV monster database from `fileName`, returns the number of
 * members (or 0 if there are errors)
 */
{
    /*
     * This is "loadDatabase()" from Homework #6, renamed and modified
     * (as for Homework #7) to take the name of the database as an
     * argument. We've also added error checking in case the database
     * is bigger than we have room for.
     */
    FILE *f_p = fopen(fileName, "r");
    int i;

    if (!f_p)
        return 0;
    if (!scanCsvHeader(f_p)) {
        fclose(f_p);
        return 0;
    }
    for (i = 0; i < MAX_MONSTERS; i++) {
        if (scanCsvRow(f_p, &monsters[i]) == 0)
            break;
    }
    if (i >= MAX_MONSTERS) /* allow for "null-name" termination */
        return -1;
    monsters[i].name[0] = '\0';
    fclose(f_p);
    return i;
}


void monsterdb_print(void)
/* print a human-readable version of the database on standard output */
{
    /*
     * ASSIGNMENT
     *
     * Implement the following:
     *
     * use monsterdb_getCount() to get the number of monsters
     * for each monster,
     *     print it with printMonster() (to standard output)
     *     follow it with a blank line (for readability)
     */
}


void monsterdb_printCsv(void)
/* prints the monster database on standard output as CSV */
{
    /*
     * ASSIGNMENT
     *
     * Implement the following:
     *
     * print the CSV header with printCsvHeader() (to standard output)
     * use monsterdb_getCount() to get the number of monsters
     * for each monster,
     *     print it with printCsvRow() (to standard output)
     */

}


/*
 * This is from the main() function in Part 2 of Laboratory #9.
 */
void monsterdb_printHtml(void)
/* prints the monster database on standard output as HTML */
{
    int i, count;

    printf("<table>\n"); /* HTML tables begin with this tag */
    printHtmlTableHeader();
    count = monsterdb_getCount();
    for (i = 0; i < count; i++) {
        printHtmlTableRow(monsters[i]);
    }
    printf("</table>\n"); /* HTML tables end with this tag */
}


/*
 * This is from Homework #6.
 */
void monsterdb_sortUsing(int (*compare_p)(
                             const void *monster0_vp,
                             const void *monster1_vp))
/* sort the monster database given a comparison function (*compare_p)() */
{
    int monsterCount = monsterdb_getCount();
    qsort(monsters, monsterCount, sizeof(struct Monster), compare_p);
}


/*
 * This is from Homework #6, but made "static".
 */
static void printCsvHeader(void)
/* print a CSV-compatible "monster" header to standard output */
{
    printf("Name,");
    printf("Film,");
    printf("Year,");
    printf("Weakness,");
    printf("Defeated by,");
    printf("Rating\n");
}


/*
 * This is from Homework #6, but made "static".
 */
static void printCsvRow(struct Monster monster)
/* print a CSV-compatible "monster" row to standard output */
{
    printf("%s,",  monster.name);
    printf("%s,",  monster.film);
    printf("%d,",  monster.year);
    printf("%s,",  monster.weakness);
    printf("%s,",  monster.defeatedBy);
    printf("%.1f\n", monster.rating);
}


/*
 * This is from Laboratory #9.
 */
static void printHtmlTableHeader(void)
/* print the header for an HTML table of monsters */
{
    printf("<tr>\n"); /* HTML table rows begin with this tag */
    printf("<th>Name</th>\n");
    printf("<th>Film</th>\n");
    printf("<th>Year</th>\n");
    printf("<th>Weakness</th>\n");
    printf("<th>Defeated by</th>\n");
    printf("<th>Rating</th>\n");
    printf("</tr>\n"); /* HTML table rows end with this tag */
}


/*
 * This is from Laboratory #9.
 */
static void printHtmlTableRow(struct Monster monster)
/* print the data in "monster" as an HTML row */
{
    printf("<tr>\n"); /* HTML table rows begin with this tag */
    printf("<td>%s</td>\n",  monster.name);
    printf("<td>%s</td>\n",   monster.film);
    printf("<td>%d</td>\n",   monster.year);
    printf("<td>%s</td>\n",   monster.weakness);
    printf("<td>%s</td>\n",   monster.defeatedBy);
    printf("<td>%.1f</td>\n", monster.rating);
    printf("</tr>\n"); /* HTML table rows end with this tag */
}


/*
 * This is "monster_print()" from Laboratory #9, but renamed and made
 * "static".
 */
static void printMonster(struct Monster monster)
/* print a "monster" in a convenient, human-readable format */
{
    int colonIndent = 15; /* set this empirically so the ':'s line up */

    /*
     * The following shows a good way to print out attributes: one per
     * line with the attribute name appearing as "ragged left" (as
     * often done for movie and TV credits) followed by a ':'.
     */
    printf("%*s: %s\n",   colonIndent, "Name",        monster.name);
    printf("%*s: %s\n",   colonIndent, "Film",        monster.film);
    printf("%*s: %d\n",   colonIndent, "Year",        monster.year);
    printf("%*s: %s\n",   colonIndent, "Weakness",    monster.weakness);
    printf("%*s: %s\n",   colonIndent, "Defeated by", monster.defeatedBy);
    printf("%*s: %.1f\n", colonIndent, "Rating",      monster.rating);
}


/*
 * This is from Homework #6, modified as you should have for Homework
 * #7. We have also made it "static", as it is purely internal to the
 * library.
 */
static int scanCsvHeader(FILE *f_p)
/* scans a CSV-compatible "monster" header for compatibility */
{
    /*
     * Read a CSV header line and verify its correctness. Return 1 if
     * it is correct and 0 if it is not.
     *
     * You need to confirm that each header field is occurring in
     * order.  The function foundColumnName() can do the dirty work
     * here. All you need to do is call it properly.
     */
    if (!foundColumnName(f_p, "Name", ','))
        return 0;
    if (!foundColumnName(f_p, "Film", ','))
        return 0;
    if (!foundColumnName(f_p, "Year", ','))
        return 0;
    if (!foundColumnName(f_p, "Weakness", ','))
        return 0;
    if (!foundColumnName(f_p, "Defeated by", ','))
        return 0;
    if (!foundColumnName(f_p, "Rating", '\n'))
        return 0;

    return 1;
}


static int scanCsvRow(FILE *f_p, struct Monster *monster_p)
/* reads a Monster row from a CSV file */
{
    /*
     * This is from Homework #6, modified as you should have for
     * Homework #7. We have also made it "static", as it is purely
     * internal to the library.
     */
    if (fscanf(f_p, "%[^,\n]s", monster_p->name) != 1)
        return 0;
    getc(f_p);
    if (fscanf(f_p, "%[^,\n]s", monster_p->film) != 1)
        return 0;
    getc(f_p);
    if (fscanf(f_p, "%d",       &monster_p->year) != 1)
        return 0;
    getc(f_p);
    if (fscanf(f_p, "%[^,\n]s", monster_p->weakness) != 1)
        return 0;
    getc(f_p);
    if (fscanf(f_p, "%[^,\n]s", monster_p->defeatedBy) != 1)
        return 0;
    getc(f_p);
    if (fscanf(f_p, "%lf",      &monster_p->rating) != 1)
        return 0;
    getc(f_p); /* should be end of line */
    return 1;
}


  1. Follow the instructions in the comments. The "ASSIGNMENT" comments mark places where you need to make changes.

  2. Put prototypes of all the non-static functions in monsterdb.h.

  3. Compile your code to produce monsterdb.o:

    $ gcc -Wall -c monsterdb.c
    

    You'll use this "object" file in the next part.

The Test Programs

The lab directory contains three source files that should all work with monsterdb. They are:

Source File What does it do? Origin
monsterdb_print.c prints the database in human-readable form Part 1 of Lab #7
monsterdb_html.c prints the database in HTML Part 2 of Lab #7
monsterdb_sort.c prints the database in CSV, sorted by increasing year Homework #6
  1. Download the file monsterdb_print.c to your lab directory. You are not permitted to modify this file.

  2. Compile it with:

    $ cc -Wall -c monsterdb_print.c
    
  3. Link it it together with monsterdb.o to build the monsterdb_print.exe program (aka "binary"):

    $ cc monsterdb_print.o monsterdb.o -o monsterdb_print.exe
    
  4. Run it and make sure it's giving the expected output.

  5. Perform steps 1-4 for monsterdb_html.c, compiling it into monsterdb_html.exe.

  6. Perform steps 1-4 for monsterdb_sort.c, compiling it into monsterdb_sort.exe.

When you have completed this, show and demonstrate your code to the lab assistant and he will sign you out.