// Common utility functions for string case conversions
///|
/// Split text into words using separators, case changes, and digit boundaries.
fn split(text : String) -> Array[String] {
if text.is_empty() {
return []
}
let words : Array[String] = []
let mut current_word = ""
let chars = text.to_array()
for i = 0; i < chars.length(); i = i + 1 {
let char = chars[i]
let char_str = char.to_string()
// Check if this character should start a new word
let should_split = if i == 0 {
false
} else {
let prev_char = chars[i - 1]
// Split on non-alphanumeric characters
if is_separator(char) {
true
} else if is_separator(prev_char) {
false // Previous was separator, this starts new word
} else if is_uppercase(char) && is_lowercase(prev_char) {
// camelCase -> PascalCase boundary
true
} else if is_uppercase(char) &&
is_uppercase(prev_char) &&
i + 1 < chars.length() &&
is_lowercase(chars[i + 1]) {
// PASCALCase -> PascalCase boundary
true
} else if is_digit(char) && is_letter(prev_char) {
// letter -> digit boundary
true
} else if is_letter(char) && is_digit(prev_char) {
// digit -> letter boundary
true
} else {
false
}
}
if should_split && !current_word.is_empty() {
words.push(current_word)
current_word = ""
}
if !is_separator(char) {
current_word = current_word + char_str
}
}
if !current_word.is_empty() {
words.push(current_word)
}
words
}
///|
/// Return whether a character should break words.
fn is_separator(char : Char) -> Bool {
match char {
' '
| '\t'
| '\n'
| '\r'
| '-'
| '_'
| '.'
| '/'
| '\\'
| ':'
| ';'
| ','
| '!'
| '?'
| '@'
| '#'
| '$'
| '%'
| '^'
| '&'
| '*'
| '('
| ')'
| '['
| ']'
| '{'
| '}'
| '<'
| '>'
| '='
| '+'
| '|'
| '`'
| '~'
| '\''
| '"' => true
_ => false
}
}
///|
/// Return whether a character is an uppercase ASCII letter.
fn is_uppercase(char : Char) -> Bool {
char >= 'A' && char <= 'Z'
}
///|
/// Return whether a character is a lowercase ASCII letter.
fn is_lowercase(char : Char) -> Bool {
char >= 'a' && char <= 'z'
}
///|
/// Return whether a character is an ASCII letter.
fn is_letter(char : Char) -> Bool {
is_uppercase(char) || is_lowercase(char)
}
///|
/// Return whether a character is an ASCII digit.
fn is_digit(char : Char) -> Bool {
char >= '0' && char <= '9'
}
///|
/// Convert one ASCII letter to uppercase.
fn to_uppercase(char : Char) -> Char {
if is_lowercase(char) {
(char.to_int() - 32) |> Int::unsafe_to_char
} else {
char
}
}
///|
/// Convert one ASCII letter to lowercase.
fn to_lowercase(char : Char) -> Char {
if is_uppercase(char) {
(char.to_int() + 32) |> Int::unsafe_to_char
} else {
char
}
}
///|
/// Convert every ASCII letter in a string to uppercase.
fn string_to_upper(text : String) -> String {
let chars = text.to_array()
let mut result = ""
for i = 0; i < chars.length(); i = i + 1 {
result = result + to_uppercase(chars[i]).to_string()
}
result
}
///|
/// Convert every ASCII letter in a string to lowercase.
fn string_to_lower(text : String) -> String {
let chars = text.to_array()
let mut result = ""
for i = 0; i < chars.length(); i = i + 1 {
result = result + to_lowercase(chars[i]).to_string()
}
result
}
///|
/// Uppercase the first character of a non-empty string.
fn capitalize(text : String) -> String {
if text.is_empty() {
text
} else {
let chars = text.to_array()
let first = to_uppercase(chars[0]).to_string()
if chars.length() == 1 {
first
} else {
// Build the rest of the string from characters
let mut rest = ""
for i = 1; i < chars.length(); i = i + 1 {
rest = rest + chars[i].to_string()
}
first + rest
}
}
}
///|
test "split basic words" {
debug_inspect(split("hello world"), content="[\"hello\", \"world\"]")
debug_inspect(
split("test string here"),
content="[\"test\", \"string\", \"here\"]",
)
}
///|
test "split camelCase" {
debug_inspect(split("camelCase"), content="[\"camel\", \"Case\"]")
debug_inspect(
split("XMLHttpRequest"),
content="[\"XML\", \"Http\", \"Request\"]",
)
debug_inspect(split("iPhone"), content="[\"i\", \"Phone\"]")
}
///|
test "split snake_case" {
debug_inspect(split("snake_case"), content="[\"snake\", \"case\"]")
debug_inspect(
split("hello_world_test"),
content="[\"hello\", \"world\", \"test\"]",
)
}
///|
test "split kebab-case" {
debug_inspect(split("kebab-case"), content="[\"kebab\", \"case\"]")
debug_inspect(
split("hello-world-test"),
content="[\"hello\", \"world\", \"test\"]",
)
}
///|
test "split with numbers" {
debug_inspect(split("test123value"), content="[\"test\", \"123\", \"value\"]")
debug_inspect(
split("version1.2.3"),
content="[\"version\", \"1\", \"2\", \"3\"]",
)
}
///|
test "split edge cases" {
debug_inspect(split(""), content="[]")
debug_inspect(split("single"), content="[\"single\"]")
debug_inspect(split("ALLCAPS"), content="[\"ALLCAPS\"]")
}
///|
test "string_to_upper" {
assert_eq(string_to_upper("hello"), "HELLO")
assert_eq(string_to_upper("Hello World"), "HELLO WORLD")
assert_eq(string_to_upper("test123"), "TEST123")
}
///|
test "string_to_lower" {
assert_eq(string_to_lower("HELLO"), "hello")
assert_eq(string_to_lower("Hello World"), "hello world")
assert_eq(string_to_lower("TEST123"), "test123")
}
///|
test "capitalize" {
assert_eq(capitalize("hello"), "Hello")
assert_eq(capitalize("HELLO"), "HELLO")
assert_eq(capitalize("test"), "Test")
assert_eq(capitalize(""), "")
}