Fork of daniellemaywood.uk/gleam — Wasm codegen work
5.7 kB
170 lines
1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: 2024 The Gleam contributors
3
4use ecow::EcoString;
5use itertools::Itertools;
6
7use crate::ast::Endianness;
8
9/// Converts any escape sequences from the given string to their correct
10/// bytewise UTF-8 representation and returns the resulting string.
11pub fn convert_string_escape_chars(str: &EcoString) -> EcoString {
12 let mut filtered_str = EcoString::new();
13 let mut str_iter = str.chars().peekable();
14 loop {
15 match str_iter.next() {
16 Some('\\') => match str_iter.next() {
17 // Check for Unicode escape sequence, e.g. \u{00012FF}
18 Some('u') => {
19 if str_iter.peek() != Some(&'{') {
20 // Invalid Unicode escape sequence
21 filtered_str.push('u');
22 continue;
23 }
24
25 // Consume the left brace after peeking
26 let _ = str_iter.next();
27
28 let codepoint_str = str_iter
29 .peeking_take_while(char::is_ascii_hexdigit)
30 .collect::<String>();
31
32 if codepoint_str.is_empty() || str_iter.peek() != Some(&'}') {
33 // Invalid Unicode escape sequence
34 filtered_str.push_str("u{");
35 filtered_str.push_str(&codepoint_str);
36 continue;
37 }
38
39 let codepoint = u32::from_str_radix(&codepoint_str, 16)
40 .ok()
41 .and_then(char::from_u32);
42
43 if let Some(codepoint) = codepoint {
44 // Consume the right brace after peeking
45 let _ = str_iter.next();
46
47 // Consider this codepoint's length instead of
48 // that of the Unicode escape sequence itself
49 filtered_str.push(codepoint);
50 } else {
51 // Invalid Unicode escape sequence
52 // (codepoint value not in base 16 or too large)
53 filtered_str.push_str("u{");
54 filtered_str.push_str(&codepoint_str);
55 }
56 }
57 Some('n') => filtered_str.push('\n'),
58 Some('r') => filtered_str.push('\r'),
59 Some('f') => filtered_str.push('\u{C}'),
60 Some('t') => filtered_str.push('\t'),
61 Some('"') => filtered_str.push('\"'),
62 Some('\\') => filtered_str.push('\\'),
63 Some(c) => filtered_str.push(c),
64 None => break,
65 },
66 Some(c) => filtered_str.push(c),
67 None => break,
68 }
69 }
70 filtered_str
71}
72
73pub fn to_snake_case(string: &str) -> EcoString {
74 let mut snake_case = EcoString::with_capacity(string.len());
75 let mut is_word_boundary = true;
76
77 for char in string.chars() {
78 match char {
79 '_' | ' ' => {
80 is_word_boundary = true;
81 continue;
82 }
83 _ if char.is_uppercase() => {
84 is_word_boundary = true;
85 }
86 _ => {}
87 }
88
89 if is_word_boundary {
90 // We don't want to push an underscore at the start of the string,
91 // even if it starts with a capital letter or other delimiter.
92 if !snake_case.is_empty() {
93 snake_case.push('_');
94 }
95 is_word_boundary = false;
96 }
97 snake_case.push(char.to_ascii_lowercase());
98 }
99
100 snake_case
101}
102
103pub fn to_upper_camel_case(string: &str) -> EcoString {
104 let mut pascal_case = EcoString::with_capacity(string.len());
105 let mut chars = string.chars();
106
107 while let Some(char) = chars.next() {
108 if char == '_' {
109 let Some(next) = chars.next() else { break };
110 pascal_case.push(next.to_ascii_uppercase());
111 } else {
112 pascal_case.push(char);
113 }
114 }
115
116 pascal_case
117}
118
119/// Converts a string into its UTF-16 representation in bytes
120pub fn string_to_utf16_bytes(string: &str, endianness: Endianness) -> Vec<u8> {
121 let mut bytes = Vec::with_capacity(string.len() * 2);
122
123 let mut character_buffer = [0, 0];
124 for character in string.chars() {
125 let segments = character.encode_utf16(&mut character_buffer);
126
127 for segment in segments {
128 let segment_bytes = match endianness {
129 Endianness::Big => segment.to_be_bytes(),
130 Endianness::Little => segment.to_le_bytes(),
131 };
132
133 bytes.push(segment_bytes[0]);
134 bytes.push(segment_bytes[1]);
135 }
136 }
137
138 bytes
139}
140
141/// Converts a string into its UTF-32 representation in bytes
142pub fn string_to_utf32_bytes(string: &str, endianness: Endianness) -> Vec<u8> {
143 let mut bytes = Vec::with_capacity(string.len() * 4);
144
145 for character in string.chars() {
146 let character_bytes = match endianness {
147 Endianness::Big => (character as u32).to_be_bytes(),
148 Endianness::Little => (character as u32).to_le_bytes(),
149 };
150 bytes.extend(character_bytes);
151 }
152
153 bytes
154}
155
156/// Gets the number of UTF-16 codepoints it would take to encode a given string.
157pub fn length_utf16(string: &str) -> usize {
158 let mut length = 0;
159
160 for char in string.chars() {
161 length += char.len_utf16()
162 }
163
164 length
165}
166
167/// Gets the number of UTF-32 codepoints in a string
168pub fn length_utf32(string: &str) -> usize {
169 string.chars().count()
170}