Skip to content

Commit 9784b3d

Browse files
Merge pull request #64 from dreamer-coding/numaric_string
Numaric string
2 parents 3f42af5 + 4b117a5 commit 9784b3d

File tree

4 files changed

+275
-0
lines changed

4 files changed

+275
-0
lines changed

code/logic/cstring.c

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,102 @@ cstring fossil_io_cstring_copy(ccstring str) {
4141
return fossil_io_cstring_create(str);
4242
}
4343

44+
static const char *units[] = {
45+
"zero", "one", "two", "three", "four", "five",
46+
"six", "seven", "eight", "nine", "ten", "eleven",
47+
"twelve", "thirteen", "fourteen", "fifteen",
48+
"sixteen", "seventeen", "eighteen", "nineteen"
49+
};
50+
51+
static const char *tens[] = {
52+
"", "", "twenty", "thirty", "forty", "fifty",
53+
"sixty", "seventy", "eighty", "ninety"
54+
};
55+
56+
// ---------------- Number -> Words ----------------
57+
int fossil_io_cstring_number_to_words(int num, char *buffer, size_t size) {
58+
if (!buffer || size == 0) return -1;
59+
buffer[0] = '\0';
60+
61+
if (num < 0 || num > 9999) return -1; // Limit to 0..9999
62+
63+
if (num >= 1000) {
64+
int thousands = num / 1000;
65+
if (strlen(buffer) + strlen(units[thousands]) + 10 >= size) return -1;
66+
strcat(buffer, units[thousands]);
67+
strcat(buffer, " thousand");
68+
num %= 1000;
69+
if (num > 0) strcat(buffer, " ");
70+
}
71+
72+
if (num >= 100) {
73+
int hundreds = num / 100;
74+
if (strlen(buffer) + strlen(units[hundreds]) + 10 >= size) return -1;
75+
strcat(buffer, units[hundreds]);
76+
strcat(buffer, " hundred");
77+
num %= 100;
78+
if (num > 0) strcat(buffer, " and ");
79+
}
80+
81+
if (num >= 20) {
82+
int t = num / 10;
83+
if (strlen(buffer) + strlen(tens[t]) + 2 >= size) return -1;
84+
strcat(buffer, tens[t]);
85+
num %= 10;
86+
if (num > 0) {
87+
strcat(buffer, "-");
88+
strcat(buffer, units[num]);
89+
}
90+
} else if (num > 0 || strlen(buffer) == 0) {
91+
if (strlen(buffer) + strlen(units[num]) + 1 >= size) return -1;
92+
strcat(buffer, units[num]);
93+
}
94+
95+
return 0;
96+
}
97+
98+
// ---------------- Words -> Number ----------------
99+
static int fossil_io_word_to_value(const char *word) {
100+
for (int i = 0; i < 20; i++) if (strcmp(word, units[i]) == 0) return i;
101+
for (int i = 2; i < 10; i++) if (strcmp(word, tens[i]) == 0) return i * 10;
102+
if (strcmp(word, "hundred") == 0) return -100; // multiplier
103+
if (strcmp(word, "thousand") == 0) return -1000; // multiplier
104+
return -1; // not found
105+
}
106+
107+
int fossil_io_cstring_number_from_words(const char *str, int *out) {
108+
if (!str || !out) return -1;
109+
110+
int total = 0;
111+
int current = 0;
112+
113+
char buffer[256];
114+
strncpy(buffer, str, sizeof(buffer)-1);
115+
buffer[sizeof(buffer)-1] = '\0';
116+
117+
// lowercase and remove extra characters
118+
for (char *p = buffer; *p; ++p) *p = (char)tolower(*p);
119+
120+
char *token = strtok(buffer, " -");
121+
while (token) {
122+
int val = fossil_io_word_to_value(token);
123+
if (val >= 0) {
124+
current += val;
125+
} else if (val == -100) { // hundred
126+
current *= 100;
127+
} else if (val == -1000) { // thousand
128+
total += current * 1000;
129+
current = 0;
130+
} else {
131+
return -1; // unknown word
132+
}
133+
token = strtok(NULL, " -");
134+
}
135+
136+
*out = total + current;
137+
return 0;
138+
}
139+
44140
cstring fossil_io_cstring_dup(ccstring str) {
45141
if (!str) return NULL;
46142
size_t length = strlen(str);

code/logic/fossil/io/cstring.h

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,29 @@ void fossil_io_cstring_free(cstring str);
5757
*/
5858
cstring fossil_io_cstring_copy(ccstring str);
5959

60+
/**
61+
* @brief Converts an English number string into an integer.
62+
*
63+
* Example: "twenty-three" -> 23
64+
*
65+
* @param str Input string containing the number in English.
66+
* @param out Pointer to integer where the parsed number will be stored.
67+
* @return 0 on success, non-zero on error (invalid input).
68+
*/
69+
int fossil_io_cstring_number_from_words(const char *str, int *out);
70+
71+
/**
72+
* @brief Converts an integer number into its English representation.
73+
*
74+
* Example: 23 -> "twenty-three"
75+
*
76+
* @param num The integer to convert.
77+
* @param buffer The output buffer to store the English string.
78+
* @param size The size of the output buffer.
79+
* @return 0 on success, non-zero if the buffer is too small.
80+
*/
81+
int fossil_io_cstring_number_to_words(int num, char *buffer, size_t size);
82+
6083
/**
6184
* @brief Duplicates the given cstring.
6285
*
@@ -437,6 +460,36 @@ namespace fossil {
437460
fossil_io_cstring_free(_str);
438461
}
439462

463+
/**
464+
* Converts an English number string into an integer.
465+
*
466+
* Example: "twenty-three" -> 23
467+
*
468+
* @param str Input string containing the number in English.
469+
* @return The parsed integer value, or throws std::invalid_argument on error.
470+
*/
471+
static int number_from_words(const std::string &str) {
472+
int value = 0;
473+
int result = fossil_io_cstring_number_from_words(str.c_str(), &value);
474+
if (result != 0) throw std::invalid_argument("Invalid English number string");
475+
return value;
476+
}
477+
478+
/**
479+
* Converts an integer number into its English representation.
480+
*
481+
* Example: 23 -> "twenty-three"
482+
*
483+
* @param num The integer to convert.
484+
* @return The English representation as a std::string.
485+
*/
486+
static std::string number_to_words(int num) {
487+
char buffer[128];
488+
int result = fossil_io_cstring_number_to_words(num, buffer, sizeof(buffer));
489+
if (result != 0) throw std::runtime_error("Buffer too small for English number string");
490+
return std::string(buffer);
491+
}
492+
440493
/**
441494
* Creates a copy of the given cstring.
442495
*

code/tests/cases/test_cstring.c

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -355,6 +355,37 @@ FOSSIL_TEST(c_test_cstring_append) {
355355
fossil_io_cstring_free(str);
356356
}
357357

358+
// Test fossil_io_cstring_number_from_words
359+
FOSSIL_TEST(c_test_cstring_number_from_words) {
360+
int value = 0;
361+
ASSUME_ITS_EQUAL_I32(0, fossil_io_cstring_number_from_words("twenty-three", &value));
362+
ASSUME_ITS_EQUAL_I32(23, value);
363+
364+
ASSUME_ITS_EQUAL_I32(0, fossil_io_cstring_number_from_words("one hundred", &value));
365+
ASSUME_ITS_EQUAL_I32(100, value);
366+
367+
ASSUME_ITS_EQUAL_I32(0, fossil_io_cstring_number_from_words("zero", &value));
368+
ASSUME_ITS_EQUAL_I32(0, value);
369+
370+
ASSUME_ITS_TRUE(fossil_io_cstring_number_from_words("not-a-number", &value) != 0);
371+
}
372+
373+
// Test fossil_io_cstring_number_to_words
374+
FOSSIL_TEST(c_test_cstring_number_to_words) {
375+
char buffer[64];
376+
ASSUME_ITS_EQUAL_I32(0, fossil_io_cstring_number_to_words(23, buffer, sizeof(buffer)));
377+
ASSUME_ITS_EQUAL_CSTR("twenty-three", buffer);
378+
379+
ASSUME_ITS_EQUAL_I32(0, fossil_io_cstring_number_to_words(100, buffer, sizeof(buffer)));
380+
ASSUME_ITS_EQUAL_CSTR("one hundred", buffer);
381+
382+
ASSUME_ITS_EQUAL_I32(0, fossil_io_cstring_number_to_words(0, buffer, sizeof(buffer)));
383+
ASSUME_ITS_EQUAL_CSTR("zero", buffer);
384+
385+
// Buffer too small
386+
ASSUME_ITS_TRUE(fossil_io_cstring_number_to_words(123456789, buffer, 5) != 0);
387+
}
388+
358389
// * * * * * * * * * * * * * * * * * * * * * * * *
359390
// * Fossil Logic Test Pool
360391
// * * * * * * * * * * * * * * * * * * * * * * * *
@@ -393,6 +424,9 @@ FOSSIL_TEST_GROUP(c_string_tests) {
393424
FOSSIL_TEST_ADD(c_string_suite, c_test_cstring_strip_quotes);
394425
FOSSIL_TEST_ADD(c_string_suite, c_test_cstring_append);
395426

427+
FOSSIL_TEST_ADD(c_string_suite, c_test_cstring_number_from_words);
428+
FOSSIL_TEST_ADD(c_string_suite, c_test_cstring_number_to_words);
429+
396430
FOSSIL_TEST_ADD(c_string_suite, c_test_cstring_stream_create_and_free);
397431
FOSSIL_TEST_ADD(c_string_suite, c_test_cstring_stream_write_and_read);
398432
FOSSIL_TEST_ADD(c_string_suite, c_test_cstring_stream_multiple_writes);

code/tests/cases/test_cstring.cpp

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -593,6 +593,90 @@ FOSSIL_TEST(cpp_test_cstring_class_append) {
593593
ASSUME_ITS_EQUAL_CSTR("Hello, World!", str.str());
594594
}
595595

596+
FOSSIL_TEST(cpp_test_cstring_number_from_words_basic) {
597+
int value = 0;
598+
ASSUME_ITS_EQUAL_I32(0, fossil_io_cstring_number_from_words("zero", &value));
599+
ASSUME_ITS_EQUAL_I32(0, value);
600+
601+
ASSUME_ITS_EQUAL_I32(0, fossil_io_cstring_number_from_words("one", &value));
602+
ASSUME_ITS_EQUAL_I32(1, value);
603+
604+
ASSUME_ITS_EQUAL_I32(0, fossil_io_cstring_number_from_words("ten", &value));
605+
ASSUME_ITS_EQUAL_I32(10, value);
606+
607+
ASSUME_ITS_EQUAL_I32(0, fossil_io_cstring_number_from_words("twenty-three", &value));
608+
ASSUME_ITS_EQUAL_I32(23, value);
609+
610+
ASSUME_ITS_EQUAL_I32(0, fossil_io_cstring_number_from_words("one hundred", &value));
611+
ASSUME_ITS_EQUAL_I32(100, value);
612+
613+
ASSUME_ITS_EQUAL_I32(0, fossil_io_cstring_number_from_words("one thousand two hundred thirty-four", &value));
614+
ASSUME_ITS_EQUAL_I32(1234, value);
615+
}
616+
617+
FOSSIL_TEST(cpp_test_cstring_number_from_words_invalid) {
618+
int value = 0;
619+
ASSUME_ITS_TRUE(fossil_io_cstring_number_from_words("not-a-number", &value) != 0);
620+
ASSUME_ITS_TRUE(fossil_io_cstring_number_from_words("", &value) != 0);
621+
ASSUME_ITS_TRUE(fossil_io_cstring_number_from_words("twenty one hundred apples", &value) != 0);
622+
}
623+
624+
FOSSIL_TEST(cpp_test_cstring_number_to_words_basic) {
625+
char buffer[128];
626+
ASSUME_ITS_EQUAL_I32(0, fossil_io_cstring_number_to_words(0, buffer, sizeof(buffer)));
627+
ASSUME_ITS_EQUAL_CSTR("zero", buffer);
628+
629+
ASSUME_ITS_EQUAL_I32(0, fossil_io_cstring_number_to_words(1, buffer, sizeof(buffer)));
630+
ASSUME_ITS_EQUAL_CSTR("one", buffer);
631+
632+
ASSUME_ITS_EQUAL_I32(0, fossil_io_cstring_number_to_words(23, buffer, sizeof(buffer)));
633+
ASSUME_ITS_EQUAL_CSTR("twenty-three", buffer);
634+
635+
ASSUME_ITS_EQUAL_I32(0, fossil_io_cstring_number_to_words(100, buffer, sizeof(buffer)));
636+
ASSUME_ITS_EQUAL_CSTR("one hundred", buffer);
637+
638+
ASSUME_ITS_EQUAL_I32(0, fossil_io_cstring_number_to_words(1234, buffer, sizeof(buffer)));
639+
ASSUME_ITS_EQUAL_CSTR("one thousand two hundred thirty-four", buffer);
640+
}
641+
642+
FOSSIL_TEST(cpp_test_cstring_number_to_words_buffer_too_small) {
643+
char buffer[8];
644+
ASSUME_ITS_TRUE(fossil_io_cstring_number_to_words(123456, buffer, sizeof(buffer)) != 0);
645+
}
646+
647+
FOSSIL_TEST(cpp_test_cstring_class_number_from_words_and_to_words) {
648+
using fossil::io::CString;
649+
ASSUME_ITS_EQUAL_I32(23, CString::number_from_words("twenty-three"));
650+
ASSUME_ITS_EQUAL_I32(100, CString::number_from_words("one hundred"));
651+
ASSUME_ITS_EQUAL_I32(0, CString::number_from_words("zero"));
652+
653+
ASSUME_ITS_EQUAL_CSTR("twenty-three", CString::number_to_words(23).c_str());
654+
ASSUME_ITS_EQUAL_CSTR("one hundred", CString::number_to_words(100).c_str());
655+
ASSUME_ITS_EQUAL_CSTR("zero", CString::number_to_words(0).c_str());
656+
}
657+
658+
FOSSIL_TEST(cpp_test_cstring_class_number_from_words_exception) {
659+
using fossil::io::CString;
660+
bool thrown = false;
661+
try {
662+
CString::number_from_words("not-a-number");
663+
} catch (const std::invalid_argument&) {
664+
thrown = true;
665+
}
666+
ASSUME_ITS_TRUE(thrown);
667+
}
668+
669+
FOSSIL_TEST(cpp_test_cstring_class_number_to_words_exception) {
670+
using fossil::io::CString;
671+
bool thrown = false;
672+
try {
673+
// Use a very large number that would overflow the buffer
674+
CString::number_to_words(1234567890);
675+
} catch (const std::runtime_error&) {
676+
thrown = true;
677+
}
678+
ASSUME_ITS_TRUE(thrown);
679+
}
596680

597681
// * * * * * * * * * * * * * * * * * * * * * * * *
598682
// * Fossil Logic Test Pool
@@ -668,6 +752,14 @@ FOSSIL_TEST_GROUP(cpp_string_tests) {
668752
FOSSIL_TEST_ADD(cpp_string_suite, cpp_test_cstring_class_strip_quotes);
669753
FOSSIL_TEST_ADD(cpp_string_suite, cpp_test_cstring_class_append);
670754

755+
FOSSIL_TEST_ADD(cpp_string_suite, cpp_test_cstring_number_from_words_basic);
756+
FOSSIL_TEST_ADD(cpp_string_suite, cpp_test_cstring_number_from_words_invalid);
757+
FOSSIL_TEST_ADD(cpp_string_suite, cpp_test_cstring_number_to_words_basic);
758+
FOSSIL_TEST_ADD(cpp_string_suite, cpp_test_cstring_number_to_words_buffer_too_small);
759+
FOSSIL_TEST_ADD(cpp_string_suite, cpp_test_cstring_class_number_from_words_and_to_words);
760+
FOSSIL_TEST_ADD(cpp_string_suite, cpp_test_cstring_class_number_from_words_exception);
761+
FOSSIL_TEST_ADD(cpp_string_suite, cpp_test_cstring_class_number_to_words_exception);
762+
671763
FOSSIL_TEST_ADD(cpp_string_suite, cpp_test_cstring_stream_class_create_and_free);
672764
FOSSIL_TEST_ADD(cpp_string_suite, cpp_test_cstring_stream_class_write_and_read);
673765
FOSSIL_TEST_ADD(cpp_string_suite, cpp_test_cstring_stream_class_multiple_writes);

0 commit comments

Comments
 (0)