From 49e089cf55f1e43ccf2226d2cbed425a706af831 Mon Sep 17 00:00:00 2001 From: "Michael Gene Brockus (Dreamer)" <55331536+dreamer-coding@users.noreply.github.com> Date: Thu, 4 Sep 2025 15:43:39 -0700 Subject: [PATCH 1/4] Update cstring.h --- code/logic/fossil/io/cstring.h | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/code/logic/fossil/io/cstring.h b/code/logic/fossil/io/cstring.h index 700952e..fe0d936 100644 --- a/code/logic/fossil/io/cstring.h +++ b/code/logic/fossil/io/cstring.h @@ -57,6 +57,29 @@ void fossil_io_cstring_free(cstring str); */ cstring fossil_io_cstring_copy(ccstring str); +/** + * @brief Converts an English number string into an integer. + * + * Example: "twenty-three" -> 23 + * + * @param str Input string containing the number in English. + * @param out Pointer to integer where the parsed number will be stored. + * @return 0 on success, non-zero on error (invalid input). + */ +int fossil_io_cstring_number_from_words(const char *str, int *out); + +/** + * @brief Converts an integer number into its English representation. + * + * Example: 23 -> "twenty-three" + * + * @param num The integer to convert. + * @param buffer The output buffer to store the English string. + * @param size The size of the output buffer. + * @return 0 on success, non-zero if the buffer is too small. + */ +int fossil_io_cstring_number_to_words(int num, char *buffer, size_t size); + /** * @brief Duplicates the given cstring. * From 7c85bf798b015370414da32b8b9c6c7e2348164d Mon Sep 17 00:00:00 2001 From: "Michael Gene Brockus (Dreamer)" <55331536+dreamer-coding@users.noreply.github.com> Date: Thu, 4 Sep 2025 15:47:28 -0700 Subject: [PATCH 2/4] Update cstring.c --- code/logic/cstring.c | 96 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) diff --git a/code/logic/cstring.c b/code/logic/cstring.c index bb21271..244b1cf 100644 --- a/code/logic/cstring.c +++ b/code/logic/cstring.c @@ -41,6 +41,102 @@ cstring fossil_io_cstring_copy(ccstring str) { return fossil_io_cstring_create(str); } +static const char *units[] = { + "zero", "one", "two", "three", "four", "five", + "six", "seven", "eight", "nine", "ten", "eleven", + "twelve", "thirteen", "fourteen", "fifteen", + "sixteen", "seventeen", "eighteen", "nineteen" +}; + +static const char *tens[] = { + "", "", "twenty", "thirty", "forty", "fifty", + "sixty", "seventy", "eighty", "ninety" +}; + +// ---------------- Number -> Words ---------------- +int fossil_io_cstring_number_to_words(int num, char *buffer, size_t size) { + if (!buffer || size == 0) return -1; + buffer[0] = '\0'; + + if (num < 0 || num > 9999) return -1; // Limit to 0..9999 + + if (num >= 1000) { + int thousands = num / 1000; + if (strlen(buffer) + strlen(units[thousands]) + 10 >= size) return -1; + strcat(buffer, units[thousands]); + strcat(buffer, " thousand"); + num %= 1000; + if (num > 0) strcat(buffer, " "); + } + + if (num >= 100) { + int hundreds = num / 100; + if (strlen(buffer) + strlen(units[hundreds]) + 10 >= size) return -1; + strcat(buffer, units[hundreds]); + strcat(buffer, " hundred"); + num %= 100; + if (num > 0) strcat(buffer, " and "); + } + + if (num >= 20) { + int t = num / 10; + if (strlen(buffer) + strlen(tens[t]) + 2 >= size) return -1; + strcat(buffer, tens[t]); + num %= 10; + if (num > 0) { + strcat(buffer, "-"); + strcat(buffer, units[num]); + } + } else if (num > 0 || strlen(buffer) == 0) { + if (strlen(buffer) + strlen(units[num]) + 1 >= size) return -1; + strcat(buffer, units[num]); + } + + return 0; +} + +// ---------------- Words -> Number ---------------- +static int fossil_io_word_to_value(const char *word) { + for (int i = 0; i < 20; i++) if (strcmp(word, units[i]) == 0) return i; + for (int i = 2; i < 10; i++) if (strcmp(word, tens[i]) == 0) return i * 10; + if (strcmp(word, "hundred") == 0) return -100; // multiplier + if (strcmp(word, "thousand") == 0) return -1000; // multiplier + return -1; // not found +} + +int fossil_io_cstring_number_from_words(const char *str, int *out) { + if (!str || !out) return -1; + + int total = 0; + int current = 0; + + char buffer[256]; + strncpy(buffer, str, sizeof(buffer)-1); + buffer[sizeof(buffer)-1] = '\0'; + + // lowercase and remove extra characters + for (char *p = buffer; *p; ++p) *p = (char)tolower(*p); + + char *token = strtok(buffer, " -"); + while (token) { + int val = fossil_io_word_to_value(token); + if (val >= 0) { + current += val; + } else if (val == -100) { // hundred + current *= 100; + } else if (val == -1000) { // thousand + total += current * 1000; + current = 0; + } else { + return -1; // unknown word + } + token = strtok(NULL, " -"); + } + + *out = total + current; + return 0; +} + cstring fossil_io_cstring_dup(ccstring str) { if (!str) return NULL; size_t length = strlen(str); From 685068708bf78c8228b9d2d1ad132040660af516 Mon Sep 17 00:00:00 2001 From: "Michael Gene Brockus (Dreamer)" Date: Thu, 4 Sep 2025 17:54:18 -0600 Subject: [PATCH 3/4] add missing cpp functions for word number to class --- code/logic/fossil/io/cstring.h | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/code/logic/fossil/io/cstring.h b/code/logic/fossil/io/cstring.h index fe0d936..2c692c8 100644 --- a/code/logic/fossil/io/cstring.h +++ b/code/logic/fossil/io/cstring.h @@ -460,6 +460,36 @@ namespace fossil { fossil_io_cstring_free(_str); } + /** + * Converts an English number string into an integer. + * + * Example: "twenty-three" -> 23 + * + * @param str Input string containing the number in English. + * @return The parsed integer value, or throws std::invalid_argument on error. + */ + static int number_from_words(const std::string &str) { + int value = 0; + int result = fossil_io_cstring_number_from_words(str.c_str(), &value); + if (result != 0) throw std::invalid_argument("Invalid English number string"); + return value; + } + + /** + * Converts an integer number into its English representation. + * + * Example: 23 -> "twenty-three" + * + * @param num The integer to convert. + * @return The English representation as a std::string. + */ + static std::string number_to_words(int num) { + char buffer[128]; + int result = fossil_io_cstring_number_to_words(num, buffer, sizeof(buffer)); + if (result != 0) throw std::runtime_error("Buffer too small for English number string"); + return std::string(buffer); + } + /** * Creates a copy of the given cstring. * From 4b117a5fedffa68556e37fba795bf029310bb8e7 Mon Sep 17 00:00:00 2001 From: "Michael Gene Brockus (Dreamer)" Date: Thu, 4 Sep 2025 17:54:25 -0600 Subject: [PATCH 4/4] add test cases --- code/tests/cases/test_cstring.c | 34 ++++++++++++ code/tests/cases/test_cstring.cpp | 92 +++++++++++++++++++++++++++++++ 2 files changed, 126 insertions(+) diff --git a/code/tests/cases/test_cstring.c b/code/tests/cases/test_cstring.c index 3ed336f..aa818c9 100644 --- a/code/tests/cases/test_cstring.c +++ b/code/tests/cases/test_cstring.c @@ -355,6 +355,37 @@ FOSSIL_TEST(c_test_cstring_append) { fossil_io_cstring_free(str); } +// Test fossil_io_cstring_number_from_words +FOSSIL_TEST(c_test_cstring_number_from_words) { + int value = 0; + ASSUME_ITS_EQUAL_I32(0, fossil_io_cstring_number_from_words("twenty-three", &value)); + ASSUME_ITS_EQUAL_I32(23, value); + + ASSUME_ITS_EQUAL_I32(0, fossil_io_cstring_number_from_words("one hundred", &value)); + ASSUME_ITS_EQUAL_I32(100, value); + + ASSUME_ITS_EQUAL_I32(0, fossil_io_cstring_number_from_words("zero", &value)); + ASSUME_ITS_EQUAL_I32(0, value); + + ASSUME_ITS_TRUE(fossil_io_cstring_number_from_words("not-a-number", &value) != 0); +} + +// Test fossil_io_cstring_number_to_words +FOSSIL_TEST(c_test_cstring_number_to_words) { + char buffer[64]; + ASSUME_ITS_EQUAL_I32(0, fossil_io_cstring_number_to_words(23, buffer, sizeof(buffer))); + ASSUME_ITS_EQUAL_CSTR("twenty-three", buffer); + + ASSUME_ITS_EQUAL_I32(0, fossil_io_cstring_number_to_words(100, buffer, sizeof(buffer))); + ASSUME_ITS_EQUAL_CSTR("one hundred", buffer); + + ASSUME_ITS_EQUAL_I32(0, fossil_io_cstring_number_to_words(0, buffer, sizeof(buffer))); + ASSUME_ITS_EQUAL_CSTR("zero", buffer); + + // Buffer too small + ASSUME_ITS_TRUE(fossil_io_cstring_number_to_words(123456789, buffer, 5) != 0); +} + // * * * * * * * * * * * * * * * * * * * * * * * * // * Fossil Logic Test Pool // * * * * * * * * * * * * * * * * * * * * * * * * @@ -393,6 +424,9 @@ FOSSIL_TEST_GROUP(c_string_tests) { FOSSIL_TEST_ADD(c_string_suite, c_test_cstring_strip_quotes); FOSSIL_TEST_ADD(c_string_suite, c_test_cstring_append); + FOSSIL_TEST_ADD(c_string_suite, c_test_cstring_number_from_words); + FOSSIL_TEST_ADD(c_string_suite, c_test_cstring_number_to_words); + FOSSIL_TEST_ADD(c_string_suite, c_test_cstring_stream_create_and_free); FOSSIL_TEST_ADD(c_string_suite, c_test_cstring_stream_write_and_read); FOSSIL_TEST_ADD(c_string_suite, c_test_cstring_stream_multiple_writes); diff --git a/code/tests/cases/test_cstring.cpp b/code/tests/cases/test_cstring.cpp index c9ffbd3..726cc4d 100644 --- a/code/tests/cases/test_cstring.cpp +++ b/code/tests/cases/test_cstring.cpp @@ -593,6 +593,90 @@ FOSSIL_TEST(cpp_test_cstring_class_append) { ASSUME_ITS_EQUAL_CSTR("Hello, World!", str.str()); } +FOSSIL_TEST(cpp_test_cstring_number_from_words_basic) { + int value = 0; + ASSUME_ITS_EQUAL_I32(0, fossil_io_cstring_number_from_words("zero", &value)); + ASSUME_ITS_EQUAL_I32(0, value); + + ASSUME_ITS_EQUAL_I32(0, fossil_io_cstring_number_from_words("one", &value)); + ASSUME_ITS_EQUAL_I32(1, value); + + ASSUME_ITS_EQUAL_I32(0, fossil_io_cstring_number_from_words("ten", &value)); + ASSUME_ITS_EQUAL_I32(10, value); + + ASSUME_ITS_EQUAL_I32(0, fossil_io_cstring_number_from_words("twenty-three", &value)); + ASSUME_ITS_EQUAL_I32(23, value); + + ASSUME_ITS_EQUAL_I32(0, fossil_io_cstring_number_from_words("one hundred", &value)); + ASSUME_ITS_EQUAL_I32(100, value); + + ASSUME_ITS_EQUAL_I32(0, fossil_io_cstring_number_from_words("one thousand two hundred thirty-four", &value)); + ASSUME_ITS_EQUAL_I32(1234, value); +} + +FOSSIL_TEST(cpp_test_cstring_number_from_words_invalid) { + int value = 0; + ASSUME_ITS_TRUE(fossil_io_cstring_number_from_words("not-a-number", &value) != 0); + ASSUME_ITS_TRUE(fossil_io_cstring_number_from_words("", &value) != 0); + ASSUME_ITS_TRUE(fossil_io_cstring_number_from_words("twenty one hundred apples", &value) != 0); +} + +FOSSIL_TEST(cpp_test_cstring_number_to_words_basic) { + char buffer[128]; + ASSUME_ITS_EQUAL_I32(0, fossil_io_cstring_number_to_words(0, buffer, sizeof(buffer))); + ASSUME_ITS_EQUAL_CSTR("zero", buffer); + + ASSUME_ITS_EQUAL_I32(0, fossil_io_cstring_number_to_words(1, buffer, sizeof(buffer))); + ASSUME_ITS_EQUAL_CSTR("one", buffer); + + ASSUME_ITS_EQUAL_I32(0, fossil_io_cstring_number_to_words(23, buffer, sizeof(buffer))); + ASSUME_ITS_EQUAL_CSTR("twenty-three", buffer); + + ASSUME_ITS_EQUAL_I32(0, fossil_io_cstring_number_to_words(100, buffer, sizeof(buffer))); + ASSUME_ITS_EQUAL_CSTR("one hundred", buffer); + + ASSUME_ITS_EQUAL_I32(0, fossil_io_cstring_number_to_words(1234, buffer, sizeof(buffer))); + ASSUME_ITS_EQUAL_CSTR("one thousand two hundred thirty-four", buffer); +} + +FOSSIL_TEST(cpp_test_cstring_number_to_words_buffer_too_small) { + char buffer[8]; + ASSUME_ITS_TRUE(fossil_io_cstring_number_to_words(123456, buffer, sizeof(buffer)) != 0); +} + +FOSSIL_TEST(cpp_test_cstring_class_number_from_words_and_to_words) { + using fossil::io::CString; + ASSUME_ITS_EQUAL_I32(23, CString::number_from_words("twenty-three")); + ASSUME_ITS_EQUAL_I32(100, CString::number_from_words("one hundred")); + ASSUME_ITS_EQUAL_I32(0, CString::number_from_words("zero")); + + ASSUME_ITS_EQUAL_CSTR("twenty-three", CString::number_to_words(23).c_str()); + ASSUME_ITS_EQUAL_CSTR("one hundred", CString::number_to_words(100).c_str()); + ASSUME_ITS_EQUAL_CSTR("zero", CString::number_to_words(0).c_str()); +} + +FOSSIL_TEST(cpp_test_cstring_class_number_from_words_exception) { + using fossil::io::CString; + bool thrown = false; + try { + CString::number_from_words("not-a-number"); + } catch (const std::invalid_argument&) { + thrown = true; + } + ASSUME_ITS_TRUE(thrown); +} + +FOSSIL_TEST(cpp_test_cstring_class_number_to_words_exception) { + using fossil::io::CString; + bool thrown = false; + try { + // Use a very large number that would overflow the buffer + CString::number_to_words(1234567890); + } catch (const std::runtime_error&) { + thrown = true; + } + ASSUME_ITS_TRUE(thrown); +} // * * * * * * * * * * * * * * * * * * * * * * * * // * Fossil Logic Test Pool @@ -668,6 +752,14 @@ FOSSIL_TEST_GROUP(cpp_string_tests) { FOSSIL_TEST_ADD(cpp_string_suite, cpp_test_cstring_class_strip_quotes); FOSSIL_TEST_ADD(cpp_string_suite, cpp_test_cstring_class_append); + FOSSIL_TEST_ADD(cpp_string_suite, cpp_test_cstring_number_from_words_basic); + FOSSIL_TEST_ADD(cpp_string_suite, cpp_test_cstring_number_from_words_invalid); + FOSSIL_TEST_ADD(cpp_string_suite, cpp_test_cstring_number_to_words_basic); + FOSSIL_TEST_ADD(cpp_string_suite, cpp_test_cstring_number_to_words_buffer_too_small); + FOSSIL_TEST_ADD(cpp_string_suite, cpp_test_cstring_class_number_from_words_and_to_words); + FOSSIL_TEST_ADD(cpp_string_suite, cpp_test_cstring_class_number_from_words_exception); + FOSSIL_TEST_ADD(cpp_string_suite, cpp_test_cstring_class_number_to_words_exception); + FOSSIL_TEST_ADD(cpp_string_suite, cpp_test_cstring_stream_class_create_and_free); FOSSIL_TEST_ADD(cpp_string_suite, cpp_test_cstring_stream_class_write_and_read); FOSSIL_TEST_ADD(cpp_string_suite, cpp_test_cstring_stream_class_multiple_writes);