// SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: 2024 The Gleam contributors use ecow::EcoString; use itertools::Itertools; use crate::ast::Endianness; /// Converts any escape sequences from the given string to their correct /// bytewise UTF-8 representation and returns the resulting string. pub fn convert_string_escape_chars(str: &EcoString) -> EcoString { let mut filtered_str = EcoString::new(); let mut str_iter = str.chars().peekable(); loop { match str_iter.next() { Some('\\') => match str_iter.next() { // Check for Unicode escape sequence, e.g. \u{00012FF} Some('u') => { if str_iter.peek() != Some(&'{') { // Invalid Unicode escape sequence filtered_str.push('u'); continue; } // Consume the left brace after peeking let _ = str_iter.next(); let codepoint_str = str_iter .peeking_take_while(char::is_ascii_hexdigit) .collect::(); if codepoint_str.is_empty() || str_iter.peek() != Some(&'}') { // Invalid Unicode escape sequence filtered_str.push_str("u{"); filtered_str.push_str(&codepoint_str); continue; } let codepoint = u32::from_str_radix(&codepoint_str, 16) .ok() .and_then(char::from_u32); if let Some(codepoint) = codepoint { // Consume the right brace after peeking let _ = str_iter.next(); // Consider this codepoint's length instead of // that of the Unicode escape sequence itself filtered_str.push(codepoint); } else { // Invalid Unicode escape sequence // (codepoint value not in base 16 or too large) filtered_str.push_str("u{"); filtered_str.push_str(&codepoint_str); } } Some('n') => filtered_str.push('\n'), Some('r') => filtered_str.push('\r'), Some('f') => filtered_str.push('\u{C}'), Some('t') => filtered_str.push('\t'), Some('"') => filtered_str.push('\"'), Some('\\') => filtered_str.push('\\'), Some(c) => filtered_str.push(c), None => break, }, Some(c) => filtered_str.push(c), None => break, } } filtered_str } pub fn to_snake_case(string: &str) -> EcoString { let mut snake_case = EcoString::with_capacity(string.len()); let mut is_word_boundary = true; for char in string.chars() { match char { '_' | ' ' => { is_word_boundary = true; continue; } _ if char.is_uppercase() => { is_word_boundary = true; } _ => {} } if is_word_boundary { // We don't want to push an underscore at the start of the string, // even if it starts with a capital letter or other delimiter. if !snake_case.is_empty() { snake_case.push('_'); } is_word_boundary = false; } snake_case.push(char.to_ascii_lowercase()); } snake_case } pub fn to_upper_camel_case(string: &str) -> EcoString { let mut pascal_case = EcoString::with_capacity(string.len()); let mut chars = string.chars(); while let Some(char) = chars.next() { if char == '_' { let Some(next) = chars.next() else { break }; pascal_case.push(next.to_ascii_uppercase()); } else { pascal_case.push(char); } } pascal_case } /// Converts a string into its UTF-16 representation in bytes pub fn string_to_utf16_bytes(string: &str, endianness: Endianness) -> Vec { let mut bytes = Vec::with_capacity(string.len() * 2); let mut character_buffer = [0, 0]; for character in string.chars() { let segments = character.encode_utf16(&mut character_buffer); for segment in segments { let segment_bytes = match endianness { Endianness::Big => segment.to_be_bytes(), Endianness::Little => segment.to_le_bytes(), }; bytes.push(segment_bytes[0]); bytes.push(segment_bytes[1]); } } bytes } /// Converts a string into its UTF-32 representation in bytes pub fn string_to_utf32_bytes(string: &str, endianness: Endianness) -> Vec { let mut bytes = Vec::with_capacity(string.len() * 4); for character in string.chars() { let character_bytes = match endianness { Endianness::Big => (character as u32).to_be_bytes(), Endianness::Little => (character as u32).to_le_bytes(), }; bytes.extend(character_bytes); } bytes } /// Gets the number of UTF-16 codepoints it would take to encode a given string. pub fn length_utf16(string: &str) -> usize { let mut length = 0; for char in string.chars() { length += char.len_utf16(); } length } /// Gets the number of UTF-32 codepoints in a string pub fn length_utf32(string: &str) -> usize { string.chars().count() }