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
32fn mark_range_error(s: &str) {
33 eprintln!("printf: '{}': Numerical result out of range", s);
34 CONV_ERROR.with(|c| c.set(true));
35}
36
37pub fn process_format_string(format: &str, args: &[&str]) -> Vec<u8> {
43 let mut output = Vec::with_capacity(256);
44 let fmt_bytes = format.as_bytes();
45
46 if args.is_empty() {
47 let stop = format_one_pass(fmt_bytes, args, &mut 0, &mut output);
49 if stop {
50 if output.last() == Some(&STOP_OUTPUT) {
52 output.pop();
53 }
54 }
55 return output;
56 }
57
58 let mut arg_idx: usize = 0;
59 loop {
60 let start_idx = arg_idx;
61 let stop = format_one_pass(fmt_bytes, args, &mut arg_idx, &mut output);
62 if stop {
63 if output.last() == Some(&STOP_OUTPUT) {
64 output.pop();
65 }
66 break;
67 }
68 if arg_idx == start_idx || arg_idx >= args.len() {
70 break;
71 }
72 }
73
74 output
75}
76
77fn format_one_pass(fmt: &[u8], args: &[&str], arg_idx: &mut usize, output: &mut Vec<u8>) -> bool {
80 let mut i = 0;
81 while i < fmt.len() {
82 match fmt[i] {
83 b'%' => {
84 i += 1;
85 if i >= fmt.len() {
86 output.push(b'%');
87 break;
88 }
89 if fmt[i] == b'%' {
90 output.push(b'%');
91 i += 1;
92 continue;
93 }
94 let stop = process_conversion(fmt, &mut i, args, arg_idx, output);
95 if stop {
96 return true;
97 }
98 }
99 b'\\' => {
100 i += 1;
101 let stop = process_format_escape(fmt, &mut i, output);
102 if stop {
103 return true;
104 }
105 }
106 ch => {
107 output.push(ch);
108 i += 1;
109 }
110 }
111 }
112 false
113}
114
115fn process_conversion(
118 fmt: &[u8],
119 i: &mut usize,
120 args: &[&str],
121 arg_idx: &mut usize,
122 output: &mut Vec<u8>,
123) -> bool {
124 let mut flags = FormatFlags::default();
126 while *i < fmt.len() {
127 match fmt[*i] {
128 b'-' => flags.left_align = true,
129 b'+' => flags.plus_sign = true,
130 b' ' => flags.space_sign = true,
131 b'0' => flags.zero_pad = true,
132 b'#' => flags.alternate = true,
133 _ => break,
134 }
135 *i += 1;
136 }
137
138 let (width, dyn_left_align) = if *i < fmt.len() && fmt[*i] == b'*' {
140 *i += 1;
141 let width_arg = consume_arg(args, arg_idx);
142 let w: i64 = width_arg.parse().unwrap_or(0);
143 if w < 0 {
144 ((-w) as usize, true) } else {
146 (w as usize, false)
147 }
148 } else {
149 (parse_decimal(fmt, i), false)
150 };
151 if dyn_left_align {
152 flags.left_align = true;
153 }
154
155 let precision = if *i < fmt.len() && fmt[*i] == b'.' {
157 *i += 1;
158 if *i < fmt.len() && fmt[*i] == b'*' {
159 *i += 1;
160 let prec_arg = consume_arg(args, arg_idx);
161 let p: i64 = prec_arg.parse().unwrap_or(0);
162 Some(if p < 0 { 0 } else { p as usize })
163 } else {
164 Some(parse_decimal(fmt, i))
165 }
166 } else {
167 None
168 };
169
170 if *i >= fmt.len() {
172 return false;
173 }
174 let conv = fmt[*i];
175 *i += 1;
176
177 let arg = consume_arg(args, arg_idx);
178
179 match conv {
180 b's' => {
181 let s = arg;
182 let formatted = apply_string_format(s, &flags, width, precision);
183 output.extend_from_slice(&formatted);
184 }
185 b'b' => {
186 let (bytes, stop) = process_b_argument(arg);
187 let formatted = apply_string_format_bytes(&bytes, &flags, width, precision);
188 output.extend_from_slice(&formatted);
189 if stop {
190 return true;
191 }
192 }
193 b'c' => {
194 if let Some(ch) = arg.chars().next() {
195 let mut buf = [0u8; 4];
196 let encoded = ch.encode_utf8(&mut buf);
197 let formatted = apply_string_format(encoded, &flags, width, precision);
198 output.extend_from_slice(&formatted);
199 } else {
200 let formatted = apply_string_format_bytes(&[0], &flags, width, precision);
202 output.extend_from_slice(&formatted);
203 }
204 }
205 b'd' | b'i' => {
206 let val = parse_integer(arg);
207 let mut buf = itoa::Buffer::new();
208 let s = buf.format(val);
209 let formatted = apply_numeric_format(s, val < 0, &flags, width, precision);
210 output.extend_from_slice(formatted.as_bytes());
211 }
212 b'u' => {
213 let val = parse_unsigned(arg);
214 let mut buf = itoa::Buffer::new();
215 let s = buf.format(val);
216 let formatted = apply_numeric_format(s, false, &flags, width, precision);
217 output.extend_from_slice(formatted.as_bytes());
218 }
219 b'o' => {
220 let val = parse_unsigned(arg);
221 let s = format!("{:o}", val);
222 let prefix = if flags.alternate && !s.starts_with('0') {
223 "0"
224 } else {
225 ""
226 };
227 let full = format!("{}{}", prefix, s);
228 let formatted = apply_numeric_format(&full, false, &flags, width, precision);
229 output.extend_from_slice(formatted.as_bytes());
230 }
231 b'x' => {
232 let val = parse_unsigned(arg);
233 let s = format!("{:x}", val);
234 let prefix = if flags.alternate && val != 0 {
235 "0x"
236 } else {
237 ""
238 };
239 let full = format!("{}{}", prefix, s);
240 let formatted = apply_numeric_format(&full, false, &flags, width, precision);
241 output.extend_from_slice(formatted.as_bytes());
242 }
243 b'X' => {
244 let val = parse_unsigned(arg);
245 let s = format!("{:X}", val);
246 let prefix = if flags.alternate && val != 0 {
247 "0X"
248 } else {
249 ""
250 };
251 let full = format!("{}{}", prefix, s);
252 let formatted = apply_numeric_format(&full, false, &flags, width, precision);
253 output.extend_from_slice(formatted.as_bytes());
254 }
255 b'f' => {
256 let val = parse_float(arg);
257 let prec = precision.unwrap_or(6);
258 let s = format!("{:.prec$}", val, prec = prec);
259 let formatted = apply_float_format(&s, val < 0.0, &flags, width);
260 output.extend_from_slice(formatted.as_bytes());
261 }
262 b'e' => {
263 let val = parse_float(arg);
264 let prec = precision.unwrap_or(6);
265 let s = format_scientific(val, prec, 'e');
266 let formatted = apply_float_format(&s, val < 0.0, &flags, width);
267 output.extend_from_slice(formatted.as_bytes());
268 }
269 b'E' => {
270 let val = parse_float(arg);
271 let prec = precision.unwrap_or(6);
272 let s = format_scientific(val, prec, 'E');
273 let formatted = apply_float_format(&s, val < 0.0, &flags, width);
274 output.extend_from_slice(formatted.as_bytes());
275 }
276 b'g' => {
277 let val = parse_float(arg);
278 let prec = precision.unwrap_or(6);
279 let s = format_g(val, prec, false);
280 let formatted = apply_float_format(&s, val < 0.0, &flags, width);
281 output.extend_from_slice(formatted.as_bytes());
282 }
283 b'G' => {
284 let val = parse_float(arg);
285 let prec = precision.unwrap_or(6);
286 let s = format_g(val, prec, true);
287 let formatted = apply_float_format(&s, val < 0.0, &flags, width);
288 output.extend_from_slice(formatted.as_bytes());
289 }
290 b'q' => {
291 let s = arg;
292 let quoted = shell_quote(s);
293 let formatted = apply_string_format("ed, &flags, width, precision);
294 output.extend_from_slice(&formatted);
295 }
296 _ => {
297 output.push(b'%');
299 output.push(conv);
300 }
301 }
302 false
303}
304
305fn consume_arg<'a>(args: &[&'a str], arg_idx: &mut usize) -> &'a str {
307 if *arg_idx < args.len() {
308 let val = args[*arg_idx];
309 *arg_idx += 1;
310 val
311 } else {
312 ""
313 }
314}
315
316fn process_format_escape(fmt: &[u8], i: &mut usize, output: &mut Vec<u8>) -> bool {
319 if *i >= fmt.len() {
320 output.push(b'\\');
321 return false;
322 }
323 match fmt[*i] {
324 b'\\' => {
325 output.push(b'\\');
326 *i += 1;
327 }
328 b'"' => {
329 output.push(b'"');
330 *i += 1;
331 }
332 b'a' => {
333 output.push(0x07);
334 *i += 1;
335 }
336 b'b' => {
337 output.push(0x08);
338 *i += 1;
339 }
340 b'c' => {
341 return true;
342 }
343 b'e' | b'E' => {
344 output.push(0x1B);
345 *i += 1;
346 }
347 b'f' => {
348 output.push(0x0C);
349 *i += 1;
350 }
351 b'n' => {
352 output.push(b'\n');
353 *i += 1;
354 }
355 b'r' => {
356 output.push(b'\r');
357 *i += 1;
358 }
359 b't' => {
360 output.push(b'\t');
361 *i += 1;
362 }
363 b'v' => {
364 output.push(0x0B);
365 *i += 1;
366 }
367 b'0' => {
368 *i += 1;
370 let val = parse_octal_digits(fmt, i, 3);
371 output.push(val);
372 }
373 b'1'..=b'7' => {
374 let val = parse_octal_digits(fmt, i, 3);
376 output.push(val);
377 }
378 b'x' => {
379 *i += 1;
380 let val = parse_hex_digits(fmt, i, 2);
381 output.push(val as u8);
382 }
383 b'u' => {
384 *i += 1;
385 let val = parse_hex_digits(fmt, i, 4);
386 if let Some(ch) = char::from_u32(val) {
387 let mut buf = [0u8; 4];
388 let encoded = ch.encode_utf8(&mut buf);
389 output.extend_from_slice(encoded.as_bytes());
390 }
391 }
392 b'U' => {
393 *i += 1;
394 let val = parse_hex_digits(fmt, i, 8);
395 if let Some(ch) = char::from_u32(val) {
396 let mut buf = [0u8; 4];
397 let encoded = ch.encode_utf8(&mut buf);
398 output.extend_from_slice(encoded.as_bytes());
399 }
400 }
401 _ => {
402 output.push(b'\\');
404 output.push(fmt[*i]);
405 *i += 1;
406 }
407 }
408 false
409}
410
411fn process_b_argument(arg: &str) -> (Vec<u8>, bool) {
414 let bytes = arg.as_bytes();
415 let mut output = Vec::with_capacity(bytes.len());
416 let mut i = 0;
417 while i < bytes.len() {
418 if bytes[i] == b'\\' {
419 i += 1;
420 if i >= bytes.len() {
421 output.push(b'\\');
422 break;
423 }
424 match bytes[i] {
425 b'\\' => {
426 output.push(b'\\');
427 i += 1;
428 }
429 b'a' => {
430 output.push(0x07);
431 i += 1;
432 }
433 b'b' => {
434 output.push(0x08);
435 i += 1;
436 }
437 b'c' => {
438 return (output, true);
439 }
440 b'e' | b'E' => {
441 output.push(0x1B);
442 i += 1;
443 }
444 b'f' => {
445 output.push(0x0C);
446 i += 1;
447 }
448 b'n' => {
449 output.push(b'\n');
450 i += 1;
451 }
452 b'r' => {
453 output.push(b'\r');
454 i += 1;
455 }
456 b't' => {
457 output.push(b'\t');
458 i += 1;
459 }
460 b'v' => {
461 output.push(0x0B);
462 i += 1;
463 }
464 b'0' => {
465 i += 1;
466 let val = parse_octal_digits(bytes, &mut i, 3);
467 output.push(val);
468 }
469 b'1'..=b'7' => {
470 let val = parse_octal_digits(bytes, &mut i, 3);
471 output.push(val);
472 }
473 b'x' => {
474 i += 1;
475 let val = parse_hex_digits(bytes, &mut i, 2);
476 output.push(val as u8);
477 }
478 _ => {
479 output.push(b'\\');
481 output.push(bytes[i]);
482 i += 1;
483 }
484 }
485 } else {
486 output.push(bytes[i]);
487 i += 1;
488 }
489 }
490 (output, false)
491}
492
493fn parse_octal_digits(data: &[u8], i: &mut usize, max_digits: usize) -> u8 {
495 let mut val: u32 = 0;
496 let mut count = 0;
497 while *i < data.len() && count < max_digits {
498 let ch = data[*i];
499 if ch >= b'0' && ch <= b'7' {
500 val = val * 8 + (ch - b'0') as u32;
501 *i += 1;
502 count += 1;
503 } else {
504 break;
505 }
506 }
507 (val & 0xFF) as u8
508}
509
510fn parse_hex_digits(data: &[u8], i: &mut usize, max_digits: usize) -> u32 {
512 let mut val: u32 = 0;
513 let mut count = 0;
514 while *i < data.len() && count < max_digits {
515 let ch = data[*i];
516 if ch.is_ascii_hexdigit() {
517 val = val * 16 + hex_digit_value(ch) as u32;
518 *i += 1;
519 count += 1;
520 } else {
521 break;
522 }
523 }
524 val
525}
526
527fn hex_digit_value(ch: u8) -> u8 {
528 match ch {
529 b'0'..=b'9' => ch - b'0',
530 b'a'..=b'f' => ch - b'a' + 10,
531 b'A'..=b'F' => ch - b'A' + 10,
532 _ => 0,
533 }
534}
535
536fn parse_decimal(data: &[u8], i: &mut usize) -> usize {
538 let mut val: usize = 0;
539 while *i < data.len() && data[*i].is_ascii_digit() {
540 val = val
541 .saturating_mul(10)
542 .saturating_add((data[*i] - b'0') as usize);
543 *i += 1;
544 }
545 val
546}
547
548fn parse_integer(s: &str) -> i64 {
551 let s = s.trim();
552 if s.is_empty() {
553 return 0;
554 }
555
556 if (s.starts_with('\'') || s.starts_with('"')) && s.len() >= 2 {
558 return s[1..].chars().next().map_or(0, |c| c as i64);
559 }
560
561 let (negative, digits) = if let Some(rest) = s.strip_prefix('-') {
563 (true, rest)
564 } else if let Some(rest) = s.strip_prefix('+') {
565 (false, rest)
566 } else {
567 (false, s)
568 };
569
570 if digits.is_empty() {
572 mark_conv_error(s);
573 return 0;
574 }
575
576 let magnitude = if let Some(hex) = digits
577 .strip_prefix("0x")
578 .or_else(|| digits.strip_prefix("0X"))
579 {
580 match u64::from_str_radix(hex, 16) {
581 Ok(v) => v,
582 Err(e) if e.kind() == &std::num::IntErrorKind::PosOverflow => {
583 mark_range_error(s);
584 if negative {
585 return i64::MIN;
586 }
587 return i64::MAX;
588 }
589 Err(_) => {
590 mark_conv_error(s);
591 0
592 }
593 }
594 } else if let Some(oct) = digits.strip_prefix('0') {
595 if oct.is_empty() {
596 0
597 } else {
598 match u64::from_str_radix(oct, 8) {
599 Ok(v) => v,
600 Err(e) if e.kind() == &std::num::IntErrorKind::PosOverflow => {
601 mark_range_error(s);
602 if negative {
603 return i64::MIN;
604 }
605 return i64::MAX;
606 }
607 Err(_) => {
608 mark_conv_error(s);
609 0
610 }
611 }
612 }
613 } else {
614 match digits.parse::<u64>() {
615 Ok(v) => v,
616 Err(e) if e.kind() == &std::num::IntErrorKind::PosOverflow => {
617 mark_range_error(s);
618 if negative {
619 return i64::MIN;
620 }
621 return i64::MAX;
622 }
623 Err(_) => {
624 mark_conv_error(s);
625 0
626 }
627 }
628 };
629
630 if negative {
631 -(magnitude as i64)
632 } else {
633 magnitude as i64
634 }
635}
636
637fn parse_unsigned(s: &str) -> u64 {
639 let s = s.trim();
640 if s.is_empty() {
641 return 0;
642 }
643
644 if (s.starts_with('\'') || s.starts_with('"')) && s.len() >= 2 {
646 return s[1..].chars().next().map_or(0, |c| c as u64);
647 }
648
649 let (negative, digits) = if let Some(rest) = s.strip_prefix('-') {
651 (true, rest)
652 } else if let Some(rest) = s.strip_prefix('+') {
653 (false, rest)
654 } else {
655 (false, s)
656 };
657
658 if digits.is_empty() {
660 mark_conv_error(s);
661 return 0;
662 }
663
664 let magnitude = if let Some(hex) = digits
665 .strip_prefix("0x")
666 .or_else(|| digits.strip_prefix("0X"))
667 {
668 match u64::from_str_radix(hex, 16) {
669 Ok(v) => v,
670 Err(e) if e.kind() == &std::num::IntErrorKind::PosOverflow => {
671 mark_range_error(s);
672 u64::MAX
673 }
674 Err(_) => {
675 mark_conv_error(s);
676 0
677 }
678 }
679 } else if let Some(oct) = digits.strip_prefix('0') {
680 if oct.is_empty() {
681 0
682 } else {
683 match u64::from_str_radix(oct, 8) {
684 Ok(v) => v,
685 Err(e) if e.kind() == &std::num::IntErrorKind::PosOverflow => {
686 mark_range_error(s);
687 u64::MAX
688 }
689 Err(_) => {
690 mark_conv_error(s);
691 0
692 }
693 }
694 }
695 } else {
696 match digits.parse::<u64>() {
697 Ok(v) => v,
698 Err(e) if e.kind() == &std::num::IntErrorKind::PosOverflow => {
699 mark_range_error(s);
700 u64::MAX
701 }
702 Err(_) => {
703 mark_conv_error(s);
704 0
705 }
706 }
707 };
708
709 if negative {
710 magnitude.wrapping_neg()
711 } else {
712 magnitude
713 }
714}
715
716fn parse_float(s: &str) -> f64 {
718 let s = s.trim();
719 if s.is_empty() {
720 return 0.0;
721 }
722
723 if (s.starts_with('\'') || s.starts_with('"')) && s.len() >= 2 {
725 return s[1..].chars().next().map_or(0.0, |c| c as u32 as f64);
726 }
727
728 if s.starts_with("0x") || s.starts_with("0X") || s.starts_with("-0x") || s.starts_with("-0X") {
730 return parse_integer(s) as f64;
732 }
733
734 s.parse::<f64>().unwrap_or(0.0)
735}
736
737#[derive(Default)]
738struct FormatFlags {
739 left_align: bool,
740 plus_sign: bool,
741 space_sign: bool,
742 zero_pad: bool,
743 alternate: bool,
744}
745
746fn apply_string_format(
748 s: &str,
749 flags: &FormatFlags,
750 width: usize,
751 precision: Option<usize>,
752) -> Vec<u8> {
753 let truncated: &str;
754 let owned: String;
755 if let Some(prec) = precision {
756 if s.len() > prec {
757 owned = s.chars().take(prec).collect();
759 truncated = &owned;
760 } else {
761 truncated = s;
762 }
763 } else {
764 truncated = s;
765 }
766
767 apply_padding(truncated.as_bytes(), flags, width)
768}
769
770fn apply_string_format_bytes(
772 s: &[u8],
773 flags: &FormatFlags,
774 width: usize,
775 precision: Option<usize>,
776) -> Vec<u8> {
777 let data = if let Some(prec) = precision {
778 if s.len() > prec { &s[..prec] } else { s }
779 } else {
780 s
781 };
782
783 apply_padding(data, flags, width)
784}
785
786fn apply_padding(data: &[u8], flags: &FormatFlags, width: usize) -> Vec<u8> {
788 if width == 0 || data.len() >= width {
789 return data.to_vec();
790 }
791 let pad_len = width - data.len();
792 let mut result = Vec::with_capacity(width);
793 if flags.left_align {
794 result.extend_from_slice(data);
795 result.resize(result.len() + pad_len, b' ');
796 } else {
797 result.resize(pad_len, b' ');
798 result.extend_from_slice(data);
799 }
800 result
801}
802
803fn apply_numeric_format(
805 num_str: &str,
806 is_negative: bool,
807 flags: &FormatFlags,
808 width: usize,
809 precision: Option<usize>,
810) -> String {
811 let raw_digits = if is_negative {
813 &num_str[1..] } else {
815 num_str
816 };
817
818 let sign: &str = if is_negative {
819 "-"
820 } else if flags.plus_sign {
821 "+"
822 } else if flags.space_sign {
823 " "
824 } else {
825 ""
826 };
827
828 let mut digits_buf = String::new();
830 let digits: &str = if let Some(prec) = precision {
831 if prec > 0 && raw_digits.len() < prec {
832 let pad = prec - raw_digits.len();
833 digits_buf.reserve(prec);
834 for _ in 0..pad {
835 digits_buf.push('0');
836 }
837 digits_buf.push_str(raw_digits);
838 &digits_buf
839 } else if prec == 0 && raw_digits == "0" {
840 ""
841 } else {
842 raw_digits
843 }
844 } else {
845 raw_digits
846 };
847
848 let content_len = sign.len() + digits.len();
849
850 if width > 0 && content_len < width {
851 let pad_len = width - content_len;
852 let mut result = String::with_capacity(width);
853 if flags.left_align {
854 result.push_str(sign);
855 result.push_str(digits);
856 for _ in 0..pad_len {
857 result.push(' ');
858 }
859 } else if flags.zero_pad && precision.is_none() {
860 result.push_str(sign);
861 for _ in 0..pad_len {
862 result.push('0');
863 }
864 result.push_str(digits);
865 } else {
866 for _ in 0..pad_len {
867 result.push(' ');
868 }
869 result.push_str(sign);
870 result.push_str(digits);
871 }
872 result
873 } else {
874 let mut result = String::with_capacity(content_len);
875 result.push_str(sign);
876 result.push_str(digits);
877 result
878 }
879}
880
881fn apply_float_format(
883 num_str: &str,
884 _is_negative: bool,
885 flags: &FormatFlags,
886 width: usize,
887) -> String {
888 let (sign_prefix, abs_str) = if num_str.starts_with('-') {
889 ("-", &num_str[1..])
890 } else if flags.plus_sign {
891 ("+", num_str)
892 } else if flags.space_sign {
893 (" ", num_str)
894 } else {
895 ("", num_str)
896 };
897
898 let content_len = sign_prefix.len() + abs_str.len();
899
900 if width > 0 && content_len < width {
901 let pad_len = width - content_len;
902 let mut result = String::with_capacity(width);
903 if flags.left_align {
904 result.push_str(sign_prefix);
905 result.push_str(abs_str);
906 for _ in 0..pad_len {
907 result.push(' ');
908 }
909 } else if flags.zero_pad {
910 result.push_str(sign_prefix);
911 for _ in 0..pad_len {
912 result.push('0');
913 }
914 result.push_str(abs_str);
915 } else {
916 for _ in 0..pad_len {
917 result.push(' ');
918 }
919 result.push_str(sign_prefix);
920 result.push_str(abs_str);
921 }
922 result
923 } else {
924 let mut result = String::with_capacity(content_len);
925 result.push_str(sign_prefix);
926 result.push_str(abs_str);
927 result
928 }
929}
930
931fn format_scientific(value: f64, prec: usize, e_char: char) -> String {
933 if value == 0.0 {
934 let sign = if value.is_sign_negative() { "-" } else { "" };
935 if prec == 0 {
936 return format!("{sign}0{e_char}+00");
937 }
938 return format!("{sign}0.{:0>prec$}{e_char}+00", "", prec = prec);
939 }
940
941 let abs = value.abs();
942 let sign = if value < 0.0 { "-" } else { "" };
943 let exp = abs.log10().floor() as i32;
944 let mantissa = abs / 10f64.powi(exp);
945
946 let factor = 10f64.powi(prec as i32);
947 let mantissa = (mantissa * factor).round() / factor;
948
949 let (mantissa, exp) = if mantissa >= 10.0 {
950 (mantissa / 10.0, exp + 1)
951 } else {
952 (mantissa, exp)
953 };
954
955 let exp_sign = if exp >= 0 { '+' } else { '-' };
956 let exp_abs = exp.unsigned_abs();
957
958 if prec == 0 {
959 format!("{sign}{mantissa:.0}{e_char}{exp_sign}{exp_abs:02}")
960 } else {
961 format!(
962 "{sign}{mantissa:.prec$}{e_char}{exp_sign}{exp_abs:02}",
963 prec = prec
964 )
965 }
966}
967
968fn format_g(value: f64, prec: usize, upper: bool) -> String {
970 let prec = if prec == 0 { 1 } else { prec };
971
972 if value == 0.0 {
973 let sign = if value.is_sign_negative() { "-" } else { "" };
974 return format!("{sign}0");
975 }
976
977 let abs = value.abs();
978 let exp = abs.log10().floor() as i32;
979 let e_char = if upper { 'E' } else { 'e' };
980
981 if exp < -4 || exp >= prec as i32 {
982 let sig_prec = prec.saturating_sub(1);
983 let s = format_scientific(value, sig_prec, e_char);
984 trim_g_trailing_zeros(&s)
985 } else {
986 let decimal_prec = if prec as i32 > exp + 1 {
987 (prec as i32 - exp - 1) as usize
988 } else {
989 0
990 };
991 let s = format!("{value:.decimal_prec$}");
992 trim_g_trailing_zeros(&s)
993 }
994}
995
996fn shell_quote(s: &str) -> String {
1004 if s.is_empty() {
1005 return "''".to_string();
1006 }
1007
1008 let needs_quoting = s.starts_with('~')
1010 || s.bytes().any(|b| {
1011 !b.is_ascii_alphanumeric()
1012 && b != b'_'
1013 && b != b'/'
1014 && b != b'.'
1015 && b != b'-'
1016 && b != b':'
1017 && b != b','
1018 && b != b'+'
1019 && b != b'@'
1020 && b != b'%'
1021 && b != b'='
1022 && b != b'^'
1023 && b != b'~'
1024 });
1025
1026 if !needs_quoting {
1027 return s.to_string();
1028 }
1029
1030 let has_control = s.bytes().any(|b| b < 0x20 || b == 0x7f || b >= 0x80);
1031 let has_single_quote = s.contains('\'');
1032
1033 if has_control {
1034 let mut result = String::new();
1038 let bytes = s.as_bytes();
1039 let mut i = 0;
1040 let mut need_sq_start = true;
1042
1043 while i < bytes.len() {
1044 if is_control_byte(bytes[i]) {
1045 if need_sq_start {
1046 result.push_str("''");
1048 need_sq_start = false;
1049 }
1050 result.push_str("$'");
1052 while i < bytes.len() && is_control_byte(bytes[i]) {
1053 emit_escape(bytes[i], &mut result);
1054 i += 1;
1055 }
1056 result.push('\'');
1057 } else {
1058 need_sq_start = false;
1059 result.push('\'');
1061 while i < bytes.len() && !is_control_byte(bytes[i]) {
1062 if bytes[i] == b'\'' {
1063 result.push_str("'\\'");
1065 } else {
1066 result.push(bytes[i] as char);
1067 }
1068 i += 1;
1069 }
1070 result.push('\'');
1071 }
1072 }
1073 result
1074 } else if !has_single_quote {
1075 format!("'{}'", s)
1076 } else {
1077 let unsafe_for_dquote = s
1079 .bytes()
1080 .any(|b| b == b'$' || b == b'`' || b == b'\\' || b == b'!' || b == b'"');
1081 if !unsafe_for_dquote {
1082 format!("\"{}\"", s)
1083 } else {
1084 let mut result = String::from("'");
1086 for byte in s.bytes() {
1087 if byte == b'\'' {
1088 result.push_str("'\\''");
1089 } else {
1090 result.push(byte as char);
1091 }
1092 }
1093 result.push('\'');
1094 result
1095 }
1096 }
1097}
1098
1099fn is_control_byte(b: u8) -> bool {
1100 b < 0x20 || b == 0x7f || b >= 0x80
1101}
1102
1103fn emit_escape(byte: u8, result: &mut String) {
1104 match byte {
1105 b'\n' => result.push_str("\\n"),
1106 b'\t' => result.push_str("\\t"),
1107 b'\r' => result.push_str("\\r"),
1108 0x07 => result.push_str("\\a"),
1109 0x08 => result.push_str("\\b"),
1110 0x0c => result.push_str("\\f"),
1111 0x0b => result.push_str("\\v"),
1112 0x1b => result.push_str("\\E"),
1113 b => {
1114 result.push_str(&format!("\\{:03o}", b));
1115 }
1116 }
1117}
1118
1119fn trim_g_trailing_zeros(s: &str) -> String {
1122 if let Some(e_pos) = s.find(['e', 'E']) {
1123 let (mantissa, exponent) = s.split_at(e_pos);
1124 if mantissa.contains('.') {
1125 let trimmed = mantissa.trim_end_matches('0').trim_end_matches('.');
1126 format!("{trimmed}{exponent}")
1127 } else {
1128 s.to_string()
1129 }
1130 } else if s.contains('.') {
1131 s.trim_end_matches('0').trim_end_matches('.').to_string()
1132 } else {
1133 s.to_string()
1134 }
1135}