diff --git a/Assignment1/makefile b/Assignment1/makefile new file mode 100644 index 0000000000000000000000000000000000000000..98655dade808fd2c6a73f8197139945b63ec66b6 --- /dev/null +++ b/Assignment1/makefile @@ -0,0 +1,20 @@ +# 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 diff --git a/Assignment1/stringfun b/Assignment1/stringfun new file mode 100755 index 0000000000000000000000000000000000000000..1714fc3b9af4d5cb39e2031252aad3a790777591 Binary files /dev/null and b/Assignment1/stringfun differ diff --git a/Assignment1/stringfun.c b/Assignment1/stringfun.c new file mode 100644 index 0000000000000000000000000000000000000000..2ca07c415a6daed87adc572e240bead8e6c938cd --- /dev/null +++ b/Assignment1/stringfun.c @@ -0,0 +1,365 @@ +#include <stdio.h> +#include <string.h> +#include <stdlib.h> + + +#define BUFFER_SZ 50 + +//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 +int reverse_string(char *, int); +int print_words(char *, int); +int replace_words(char *, const char *, const char *, int); + +int setup_buff(char *buff, char *user_str, int len){ + //TODO: #4: Implement the setup buff as per the directions + char *src = user_str; + char *dest = buff; + int count = 0, is_space = 0; + + // Skip leading spaces + while (*src == ' ' || *src == '\t') { + src++; + } + + // Process the string + while (*src != '\0' && count < len) { + // Check for space + if (*src == ' ' || *src == '\t') { + if (!is_space) { // Avoid consecutive spaces + *dest++ = ' '; + count++; + is_space = 1; + } + } else { + *dest++ = *src; + count++; + is_space = 0; + } + src++; + } + + // Trim trailing space + if (count > 0 && *(dest - 1) == ' ') { + dest--; + count--; + } + + if (*src != '\0') return -1; // Input exceeds buffer size + + // Fill remaining buffer with dots + while (count < len) { + *dest++ = '.'; + count++; + } + + return count; +} + +void print_buff(char *buff, int len){ + printf("Buffer: "); + putchar('['); + for (int i=0; i<len; i++){ + putchar(*(buff+i)); + } + putchar(']'); + putchar('\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){ + if (buff == NULL || len <= 0 || str_len <= 0) { + return -1; // Error + } + + int word_count = 0; + + for (int i = 0; i < str_len; i++){ + if (*(buff + i) == ' '){ + word_count++; + } + } + + return word_count; +} + +//ADD OTHER HELPER FUNCTIONS HERE FOR OTHER REQUIRED PROGRAM OPTIONS +int reverse_string(char *buff, int str_len) { + if (buff == NULL || str_len <= 0) { + return -1; // Error + } + + // Determine the actual length + int actual_len = 0; + while (actual_len < str_len && *(buff + actual_len) != '.') { + actual_len++; + } + + // Reverse string + char *start = buff; + char *end = buff + actual_len - 1; + char temp; + + while (start < end) { + temp = *start; + *start = *end; + *end = temp; + + start++; + end--; + } + + // Print the reversed string + printf("Reversed String: "); + for (int i = 0; i < actual_len; i++) { + putchar(*(buff + i)); + } + putchar('\n'); + + return 0; +} + +int print_words(char *buff, int str_len) { + if (buff == NULL || str_len <= 0) { + return -1; // Error + } + + // Determine the actual length + int actual_len = 0; + while (actual_len < str_len && *(buff + actual_len) != '.') { + actual_len++; + } + + printf("Word Print\n----------\n"); + int word_start = 0, word_length = 0, word_count = 1; + + for (int i = 0; i <= actual_len; i++) { + // Detect the end of a word + if (i == actual_len || *(buff + i) == ' ') { + if (word_length > 0) { + // Print the current word + printf("%d. ", word_count++); + for (int j = word_start; j < word_start + word_length; j++) { + putchar(*(buff + j)); + } + printf(" (%d)\n", word_length); + word_length = 0; + } + } else { + // Track the start of a word and its length + if (word_length == 0) word_start = i; + word_length++; + } + } + + return 0; +} + +int replace_word(char *buff, const char *find, const char *replace, int len) { + if (buff == NULL || find == NULL || replace == NULL || len <= 0) { + return -1; // Error + } + + char *start = buff; + char *match = NULL; + int find_len = 0, replace_len = 0; + + // Calculate lengths of `find` and `replace` + while (*(find + find_len) != '\0') { + find_len++; + } + while (*(replace + replace_len) != '\0') { + replace_len++; + } + + // Search for the word to replace + while (*start != '\0' && start < buff + len) { + if (*start == *find) { // Possible match found + char *temp_buff = start; + char *temp_find = (char *)find; + while (*temp_buff == *temp_find && *temp_find != '\0') { + temp_buff++; + temp_find++; + } + + if (*temp_find == '\0') { // Full match found + match = start; + break; + } + } + start++; + } + + if (match == NULL) { + printf("Word not found: %s\n", find); + return -1; // No replacement found + } + + // If replacement makes the string too long, truncate + int remaining_len = len - (match - buff); + if (replace_len > find_len && replace_len - find_len > remaining_len) { + printf("Error: Replacement would exceed buffer size\n"); + return -1; // Error + } + + // Perform the replacement + char *end_of_buff = buff + len; // To prevent buffer overflow + char *src = match + find_len; // Start of the rest of the string + char *dest = match + replace_len; // New position after replacement + + if (replace_len != find_len) { + // Shift the remaining part of the string + while (end_of_buff > dest && src < end_of_buff) { + *(end_of_buff - 1) = *(src + (end_of_buff - dest - 1)); + end_of_buff--; + } + } + + // Copy the replacement word into the buffer + for (int i = 0; i < replace_len; i++) { + *(match + i) = *(replace + i); + } + + printf("Modified String: "); + for (int i = 0; i < len && *(buff + i) != '.'; i++) { + putchar(*(buff + i)); + } + putchar('\n'); + + return 0; +} + + +int main(int argc, char *argv[]){ + + char *buff; //placehoder for the internal buffer + 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? + // argc < 2, ensures that the program does not attempt to access + // argv[1] if no arguments are passed (avoiding out-of-bounds + // memory access). + // *argv[1] != '-', validates that the provided argument starts with a -, + // ensuring it's an option flag. + + 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 + // This ensures that the program does not proceed if the user has not + // provided the required input string, as any other operator other than + // -h need string as input. + if (argc < 3){ + usage(argv[0]); + exit(1); + } + + input_string = 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 + + buff = (char *) malloc(BUFFER_SZ * sizeof(char)); + 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); + exit(2); + } + + switch (opt) { + case 'c': + rc = count_words(buff, BUFFER_SZ, user_str_len); + if (rc < 0) { + printf("Error counting words, rc = %d\n", rc); + free(buff); + return -1; + } + printf("Word Count: %d\n", rc); + break; + + case 'r': + rc = reverse_string(buff, user_str_len); + if (rc < 0) { + printf("Error reversing string, rc = %d\n", rc); + free(buff); + return -1; + } + break; + + case 'w': + rc = print_words(buff, user_str_len); + if (rc < 0) { + printf("Error printing words, rc = %d\n", rc); + free(buff); + return -1; + } + break; + case 'x': + if (argc < 5) { + printf("Error: Missing arguments for -x. Usage: ./program -x \"string\" \"find\" \"replace\"\n"); + free(buff); + return -1; + } + + rc = replace_word(buff, argv[3], argv[4], BUFFER_SZ); + if (rc < 0) { + printf("Error replacing word\n"); + free(buff); + return -1; + } + 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); +} + +//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 +// Although we know buff has exactly 50 bytes, passing the length explicitly +// Makes the function reusable with buffers of different sizes in other contexts. +// Improves code readability by clarifying the buffer's size directly in the function call. \ No newline at end of file diff --git a/Assignment1/test.sh b/Assignment1/test.sh new file mode 100755 index 0000000000000000000000000000000000000000..f2b431b8bdf429bc90753656d0f73ef64cd3b2cc --- /dev/null +++ b/Assignment1/test.sh @@ -0,0 +1,100 @@ +#!/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!" ] +}