1use std::fmt;
27
28use crate::engine::{
29 encode_loop, is_unicode_noncharacter, needs_byte_string_encoding, write_byte_string_encoded,
30 write_c0_named_escape,
31};
32
33pub fn for_go_string(input: &str) -> String {
54 let mut out = String::with_capacity(input.len());
55 write_go_string(&mut out, input).expect("writing to string cannot fail");
56 out
57}
58
59pub fn write_go_string<W: fmt::Write>(out: &mut W, input: &str) -> fmt::Result {
63 encode_loop(out, input, needs_go_string_encoding, |out, c, _next| {
64 write_go_text_encoded(out, c, '"')
65 })
66}
67
68fn needs_go_string_encoding(c: char) -> bool {
69 matches!(c, '\x00'..='\x1F' | '\x7F' | '"' | '\\') || is_unicode_noncharacter(c as u32)
70}
71
72pub fn for_go_char(input: &str) -> String {
92 let mut out = String::with_capacity(input.len());
93 write_go_char(&mut out, input).expect("writing to string cannot fail");
94 out
95}
96
97pub fn write_go_char<W: fmt::Write>(out: &mut W, input: &str) -> fmt::Result {
101 encode_loop(out, input, needs_go_char_encoding, |out, c, _next| {
102 write_go_text_encoded(out, c, '\'')
103 })
104}
105
106fn needs_go_char_encoding(c: char) -> bool {
107 matches!(c, '\x00'..='\x1F' | '\x7F' | '\'' | '\\') || is_unicode_noncharacter(c as u32)
108}
109
110fn write_go_text_encoded<W: fmt::Write>(out: &mut W, c: char, quote: char) -> fmt::Result {
117 if let Some(r) = write_c0_named_escape(out, c) {
118 return r;
119 }
120 match c {
121 '"' if quote == '"' => out.write_str("\\\""),
122 '\'' if quote == '\'' => out.write_str("\\'"),
123 c if is_unicode_noncharacter(c as u32) => out.write_char(' '),
124 c => write!(out, "\\x{:02x}", c as u32),
126 }
127}
128
129pub fn for_go_byte_string(input: &str) -> String {
151 let mut out = String::with_capacity(input.len());
152 write_go_byte_string(&mut out, input).expect("writing to string cannot fail");
153 out
154}
155
156pub fn write_go_byte_string<W: fmt::Write>(out: &mut W, input: &str) -> fmt::Result {
160 encode_loop(out, input, needs_byte_string_encoding, |out, c, _next| {
161 write_byte_string_encoded(out, c, write_c0_named_escape)
162 })
163}
164
165#[cfg(test)]
166mod tests {
167 use super::*;
168
169 #[test]
172 fn string_passthrough() {
173 assert_eq!(for_go_string("hello world"), "hello world");
174 assert_eq!(for_go_string(""), "");
175 assert_eq!(
176 for_go_string("cafe\u{0301} \u{65E5}\u{672C}\u{8A9E}"),
177 "cafe\u{0301} \u{65E5}\u{672C}\u{8A9E}"
178 );
179 assert_eq!(for_go_string("\u{1F600}"), "\u{1F600}");
180 }
181
182 #[test]
183 fn string_escapes_double_quote() {
184 assert_eq!(for_go_string(r#"a"b"#), r#"a\"b"#);
185 }
186
187 #[test]
188 fn string_passes_single_quote() {
189 assert_eq!(for_go_string("a'b"), "a'b");
190 }
191
192 #[test]
193 fn string_escapes_backslash() {
194 assert_eq!(for_go_string(r"a\b"), r"a\\b");
195 }
196
197 #[test]
198 fn string_named_escapes() {
199 assert_eq!(for_go_string("\x07"), "\\a");
200 assert_eq!(for_go_string("\x08"), "\\b");
201 assert_eq!(for_go_string("\t"), "\\t");
202 assert_eq!(for_go_string("\n"), "\\n");
203 assert_eq!(for_go_string("\x0B"), "\\v");
204 assert_eq!(for_go_string("\x0C"), "\\f");
205 assert_eq!(for_go_string("\r"), "\\r");
206 }
207
208 #[test]
209 fn string_hex_escapes_for_controls() {
210 assert_eq!(for_go_string("\x00"), "\\x00");
211 assert_eq!(for_go_string("\x01"), "\\x01");
212 assert_eq!(for_go_string("\x06"), "\\x06");
213 assert_eq!(for_go_string("\x0E"), "\\x0e");
214 assert_eq!(for_go_string("\x1F"), "\\x1f");
215 assert_eq!(for_go_string("\x7F"), "\\x7f");
216 }
217
218 #[test]
219 fn string_nonchars_replaced() {
220 assert_eq!(for_go_string("\u{FDD0}"), " ");
221 assert_eq!(for_go_string("\u{FFFE}"), " ");
222 }
223
224 #[test]
225 fn string_writer_matches() {
226 let input = "test\x00\"\\\n cafe\u{0301}";
227 let mut w = String::new();
228 write_go_string(&mut w, input).unwrap();
229 assert_eq!(for_go_string(input), w);
230 }
231
232 #[test]
235 fn char_passthrough() {
236 assert_eq!(for_go_char("hello world"), "hello world");
237 assert_eq!(for_go_char(""), "");
238 assert_eq!(for_go_char("cafe\u{0301}"), "cafe\u{0301}");
239 }
240
241 #[test]
242 fn char_escapes_single_quote() {
243 assert_eq!(for_go_char("a'b"), r"a\'b");
244 }
245
246 #[test]
247 fn char_passes_double_quote() {
248 assert_eq!(for_go_char(r#"a"b"#), r#"a"b"#);
249 }
250
251 #[test]
252 fn char_escapes_backslash() {
253 assert_eq!(for_go_char(r"a\b"), r"a\\b");
254 }
255
256 #[test]
257 fn char_named_escapes() {
258 assert_eq!(for_go_char("\x07"), "\\a");
259 assert_eq!(for_go_char("\x08"), "\\b");
260 assert_eq!(for_go_char("\t"), "\\t");
261 assert_eq!(for_go_char("\n"), "\\n");
262 assert_eq!(for_go_char("\x0B"), "\\v");
263 assert_eq!(for_go_char("\x0C"), "\\f");
264 assert_eq!(for_go_char("\r"), "\\r");
265 }
266
267 #[test]
268 fn char_hex_escapes_for_controls() {
269 assert_eq!(for_go_char("\x01"), "\\x01");
270 assert_eq!(for_go_char("\x7F"), "\\x7f");
271 }
272
273 #[test]
274 fn char_nonchars_replaced() {
275 assert_eq!(for_go_char("\u{FDD0}"), " ");
276 }
277
278 #[test]
279 fn char_writer_matches() {
280 let input = "test\x00'\\\n cafe\u{0301}";
281 let mut w = String::new();
282 write_go_char(&mut w, input).unwrap();
283 assert_eq!(for_go_char(input), w);
284 }
285
286 #[test]
289 fn byte_string_passthrough() {
290 assert_eq!(for_go_byte_string("hello world"), "hello world");
291 assert_eq!(for_go_byte_string(""), "");
292 }
293
294 #[test]
295 fn byte_string_escapes_double_quote() {
296 assert_eq!(for_go_byte_string(r#"a"b"#), r#"a\"b"#);
297 }
298
299 #[test]
300 fn byte_string_escapes_backslash() {
301 assert_eq!(for_go_byte_string(r"a\b"), r"a\\b");
302 }
303
304 #[test]
305 fn byte_string_named_escapes() {
306 assert_eq!(for_go_byte_string("\x07"), "\\a");
307 assert_eq!(for_go_byte_string("\x08"), "\\b");
308 assert_eq!(for_go_byte_string("\t"), "\\t");
309 assert_eq!(for_go_byte_string("\n"), "\\n");
310 assert_eq!(for_go_byte_string("\x0B"), "\\v");
311 assert_eq!(for_go_byte_string("\x0C"), "\\f");
312 assert_eq!(for_go_byte_string("\r"), "\\r");
313 }
314
315 #[test]
316 fn byte_string_hex_for_controls() {
317 assert_eq!(for_go_byte_string("\x00"), "\\x00");
318 assert_eq!(for_go_byte_string("\x01"), "\\x01");
319 assert_eq!(for_go_byte_string("\x7F"), "\\x7f");
320 }
321
322 #[test]
323 fn byte_string_non_ascii_as_utf8_bytes() {
324 assert_eq!(for_go_byte_string("\u{0301}"), r"\xcc\x81");
326 assert_eq!(for_go_byte_string("cafe\u{0301}"), r"cafe\xcc\x81");
328 assert_eq!(for_go_byte_string("\u{65E5}"), r"\xe6\x97\xa5");
330 assert_eq!(for_go_byte_string("\u{1F600}"), r"\xf0\x9f\x98\x80");
332 }
333
334 #[test]
335 fn byte_string_nonchars_as_bytes() {
336 assert_eq!(for_go_byte_string("\u{FDD0}"), r"\xef\xb7\x90");
338 }
339
340 #[test]
341 fn byte_string_single_quote_passes() {
342 assert_eq!(for_go_byte_string("a'b"), "a'b");
343 }
344
345 #[test]
346 fn byte_string_writer_matches() {
347 let input = "test\x00\"\\cafe\u{0301}";
348 let mut w = String::new();
349 write_go_byte_string(&mut w, input).unwrap();
350 assert_eq!(for_go_byte_string(input), w);
351 }
352}