Select Git revision
-
Ziheng Chen authoredZiheng Chen authored
stringfun.c 15.96 KiB
#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.
*/