This course website is from a previous semester. If you are currently in the class, please make sure you are viewing the latest course website instead of this old one.
The goal of this homework assignment is to allow you to practice using
pointers, arrays, and strings in C99. The first activity involves
building a string utilities library, while the second activity requires you
to use this library to build a str
utility similar to tr.
For this assignment, record your source code and any responses to the
following activities in the homework07
folder of your assignments
GitHub repository and push your work by noon Saturday, March 26.
Before starting this homework assignment, you should first perform a git
pull
to retrieve any changes in your remote GitHub repository:
$ cd path/to/repository # Go to assignments repository
$ git checkout master # Make sure we are in master branch
$ git pull --rebase # Get any remote changes not present locally
Next, create a new branch for this assignment:
$ git checkout -b homework07 # Create homework07 branch and check it out
You are now ready to work on the activities below.
In Python, we had the luxury of strings being first-class objects with all sorts of useful methods such as str.lower or str.strip. Unfortunately, strings in C are just arrays that utilize the sentinel pattern for denoting the end of the strings and the standard library is a bit bare when it comes to manipulating strings.
To rectify this situation, for the first activity you are to create a string
utilities library, libstr
, which contains functions such as str_lower
and
str_strip
which implement some of the functionality present in Python but
lacking in C99.
To help you get started, the instructor has provided you with the following skeleton code:
# Go to homework07 folder
$ cd homework07
# Download Makefile, library.c, main.c, and str.h
$ curl -L --remote-name-all https://raw.githubusercontent.com/nd-cse-20289-sp22/cse-20289-sp22-assignments/master/homework07/{Makefile,library.c,main.c,str.h}
# Download test scripts (optional)
$ curl -L --remote-name-all https://raw.githubusercontent.com/nd-cse-20289-sp22/cse-20289-sp22-assignments/master/homework07/{test_libstr.py,test_str.sh}
Once downloaded, you should see the following files in your homework07
directory:
homework07
\_ Makefile # This is the Makefile for building all the project artifacts
\_ README.md # This is the README file for recording your responses
\_ library.c # This is the C99 implementation file for the string utilities library
\_ main.c # This is the C99 implementation file for the string utility
\_ str.h # This is the C99 header file for the string utilities library
\_ test_libstr.py # This is the Python test script for the string utilities library
\_ test_str.py # This is the Shell test script for the string utility
The details on what you need to implement are described in the following sections.
str.h
¶The str.h
file is the header file for the string utilities library, which
means it contains the function prototypes:
/* str.h: string utilities library */
#pragma once
#include <stdbool.h>
#include <stdlib.h>
/* Case */
void str_lower(char *s);
void str_upper(char *s);
void str_title(char *s);
/* Strip */
void str_chomp(char *s);
void str_strip(char *s);
/* Reverse */
void str_reverse(char *s);
/* Translation */
void str_translate(char *s, char *from, char *to);
Other programs will #include
this file in order to use the functions we
will be implementing in this library.
Note: For this task, you do not need to modify this file. Instead, you should review it and ensure you understand the provided code.
Makefile
¶The Makefile
contains all the rules or recipes for building the
project artifacts (e.g. libstr.a
, libstr.so
, etc.):
CC= gcc
CFLAGS= -g -Wall -std=gnu99
LD= gcc
LDFLAGS= -L.
AR= ar
ARFLAGS= rcs
TARGETS= libstr.a \
libstr.so
all: $(TARGETS)
test:
@$(MAKE) -sk test-all
test-all: test-libstr test-str
test-libstr: libstr.so
curl -sLO https://raw.githubusercontent.com/nd-cse-20289-sp22/cse-20289-sp22-assignments/master/homework07/test_libstr.py
chmod +x test_libstr.py
./test_libstr.py -v
test-str: str-static str-dynamic
curl -sLO https://raw.githubusercontent.com/nd-cse-20289-sp22/cse-20289-sp22-assignments/master/homework07/test_str.sh
chmod +x test_str.sh
./test_str.sh
clean:
rm -f $(TARGETS) *.o
# TODO: Add rules for libstr.a libstr.so
# TODO: Add rules for str-dynamic str-static
For this task, you will need to add rules for building the static library
libstr.a
and the shared library libstr.so
. Besure to have a recipe
for any intermediate object files that both libraries require as shown in
the DAG below:
You must use the CC
, CFLAGS
, LD
, LDFLAGS
, AR
, and ARFLAGS
variables when appropriate in your rules. You should also consider
using automatic variables such as $@
and $<
as well.
Once you have a working Makefile
, you should be able to run the following commands:
# Build all TARGETS
$ make
gcc -g -Wall -std=gnu99 -fPIC -c -o library.o library.c
ar rcs libstr.a library.o
gcc -L. -shared -o libstr.so library.o
# Run all tests
$ make test
test_00_str_lower (__main__.StrTestCase) ... FAIL
test_01_str_upper (__main__.StrTestCase) ... FAIL
test_02_str_title (__main__.StrTestCase) ... FAIL
test_03_str_chomp (__main__.StrTestCase) ... FAIL
test_04_str_strip (__main__.StrTestCase) ... FAIL
test_05_str_reverse (__main__.StrTestCase) ... FAIL
test_06_str_translate (__main__.StrTestCase) ... FAIL
Score 2.16 / 7.00
Status Failure
...
# Remove generated artifacts
$ make clean
rm -f libstr.a libstr.so *.o
Note: The tests will fail if you haven't implemented the string utilities library.
You must include the -Wall
flag in your CFLAGS
when you compile. This
also means that your code must compile without any warnings, otherwise
points will be deducted.
library.c
¶The library.c
file contains the C99 implementation for the string
utilities library.
For this task, you will need to implement the following functions:
void str_lower(char *s)
This function converts all the letters in
s
to lowercase (e.g.s.lower()
in Python)
char
to lowercase.
void str_upper(char *s)
This function converts all the letters in
s
to uppercase (e.g.s.upper()
in Python).
char
to uppercase.
void str_title(char *s)
This function converts all the letters in
s
to titlecase (e.g.s.title()
in Python).
void str_chomp(char *s)
This function removes a trailing newline character from
s
if one is present (e.g.s[:-1] if s[-1] == '\n' else s
in Python).
void str_strip(char *s)
This function removes any whitespace from the front and back of
s
(e.g.s.strip()
in Python).
void str_reverse(char *s)
This function reverses the letters in
s
(e.g.s[::-1]
in Python).
chars
.
void str_translate(char *s, char *from, char *to)
This function translates the letters in
s
using the mapping provided byfrom
andto
(e.g.s.translate(string.maketrans(from, to))
in Python).
The functions above must perform modifications in-place (if necessary). That is, you must not allocate any additional temporary strings as scratch space. Moreover, you should not need to use the heap or dynamic memory allocation.
The functions above must run in O(n)
(ie. linear) time and use
O(1)
(ie. constant) space.
The functions above must only use pointers when iterating through a string or accessing individual characters in a string. That is, you cannot index into a string:
/* Allowed: Iterate through string using pointers */
for (char *c = s; *c; c++)
putc(*c, stdout);
/* Forbidden: Iterate through string using array index */
for (size_t i = 0; i < strlen(s); i++) {
putc(s[i], stdout); // Not allowed
putc(*(s + i), stdout); // Also not allowed
}
That said, you can still do pointer arithmetic to initialize a pointer:
/* Allowed: Get back of a string */
char *tail = s + strlen(s) - 1;
test_libstr.py
¶As you implement the functions in library.c
, you should use the
test_libstr.py
script to test each function:
# Build artifacts
$ make
# Run test script manually
$ ./test_libstr.py -v
test_00_str_lower (__main__.StrTestCase) ... ok
test_01_str_upper (__main__.StrTestCase) ... ok
test_02_str_title (__main__.StrTestCase) ... ok
test_03_str_chomp (__main__.StrTestCase) ... ok
test_04_str_strip (__main__.StrTestCase) ... ok
test_05_str_reverse (__main__.StrTestCase) ... ok
test_06_str_translate (__main__.StrTestCase) ... ok
Score 7.00 / 7.00
Status Success
----------------------------------------------------------------------
Ran 7 tests in 0.001s
OK
Alternatively, you can both build the artifacts and run the test script by doing the following:
# Build and run test scripts
$ make test
To use Python to interactively test a function, you can do something like the following:
# Import ctypes package
>>> import ctypes
# Load string utilies shared library
>>> libstr = ctypes.CDLL('./libstr.so')
# Call str_lowercase function
>>> s = b'Hello, World!'
>>> libstr.str_lower(s)
>>> s
b'hello, world!'
Of course, you are free to create your own test programs to debug and test your string utilities library.
You should practice iterative development. That is, rather than writing a bunch of code and then debugging it all at once, you should concentrate on one function at a time and then test that one thing at a time. The provided unit tests allow you to check on the correctness of the individual functions without implementing everything at once. Take advantage of this and build one thing at a time.
Once you have a working string utilities library, you are to complete the
str
utility:
$ ./str-static -h # Display usage
Usage: ./str-static SOURCE TARGET
Post Translation filters:
-s Strip whitespace
-r Reverse line
-l Convert to lowercase
-u Convert to uppercase
-t Convert to titlecase
$ echo " Hello world" | ./str-static # Just echo input
Hello world
$ echo " Hello world" | ./str-static -s # Strip whitespace
Hello world
$ echo " Hello world" | ./str-static -r # Reverse letters
dlrow olleH
$ echo " Hello world" | ./str-static -l # Lowercase
hello world
$ echo " Hello world" | ./str-static -u # Uppercase
HELLO WORLD
$ echo " Hello world" | ./str-static -t # Titlecase
Hello World
$ echo " Hello world" | ./str-static 'aeio' '4310' # Translate
H3ll0 w0rld
$ echo " Hello world" | ./str-static -s -l 'aeio' '4310' # Translate, strip, and lowercase
h3ll0 w0rld
The str
utility must use the corresponding functions from the string
utilities library you created above to translate and filter the input text.
Makefile
¶The first task is to modify the Makefile
to include additional rules for
the following targets:
str-static
: This is a static executable of main.c
.
str-dynamic
: This is a dynamic executable of main.c
.
Once again, be sure to add any rules for any intermediate object files.
Additionally, be sure to add str-static
and str-dynamic
to the all
recipe and to add test-str
to the test-all
recipe.
Once you have a working Makefile
, you should be able to run the following commands:
# Build all TARGETS
$ make
gcc -g -Wall -std=gnu99 -fPIC -c -o library.o library.c
ar rcs libstr.a library.o
gcc -L. -shared -o libstr.so library.o
gcc -g -Wall -std=gnu99 -c -o main.o main.c
gcc -L. -o str-dynamic main.o -lstr
gcc -L. -static -o str-static main.o -lstr
# Run all tests
$ make test
test_00_str_lower (__main__.StrTestCase) ... ok
test_01_str_upper (__main__.StrTestCase) ... ok
test_02_str_title (__main__.StrTestCase) ... ok
test_03_str_chomp (__main__.StrTestCase) ... ok
test_04_str_strip (__main__.StrTestCase) ... ok
test_05_str_reverse (__main__.StrTestCase) ... ok
test_06_str_translate (__main__.StrTestCase) ... ok
Score 7.00 / 7.00
Status Success
----------------------------------------------------------------------
Ran 7 tests in 0.001s
OK
Testing str utility...
str -h ... Success
str -h (valgrind) ... Success
str ... Success
str (valgrind) ... Success
str aeio 4310 ... Success
str aeio 4310 (valgrind) ... Success
str -s aeio 4310 ... Success
str -s aeio 4310 (valgrind) ... Success
str -r aeio 4310 ... Success
str -r aeio 4310 (valgrind) ... Success
str -l aeio 4310 ... Success
str -l aeio 4310 (valgrind) ... Success
str -u aeio 4310 ... Success
str -u aeio 4310 (valgrind) ... Success
str -t aeio 4310 ... Success
str -t aeio 4310 (valgrind) ... Success
str -l -u aeio 4310 ... Success
str -l -u aeio 4310 (valgrind) ... Success
str -l -s aeio 4310 ... Success
str -l -s aeio 4310 (valgrind) ... Success
str -r -u aeio 4310 ... Success
str -r -u aeio 4310 (valgrind) ... Success
str -r -s -t aeio 4310 ... Success
str -r -s -t aeio 4310 (valgrind) ... Success
Score 3.00 / 3.00
Status Success
# Remove generated artifacts
$ make clean
rm -f libstr.a libstr.so str-dynamic str-static *.o
main.c
¶The main.c
file is contains the C99 implementation of the string utility
described above:
/* main.c: string library utility */
#include "str.h"
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/* Globals */
char *PROGRAM_NAME = NULL;
/* Flags */
enum {
/* TODO: Enumerate Flags */
};
/* Functions */
void usage(int status) {
fprintf(stderr, "Usage: %s SOURCE TARGET\n\n", PROGRAM_NAME);
fprintf(stderr, "Post Translation filters:\n\n");
fprintf(stderr, " -s Strip whitespace\n");
fprintf(stderr, " -r Reverse line\n");
fprintf(stderr, " -l Convert to lowercase\n");
fprintf(stderr, " -u Convert to uppercase\n");
fprintf(stderr, " -t Convert to titlecase\n");
exit(status);
}
void translate_stream(FILE *stream, char *source, char *target, int flag) {
/* TODO: Process each line in stream by performing transformations */
}
/* Main Execution */
int main(int argc, char *argv[]) {
/* TODO: Parse command line arguments */
/* TODO: Translate Stream */
return EXIT_SUCCESS;
}
In addition to implementing command line parsing in the main
function and
you will need to implement the translate_stream
function
void translate_stream(FILE *stream, char *source, char *target, int flag)
This function reads one line at a time from the
stream
and performs string translation based on the values insource
andtarget
. Any post processing is controlled byflag
, which is a bitmask that specifies which filters to apply (ie. strip, reverse, lower, upper, title).
Note: You should apply the filters in the order specified above. Overall, the flow for each line of input should be: chomp, translate, apply filters, and then print result.
Hint: To handle the flag
bitmask, you should have an enumerate for
each filter, where each filter corresponds to a particular bit field:
enum { /* Define Flags */
STRIP = 1<<1,
...
};
flag |= STRIP; /* Set a Flag */
if (flag & STRIP) { /* Check a Flag */
...
}
If you need a review of enum
, checkout Chapter 22. Enumerated Types: enum.
test_str.sh
¶To test your str
utility, you can use the provided test_str.sh
script:
# Build artifacts
$ make
# Run test script manually
$ ./test_str.sh
Testing str utility...
str -h ... Success
str -h (valgrind) ... Success
str ... Success
str (valgrind) ... Success
str aeio 4310 ... Success
str aeio 4310 (valgrind) ... Success
str -s aeio 4310 ... Success
str -s aeio 4310 (valgrind) ... Success
str -r aeio 4310 ... Success
str -r aeio 4310 (valgrind) ... Success
str -l aeio 4310 ... Success
str -l aeio 4310 (valgrind) ... Success
str -u aeio 4310 ... Success
str -u aeio 4310 (valgrind) ... Success
str -t aeio 4310 ... Success
str -t aeio 4310 (valgrind) ... Success
str -l -u aeio 4310 ... Success
str -l -u aeio 4310 (valgrind) ... Success
str -l -s aeio 4310 ... Success
str -l -s aeio 4310 (valgrind) ... Success
str -r -u aeio 4310 ... Success
str -r -u aeio 4310 (valgrind) ... Success
str -r -s -t aeio 4310 ... Success
str -r -s -t aeio 4310 (valgrind) ... Success
Score 3.00 / 3.00
Status Success
Alternatively, you can both build the artifacts and run the test script by doing the following:
# Build and run test scripts
$ make test
The test_str.sh
shell script will use the valgrind tool to verify that
your program does not contain any memory errors such as memory leaks,
uninitialized values, or invalid accesses.
You can run valgrind manually by doing:
$ echo " Hello World" | valgrind --leak-check=full ./str-dynamic
==28627== Memcheck, a memory error detector
==28627== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==28627== Using Valgrind-3.12.0 and LibVEX; rerun with -h for copyright info
==28627== Command: ./str-dynamic
==28627==
Hello World
==28627==
==28627== HEAP SUMMARY:
==28627== in use at exit: 0 bytes in 0 blocks
==28627== total heap usage: 2 allocs, 2 frees, 5,120 bytes allocated
==28627==
==28627== All heap blocks were freed -- no leaks are possible
==28627==
==28627== For counts of detected and suppressed errors, rerun with: -v
==28627== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
Note: You should run valgrind on the dynamic executable rather than the static executable. Likewise, valgrind may behave differently on macOS than it does on Linux, so beware of spurious errors on the former.
Once you have completed all the activities above, you are to complete the following reflection quiz:
As with Reading 01, you will need to store your answers in a
homework07/answers.json
file. You can use the form above to generate the
contents of this file, or you can write the JSON by hand.
To test your quiz, you can use the check.py
script:
$ ../.scripts/check.py
Checking homework07 quiz ...
Q01 0.40
Q02 0.30
Q03 0.40
Q04 0.20
Q05 0.70
Score 2.00 / 2.00
Status Success
There are three extra credit opportunities:
You may do one, two, or all three for extra credit.
Remember that you can always forgo these Guru Points for two extra
days to do the homework. That is, if you need an extension, you can simply
skip the Guru Point and you will automatically have until Monday to complete
the assignment for full credit.
Just leave a note on your Pull Request of your intensions.
Note: For this week, we will waive forgoing the Guru Points in order to get two extra days to do the homework. This means, that you can take the self-service extension and still do all of the Guru Points.
For extra credit, install a Linux distribution either on a partition on your laptop, in a virtual machine, or on a Raspberry Pi, and then install and run neofetch.
Using Windows Subsystem for Linux (WSL) does not count for this Guru Point. As mentioned in class, and as noted previously, the point of this extra credit opportunity is for you to experience installing an operating system. WSL circumvents that experience and thus is not sufficient.
There are a number of Linux distributions and which one you wish to install is up to you. That said, these are probably the most popular ones:
You can find a more exhaustive list on Distrowatch. For virtualization software, Virtualbox is a popular choice.
If you are interested in chatting about Linux, open source, or technology in general, feel free to join us at NDLUG.
To get credit for this Guru Point, you must show either the instructor or TA neofetch running on your Linux setup (or attached a video / screenshot to your Pull Request with identifying information such as your name or netid). You have up until one week after this assignment is due to verify your Guru Point.
For extra credit, you are to demonstrate your Homework 07 code running
inside a Docker container (hint: you can use the
pbui/cse-20289-sp22-assignments
image, which is what is used to test your
code on GitHub).
This means you need to use Docker (on your own machine) to spin up a
container environment that has your code and then you run make test
from inside that container.
To get credit for this Guru Point, you must show either the instructor or TA a demonstration of your code running in a Docker container (or attached a video / screenshot to your Pull Request with identifying information such as your name or netid). You have up until one week after this assignment is due to verify your Guru Point.
For extra credit, you are to test drive, that is try out, the Kelpup Linux distribution created by one of your TAs, Bridget Goodwine, as part of CSE 40677 Open Source Software Distribution. This means you will need to download the ISO file from the website, burn it to a USB drive, and then boot it up on your own computer.
Once it has been loaded, play around with it and take notes of any issues or problems you encountered.
To get credit for this Guru Point, you must file an issue on the Kelpup project issues page:
https://github.com/kelpup/woof-CE/issues
In this issue, report a bug or make a feature request. Likewise, attach a photo of your computer running Kelpup.
You have up until one week after this assignment is due to verify your Guru Point.
To submit your assignment, please commit your work to the homework07
folder
of your homework07
branch in your assignments GitHub repository.
Your homework07
folder should only contain the following files:
Makefile
README.md
answers.json
library.c
main.c
str.h
Note: You do not need to commit the test scripts because the Makefile
automatically downloads them.
#--------------------------------------------------
# BE SURE TO DO THE PREPARATION STEPS IN ACTIVITY 0
#--------------------------------------------------
$ cd homework07 # Go to Homework 06 directory
...
$ git add Makefile # Mark changes for commit
$ git add library.c # Mark changes for commit
$ git add str.h # Mark changes for commit
$ git commit -m "homework07: Activity 1" # Record changes
...
$ git add Makefile # Mark changes for commit
$ git add main.c # Mark changes for commit
$ git commit -m "homework07: Activity 2" # Record changes
...
$ git add answers.json # Mark changes for commit
$ git commit -m "homework07: Activity 3" # Record changes
$ git push -u origin homework07 # Push branch to GitHub
Remember to create a Pull Request and assign the appropriate TA from the Reading 09 TA List.
DO NOT MERGE your own Pull Request. The TAs use open Pull Requests to keep track of which assignments to grade. Closing them yourself will cause a delay in grading and confuse the TAs.