CS253: Software Development with C++

Spring 2021

HW 3

CS253 HW3: Time to get classy!                

Changes                

Previous versions erroneously showed the filename or class as “Word”, singular. That’s wrong—it should be “Words”, plural: class Words, Words.h, Words.cc.                 

A compile-time error is also acceptable if the value of .size() or .empty() is ignored.                 

Description                

Contrary to the predictions of nearly everybody, management is pleased with your previous work, and want you to write a standalone class called Words, a simple lexical analyzer. Specifically, you will provide Words.h, which will contain the interface of that class, and the library libhw3.a, which will contain the implementation of that class.                 

Input Format                

The definitions of words, whitespace, backslash, and parens, are the same as HW2, except that the input may contain multiple lines.                 

Methods                

One method is forbidden:                

no default ctor
The default (no-argument) ctor for Words must fail to compile. This is not a run-time error; it’s a compile-time error.

Words must have the following public methods:                

Words(string input)
Perform lexical analysis on the multi-line input, as if .analyze() were called.
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.
.analyze(string input)
Perform lexical analysis on the given multi-line input. Calling this method more than once will start from scratch, and ignore any previous input.
.size()
Return a size_t representing the numer of words detected by the lexical analysis. For example, Words(" a +=#hi\nb ").size() would return a size_t with the value 3. Ignoring the result of .size() must generate a compile-time warning or a compile-time error.
.empty()
Return true iff there are no words in this object. Ignoring the result of this method must generate a compile-time warning or a compile-time error.
.get(size_t n, string &, bool &)
The first argument n is an index into the words, where 0 represents the first word. If n is out of range, return false to indicate failure. Otherwise, set the string argument to the corresponding word, set the bool argument to true if that word was escaped in the input, and false if not, and return true to indicate success. By “escaped”, I mean if any character of that word was escaped by a backslash.

The types and names in the method descriptions, above, do not determine the C++ declarations of those methods. They only serve to informally describe what sort of arguments a method might take. For example, I expect that you will give a name to all the arguments of .get(). You might pass certain arguments by reference, use const, declare return types, etc.                 

Non-methods:                

ostream << Words
Write the words in this object to the ostream, separated by commas. cout << Words("foo)") must emit exactly the five characters foo,) and nothing else.

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

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 Words class, the user need only #include "Words.h", not any other header files.                 

Non-Requirements                

Several things are not specified by this assignment. That means that the answer to these questions is “It’s up to you.”

Debugging                

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

    export STACK_FRAME_LINK_OVERRIDE=ffff-ad921d60486366258809553a3db49a4a

Libraries                

libhw3.a is a library file. It contains a number of *.o (object) files. It must contain Words.o, but it may also contain whatever other *.o files you need. The CMakeLists.txt shown creates libhw3.a. It does not contain main().                 

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 libhw3.a. Particularly, do not put main() in Words.h or Words.cc. You will also have to create Words.h, and put it into hw3.tar. We will test your program by doing something like this:                 

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

We will supply a main program to do the testing that we want. You should do something similar. It’s your choice whether to include your test program in your hw3.tar file. However, cmake . && make must work. If it fails because you didn’t package test.cc, but your CMakeLists.txt requires test.cc, then your build failed, and you get no points. Test your tar file, not just your code.                 

Sample Run                

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

% cat CMakeLists.txt
cmake_minimum_required(VERSION 3.11)
project(hw3)

# Are we in the wrong directory?
if(CMAKE_SOURCE_DIR MATCHES "[Hh][Ww]([0-9])$")
   if(PROJECT_NAME MATCHES "[^${CMAKE_MATCH_1}]$")
      message(FATAL_ERROR "Building ${PROJECT_NAME} in ${CMAKE_SOURCE_DIR}")
   endif()
endif()

# 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
    -fstack-protector-all -g -O3 -std=c++17 -Walloc-zero -Walloca
    -Wctor-dtor-privacy -Wduplicated-cond -Wduplicated-branches
    -Werror -Wextra-semi -Wfatal-errors -Winit-self -Wlogical-op
    -Wold-style-cast -Wshadow -Wunused-const-variable=1
    -Wzero-as-null-pointer-constant)

# add_compile_options must be BEFORE add_executable.

# Create the executable from the source file main.cc:
add_library(${PROJECT_NAME} Words.cc)
add_executable(test test.cc)
target_link_libraries(test ${PROJECT_NAME})

# Create a tar file every time:
add_custom_target(${PROJECT_NAME}.tar ALL COMMAND
    tar -cf ${PROJECT_NAME}.tar *.cc *.h CMakeLists.txt)
% cmake . && make
… cmake output appears here …
… make output appears here …
% cat test.cc
#include "Words.h"
#include "Words.h"      // I meant to do that.
#include <iostream>
#include <cassert>

using namespace std;

int main() {
    string w;
    bool b;
    const auto prog = R"(
        n = \a +\=(b+1048575) # 😈
        )";

    Words words(prog);
    cout << "There are " << words.size() << " words.\n";
    for (size_t i=0; i<words.size(); i++) {
        words.get(i, w, b);
        cout << i << ": “" << w << "”";
        if (b)
            cout << " (escaped)";
        cout << '\n';
    }
    cout << words << '\n';

    words.analyze(")\tQ\n≠\rFNORD\f\v ");
    assert(words.size() == 4);

    const auto w2(words);
    assert(w2.get(3, w, b) && w == "FNORD" && !b);
    assert(!w2.get(999, w, b));

    cout << w2 << "\nDone.\n";

    return 0;
}
% ./test
There are 9 words.
0: “n”
1: “=”
2: “a” (escaped)
3: “+=” (escaped)
4: “(”
5: “b+1048575”
6: “)”
7: “#”
8: “😈”
n,=,a,+=,(,b+1048575,),#,😈
),Q,≠,FNORD
Done.

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 work:                

In Canvas, check in the file hw3.tar to the assignment “HW3”. It’s due 10:00:00ᴘᴍ MT Saturday, with a 24-hour late period for a 25% penalty.                 

How to receive negative points:                

Turn in someone else’s work.