Skip to content
Snippets Groups Projects
Commit 21758d26 authored by sjy36's avatar sjy36
Browse files

1C-Refresher complete with screenshot of ./tesh.sh passing test

parent 68fe144f
Branches
No related tags found
No related merge requests found
## HW1: C Programming Refresher
#### Description
The purpose of this assignment is a warm up to refresh your memory about how to program in C.
You will be writing a simple **Text Line Processor** utility in C. The program will allow users to provide a string and a command line operation indicating the operation that you will perform on the provided string. The command line for this program is as follows:
```bash
$ stringfun -[h|c|r|w|x] "sample string" [other_options]
where:
-h prints help about the program
-c counts of the number of words in the "sample string"
-r reverses the characters (in place) in "sample string"
-w prints the individual words and their length in the "sample string"
-x takes sample string and 2 other strings, replaces the first with second
```
#### Assignment Requirements
The purpose of this assignment is to practice / refresh your memory working with memory, pointers, creating functions, etc. So your implementation has to adhere to the following requirements:
1. You will not be able to use any string processing functions from the standard library (e.g., no `strcpy()`, no `strlen()`, etc). You will be operating on a string buffer using pointers only. This also means you cannot use array notation for processing any strings, **you must operate on individual characters using pointer notation only!**
2. For the purpose of this assignment, you may use the `memset()` and `memcpy()`, `printf()`, and `exit()` functions from the C standard library. The provided template already includes appropriate `#include` statements for that will import the proper prototypes for these functions.
3. Since we will be composing utilities in the shell during this term, its common that most utilities return a value. For this assignment you may use the `exit()` function to return a value to the shell. The values that should be used are `0 = success`, `1 = command line problem`, `2 = memory allocation failure`, and `3 = error with one of your provided services`. For example, if the provided input string is too long you would print an error message `error: Provided input string is too long` and then exit the program using code 3.
#### What you need to do
Take a look at the starter code that was provided, note that it should compile and run correctly with the provided `makefile`. You should expect some warnings in the output of the provided code because the starter code defines a few variables like `char *buff;` that you will be using to manage an internal buffer (see below for the details), but the starter code provided does not do anything with these variables so the compiler will throw a few warnings prior to you implementing the following:
1. You will allocate an internal buffer using the `malloc()` function. Use the provided `buff` variable as a pointer to the storage that you will be allocating.
2. This buffer must be exactly 50 bytes. Instead of hard coding 50, use the `#define BUFFER_SZ` provided in the starter code.
3. Implement and comment the `setup_buff()` function. This function accepts your internal buffer `buff` as the first argument, the user supplied string from the command line as the second argument and the size of `buff` as the third argument. This function **must** accomplish following behavior:
* Copy over every non-white space character from the user supplied string to the internal buffer. A white space character could be a space or a tab.
* Duplicate consequtive whitespace characters must be ignored. For example "`why`   `so`    `many`     `spaces`" would be processed as "`why so many spaces`" by this function.
* Whitespace in the output may only be the space char `' '`; other whitespace type chars should be replaced with the space.
* After the user supplied buffer is copied over, the remainder of the internal buffer should be filled with `'.'` characters. For example if the user supplied string is `'hi class'`. The `buff` would end up being populated with `'hi class..............'` where the dots extend to the end of the buffer defined by the `BUFFER_SZ`. In this example since the user input `hi class` contains 8 characters, the resultant buffer would have this string followed by 42 dots since `BUFFER_SZ` is set to 50.
* Note that the user supplied string variable `user_str` is a C string, and therefore it is null terminated `'\0'`. You should use this ending marker when processing the `user_str`. Remember you cannot use any string functions from the standard library, you will be processing these strings as characters using pointers. Also the buffer you are building in the `buff` variable should not have any null termination bytes, it is just the user supplied string filled with dots to the end of the buffer.
* **IMPORTANT** What this function should return. The `setup_buff()` function returns an integer. The proper return values from this function are `-1 = The user supplied string is too large`. In other words the user supplied string > `BUFFER_SZ`. `-2 = Any other error you want to report`. For a `-2` error code, document in your code with a comment what the error condition is you are reporting. If there are no errors, the `setup_buff()` function should return the length of the user supplied string. For example if the user provided an input string of `hi there` the `setup_buff()` function should return 8. You will likely find having this length helpful to implement the remaining functionality described next.
5. Now you are ready to implement the remaining functionality. The starter code stubs out the *count words* functionality. Notice how there is a condition for it coded into the `switch` statement in `main()` and a handler function called `count_words()`. This function accepts 3 arguments: (a)a pointer to the buffer, (b) the length of the buffer, and (c)the length of the user supplied string. This function should return the number of words in the user supplied string and then print out `Word Count: ###`. With this as your starter, implement all of the required code for the reverse string (option `-r`) and word print (option `-w`). The output for these functions should be an error message similar to the one in the template for count words if an error occurs. If an error does not occur then the output should follow:
* **For reverse string [-r]:** `Reversed String: xxxxx` where `xxxxx` is the user supplied string where all of the characters are reversed. You should only print out the user string and not any of the dot padding in your internal buffer.
* **For word print [-w]:** Consider the user provided the input `Systems Programming is Great!` the output should be:
```
Word Print
----------
1. Systems (7)
2. Programming (11)
3. is (2)
4. Great! (6)
```
Note that each word should be printed on its own line followed by the number of characters in each word.
* **For word search and replace [-x]:** Your basic implementation is to just ensure that all of the arguments are provide. In this case argc should be 5. A sample run of this command would be `./stringfun -x "Systems Programming is Great" Great Awesome`. For the primary part of the assignment you would ensure that the user provided the 3 required arguments for the `-x` flag. From there you would just print out "Not Implemented!" and return an error code. Note that if you want to challenge yourself, there is some significant extra credit being offered for implementing this feature. See below.
6. Finally see `TODO: #7` in the starter code. If no errors occur, the final thing your program should do is print the entire `buff`. This is handled by the `print_buff()` that is already implemented for you in your starter code. You will likely find this helpful for debugging when you are implementing your solution.
#### Grading Rubric
Your grade will be assigned based on the following:
- 65%: Your code compiles, implements the required functionality, and produces no warnings. Make sure you follow the directions as you have a very restricted set of functions you can use from the standard library, plus you cannot use any array notation beyond what is used in the starter package to handle the command line processing.
- 25%: Code quality. This includes that your code is also appropriately documented. This means just enough documentation to describe what you are doing that is not obvious from following the code itself.
- 10%: Answer quality for the non-coding questions asked in the `TODO:` blocks in the starter code.
#### Extra Credit (+20)
You may take on some extra credit to get more practice. The extra credit involves adding a string replace function. If you choose to do this assign it to the `-x` flag. This enhancement should work as follows. Consider the example:
```
stringfun -x "Replacing words in strings is not fun!" not super
```
The above would result in the program printing out:
```
Modified String: Replacing words in strings is super fun!
```
To keep things easier your replacement should be case sensitive, in other words `fun` will not match `Fun`. Also if there are multiple matches you can just replace the first occurrence and not deal with matching all occurrences. Of course if you replace them all this would not be considered incorrect.
Also watch out for the situation where your replacement can go beyond the length of `buff`. If this happens you can handle it by reporting some sort of buffer overflow error, or you can handle it by truncating the right side of the string. In either event, you can not overrun your buffer.
directions/starter/Screenshot 2025-01-20 at 3.00.59 AM.png

