CS253: Software Development with C++

Fall 2019

HW 4

CS253 HW4: Time to get classy!

Description

For this assignment, you will convert your HW3 work to a standalone class, called Enemy, which will contain a single enemy. Specifically, you will provide Enemy.h, which will contain the interface of that class, and the library libhw4.a, which will contain the implementation of that class.                 

Methods

Enemy must have the following public methods:

Enemy()
Create a Enemy containing no key/value pairs. This is an exception to the usual rule of a mandatory Name key.
Enemy(string keyfile)
Associate a keyfile with this enemy, for error-checking. The keyfile should contain one alphanumeric key per line. If it doesn’t, or if any other problem with the file or its contents is encountered, throw a runtime_error, mentioning the filename.
Copy constructor
Copy all information from another object of the same class.
Assignment operator
Copy all information from another object of the same class, replacing any previous information.
Destructor
Destroy.
.read(istream)
Read an Enemy from istream, replacing any previous information.
  • If a keyfile was provided in the ctor, and any key in the Enemy doesn’t appear in the keyfile, throw a runtime_error, mentioning the bad key.
  • If a non-alphanumeric key is encountered, throw a runtime_error, mentioning the bad key.
  • If duplicate keys are encountered, throw a runtime_error, mentioning the bad key.
  • If no Name key is read, throw a runtime_error.
  • Return true if an enemy was read, false if no enemy found.
.write(ostream)
Write the content of the Enemy to the ostream, as described in Output Format. Don’t add any extra blank lines.
.write(string filename)
Write the content of the Enemy to the given file, as described in Output Format. Throw a runtime_error, mentioning the filename, upon failure. Don’t add any extra blank lines.
.field(string key)
Return the corresponding value for this key. Throw a range_error mentioning the bad key, if key not found.
.show_name(bool)
This concerns keys that are exactly Name. If the argument is true or missing, show Name key/value pairs when writing the Enemy (via .write() or <<). If false, don’t.
.show_other(bool)
If the argument is true or missing, show non-Name, non-Link… key/value pairs when writing the Enemy (via .write() or <<). If false, don’t.
.show_link(bool)
This concerns keys that start with exactly Link, followed by any number of alphanumeric characters. If the argument is true or missing, show Link… key/value pairs when writing the Enemy (via .write() or <<). If false, don’t.
.clear()
Make this Enemy have no key/value pairs.
.size()
Return a size_t containing the number of key/value pairs in this Enemy.
.empty()
Return true iff Enemy has no key/value pairs.

Non-methods:

ostream << Enemy
The << output operator must produce the same output as .write(ostream).

Const-correctness, for arguments, methods, and operators, is your job. For example, it must be possible to call .size() on a const Enemy, or to pass a const string to .field().                 

You may define other methods or data, public or private, as you see fit. You may define other classes, as you see fit. However, to use the Enemy class, the user need only #include "Enemy.h", not any other header files.                 

Errors

All errors are indicated by throwing a runtime_error or range_error, as indicated, and must include any offending data: filename, key, etc., as part of the string returned by exception.what().                 

Input Format

Ignore trailing whitespace on all lines. If a line begins with whitespace, then it is a continuation of the previous line of the same input stream. Conceptually, replace the newline of the previous line and the leading whitespace on the next line with a single space.                 

Ignore any empty lines before an enemy starts. An enemy is described by a number of lines, ending at an empty line or end-of-file. Each line starts with an alphanumeric key, followed by whitespace, followed by a value. Neither the key nor the value may empty.                 

Output Format

Use the minimum number of spaces (but at least one) between the key and the value such that all the displayed values (of a given enemy) line up in the same column. However, different enemies don’t have to have their values in the same column. Do not emit trailing whitespace on a line. Do not write any blank lines as part of an enemy.                 

Within a given enemy, keys & values are displayed in this order:

  1. Name
  2. anything but Name and Link
  3. Link
    Multiple Link… lines are displayed in the input order.

Except as specified above, the output order is the same as the input order.                 

Debugging

If you encounter “STACK FRAME LINK OVERFLOW”, then try this:

    export STACK_FRAME_LINK_OVERRIDE=ffff-ad921d60486366258809553a3db49a4a

Libraries

libhw4.a is a library file. It contains a number of *.o (object) files. It must contain Enemy.o, but it may also contain whatever other *.o files you need. To create a library file:                 

    ar -rcs libhw4.a Enemy.o

To list the contents of an archive:                 

    ar -tv libhw4.a

Testing

You will have to write a main() function to test your code. Put it in a separate file, and do not make it part of libhw4.a. Particularly, do not put main() in Enemy.h or Enemy.cc. You will also have to create Enemy.h, and put it into hw4.tar. We will test your program by doing something like this:                 

    mkdir a-new-directory
    cd the-new-directory
    tar -x </some/where/else/hw4.tar
    cmake . && make
    cp /some/other/place/test-program.cc .
    g++ -Wall test-program.cc libhw4.a
    ./a.out

