1use std::fmt;
26
27use crate::engine::{encode_loop, is_unicode_noncharacter, write_utf8_hex_bytes};
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() => write_utf8_hex_bytes(out, c),
182 c => write!(out, "\\x{:02x}", c as u32),
184 }
185}
186
187#[cfg(test)]
188mod tests {
189 use super::*;
190
191 #[test]
194 fn string_passthrough() {
195 assert_eq!(for_rust_string("hello world"), "hello world");
196 assert_eq!(for_rust_string(""), "");
197 assert_eq!(for_rust_string("café 日本語"), "café 日本語");
198 assert_eq!(for_rust_string("😀"), "😀");
199 }
200
201 #[test]
202 fn string_escapes_double_quote() {
203 assert_eq!(for_rust_string(r#"a"b"#), r#"a\"b"#);
204 }
205
206 #[test]
207 fn string_passes_single_quote() {
208 assert_eq!(for_rust_string("a'b"), "a'b");
209 }
210
211 #[test]
212 fn string_escapes_backslash() {
213 assert_eq!(for_rust_string(r"a\b"), r"a\\b");
214 }
215
216 #[test]
217 fn string_named_escapes() {
218 assert_eq!(for_rust_string("\0"), "\\0");
219 assert_eq!(for_rust_string("\t"), "\\t");
220 assert_eq!(for_rust_string("\n"), "\\n");
221 assert_eq!(for_rust_string("\r"), "\\r");
222 }
223
224 #[test]
225 fn string_hex_escapes_for_controls() {
226 assert_eq!(for_rust_string("\x01"), "\\x01");
227 assert_eq!(for_rust_string("\x08"), "\\x08");
228 assert_eq!(for_rust_string("\x0B"), "\\x0b");
229 assert_eq!(for_rust_string("\x0C"), "\\x0c");
230 assert_eq!(for_rust_string("\x1F"), "\\x1f");
231 assert_eq!(for_rust_string("\x7F"), "\\x7f");
232 }
233
234 #[test]
235 fn string_nonchars_replaced() {
236 assert_eq!(for_rust_string("\u{FDD0}"), " ");
237 assert_eq!(for_rust_string("\u{FFFE}"), " ");
238 }
239
240 #[test]
241 fn string_writer_matches() {
242 let input = "test\0\"\\\n café";
243 let mut w = String::new();
244 write_rust_string(&mut w, input).unwrap();
245 assert_eq!(for_rust_string(input), w);
246 }
247
248 #[test]
251 fn char_passthrough() {
252 assert_eq!(for_rust_char("hello world"), "hello world");
253 assert_eq!(for_rust_char(""), "");
254 assert_eq!(for_rust_char("café"), "café");
255 }
256
257 #[test]
258 fn char_escapes_single_quote() {
259 assert_eq!(for_rust_char("a'b"), r"a\'b");
260 }
261
262 #[test]
263 fn char_passes_double_quote() {
264 assert_eq!(for_rust_char(r#"a"b"#), r#"a"b"#);
265 }
266
267 #[test]
268 fn char_escapes_backslash() {
269 assert_eq!(for_rust_char(r"a\b"), r"a\\b");
270 }
271
272 #[test]
273 fn char_named_escapes() {
274 assert_eq!(for_rust_char("\0"), "\\0");
275 assert_eq!(for_rust_char("\t"), "\\t");
276 assert_eq!(for_rust_char("\n"), "\\n");
277 assert_eq!(for_rust_char("\r"), "\\r");
278 }
279
280 #[test]
281 fn char_hex_escapes_for_controls() {
282 assert_eq!(for_rust_char("\x01"), "\\x01");
283 assert_eq!(for_rust_char("\x7F"), "\\x7f");
284 }
285
286 #[test]
287 fn char_nonchars_replaced() {
288 assert_eq!(for_rust_char("\u{FDD0}"), " ");
289 }
290
291 #[test]
292 fn char_writer_matches() {
293 let input = "test\0'\\\n café";
294 let mut w = String::new();
295 write_rust_char(&mut w, input).unwrap();
296 assert_eq!(for_rust_char(input), w);
297 }
298
299 #[test]
302 fn byte_string_passthrough() {
303 assert_eq!(for_rust_byte_string("hello world"), "hello world");
304 assert_eq!(for_rust_byte_string(""), "");
305 }
306
307 #[test]
308 fn byte_string_escapes_double_quote() {
309 assert_eq!(for_rust_byte_string(r#"a"b"#), r#"a\"b"#);
310 }
311
312 #[test]
313 fn byte_string_escapes_backslash() {
314 assert_eq!(for_rust_byte_string(r"a\b"), r"a\\b");
315 }
316
317 #[test]
318 fn byte_string_named_escapes() {
319 assert_eq!(for_rust_byte_string("\0"), "\\0");
320 assert_eq!(for_rust_byte_string("\t"), "\\t");
321 assert_eq!(for_rust_byte_string("\n"), "\\n");
322 assert_eq!(for_rust_byte_string("\r"), "\\r");
323 }
324
325 #[test]
326 fn byte_string_hex_for_controls() {
327 assert_eq!(for_rust_byte_string("\x01"), "\\x01");
328 assert_eq!(for_rust_byte_string("\x7F"), "\\x7f");
329 }
330
331 #[test]
332 fn byte_string_non_ascii_as_utf8_bytes() {
333 assert_eq!(for_rust_byte_string("é"), r"\xc3\xa9");
335 assert_eq!(for_rust_byte_string("café"), r"caf\xc3\xa9");
337 assert_eq!(for_rust_byte_string("日"), r"\xe6\x97\xa5");
339 assert_eq!(for_rust_byte_string("😀"), r"\xf0\x9f\x98\x80");
341 }
342
343 #[test]
344 fn byte_string_nonchars_as_bytes() {
345 assert_eq!(for_rust_byte_string("\u{FDD0}"), r"\xef\xb7\x90");
347 }
348
349 #[test]
350 fn byte_string_single_quote_passes() {
351 assert_eq!(for_rust_byte_string("a'b"), "a'b");
352 }
353
354 #[test]
355 fn byte_string_writer_matches() {
356 let input = "test\0\"\\café";
357 let mut w = String::new();
358 write_rust_byte_string(&mut w, input).unwrap();
359 assert_eq!(for_rust_byte_string(input), w);
360 }
361}