1use std::fmt;
26
27use crate::engine::{encode_loop, is_unicode_noncharacter};
28
29pub fn for_rust_string(input: &str) -> String {
49 let mut out = String::with_capacity(input.len());
50 write_rust_string(&mut out, input).expect("writing to string cannot fail");
51 out
52}
53
54pub fn write_rust_string<W: fmt::Write>(out: &mut W, input: &str) -> fmt::Result {
58 encode_loop(out, input, needs_rust_string_encoding, |out, c, _next| {
59 write_rust_text_encoded(out, c, '"')
60 })
61}
62
63fn needs_rust_string_encoding(c: char) -> bool {
64 matches!(c, '\x00'..='\x1F' | '\x7F' | '"' | '\\') || is_unicode_noncharacter(c as u32)
65}
66
67pub fn for_rust_char(input: &str) -> String {
87 let mut out = String::with_capacity(input.len());
88 write_rust_char(&mut out, input).expect("writing to string cannot fail");
89 out
90}
91
92pub fn write_rust_char<W: fmt::Write>(out: &mut W, input: &str) -> fmt::Result {
96 encode_loop(out, input, needs_rust_char_encoding, |out, c, _next| {
97 write_rust_text_encoded(out, c, '\'')
98 })
99}
100
101fn needs_rust_char_encoding(c: char) -> bool {
102 matches!(c, '\x00'..='\x1F' | '\x7F' | '\'' | '\\') || is_unicode_noncharacter(c as u32)
103}
104
105fn write_rust_text_encoded<W: fmt::Write>(out: &mut W, c: char, quote: char) -> fmt::Result {
112 match c {
113 '\0' => out.write_str("\\0"),
114 '\t' => out.write_str("\\t"),
115 '\n' => out.write_str("\\n"),
116 '\r' => out.write_str("\\r"),
117 '\\' => out.write_str("\\\\"),
118 '"' if quote == '"' => out.write_str("\\\""),
119 '\'' if quote == '\'' => out.write_str("\\'"),
120 c if is_unicode_noncharacter(c as u32) => out.write_char(' '),
121 c => write!(out, "\\x{:02x}", c as u32),
123 }
124}
125
126pub fn for_rust_byte_string(input: &str) -> String {
147 let mut out = String::with_capacity(input.len());
148 write_rust_byte_string(&mut out, input).expect("writing to string cannot fail");
149 out
150}
151
152pub fn write_rust_byte_string<W: fmt::Write>(out: &mut W, input: &str) -> fmt::Result {
156 encode_loop(
157 out,
158 input,
159 needs_rust_byte_string_encoding,
160 write_rust_byte_string_encoded,
161 )
162}
163
164fn needs_rust_byte_string_encoding(c: char) -> bool {
165 matches!(c, '\x00'..='\x1F' | '\x7F' | '"' | '\\') || !c.is_ascii()
166}
167
168fn write_rust_byte_string_encoded<W: fmt::Write>(
169 out: &mut W,
170 c: char,
171 _next: Option<char>,
172) -> fmt::Result {
173 match c {
174 '\0' => out.write_str("\\0"),
175 '\t' => out.write_str("\\t"),
176 '\n' => out.write_str("\\n"),
177 '\r' => out.write_str("\\r"),
178 '"' => out.write_str("\\\""),
179 '\\' => out.write_str("\\\\"),
180 c if !c.is_ascii() => {
182 let mut buf = [0u8; 4];
183 let encoded = c.encode_utf8(&mut buf);
184 for b in encoded.as_bytes() {
185 write!(out, "\\x{b:02x}")?;
186 }
187 Ok(())
188 }
189 c => write!(out, "\\x{:02x}", c as u32),
191 }
192}
193
194#[cfg(test)]
195mod tests {
196 use super::*;
197
198 #[test]
201 fn string_passthrough() {
202 assert_eq!(for_rust_string("hello world"), "hello world");
203 assert_eq!(for_rust_string(""), "");
204 assert_eq!(for_rust_string("café 日本語"), "café 日本語");
205 assert_eq!(for_rust_string("😀"), "😀");
206 }
207
208 #[test]
209 fn string_escapes_double_quote() {
210 assert_eq!(for_rust_string(r#"a"b"#), r#"a\"b"#);
211 }
212
213 #[test]
214 fn string_passes_single_quote() {
215 assert_eq!(for_rust_string("a'b"), "a'b");
216 }
217
218 #[test]
219 fn string_escapes_backslash() {
220 assert_eq!(for_rust_string(r"a\b"), r"a\\b");
221 }
222
223 #[test]
224 fn string_named_escapes() {
225 assert_eq!(for_rust_string("\0"), "\\0");
226 assert_eq!(for_rust_string("\t"), "\\t");
227 assert_eq!(for_rust_string("\n"), "\\n");
228 assert_eq!(for_rust_string("\r"), "\\r");
229 }
230
231 #[test]
232 fn string_hex_escapes_for_controls() {
233 assert_eq!(for_rust_string("\x01"), "\\x01");
234 assert_eq!(for_rust_string("\x08"), "\\x08");
235 assert_eq!(for_rust_string("\x0B"), "\\x0b");
236 assert_eq!(for_rust_string("\x0C"), "\\x0c");
237 assert_eq!(for_rust_string("\x1F"), "\\x1f");
238 assert_eq!(for_rust_string("\x7F"), "\\x7f");
239 }
240
241 #[test]
242 fn string_nonchars_replaced() {
243 assert_eq!(for_rust_string("\u{FDD0}"), " ");
244 assert_eq!(for_rust_string("\u{FFFE}"), " ");
245 }
246
247 #[test]
248 fn string_writer_matches() {
249 let input = "test\0\"\\\n café";
250 let mut w = String::new();
251 write_rust_string(&mut w, input).unwrap();
252 assert_eq!(for_rust_string(input), w);
253 }
254
255 #[test]
258 fn char_passthrough() {
259 assert_eq!(for_rust_char("hello world"), "hello world");
260 assert_eq!(for_rust_char(""), "");
261 assert_eq!(for_rust_char("café"), "café");
262 }
263
264 #[test]
265 fn char_escapes_single_quote() {
266 assert_eq!(for_rust_char("a'b"), r"a\'b");
267 }
268
269 #[test]
270 fn char_passes_double_quote() {
271 assert_eq!(for_rust_char(r#"a"b"#), r#"a"b"#);
272 }
273
274 #[test]
275 fn char_escapes_backslash() {
276 assert_eq!(for_rust_char(r"a\b"), r"a\\b");
277 }
278
279 #[test]
280 fn char_named_escapes() {
281 assert_eq!(for_rust_char("\0"), "\\0");
282 assert_eq!(for_rust_char("\t"), "\\t");
283 assert_eq!(for_rust_char("\n"), "\\n");
284 assert_eq!(for_rust_char("\r"), "\\r");
285 }
286
287 #[test]
288 fn char_hex_escapes_for_controls() {
289 assert_eq!(for_rust_char("\x01"), "\\x01");
290 assert_eq!(for_rust_char("\x7F"), "\\x7f");
291 }
292
293 #[test]
294 fn char_nonchars_replaced() {
295 assert_eq!(for_rust_char("\u{FDD0}"), " ");
296 }
297
298 #[test]
299 fn char_writer_matches() {
300 let input = "test\0'\\\n café";
301 let mut w = String::new();
302 write_rust_char(&mut w, input).unwrap();
303 assert_eq!(for_rust_char(input), w);
304 }
305
306 #[test]
309 fn byte_string_passthrough() {
310 assert_eq!(for_rust_byte_string("hello world"), "hello world");
311 assert_eq!(for_rust_byte_string(""), "");
312 }
313
314 #[test]
315 fn byte_string_escapes_double_quote() {
316 assert_eq!(for_rust_byte_string(r#"a"b"#), r#"a\"b"#);
317 }
318
319 #[test]
320 fn byte_string_escapes_backslash() {
321 assert_eq!(for_rust_byte_string(r"a\b"), r"a\\b");
322 }
323
324 #[test]
325 fn byte_string_named_escapes() {
326 assert_eq!(for_rust_byte_string("\0"), "\\0");
327 assert_eq!(for_rust_byte_string("\t"), "\\t");
328 assert_eq!(for_rust_byte_string("\n"), "\\n");
329 assert_eq!(for_rust_byte_string("\r"), "\\r");
330 }
331
332 #[test]
333 fn byte_string_hex_for_controls() {
334 assert_eq!(for_rust_byte_string("\x01"), "\\x01");
335 assert_eq!(for_rust_byte_string("\x7F"), "\\x7f");
336 }
337
338 #[test]
339 fn byte_string_non_ascii_as_utf8_bytes() {
340 assert_eq!(for_rust_byte_string("é"), r"\xc3\xa9");
342 assert_eq!(for_rust_byte_string("café"), r"caf\xc3\xa9");
344 assert_eq!(for_rust_byte_string("日"), r"\xe6\x97\xa5");
346 assert_eq!(for_rust_byte_string("😀"), r"\xf0\x9f\x98\x80");
348 }
349
350 #[test]
351 fn byte_string_nonchars_as_bytes() {
352 assert_eq!(for_rust_byte_string("\u{FDD0}"), r"\xef\xb7\x90");
354 }
355
356 #[test]
357 fn byte_string_single_quote_passes() {
358 assert_eq!(for_rust_byte_string("a'b"), "a'b");
359 }
360
361 #[test]
362 fn byte_string_writer_matches() {
363 let input = "test\0\"\\café";
364 let mut w = String::new();
365 write_rust_byte_string(&mut w, input).unwrap();
366 assert_eq!(for_rust_byte_string(input), w);
367 }
368}