From 9575698fa439e381255f575dbc6f012671d7f923 Mon Sep 17 00:00:00 2001 From: Christoffer Lerno Date: Wed, 23 Jul 2025 00:26:44 +0200 Subject: [PATCH] Add String conversion functions snake_case -> PascalCase and vice versa. --- lib/std/core/string.c3 | 88 +++++++++++++++++++++++++++++++++ releasenotes.md | 1 + test/unit/stdlib/core/string.c3 | 53 ++++++++++++++++++++ 3 files changed, 142 insertions(+) diff --git a/lib/std/core/string.c3 b/lib/std/core/string.c3 index 04e2e1efb..78ecbfc29 100644 --- a/lib/std/core/string.c3 +++ b/lib/std/core/string.c3 @@ -1,5 +1,7 @@ module std::core::string; import std::io; +import std::core::mem::allocator; + typedef String @if(!$defined(String)) = inline char[]; <* @@ -775,6 +777,92 @@ fn String String.to_upper_copy(self, Allocator allocator) return copy; } +fn String String.capitalize_copy(self, Allocator allocator) +{ + String s = self.copy(allocator); + if (s.len > 0 && s[0].is_lower()) + { + s[0] &= (char)~0x20; + } + return s; +} + +<* + Convert a string from `snake_case` to PascalCase. + + @param [in] self + @return `"FooBar" from "foo_bar" the resulting pointer may safely be cast to ZString.` +*> +fn String String.snake_to_pascal_copy(self, Allocator allocator) +{ + Splitter splitter = self.tokenize("_"); + char[] new_string = allocator::alloc_array(allocator, char, self.len + 1); + usz index = 0; + while (try s = splitter.next()) + { + assert(s.len > 0); + char c = s[0]; + if (c.is_lower()) c = c.to_upper(); + new_string[index++] = c; + s = s[1..]; + new_string[index:s.len] = s[..]; + index += s.len; + } + new_string[index] = 0; + return (String)new_string[:index]; +} + +<* + Movifies the current string from `snake_case` to PascalCase. + + @param [inout] self +*> +fn void String.convert_snake_to_pascal(&self) +{ + Splitter splitter = self.tokenize("_"); + String new_string = *self; + usz index = 0; + while (try s = splitter.next()) + { + assert(s.len > 0); + char c = s[0]; + if (c.is_lower()) c = c.to_upper(); + new_string[index++] = c; + s = s[1..]; + new_string[index:s.len] = s[..]; + index += s.len; + } + *self = new_string[:index]; +} + +<* + Convert a string from `PascalCase` to `snake_case`. + + @param [in] self + @return `"foo_bar" from "FooBar" the resulting pointer may safely be cast to ZString.` +*> +fn String String.pascal_to_snake_copy(self, Allocator allocator) => @pool() +{ + DString d; + d.init(tmem, (usz)(self.len * 1.5)); + usz index = 0; + foreach (i, c : self) + { + if (c.is_upper()) + { + if (i > 0 && ((self[i - 1].is_lower() || self[i - 1].is_digit()) || (i < self.len - 1 && self[i + 1].is_lower()))) + { + d.append_char('_'); + } + d.append_char(c.to_lower()); + continue; + } + d.append_char(c); + } + return d.copy_str(allocator); +} + + fn StringIterator String.iterator(self) { return { self, 0 }; diff --git a/releasenotes.md b/releasenotes.md index a434302b2..3a3605a39 100644 --- a/releasenotes.md +++ b/releasenotes.md @@ -87,6 +87,7 @@ - Added `Ref` and `RefCounted` experimental functionality. - Added `Volatile` generic type. - Added `UnalignedRef` generic type. +- Add String conversion functions snake_case -> PascalCase and vice versa. ## 0.7.3 Change list diff --git a/test/unit/stdlib/core/string.c3 b/test/unit/stdlib/core/string.c3 index 1505ee7ec..2a1c91dcb 100644 --- a/test/unit/stdlib/core/string.c3 +++ b/test/unit/stdlib/core/string.c3 @@ -311,4 +311,57 @@ fn void test_float() { test::eq_approx(- 2.04632e-05," - 2.04632e-05 ".to_float()!!, delta: 0.00001e-5); test::eq_approx(2.04632e-05," + 2.04632e-05 ".to_float()!!, delta: 0.00001e-5); +} + +fn void test_pascal_to_snake() +{ + String[2][*] test_cases = { + { "HTTPRequest2Handler", "http_request2_handler" }, + { "MyJSON2XMLConverter", "my_json2_xml_converter"}, + { "Foo3C3CBazHello", "foo3_c3_c_baz_hello" }, + { "TLAWithABCs", "tla_with_ab_cs" }, + { "HTMLToPDFConverter", "html_to_pdf_converter"}, + { "OAuth2Token", "o_auth2_token" }, + { "startMIDDLELast", "start_middle_last" } + }; + foreach (s : test_cases) + { + test::eq(s[0].pascal_to_snake_copy(tmem), s[1]); + } +} + +fn void test_snake_pascal() +{ + String[2][*] test_cases = { + { "http_request2_handler", "HttpRequest2Handler", }, + { "my_json2_xml_converter", "MyJson2XmlConverter", }, + { "foo3_c3_c_baz_hello", "Foo3C3CBazHello", }, + { "tla_with_ab_cs", "TlaWithAbCs", }, + { "html_to_pdf_converter", "HtmlToPdfConverter", }, + { "o_auth2_token", "OAuth2Token", }, + { "start_middle_last", "StartMiddleLast", } + }; + foreach (s : test_cases) + { + test::eq(s[0].snake_to_pascal_copy(tmem), s[1]); + } +} + +fn void test_snake_pascal_self_modify() +{ + String[2][*] test_cases = { + { "http_request2_handler", "HttpRequest2Handler", }, + { "my_json2_xml_converter", "MyJson2XmlConverter", }, + { "foo3_c3_c_baz_hello", "Foo3C3CBazHello", }, + { "tla_with_ab_cs", "TlaWithAbCs", }, + { "html_to_pdf_converter", "HtmlToPdfConverter", }, + { "o_auth2_token", "OAuth2Token", }, + { "start_middle_last", "StartMiddleLast", } + }; + foreach (s : test_cases) + { + String s2 = s[0].tcopy(); + s2.convert_snake_to_pascal(); + test::eq(s2, s[1]); + } } \ No newline at end of file