We will supply a main program to do the testing that we want. You should do something similar.                 

Sample Run

Here is a sample run, where % is my shell prompt:                 

% cat CMakeLists.txt
cmake_minimum_required(VERSION 3.14)

# Using -Wall is required:
add_compile_options(-Wall)

# These compile flags are highly recommended, but not required:
add_compile_options(-Wextra -Wpedantic)

# Optional super-strict mode:
add_compile_options(-fmessage-length=80 -fno-diagnostics-show-option)
add_compile_options(-fstack-protector-all -g -O3 -std=c++14 -Walloc-zero)
add_compile_options(-Walloca -Wctor-dtor-privacy -Wduplicated-cond)
add_compile_options(-Wduplicated-branches -Werror -Wfatal-errors -Winit-self)
add_compile_options(-Wlogical-op -Wold-style-cast -Wshadow)
add_compile_options(-Wunused-const-variable=1 -Wzero-as-null-pointer-constant)

# add_compile_options must be BEFORE add_executable or add_library.

add_library(hw4 Enemy.cc Keys.cc)
add_executable(test test.cc)
target_link_libraries(test hw4)

# Create a tar file every time:
add_custom_target(hw4.tar ALL COMMAND tar cf hw4.tar *.cc *.h CMakeLists.txt)

% cat keys
why
Name
Linkage
BodyType
LinkURL
% cat pony-villains


BodyType Unicorn
Linkage Former enemy, now friend
Name Starlight Glimmer
why Jealous of Twilight Sparkle
LinkURL https://mlp.fandom.com/wiki/Starlight_Glimmer?7602175


Name Discord       
BodyType            Chimera       
Motif         He’s     
 an avatar       
                    of chaos      
LinkURL
        https://mlp.fandom.com/wiki/Discord


% cat test.cc
#include "Enemy.h"
#include <iostream>
#include <fstream>
#include <string>
#include <cassert>
#include <stdexcept>
#include <sstream>

using namespace std;

int main() {
    const Enemy a;
    assert(a.empty());
    assert(a.size() == 0);

    ifstream lair("pony-villains");
    assert(lair);
    Enemy b("keys"), c, d;
    bool got_b = b.read(lair);
    bool got_c = c.read(lair);
    bool got_d = d.read(lair);

    assert(got_b);
    assert(b.size() == 5);
    assert(!b.empty());

    assert(got_c);
    assert(c.size() == 4);
    assert(!c.empty());

    assert(!got_d);

    cout << b << "––––––––––––––––\n" << c;

    b = c;
    assert(b.size() == 4);

    assert(b.field("BodyType"s) == "Chimera");

    c.show_other(false);
    cout << "::::::::\n" << c;
    c.show_other();
    c.show_link(false);
    cout << "********\n" << c;

    stringstream iss("\n\n\nName \t\v\f xyz  \n\n\n");
    assert(c.read(iss));
    assert(c.size() == 1);
    assert(c.field("Name") == "xyz");
    ostringstream oss;
    assert(oss << c);
    assert(oss.str() == "Name xyz\n");

    // Try a field that’s not there:
    try {
        string s = b.field("foobar");
        cerr << "ERROR: .field() worked?\n";
    }
    catch (const range_error &err) {
        cout << "Good, range_error = " << err.what() << '\n';
    }
    catch (const exception &err) {
        cerr << "ERROR: exception = " << err.what() << '\n';
    }
    catch (...) {
        cerr << "ERROR: What the ☆⚡🔪☠§❢💣 did you throw?\n";
    }

    return 0;
}
% cmake .
… cmake output appears here …
% make
… make output appears here …
% ./test
Name     Starlight Glimmer
BodyType Unicorn
why      Jealous of Twilight Sparkle
Linkage  Former enemy, now friend
LinkURL  https://mlp.fandom.com/wiki/Starlight_Glimmer?7602175
––––––––––––––––
Name     Discord
BodyType Chimera
Motif    He’s an avatar of chaos
LinkURL  https://mlp.fandom.com/wiki/Discord
::::::::
Name    Discord
LinkURL https://mlp.fandom.com/wiki/Discord
********
Name     Discord
BodyType Chimera
Motif    He’s an avatar of chaos
Good, range_error = key “foobar” not found

Hints

Requirements

If you have any questions about the requirements, ask. In the real world, your programming tasks will almost always be vague and incompletely specified. Same here.                 

Tar file

    cmake . && make

How to submit your homework:

    ~cs253/bin/checkin HW4 hw4.tar

How to receive negative points:

Turn in someone else’s work.