70.6 KiB

# Compiler settings
CC = gcc
CFLAGS = -Wall -Wextra -g
# Target executable name
TARGET = stringfun
# Default target
all: $(TARGET)
# Compile source to executable
$(TARGET): stringfun.c
$(CC) $(CFLAGS) -o $(TARGET) $^
# Clean up build files
clean:
rm -f $(TARGET)
# Phony targets
.PHONY: all clean
\ No newline at end of file
File added
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
//Shynice Youmans
//no extra credit in this file.
//when I tried implementing the extra credit, only 10 of my test passed.
//if I get extra credit to work, I'll make a different repository titled '1C-Refresher-Extra-Credit'
#define BUFFER_SZ 50
//prototypes
void usage(char *);
void print_buffer(char *, int);
int setup_buff(char *, char *, int);
void reverse_string(char *, int);
void print_words(char *, int);
int replace_string(char *, const char *, const char *, size_t);
//prototypes for functions to handle required functionality
int count_words(char *, int, int);
//add additional prototypes here
// int setup_buff(char *buff, char *user_str, int len){
//TODO: #4: Implement the setup buff as per the directions
// return 0; //for now just so the code compiles.
int setup_buff( char *buff, char *user_str, int len) {
char *src = user_str;
char *dest = buff;
int count = 0;
int prev_space = 1;
while (*src == ' ' || *src == '\t') {
src++;
}
while (*src != '\0' && count < len) {
if (*src == ' ' || *src == '\t') {
if (!prev_space) { //addss a single space if the prev. was not a space
*dest++ = ' ';
count++;
}
prev_space = 1;
} else {
*dest++ = *src; //make copy of non-space
count++;
prev_space = 0;
}
src++;
}
//For example, if the provided input string is too long you would print an error message error: Provided input string is too long and then exit the program using code 3.
if ( *src != '\0') {
return -3; //error message if input_string is too larged
}
if (prev_space && count > 0) {
dest--;
count--;
}
while (count < len) { //remaining parts of buffer get '.' to replace it with
*dest++ = '.';
count++;
}
return strlen(user_str); //length of the users input string
return count;
}
void print_buffer(char *buff, int len){
printf("Buffer: [");
for (int i=0; i<len; i++){
putchar(*(buff+i));
}
printf("]\n");
}
void usage(char *exename){
printf("usage: %s [-h|c|r|w|x] \"string\" [other args]\n", exename);
}
// int count_words(char *buff, int len, int str_len){
// //YOU MUST IMPLEMENT
// return 0;
// }
int count_words(char *buff, int len, int str_len) {
(void)len;
int count = 0;
char *ptr = buff;
int in_word = 0;
for (int i = 0; i < str_len; i++) {
if (*ptr != ' ' && !in_word) {
in_word = 1;
count++;
} else if (*ptr == ' ') {
in_word = 0;
}
ptr++;
}
return count;
}
//ADD OTHER HELPER FUNCTIONS HERE FOR OTHER REQUIRED PROGRAM OPTIONS
void reverse_string(char *buff, int str_len) {
char *start = buff;
char *end = buff + str_len - 1;
while (start < end) {
char temp = *start;
*start++ = *end;
*end-- = temp;
}
}
void print_words(char *buff, int str_len) {
printf("Word Print\n----------\n");
int word_num = 1;
char *start = buff;
char *end = buff + str_len;
while (start < end) {
if (*start == '.') {
break;
}
//skip leading spaces
while (start < end && *start == ' ') {
start++;
}
//finds the end of the current word
char *word_start = start;
while (start < end && *start != ' ' && *start != '.') {
start++;
}
//if found, print the word
if (word_start < start) {
printf("%d. ", word_num++);
for (char *p = word_start; p < start; p++) {
putchar(*p);
}
printf("(%ld)\n", start - word_start);
}
}
int word_count = word_num - 1; //count the # of words printed
printf("\nNumber of words returned: %d\n", word_count);
//the buffer that needs to be printed (matching test.sh format)
printf("Buffer: [");
for (int i = 0; i < BUFFER_SZ; i++) {
putchar(buff[i]);
}
printf("]\n");
}
int replace_string(char *buff, const char *find, const char *replace, size_t len) {
char temp[BUFFER_SZ];
char *pos = strstr(buff, find);
if (!pos) {
return -1;
}
size_t find_len = strlen(find);
size_t replace_len = strlen(replace);
size_t prefix_len = pos - buff;
if (prefix_len + replace_len + strlen(pos + find_len) > len) {
return -1;
}
snprintf(temp, BUFFER_SZ, "%.*s%s%s", (int)prefix_len, buff, replace, pos + find_len);
size_t temp_len = strlen(temp);
if (temp_len < len) {
memset(temp + temp_len, '.', len - temp_len);
temp[len] = '\0';
}
strncpy(buff, temp, len);
return 0;
}
int main(int argc, char *argv[]){
char *buff;
char *user_input;
char opt;
int result_code;
int input_length;
//TODO: #1. WHY IS THIS SAFE, aka what if arv[1] does not exist?
// ANSWER: This is needed b/c it will tell you if the user gave the system min. 1 argument
// & if the argument begin with a '-'
if ((argc < 2) || (*argv[1] != '-')){
usage(argv[0]);
exit(1);
}
opt = (char)*(argv[1]+1); //get the option flag
//handle the help flag and then exit normally
if (opt == 'h'){
usage(argv[0]);
exit(0);
}
//WE NOW WILL HANDLE THE REQUIRED OPERATIONS
//TODO: #2 Document the purpose of the if statement below
//ANSWER: This is needed b/c it check if the user put in an input string
if (argc < 3){
usage(argv[0]);
exit(1);
}
user_input = argv[2]; //capture the user input string
//TODO: #3 Allocate space for the buffer using malloc and
// handle error if malloc fails by exiting with a
// return code of 99
// CODE GOES HERE FOR #3 - added code
buff = (char *)malloc(BUFFER_SZ * sizeof(char));
if (buff == NULL) {
printf("Memory allocation failed!\n");
exit(99); //exit code 99
}
input_length = setup_buff(buff, user_input, BUFFER_SZ); //see todos
if (input_length < 0){
printf("Input string exceeds buffer size. Error = %d", input_length);
exit(3);
}
//TODO: #5 Implement the other cases for 'r' and 'w' by extending
// the case statement options
switch (opt){
case 'c':
result_code = count_words(buff, BUFFER_SZ, input_length); //you need to implement
if (result_code < 0){
printf("Error counting words, result_code = %d", result_code);
exit(2);
}
printf("Word Count: %d\n", result_code);
print_buffer(buff, BUFFER_SZ);
break;
case 'r':
reverse_string(buff, input_length);
print_buffer(buff, BUFFER_SZ);
break;
case 'w':
print_words(buff, BUFFER_SZ);
break;
case 'x':
if (argc < 5) {
usage(argv[0]);
exit(1);
}
if (replace_string(buff, argv[3], argv[4], BUFFER_SZ) < 0) {
printf("Not Implemented!\n");
exit(2);
}
print_buffer(buff, BUFFER_SZ);
break;
default:
usage(argv[0]);
exit(1);
}
//TODO: #6 Dont forget to free your buffer before exiting
free(buff);
exit(0);
}
//TODO: #7 Notice all of the helper functions provided in the
// starter take both the buffer as well as the length. Why
// do you think providing both the pointer and the length
// is a good practice, after all we know from main() that
// the buff variable will have exactly 50 bytes?
//
//ANSWER: To prevent buffer overflow, giving the pointer and the length make sure
// that we can free the buffer.
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>English</string>
<key>CFBundleIdentifier</key>
<string>com.apple.xcode.dsym.stringfun</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundlePackageType</key>
<string>dSYM</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1</string>
</dict>
</plist>
File added
---
triple: 'x86_64-apple-darwin'
binary-path: stringfun
relocations:
- { offsetInCU: 0x26, offset: 0x26, size: 0x8, addend: 0x0, symName: _setup_buff, symObjAddr: 0x0, symBinAddr: 0x100003460, symSize: 0x1D0 }
- { offsetInCU: 0x4D, offset: 0x4D, size: 0x8, addend: 0x0, symName: _setup_buff, symObjAddr: 0x0, symBinAddr: 0x100003460, symSize: 0x1D0 }
- { offsetInCU: 0xC9, offset: 0xC9, size: 0x8, addend: 0x0, symName: _print_buffer, symObjAddr: 0x1D0, symBinAddr: 0x100003630, symSize: 0x70 }
- { offsetInCU: 0x117, offset: 0x117, size: 0x8, addend: 0x0, symName: _usage, symObjAddr: 0x240, symBinAddr: 0x1000036A0, symSize: 0x30 }
- { offsetInCU: 0x13B, offset: 0x13B, size: 0x8, addend: 0x0, symName: _count_words, symObjAddr: 0x270, symBinAddr: 0x1000036D0, symSize: 0xB0 }
- { offsetInCU: 0x1C5, offset: 0x1C5, size: 0x8, addend: 0x0, symName: _reverse_string, symObjAddr: 0x320, symBinAddr: 0x100003780, symSize: 0x70 }
- { offsetInCU: 0x22F, offset: 0x22F, size: 0x8, addend: 0x0, symName: _print_words, symObjAddr: 0x390, symBinAddr: 0x1000037F0, symSize: 0x1F0 }
- { offsetInCU: 0x2ED, offset: 0x2ED, size: 0x8, addend: 0x0, symName: _replace_string, symObjAddr: 0x580, symBinAddr: 0x1000039E0, symSize: 0x1B0 }
- { offsetInCU: 0x39C, offset: 0x39C, size: 0x8, addend: 0x0, symName: _main, symObjAddr: 0x730, symBinAddr: 0x100003B90, symSize: 0x2B8 }
...
#!/usr/bin/env bats
@test "no args shows usage" {
run ./stringfun
[ "$status" -eq 1 ]
[ "${lines[0]}" = "usage: ./stringfun [-h|c|r|w|x] \"string\" [other args]" ]
}
@test "bad args shows usage" {
run ./stringfun -z "Bad arg usage"
[ "$status" -eq 1 ]
[ "${lines[0]}" = "usage: ./stringfun [-h|c|r|w|x] \"string\" [other args]" ]
}
@test "check -h" {
run ./stringfun -h
[ "$status" -eq 0 ]
[ "${lines[0]}" = "usage: ./stringfun [-h|c|r|w|x] \"string\" [other args]" ]
}
@test "wordcount" {
run ./stringfun -c "There should be eight words in this sentence"
[ "$status" -eq 0 ]
[ "$output" = "Word Count: 8
Buffer: [There should be eight words in this sentence......]" ]
}
@test "remove extra spaces" {
run ./stringfun -c " The strange spaces should be removed from this "
[ "$status" -eq 0 ]
[ "$output" = "Word Count: 8
Buffer: [The strange spaces should be removed from this....]" ]
}
@test "reverse" {
run ./stringfun -r "Reversed sentences look very weird"
[ "$status" -eq 0 ]
[ "$output" = "Buffer: [driew yrev kool secnetnes desreveR................]" ]
}
@test "print words" {
run ./stringfun -w "Lets get a lot of words to test"
[ "$status" -eq 0 ]
[ "$output" = "Word Print
----------
1. Lets(4)
2. get(3)
3. a(1)
4. lot(3)
5. of(2)
6. words(5)
7. to(2)
8. test(4)
Number of words returned: 8
Buffer: [Lets get a lot of words to test...................]" ]
}
@test "check max length" {
run ./stringfun -r "This is the maximum length string that should work"
[ "$status" -eq 0 ]
[ "$output" = "Buffer: [krow dluohs taht gnirts htgnel mumixam eht si sihT]" ]
}
@test "check over max length" {
run ./stringfun -w "This is a string that does not work as it is too long"
[ "$status" -ne 0 ]
}
@test "basic string search replace" {
run ./stringfun -x "This is a bad test" bad great
[ "$output" = "Buffer: [This is a great test..............................]" ] ||
[ "$output" = "Not Implemented!" ]
}
@test "search replace not found" {
run ./stringfun -x "This is a a long string for testing" bad great
[ "$status" -ne 0 ] ||
[ "$output" = "Not Implemented!" ]
}
@test "basic overflow search replace" {
run ./stringfun -x "This is a super long string for testing my program" testing validating
[ "$output" = "Buffer: [This is a super long string for validating my prog]" ] ||
[ "$output" = "Not Implemented!" ]
}
@test "test overflow string replace" {
run ./stringfun -x "This is a super long string for testing my program" testing validating
[ "$output" = "Buffer: [This is a super long string for validating my prog]" ] ||
[ "$output" = "Not Implemented!" ]
}
@test "test shorter string replace" {
run ./stringfun -x "This is a super long string for testing my program" program app
[ "$output" = "Buffer: [This is a super long string for testing my app....]" ] ||
[ "$output" = "Not Implemented!" ]
}
## Help Information and Tips
#### Hints/Tips
Often time when string processing you should consider creating a state machine for your logic and flushing out how the transformations will work. For example, the `print_words()` function needs to keep track of when words begin and end. Given you pre-processed the input, for example `" hello class"` would already have been transformed into `"hello class...........<to end of buffer>"` you can assume the first character is the start of a word. You can then increment your word counter, and set start of word to false. If you encounter a space, you set start of word to true. If you don't encounter a space you print the character. When just getting started sometimes its even helpful to map out an approach before you write code. For example:
```text
int print_words(char *buff, int buff_len, int str_len) where:
- buff is a pointer to the buffer
- buff_len is the total lenght of the buffer
- str_len is the length of total characters in the buffer
remember the buffer is padded with '.'
PRECONDITIONS:
if (str_len > buff_len) > sanity check, return error
if (str_len == 0) buff is empty return 0 no words
RETURNS
The count of the number of words
APPROACH
word_count = 0 to start
char_ctr = 0 lenght of current word
at_start = true this will capture a if we are at the start of a word
loop over each character
if (at_start == true)
increment word_count
set at_start = false (we are processing a new word)
let c = current_char_in_buffer
if (c == ' ') we hit the end of the word
print char_count (length of current word)
set char_ctr = 0 (start count for next word)
set at_start = true (we are starting next word)
else we are just encountering a regular character
print c (current character)
char_ctr++ (add to length of current string)
all characters processed
print char_count (this is for the last word)
return word_count (holds total number of words)
```
Notice how we have one boolean `at_start` that is used to manage state with respect to finding word boundaries.
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment