1use std::fmt;
26
27use crate::engine::{
28 encode_loop, is_unicode_noncharacter, needs_byte_string_encoding, write_byte_string_encoded,
29 write_rust_named_escape,
30};
31
32pub fn for_rust_string(input: &str) -> String {
48 let mut out = String::with_capacity(input.len());
49 write_rust_string(&mut out, input).expect("writing to string cannot fail");
50 out
51}
52
53pub fn write_rust_string<W: fmt::Write>(out: &mut W, input: &str) -> fmt::Result {
57 encode_loop(out, input, needs_rust_string_encoding, |out, c, _next| {
58 write_rust_text_encoded(out, c, '"')
59 })
60}
61
62fn needs_rust_string_encoding(c: char) -> bool {
63 matches!(c, '\x00'..='\x1F' | '\x7F' | '"' | '\\') || is_unicode_noncharacter(c as u32)
64}
65
66pub fn for_rust_char(input: &str) -> String {
82 let mut out = String::with_capacity(input.len());
83 write_rust_char(&mut out, input).expect("writing to string cannot fail");
84 out
85}
86
87pub fn write_rust_char<W: fmt::Write>(out: &mut W, input: &str) -> fmt::Result {
91 encode_loop(out, input, needs_rust_char_encoding, |out, c, _next| {
92 write_rust_text_encoded(out, c, '\'')
93 })
94}
95
96fn needs_rust_char_encoding(c: char) -> bool {
97 matches!(c, '\x00'..='\x1F' | '\x7F' | '\'' | '\\') || is_unicode_noncharacter(c as u32)
98}
99
100fn write_rust_text_encoded<W: fmt::Write>(out: &mut W, c: char, quote: char) -> fmt::Result {
103 match c {
104 '\0' => out.write_str("\\0"),
105 '\t' => out.write_str("\\t"),
106 '\n' => out.write_str("\\n"),
107 '\r' => out.write_str("\\r"),
108 '\\' => out.write_str("\\\\"),
109 '"' if quote == '"' => out.write_str("\\\""),
110 '\'' if quote == '\'' => out.write_str("\\'"),
111 c if is_unicode_noncharacter(c as u32) => out.write_char(' '),
112 c => write!(out, "\\x{:02x}", c as u32),
114 }
115}
116
117pub fn for_rust_byte_string(input: &str) -> String {
134 let mut out = String::with_capacity(input.len());
135 write_rust_byte_string(&mut out, input).expect("writing to string cannot fail");
136 out
137}
138
139pub fn write_rust_byte_string<W: fmt::Write>(out: &mut W, input: &str) -> fmt::Result {
143 encode_loop(out, input, needs_byte_string_encoding, |out, c, _next| {
144 write_byte_string_encoded(out, c, write_rust_named_escape)
145 })
146}
147
148#[cfg(test)]
149mod tests {
150 use super::*;
151
152 #[test]
155 fn string_passthrough() {
156 assert_eq!(for_rust_string("hello world"), "hello world");
157 assert_eq!(for_rust_string(""), "");
158 assert_eq!(for_rust_string("café 日本語"), "café 日本語");
159 assert_eq!(for_rust_string("😀"), "😀");
160 }
161
162 #[test]
163 fn string_escapes_double_quote() {
164 assert_eq!(for_rust_string(r#"a"b"#), r#"a\"b"#);
165 }
166
167 #[test]
168 fn string_passes_single_quote() {
169 assert_eq!(for_rust_string("a'b"), "a'b");
170 }
171
172 #[test]
173 fn string_escapes_backslash() {
174 assert_eq!(for_rust_string(r"a\b"), r"a\\b");
175 }
176
177 #[test]
178 fn string_named_escapes() {
179 assert_eq!(for_rust_string("\0"), "\\0");
180 assert_eq!(for_rust_string("\t"), "\\t");
181 assert_eq!(for_rust_string("\n"), "\\n");
182 assert_eq!(for_rust_string("\r"), "\\r");
183 }
184
185 #[test]
186 fn string_hex_escapes_for_controls() {
187 assert_eq!(for_rust_string("\x01"), "\\x01");
188 assert_eq!(for_rust_string("\x08"), "\\x08");
189 assert_eq!(for_rust_string("\x0B"), "\\x0b");
190 assert_eq!(for_rust_string("\x0C"), "\\x0c");
191 assert_eq!(for_rust_string("\x1F"), "\\x1f");
192 assert_eq!(for_rust_string("\x7F"), "\\x7f");
193 }
194
195 #[test]
196 fn string_nonchars_replaced() {
197 assert_eq!(for_rust_string("\u{FDD0}"), " ");
198 assert_eq!(for_rust_string("\u{FFFE}"), " ");
199 }
200
201 #[test]
202 fn string_writer_matches() {
203 let input = "test\0\"\\\n café";
204 let mut w = String::new();
205 write_rust_string(&mut w, input).unwrap();
206 assert_eq!(for_rust_string(input), w);
207 }
208
209 #[test]
212 fn char_passthrough() {
213 assert_eq!(for_rust_char("hello world"), "hello world");
214 assert_eq!(for_rust_char(""), "");
215 assert_eq!(for_rust_char("café"), "café");
216 }
217
218 #[test]
219 fn char_escapes_single_quote() {
220 assert_eq!(for_rust_char("a'b"), r"a\'b");
221 }
222
223 #[test]
224 fn char_passes_double_quote() {
225 assert_eq!(for_rust_char(r#"a"b"#), r#"a"b"#);
226 }
227
228 #[test]
229 fn char_escapes_backslash() {
230 assert_eq!(for_rust_char(r"a\b"), r"a\\b");
231 }
232
233 #[test]
234 fn char_named_escapes() {
235 assert_eq!(for_rust_char("\0"), "\\0");
236 assert_eq!(for_rust_char("\t"), "\\t");
237 assert_eq!(for_rust_char("\n"), "\\n");
238 assert_eq!(for_rust_char("\r"), "\\r");
239 }
240
241 #[test]
242 fn char_hex_escapes_for_controls() {
243 assert_eq!(for_rust_char("\x01"), "\\x01");
244 assert_eq!(for_rust_char("\x7F"), "\\x7f");
245 }
246
247 #[test]
248 fn char_nonchars_replaced() {
249 assert_eq!(for_rust_char("\u{FDD0}"), " ");
250 }
251
252 #[test]
253 fn char_writer_matches() {
254 let input = "test\0'\\\n café";
255 let mut w = String::new();
256 write_rust_char(&mut w, input).unwrap();
257 assert_eq!(for_rust_char(input), w);
258 }
259
260 #[test]
263 fn byte_string_passthrough() {
264 assert_eq!(for_rust_byte_string("hello world"), "hello world");
265 assert_eq!(for_rust_byte_string(""), "");
266 }
267
268 #[test]
269 fn byte_string_escapes_double_quote() {
270 assert_eq!(for_rust_byte_string(r#"a"b"#), r#"a\"b"#);
271 }
272
273 #[test]
274 fn byte_string_escapes_backslash() {
275 assert_eq!(for_rust_byte_string(r"a\b"), r"a\\b");
276 }
277
278 #[test]
279 fn byte_string_named_escapes() {
280 assert_eq!(for_rust_byte_string("\0"), "\\0");
281 assert_eq!(for_rust_byte_string("\t"), "\\t");
282 assert_eq!(for_rust_byte_string("\n"), "\\n");
283 assert_eq!(for_rust_byte_string("\r"), "\\r");
284 }
285
286 #[test]
287 fn byte_string_hex_for_controls() {
288 assert_eq!(for_rust_byte_string("\x01"), "\\x01");
289 assert_eq!(for_rust_byte_string("\x7F"), "\\x7f");
290 }
291
292 #[test]
293 fn byte_string_non_ascii_as_utf8_bytes() {
294 assert_eq!(for_rust_byte_string("é"), r"\xc3\xa9");
296 assert_eq!(for_rust_byte_string("café"), r"caf\xc3\xa9");
298 assert_eq!(for_rust_byte_string("日"), r"\xe6\x97\xa5");
300 assert_eq!(for_rust_byte_string("😀"), r"\xf0\x9f\x98\x80");
302 }
303
304 #[test]
305 fn byte_string_nonchars_as_bytes() {
306 assert_eq!(for_rust_byte_string("\u{FDD0}"), r"\xef\xb7\x90");
308 }
309
310 #[test]
311 fn byte_string_single_quote_passes() {
312 assert_eq!(for_rust_byte_string("a'b"), "a'b");
313 }
314
315 #[test]
316 fn byte_string_writer_matches() {
317 let input = "test\0\"\\café";
318 let mut w = String::new();
319 write_rust_byte_string(&mut w, input).unwrap();
320 assert_eq!(for_rust_byte_string(input), w);
321 }
322}