1const STOP_OUTPUT: u8 = 0xFF;
9
10use std::cell::Cell;
11
12thread_local! {
13 static CONV_ERROR: Cell<bool> = const { Cell::new(false) };
15}
16
17pub fn reset_conv_error() {
19 CONV_ERROR.with(|c| c.set(false));
20}
21
22pub fn had_conv_error() -> bool {
24 CONV_ERROR.with(|c| c.get())
25}
26
27fn mark_conv_error(s: &str) {
28 eprintln!("printf: '{}': expected a numeric value", s);
29 CONV_ERROR.with(|c| c.set(true));
30}
31
32pub fn process_format_string(format: &str, args: &[&str]) -> Vec<u8> {
38 let mut output = Vec::with_capacity(256);
39 let fmt_bytes = format.as_bytes();
40
41 if args.is_empty() {
42 let stop = format_one_pass(fmt_bytes, args, &mut 0, &mut output);
44 if stop {
45 if output.last() == Some(&STOP_OUTPUT) {
47 output.pop();
48 }
49 }
50 return output;
51 }
52
53 let mut arg_idx: usize = 0;
54 loop {
55 let start_idx = arg_idx;
56 let stop = format_one_pass(fmt_bytes, args, &mut arg_idx, &mut output);
57 if stop {
58 if output.last() == Some(&STOP_OUTPUT) {
59 output.pop();
60 }
61 break;
62 }
63 if arg_idx == start_idx || arg_idx >= args.len() {
65 break;
66 }
67 }
68
69 output
70}
71
72fn format_one_pass(fmt: &[u8], args: &[&str], arg_idx: &mut usize, output: &mut Vec<u8>) -> bool {
75 let mut i = 0;
76 while i < fmt.len() {
77 match fmt[i] {
78 b'%' => {
79 i += 1;
80 if i >= fmt.len() {
81 output.push(b'%');
82 break;
83 }
84 if fmt[i] == b'%' {
85 output.push(b'%');
86 i += 1;
87 continue;
88 }
89 let stop = process_conversion(fmt, &mut i, args, arg_idx, output);
90 if stop {
91 return true;
92 }
93 }
94 b'\\' => {
95 i += 1;
96 let stop = process_format_escape(fmt, &mut i, output);
97 if stop {
98 return true;
99 }
100 }
101 ch => {
102 output.push(ch);
103 i += 1;
104 }
105 }
106 }
107 false
108}
109
110fn process_conversion(
113 fmt: &[u8],
114 i: &mut usize,
115 args: &[&str],
116 arg_idx: &mut usize,
117 output: &mut Vec<u8>,
118) -> bool {
119 let mut flags = FormatFlags::default();
121 while *i < fmt.len() {
122 match fmt[*i] {
123 b'-' => flags.left_align = true,
124 b'+' => flags.plus_sign = true,
125 b' ' => flags.space_sign = true,
126 b'0' => flags.zero_pad = true,
127 b'#' => flags.alternate = true,
128 _ => break,
129 }
130 *i += 1;
131 }
132
133 let width = parse_decimal(fmt, i);
135
136 let precision = if *i < fmt.len() && fmt[*i] == b'.' {
138 *i += 1;
139 Some(parse_decimal(fmt, i))
140 } else {
141 None
142 };
143
144 if *i >= fmt.len() {
146 return false;
147 }
148 let conv = fmt[*i];
149 *i += 1;
150
151 let arg = consume_arg(args, arg_idx);
152
153 match conv {
154 b's' => {
155 let s = arg;
156 let formatted = apply_string_format(s, &flags, width, precision);
157 output.extend_from_slice(&formatted);
158 }
159 b'b' => {
160 let (bytes, stop) = process_b_argument(arg);
161 let formatted = apply_string_format_bytes(&bytes, &flags, width, precision);
162 output.extend_from_slice(&formatted);
163 if stop {
164 return true;
165 }
166 }
167 b'c' => {
168 if let Some(ch) = arg.chars().next() {
169 let mut buf = [0u8; 4];
170 let encoded = ch.encode_utf8(&mut buf);
171 let formatted = apply_string_format(encoded, &flags, width, precision);
172 output.extend_from_slice(&formatted);
173 } else {
174 let formatted = apply_string_format_bytes(&[0], &flags, width, precision);
176 output.extend_from_slice(&formatted);
177 }
178 }
179 b'd' | b'i' => {
180 let val = parse_integer(arg);
181 let s = format!("{}", val);
182 let formatted = apply_numeric_format(&s, val < 0, &flags, width, precision);
183 output.extend_from_slice(formatted.as_bytes());
184 }
185 b'u' => {
186 let val = parse_unsigned(arg);
187 let s = format!("{}", val);
188 let formatted = apply_numeric_format(&s, false, &flags, width, precision);
189 output.extend_from_slice(formatted.as_bytes());
190 }
191 b'o' => {
192 let val = parse_unsigned(arg);
193 let s = format!("{:o}", val);
194 let prefix = if flags.alternate && !s.starts_with('0') {
195 "0"
196 } else {
197 ""
198 };
199 let full = format!("{}{}", prefix, s);
200 let formatted = apply_numeric_format(&full, false, &flags, width, precision);
201 output.extend_from_slice(formatted.as_bytes());
202 }
203 b'x' => {
204 let val = parse_unsigned(arg);
205 let s = format!("{:x}", val);
206 let prefix = if flags.alternate && val != 0 {
207 "0x"
208 } else {
209 ""
210 };
211 let full = format!("{}{}", prefix, s);
212 let formatted = apply_numeric_format(&full, false, &flags, width, precision);
213 output.extend_from_slice(formatted.as_bytes());
214 }
215 b'X' => {
216 let val = parse_unsigned(arg);
217 let s = format!("{:X}", val);
218 let prefix = if flags.alternate && val != 0 {
219 "0X"
220 } else {
221 ""
222 };
223 let full = format!("{}{}", prefix, s);
224 let formatted = apply_numeric_format(&full, false, &flags, width, precision);
225 output.extend_from_slice(formatted.as_bytes());
226 }
227 b'f' => {
228 let val = parse_float(arg);
229 let prec = precision.unwrap_or(6);
230 let s = format!("{:.prec$}", val, prec = prec);
231 let formatted = apply_float_format(&s, val < 0.0, &flags, width);
232 output.extend_from_slice(formatted.as_bytes());
233 }
234 b'e' => {
235 let val = parse_float(arg);
236 let prec = precision.unwrap_or(6);
237 let s = format_scientific(val, prec, 'e');
238 let formatted = apply_float_format(&s, val < 0.0, &flags, width);
239 output.extend_from_slice(formatted.as_bytes());
240 }
241 b'E' => {
242 let val = parse_float(arg);
243 let prec = precision.unwrap_or(6);
244 let s = format_scientific(val, prec, 'E');
245 let formatted = apply_float_format(&s, val < 0.0, &flags, width);
246 output.extend_from_slice(formatted.as_bytes());
247 }
248 b'g' => {
249 let val = parse_float(arg);
250 let prec = precision.unwrap_or(6);
251 let s = format_g(val, prec, false);
252 let formatted = apply_float_format(&s, val < 0.0, &flags, width);
253 output.extend_from_slice(formatted.as_bytes());
254 }
255 b'G' => {
256 let val = parse_float(arg);
257 let prec = precision.unwrap_or(6);
258 let s = format_g(val, prec, true);
259 let formatted = apply_float_format(&s, val < 0.0, &flags, width);
260 output.extend_from_slice(formatted.as_bytes());
261 }
262 b'q' => {
263 let s = arg;
264 let quoted = shell_quote(s);
265 let formatted = apply_string_format("ed, &flags, width, precision);
266 output.extend_from_slice(&formatted);
267 }
268 _ => {
269 output.push(b'%');
271 output.push(conv);
272 }
273 }
274 false
275}
276
277fn consume_arg<'a>(args: &[&'a str], arg_idx: &mut usize) -> &'a str {
279 if *arg_idx < args.len() {
280 let val = args[*arg_idx];
281 *arg_idx += 1;
282 val
283 } else {
284 ""
285 }
286}
287
288fn process_format_escape(fmt: &[u8], i: &mut usize, output: &mut Vec<u8>) -> bool {
291 if *i >= fmt.len() {
292 output.push(b'\\');
293 return false;
294 }
295 match fmt[*i] {
296 b'\\' => {
297 output.push(b'\\');
298 *i += 1;
299 }
300 b'"' => {
301 output.push(b'"');
302 *i += 1;
303 }
304 b'a' => {
305 output.push(0x07);
306 *i += 1;
307 }
308 b'b' => {
309 output.push(0x08);
310 *i += 1;
311 }
312 b'c' => {
313 return true;
314 }
315 b'e' | b'E' => {
316 output.push(0x1B);
317 *i += 1;
318 }
319 b'f' => {
320 output.push(0x0C);
321 *i += 1;
322 }
323 b'n' => {
324 output.push(b'\n');
325 *i += 1;
326 }
327 b'r' => {
328 output.push(b'\r');
329 *i += 1;
330 }
331 b't' => {
332 output.push(b'\t');
333 *i += 1;
334 }
335 b'v' => {
336 output.push(0x0B);
337 *i += 1;
338 }
339 b'0' => {
340 *i += 1;
342 let val = parse_octal_digits(fmt, i, 3);
343 output.push(val);
344 }
345 b'1'..=b'7' => {
346 let val = parse_octal_digits(fmt, i, 3);
348 output.push(val);
349 }
350 b'x' => {
351 *i += 1;
352 let val = parse_hex_digits(fmt, i, 2);
353 output.push(val as u8);
354 }
355 b'u' => {
356 *i += 1;
357 let val = parse_hex_digits(fmt, i, 4);
358 if let Some(ch) = char::from_u32(val) {
359 let mut buf = [0u8; 4];
360 let encoded = ch.encode_utf8(&mut buf);
361 output.extend_from_slice(encoded.as_bytes());
362 }
363 }
364 b'U' => {
365 *i += 1;
366 let val = parse_hex_digits(fmt, i, 8);
367 if let Some(ch) = char::from_u32(val) {
368 let mut buf = [0u8; 4];
369 let encoded = ch.encode_utf8(&mut buf);
370 output.extend_from_slice(encoded.as_bytes());
371 }
372 }
373 _ => {
374 output.push(b'\\');
376 output.push(fmt[*i]);
377 *i += 1;
378 }
379 }
380 false
381}
382
383fn process_b_argument(arg: &str) -> (Vec<u8>, bool) {
386 let bytes = arg.as_bytes();
387 let mut output = Vec::with_capacity(bytes.len());
388 let mut i = 0;
389 while i < bytes.len() {
390 if bytes[i] == b'\\' {
391 i += 1;
392 if i >= bytes.len() {
393 output.push(b'\\');
394 break;
395 }
396 match bytes[i] {
397 b'\\' => {
398 output.push(b'\\');
399 i += 1;
400 }
401 b'a' => {
402 output.push(0x07);
403 i += 1;
404 }
405 b'b' => {
406 output.push(0x08);
407 i += 1;
408 }
409 b'c' => {
410 return (output, true);
411 }
412 b'e' | b'E' => {
413 output.push(0x1B);
414 i += 1;
415 }
416 b'f' => {
417 output.push(0x0C);
418 i += 1;
419 }
420 b'n' => {
421 output.push(b'\n');
422 i += 1;
423 }
424 b'r' => {
425 output.push(b'\r');
426 i += 1;
427 }
428 b't' => {
429 output.push(b'\t');
430 i += 1;
431 }
432 b'v' => {
433 output.push(0x0B);
434 i += 1;
435 }
436 b'0' => {
437 i += 1;
438 let val = parse_octal_digits(bytes, &mut i, 3);
439 output.push(val);
440 }
441 b'1'..=b'7' => {
442 let val = parse_octal_digits(bytes, &mut i, 3);
443 output.push(val);
444 }
445 b'x' => {
446 i += 1;
447 let val = parse_hex_digits(bytes, &mut i, 2);
448 output.push(val as u8);
449 }
450 _ => {
451 output.push(b'\\');
453 output.push(bytes[i]);
454 i += 1;
455 }
456 }
457 } else {
458 output.push(bytes[i]);
459 i += 1;
460 }
461 }
462 (output, false)
463}
464
465fn parse_octal_digits(data: &[u8], i: &mut usize, max_digits: usize) -> u8 {
467 let mut val: u32 = 0;
468 let mut count = 0;
469 while *i < data.len() && count < max_digits {
470 let ch = data[*i];
471 if ch >= b'0' && ch <= b'7' {
472 val = val * 8 + (ch - b'0') as u32;
473 *i += 1;
474 count += 1;
475 } else {
476 break;
477 }
478 }
479 (val & 0xFF) as u8
480}
481
482fn parse_hex_digits(data: &[u8], i: &mut usize, max_digits: usize) -> u32 {
484 let mut val: u32 = 0;
485 let mut count = 0;
486 while *i < data.len() && count < max_digits {
487 let ch = data[*i];
488 if ch.is_ascii_hexdigit() {
489 val = val * 16 + hex_digit_value(ch) as u32;
490 *i += 1;
491 count += 1;
492 } else {
493 break;
494 }
495 }
496 val
497}
498
499fn hex_digit_value(ch: u8) -> u8 {
500 match ch {
501 b'0'..=b'9' => ch - b'0',
502 b'a'..=b'f' => ch - b'a' + 10,
503 b'A'..=b'F' => ch - b'A' + 10,
504 _ => 0,
505 }
506}
507
508fn parse_decimal(data: &[u8], i: &mut usize) -> usize {
510 let mut val: usize = 0;
511 while *i < data.len() && data[*i].is_ascii_digit() {
512 val = val
513 .saturating_mul(10)
514 .saturating_add((data[*i] - b'0') as usize);
515 *i += 1;
516 }
517 val
518}
519
520fn parse_integer(s: &str) -> i64 {
523 let s = s.trim();
524 if s.is_empty() {
525 return 0;
526 }
527
528 if (s.starts_with('\'') || s.starts_with('"')) && s.len() >= 2 {
530 return s[1..].chars().next().map_or(0, |c| c as i64);
531 }
532
533 let (negative, digits) = if let Some(rest) = s.strip_prefix('-') {
535 (true, rest)
536 } else if let Some(rest) = s.strip_prefix('+') {
537 (false, rest)
538 } else {
539 (false, s)
540 };
541
542 if digits.is_empty() {
544 mark_conv_error(s);
545 return 0;
546 }
547
548 let magnitude = if let Some(hex) = digits
549 .strip_prefix("0x")
550 .or_else(|| digits.strip_prefix("0X"))
551 {
552 u64::from_str_radix(hex, 16).unwrap_or_else(|_| {
553 mark_conv_error(s);
554 0
555 })
556 } else if let Some(oct) = digits.strip_prefix('0') {
557 if oct.is_empty() {
558 0
559 } else {
560 u64::from_str_radix(oct, 8).unwrap_or_else(|_| {
561 mark_conv_error(s);
562 0
563 })
564 }
565 } else {
566 digits.parse::<u64>().unwrap_or_else(|_| {
567 mark_conv_error(s);
568 0
569 })
570 };
571
572 if negative {
573 -(magnitude as i64)
574 } else {
575 magnitude as i64
576 }
577}
578
579fn parse_unsigned(s: &str) -> u64 {
581 let s = s.trim();
582 if s.is_empty() {
583 return 0;
584 }
585
586 if (s.starts_with('\'') || s.starts_with('"')) && s.len() >= 2 {
588 return s[1..].chars().next().map_or(0, |c| c as u64);
589 }
590
591 let (negative, digits) = if let Some(rest) = s.strip_prefix('-') {
593 (true, rest)
594 } else if let Some(rest) = s.strip_prefix('+') {
595 (false, rest)
596 } else {
597 (false, s)
598 };
599
600 if digits.is_empty() {
602 mark_conv_error(s);
603 return 0;
604 }
605
606 let magnitude = if let Some(hex) = digits
607 .strip_prefix("0x")
608 .or_else(|| digits.strip_prefix("0X"))
609 {
610 u64::from_str_radix(hex, 16).unwrap_or_else(|_| {
611 mark_conv_error(s);
612 0
613 })
614 } else if let Some(oct) = digits.strip_prefix('0') {
615 if oct.is_empty() {
616 0
617 } else {
618 u64::from_str_radix(oct, 8).unwrap_or_else(|_| {
619 mark_conv_error(s);
620 0
621 })
622 }
623 } else {
624 digits.parse::<u64>().unwrap_or_else(|_| {
625 mark_conv_error(s);
626 0
627 })
628 };
629
630 if negative {
631 magnitude.wrapping_neg()
632 } else {
633 magnitude
634 }
635}
636
637fn parse_float(s: &str) -> f64 {
639 let s = s.trim();
640 if s.is_empty() {
641 return 0.0;
642 }
643
644 if (s.starts_with('\'') || s.starts_with('"')) && s.len() >= 2 {
646 return s[1..].chars().next().map_or(0.0, |c| c as u32 as f64);
647 }
648
649 if s.starts_with("0x") || s.starts_with("0X") || s.starts_with("-0x") || s.starts_with("-0X") {
651 return parse_integer(s) as f64;
653 }
654
655 s.parse::<f64>().unwrap_or(0.0)
656}
657
658#[derive(Default)]
659struct FormatFlags {
660 left_align: bool,
661 plus_sign: bool,
662 space_sign: bool,
663 zero_pad: bool,
664 alternate: bool,
665}
666
667fn apply_string_format(
669 s: &str,
670 flags: &FormatFlags,
671 width: usize,
672 precision: Option<usize>,
673) -> Vec<u8> {
674 let truncated: &str;
675 let owned: String;
676 if let Some(prec) = precision {
677 if s.len() > prec {
678 owned = s.chars().take(prec).collect();
680 truncated = &owned;
681 } else {
682 truncated = s;
683 }
684 } else {
685 truncated = s;
686 }
687
688 apply_padding(truncated.as_bytes(), flags, width)
689}
690
691fn apply_string_format_bytes(
693 s: &[u8],
694 flags: &FormatFlags,
695 width: usize,
696 precision: Option<usize>,
697) -> Vec<u8> {
698 let data = if let Some(prec) = precision {
699 if s.len() > prec { &s[..prec] } else { s }
700 } else {
701 s
702 };
703
704 apply_padding(data, flags, width)
705}
706
707fn apply_padding(data: &[u8], flags: &FormatFlags, width: usize) -> Vec<u8> {
709 if width == 0 || data.len() >= width {
710 return data.to_vec();
711 }
712 let pad_len = width - data.len();
713 let mut result = Vec::with_capacity(width);
714 if flags.left_align {
715 result.extend_from_slice(data);
716 result.resize(result.len() + pad_len, b' ');
717 } else {
718 result.resize(pad_len, b' ');
719 result.extend_from_slice(data);
720 }
721 result
722}
723
724fn apply_numeric_format(
726 num_str: &str,
727 is_negative: bool,
728 flags: &FormatFlags,
729 width: usize,
730 precision: Option<usize>,
731) -> String {
732 let digits = if is_negative {
734 &num_str[1..] } else {
736 num_str
737 };
738
739 let digits = if let Some(prec) = precision {
740 if prec > 0 && digits.len() < prec {
741 let padding = "0".repeat(prec - digits.len());
742 format!("{}{}", padding, digits)
743 } else if prec == 0 && digits == "0" {
744 String::new()
745 } else {
746 digits.to_string()
747 }
748 } else {
749 digits.to_string()
750 };
751
752 let sign = if is_negative {
753 "-".to_string()
754 } else if flags.plus_sign {
755 "+".to_string()
756 } else if flags.space_sign {
757 " ".to_string()
758 } else {
759 String::new()
760 };
761
762 let content = format!("{}{}", sign, digits);
763
764 if width > 0 && content.len() < width {
765 let pad_len = width - content.len();
766 if flags.left_align {
767 format!("{}{}", content, " ".repeat(pad_len))
768 } else if flags.zero_pad && precision.is_none() {
769 format!("{}{}{}", sign, "0".repeat(pad_len), digits)
770 } else {
771 format!("{}{}", " ".repeat(pad_len), content)
772 }
773 } else {
774 content
775 }
776}
777
778fn apply_float_format(
780 num_str: &str,
781 _is_negative: bool,
782 flags: &FormatFlags,
783 width: usize,
784) -> String {
785 let (sign_prefix, abs_str) = if num_str.starts_with('-') {
786 ("-", &num_str[1..])
787 } else if flags.plus_sign {
788 ("+", num_str)
789 } else if flags.space_sign {
790 (" ", num_str)
791 } else {
792 ("", num_str)
793 };
794
795 let content = format!("{}{}", sign_prefix, abs_str);
796
797 if width > 0 && content.len() < width {
798 let pad_len = width - content.len();
799 if flags.left_align {
800 format!("{}{}", content, " ".repeat(pad_len))
801 } else if flags.zero_pad {
802 format!("{}{}{}", sign_prefix, "0".repeat(pad_len), abs_str)
803 } else {
804 format!("{}{}", " ".repeat(pad_len), content)
805 }
806 } else {
807 content
808 }
809}
810
811fn format_scientific(value: f64, prec: usize, e_char: char) -> String {
813 if value == 0.0 {
814 let sign = if value.is_sign_negative() { "-" } else { "" };
815 if prec == 0 {
816 return format!("{sign}0{e_char}+00");
817 }
818 return format!("{sign}0.{:0>prec$}{e_char}+00", "", prec = prec);
819 }
820
821 let abs = value.abs();
822 let sign = if value < 0.0 { "-" } else { "" };
823 let exp = abs.log10().floor() as i32;
824 let mantissa = abs / 10f64.powi(exp);
825
826 let factor = 10f64.powi(prec as i32);
827 let mantissa = (mantissa * factor).round() / factor;
828
829 let (mantissa, exp) = if mantissa >= 10.0 {
830 (mantissa / 10.0, exp + 1)
831 } else {
832 (mantissa, exp)
833 };
834
835 let exp_sign = if exp >= 0 { '+' } else { '-' };
836 let exp_abs = exp.unsigned_abs();
837
838 if prec == 0 {
839 format!("{sign}{mantissa:.0}{e_char}{exp_sign}{exp_abs:02}")
840 } else {
841 format!(
842 "{sign}{mantissa:.prec$}{e_char}{exp_sign}{exp_abs:02}",
843 prec = prec
844 )
845 }
846}
847
848fn format_g(value: f64, prec: usize, upper: bool) -> String {
850 let prec = if prec == 0 { 1 } else { prec };
851
852 if value == 0.0 {
853 let sign = if value.is_sign_negative() { "-" } else { "" };
854 return format!("{sign}0");
855 }
856
857 let abs = value.abs();
858 let exp = abs.log10().floor() as i32;
859 let e_char = if upper { 'E' } else { 'e' };
860
861 if exp < -4 || exp >= prec as i32 {
862 let sig_prec = prec.saturating_sub(1);
863 let s = format_scientific(value, sig_prec, e_char);
864 trim_g_trailing_zeros(&s)
865 } else {
866 let decimal_prec = if prec as i32 > exp + 1 {
867 (prec as i32 - exp - 1) as usize
868 } else {
869 0
870 };
871 let s = format!("{value:.decimal_prec$}");
872 trim_g_trailing_zeros(&s)
873 }
874}
875
876fn shell_quote(s: &str) -> String {
884 if s.is_empty() {
885 return "''".to_string();
886 }
887
888 let needs_quoting = s.bytes().any(|b| {
890 !b.is_ascii_alphanumeric()
891 && b != b'_'
892 && b != b'/'
893 && b != b'.'
894 && b != b'-'
895 && b != b':'
896 && b != b','
897 && b != b'+'
898 && b != b'@'
899 && b != b'%'
900 });
901
902 if !needs_quoting {
903 return s.to_string();
904 }
905
906 let has_control = s.bytes().any(|b| b < 0x20 || b == 0x7f || b >= 0x80);
908 let has_single_quote = s.contains('\'');
909
910 if has_control {
911 let mut result = String::from("$'");
913 for byte in s.bytes() {
914 match byte {
915 b'\'' => result.push_str("\\'"),
916 b'\\' => result.push_str("\\\\"),
917 b'\n' => result.push_str("\\n"),
918 b'\t' => result.push_str("\\t"),
919 b'\r' => result.push_str("\\r"),
920 0x07 => result.push_str("\\a"),
921 0x08 => result.push_str("\\b"),
922 0x0c => result.push_str("\\f"),
923 0x0b => result.push_str("\\v"),
924 0x1b => result.push_str("\\E"),
925 b if b < 0x20 || b == 0x7f => {
926 result.push_str(&format!("\\{:03o}", b));
927 }
928 b if b >= 0x80 => {
929 result.push_str(&format!("\\{:03o}", b));
930 }
931 _ => result.push(byte as char),
932 }
933 }
934 result.push('\'');
935 result
936 } else if !has_single_quote {
937 format!("'{}'", s)
939 } else {
940 let unsafe_for_dquote = s
944 .bytes()
945 .any(|b| b == b'$' || b == b'`' || b == b'\\' || b == b'!' || b == b'"');
946 if !unsafe_for_dquote {
947 format!("\"{}\"", s)
949 } else {
950 let mut result = String::from("$'");
952 for byte in s.bytes() {
953 match byte {
954 b'\'' => result.push_str("\\'"),
955 b'\\' => result.push_str("\\\\"),
956 _ => result.push(byte as char),
957 }
958 }
959 result.push('\'');
960 result
961 }
962 }
963}
964
965fn trim_g_trailing_zeros(s: &str) -> String {
968 if let Some(e_pos) = s.find(['e', 'E']) {
969 let (mantissa, exponent) = s.split_at(e_pos);
970 if mantissa.contains('.') {
971 let trimmed = mantissa.trim_end_matches('0').trim_end_matches('.');
972 format!("{trimmed}{exponent}")
973 } else {
974 s.to_string()
975 }
976 } else if s.contains('.') {
977 s.trim_end_matches('0').trim_end_matches('.').to_string()
978 } else {
979 s.to_string()
980 }
981}