diff --git a/w3-hw/stringfun.c b/w3-hw/stringfun.c new file mode 100644 index 0000000000000000000000000000000000000000..51e81c9eceeeb7eece8e3d6ef888d65ba0fba91c --- /dev/null +++ b/w3-hw/stringfun.c @@ -0,0 +1,527 @@ +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <stdbool.h> + + +#define BUFFER_SZ 50 +#define SPACE_CHAR ' ' + +//prototypes +void usage(char *); +void print_buff(char *, int); +int setup_buff(char *, char *, int); + +//prototypes for functions to handle required functionality +int count_words(char *, int, int); +//add additional prototypes here +void reverse_string(char *, int); +void word_print(char *, int); +int string_replace(char *, int, char *, char *); +int my_strlen(char *); // Custom strlen function + +void usage(char *exename){ + printf("usage: %s [-h|c|r|w|x] \"string\" [other args]\n", exename); + + //printf("Options:\n"); + // printf(" -h : Display help information\n"); + // printf(" -c : Count words in the string\n"); + // printf(" -r : Reverse the string\n"); + // printf(" -w : Print words and their lengths\n"); + // printf(" -x : Replace a word in the string\n"); + +} + +// Custom implementation of strlen() to calculate string length +int my_strlen(char *str) { + int length = 0; // Initialize length counter + while (*(str + length) != '\0') { // Loop until null terminator is reached + length++; // Increment length for each character + } + return length; // Return the calculated length +} + +int setup_buff(char *buff, char *user_str, int len){ + //TODO: #4: Implement the setup buff as per the directions + int user_str_len = 0; // Tracks the length of the user string + int buff_idx = 0; // Index for the buffer + bool space_pending = false; // Tracks whether a space is waiting to be added + char *p = user_str; // Pointer to iterate through the user string + + bool started = false; // Tracks if non-space characters have been encountered + + // if (my_strlen(user_str) > len) { + // return -1; // Input string is too large for the buffer + // } + + // if (my_strlen(user_str) == 0) { + // return -2; // Return error for empty input + // } + + + // Process each character in the input string + while (*p != '\0') { + if (*p == SPACE_CHAR || *p == '\t') { // Check for whitespace + if (started && !space_pending && buff_idx < len) { // Add a single space only once + //if (buff_idx < len) { + *(buff + buff_idx++) = SPACE_CHAR; + user_str_len++; + space_pending = true; // Set space pending to true + // } + } + } else { + // break; + // Non-whitespace character + + if (buff_idx <= len) { + *(buff + buff_idx++) = *p; // Copy the character to the buffer + user_str_len++; + space_pending = false; // Reset space pending for new word + started = true; + } else { + break; + } + + } + p++; // Move to the next character + } + // Remove the trailing space if any + if (buff_idx > 0 && *(buff + buff_idx - 1) == SPACE_CHAR) { + buff_idx--; + user_str_len--; + } + + // Fill the remaining buffer space with dots + while (buff_idx < len) { + *(buff + buff_idx++) = '.'; + } + + return (user_str_len > len) ? -1 : user_str_len; // Return length or error + + //return 0; //for now just so the code compiles. +} + +void print_buff(char *buff, int len){ + printf("Buffer: ["); + for (int i=0; i<len; i++){ + putchar(*(buff+i)); + } + printf("]\n"); +} + + + +int count_words(char *buff, int len, int str_len){ + //YOU MUST IMPLEMENT + + // Ensure that str_len does not exceed len to avoid buffer overflows + if (str_len > len) { + printf("Error: String length exceeds buffer length.\n"); + return -1; // Return error code for invalid input + } + + int wc = 0; // Word count + bool word_start = false; // Tracks if we are at the start of a word + + for (int i = 0; i < str_len; i++) { + char current = *(buff + i); // Get the current character + + //if (current == '.') { + // Stop counting at the first dot + // break; + // } + + if (!word_start && current != SPACE_CHAR) { + wc++; // Increment word count at word start + word_start = true; // Mark that we are in a word + } else if (current == SPACE_CHAR) { + word_start = false; // End the current word + } + } + + return wc; // Return the total word count + + + // return 0; +} + +//ADD OTHER HELPER FUNCTIONS HERE FOR OTHER REQUIRED PROGRAM OPTIONS + +// Reverse the string in the buffer in place +void reverse_string(char *buff, int str_len) { + int start = 0; // Start index + int end = str_len - 1; // End index + + while (start < end) { // Swap until the indices meet + char temp = *(buff + start); // Temporary storage for start character + *(buff + start) = *(buff + end); // Swap start and end characters + *(buff + end) = temp; + + start++; // Move start index forward + end--; // Move end index backward + } +} + + + +void word_print(char *buff, int str_len) { + int word_count = 0; // Total word count + int char_count = 0; // Length of the current word + bool at_start = true; // Tracks if at the start of a new word + + printf("Word Print\n"); + printf("----------\n"); + + + // Loop through the buffer for the string length + for (int i = 0; i < str_len; i++) { + char current = *(buff + i); // Get the current character + + if (at_start && current != SPACE_CHAR) { + // New word found + word_count++; + printf("%d. ", word_count); // Print word index + at_start = false; // Mark that we are processing a word + } + + if (current == SPACE_CHAR) { + // End of a word + if (char_count > 0) { + printf("(%d)\n", char_count); // Print word length + char_count = 0; // Reset character count for next word + at_start = true; // Mark start of a new word + } + } else { + if (*(buff + i) == '.') { + // Stop processing at the first dot (truncated word) + break; + } + putchar(current); // Print the current character + char_count++; // Increment the character count + } + } + + // Handle the last word (if it doesn't end with a space) + if (char_count > 0) { + printf("(%d)\n", char_count); // Print the length of the last word + } + + printf("\nNumber of words returned: %d\n", word_count); +} + +/* + +int string_replace(char *buff, int len, char *find, char *replace) { + char *match = strstr(buff, find); // Find the first occurrence of the substring + if (!match) { + return -2; // Substring not found + } + + int find_len = my_strlen(find); // Length of the substring to find + int replace_len = my_strlen(replace); // Length of the replacement string + int buff_len = my_strlen(buff); // Current length of the buffer content + + // Calculate the new length after replacement + int new_len = buff_len - find_len + replace_len; + + if (new_len > len) { + // Truncate the replacement string to fit within the buffer + replace_len -= (new_len - len); + new_len = len; // Adjust the final buffer length + } + + // Check if truncation has shortened the replacement string to zero + if (replace_len <= 0) { + return -1; // Error: Replacement string cannot fit in the buffer + } + + // Move characters after the match to accommodate the replacement + memmove(match + replace_len, // Destination: shift characters after replacement + match + find_len, // Source: characters after the found substring + buff_len - (match - buff) - find_len); // Number of characters to move + + // Copy the (possibly truncated) replacement string into the buffer + memcpy(match, replace, replace_len); + + // Fill the remaining buffer with dots + for (int i = new_len; i < len; i++) { + buff[i] = '.'; // Fill unused space with dots + } + + // Ensure null-termination for safety + buff[len] = '\0'; + + return 0; // Success +} +*/ + +int string_replace(char *buff, int len, char *find, char *replace) { + char *match = strstr(buff, find); // Locate the substring to replace + if (!match) { + return -2; // Substring not found + } + + int find_len = my_strlen(find); // Length of the substring to find + int replace_len = my_strlen(replace); // Length of the replacement string + int buff_len = my_strlen(buff); // Current length of the buffer content + + // Calculate the new length after replacement + int new_len = buff_len - find_len + replace_len; + + // Allocate a temporary buffer + char *temp_buff = (char *)malloc(len); + if (!temp_buff) { + return -3; // Memory allocation failed + } + + // Copy everything up to the match into the temporary buffer + int prefix_len = match - buff; + strncpy(temp_buff, buff, prefix_len); + + // Append the (possibly truncated) replacement string to the temporary buffer + if (replace_len + prefix_len > len) { + replace_len = len - prefix_len; // Truncate the replacement string if necessary + } + strncpy(temp_buff + prefix_len, replace, replace_len); + + // Append the remainder of the original buffer after the match + int suffix_start = prefix_len + find_len; + int remaining_space = len - (prefix_len + replace_len); + if (remaining_space > 0) { + strncpy(temp_buff + prefix_len + replace_len, buff + suffix_start, remaining_space); + } + + // Ensure the result is properly padded with dots + for (int i = new_len; i < len; i++) { + temp_buff[i] = '.'; + } + + // Copy the result back to the original buffer + memcpy(buff, temp_buff, len); + + // Free the temporary buffer + free(temp_buff); + + return 0; // Success +} + + + + + + + + + +int main(int argc, char *argv[]){ + + char *buff; // = malloc(BUFFER_SZ); // Allocate buffer //placehoder for the internal buffer + + + //if (!buff) { + // printf("Error: Memory allocation failed.\n"); + // exit(99); // Exit on memory allocation failure + //} + + + char *input_string; //holds the string provided by the user on cmd line + char opt; //used to capture user option from cmd line + int rc; //used for return codes + int user_str_len; //length of user supplied string + + //TODO: #1. WHY IS THIS SAFE, aka what if arv[1] does not exist? + // PLACE A COMMENT BLOCK HERE EXPLAINING + + /* This check ensures that the program has received the correct number of arguments. + - If argc < 2, argv[1] does not exist, so the program safely exits with usage instructions. + - The second condition (*argv[1] != '-') ensures that the first argument provided + starts with a dash ('-'), as required by the command-line syntax. + By combining these checks, + we prevent undefined behavior from accessing argv[1] + when it does not exist. + */ + + + + if ((argc < 2) || (*argv[1] != '-')){ + usage(argv[0]); + // free(buff); + 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]); + // free(buff); + exit(0); + } + + //WE NOW WILL HANDLE THE REQUIRED OPERATIONS + + //TODO: #2 Document the purpose of the if statement below + // PLACE A COMMENT BLOCK HERE EXPLAINING + + /* + This check ensures that the user provides the required arguments: + - argc < 3 indicates that the user has not supplied both an option (argv[1]) + and the input string (argv[2]). + - If this condition is 'true', the program prints usage instructions + and exits with an error code of 1 to indicate invalid usage. + This prevents undefined behavior caused by accessing missing arguments. + */ + + + + + + if (argc < 3){ + usage(argv[0]); + // free(buff); + exit(1); + } + + + + input_string = argv[2]; //capture the user input string + //user_str_len = setup_buff(buff, input_string, BUFFER_SZ); + + //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 + + // Allocate space for the buffer + buff = (char *) malloc(BUFFER_SZ); + if (!buff) { + // printf("Error: Memory allocation failed.\n"); + exit(99); + } + + user_str_len = setup_buff(buff, input_string, BUFFER_SZ); //see todos + if (user_str_len < 0){ + //printf("Error setting up buffer, error = %d", user_str_len); + free(buff); + exit(2); + } + + switch (opt){ + case 'c':{ + rc = count_words(buff, BUFFER_SZ, user_str_len); //you need to implement + if (rc < 0){ + printf("Error counting words, rc = %d", rc); + free(buff); + exit(2); + } + printf("Word Count: %d\n", rc); + print_buff(buff, BUFFER_SZ); + break; + } + + + //TODO: #5 Implement the other cases for 'r' and 'w' by extending + // the case statement options + case 'r':{ + reverse_string(buff, user_str_len); + // printf("Reversed String: %.*s\n", user_str_len, buff); + print_buff(buff,BUFFER_SZ); + break; + } + + case 'w':{ + //printf("Word Print\n----------\n"); + word_print(buff, user_str_len); + print_buff(buff,BUFFER_SZ); + break; + } + + case 'x': { + if (argc < 5) { + //printf("Not Implemented!\n"); + free(buff); + exit(1); + } + + char *find = argv[3]; + char *replace = argv[4]; + + rc = string_replace(buff, BUFFER_SZ, find, replace); + if (rc == -2) { + // printf("Not Implemented!\n"); + free(buff); + exit(3); + } else if (rc == -1) { + // printf("Not Implemented!\n"); + free(buff); + exit(3); + } + + // Print the buffer + printf("Buffer: ["); + for (int i = 0; i < BUFFER_SZ; i++) { + putchar(buff[i]); + } + printf("]\n"); + + //printf("Not Implemented!\n"); + break; + } + + + + + default: + usage(argv[0]); + free(buff); + exit(1); + } + + //TODO: #6 Dont forget to free your buffer before exiting + //print_buff(buff,BUFFER_SZ); + free(buff); + //exit(0); + return 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? +// +// PLACE YOUR ANSWER HERE + +/* +Passing both the buffer pointer and its length to the helper functions +is considered good practice for several reasons: + +1. Code Resuable: + The functions become more general and reusable. They are not tied + to a specific buffer size, allowing them to work with buffers + of different sizes in other programs or contexts. + +2. Flexibility: + If the buffer size (BUFFER_SZ) changes in the future, the helper + functions do not need to be modified. The updated size is passed + automatically, making the code easier to maintain. + +3. Safety: + Explicitly passing the length ensures that the helper functions + do not read or write beyond the allocated memory. This prevents + buffer overflows, which could lead to undefined behavior or + security vulnerabilities. + +4. Error Checking: + Having the buffer length allows the helper functions to validate + inputs (e.g., ensuring the string length does not exceed the buffer + size). This improves the program's robustness by catching errors early. + +5. Readable: + Including the buffer length makes it clear that the function + operates on a fixed-size buffer and enforces constraints. + It also serves as documentation for the function's behavior + and expected input. +*/ + +