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