1 // Copyright 2016 The Rust Project Developers. See the COPYRIGHT
2 // file at the top-level directory of this distribution and at
3 // http://rust-lang.org/COPYRIGHT.
5 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8 // option. This file may not be copied, modified, or distributed
9 // except according to those terms.
12 use super::strcursor::StrCursor as Cur;
14 /// Represents a single `printf`-style substitution.
15 #[derive(Clone, Debug)]
16 pub enum Substitution<'a> {
17 /// A formatted output substitution.
19 /// A literal `%%` escape.
23 impl<'a> Substitution<'a> {
24 pub fn as_str(&self) -> &str {
26 Substitution::Format(ref fmt) => fmt.span,
27 Substitution::Escape => "%%",
31 /// Translate this substitution into an equivalent Rust formatting directive.
33 /// This ignores cases where the substitution does not have an exact equivalent, or where
34 /// the substitution would be unnecessary.
35 pub fn translate(&self) -> Option<String> {
37 Substitution::Format(ref fmt) => fmt.translate(),
38 Substitution::Escape => None,
43 #[derive(Clone, Debug)]
44 /// A single `printf`-style formatting directive.
45 pub struct Format<'a> {
46 /// The entire original formatting directive.
48 /// The (1-based) parameter to be converted.
49 pub parameter: Option<u16>,
52 /// Minimum width of the output.
53 pub width: Option<Num>,
54 /// Precision of the conversion.
55 pub precision: Option<Num>,
56 /// Length modifier for the conversion.
57 pub length: Option<&'a str>,
58 /// Type of parameter being converted.
63 /// Translate this directive into an equivalent Rust formatting directive.
65 /// Returns `None` in cases where the `printf` directive does not have an exact Rust
66 /// equivalent, rather than guessing.
67 pub fn translate(&self) -> Option<String> {
70 let (c_alt, c_zero, c_left, c_plus) = {
71 let mut c_alt = false;
72 let mut c_zero = false;
73 let mut c_left = false;
74 let mut c_plus = false;
75 for c in self.flags.chars() {
84 (c_alt, c_zero, c_left, c_plus)
87 // Has a special form in Rust for numbers.
88 let fill = if c_zero { Some("0") } else { None };
90 let align = if c_left { Some("<") } else { None };
92 // Rust doesn't have an equivalent to the `' '` flag.
93 let sign = if c_plus { Some("+") } else { None };
95 // Not *quite* the same, depending on the type...
98 let width = match self.width {
100 // NOTE: Rust doesn't support this.
103 w @ Some(Num::Arg(_)) => w,
104 w @ Some(Num::Num(_)) => w,
108 let precision = self.precision;
110 // NOTE: although length *can* have an effect, we can't duplicate the effect in Rust, so
111 // we just ignore it.
113 let (type_, use_zero_fill, is_int) = match self.type_ {
114 "d" | "i" | "u" => (None, true, true),
115 "f" | "F" => (None, false, false),
116 "s" | "c" => (None, false, false),
117 "e" | "E" => (Some(self.type_), true, false),
118 "x" | "X" | "o" => (Some(self.type_), true, true),
119 "p" => (Some(self.type_), false, true),
120 "g" => (Some("e"), true, false),
121 "G" => (Some("E"), true, false),
125 let (fill, width, precision) = match (is_int, width, precision) {
126 (true, Some(_), Some(_)) => {
127 // Rust can't duplicate this insanity.
130 (true, None, Some(p)) => (Some("0"), Some(p), None),
131 (true, w, None) => (fill, w, None),
132 (false, w, p) => (fill, w, p),
135 let align = match (self.type_, width.is_some(), align.is_some()) {
136 ("s", true, false) => Some(">"),
140 let (fill, zero_fill) = match (fill, use_zero_fill) {
141 (Some("0"), true) => (None, true),
142 (fill, _) => (fill, false),
145 let alt = match type_ {
146 Some("x") | Some("X") => alt,
150 let has_options = fill.is_some()
156 || precision.is_some()
160 // Initialise with a rough guess.
161 let cap = self.span.len() + if has_options { 2 } else { 0 };
162 let mut s = String::with_capacity(cap);
166 if let Some(arg) = self.parameter {
167 write!(s, "{}", arg.checked_sub(1)?).ok()?;
173 let align = if let Some(fill) = fill {
180 if let Some(align) = align {
184 if let Some(sign) = sign {
196 if let Some(width) = width {
197 width.translate(&mut s).ok()?;
200 if let Some(precision) = precision {
202 precision.translate(&mut s).ok()?;
205 if let Some(type_) = type_ {
215 /// A general number used in a `printf` formatting directive.
216 #[derive(Copy, Clone, Debug)]
218 // The range of these values is technically bounded by `NL_ARGMAX`... but, at least for GNU
219 // libc, it apparently has no real fixed limit. A `u16` is used here on the basis that it
220 // is *vanishingly* unlikely that *anyone* is going to try formatting something wider, or
221 // with more precision, than 32 thousand positions which is so wide it couldn't possibly fit
224 /// A specific, fixed value.
226 /// The value is derived from a positional argument.
228 /// The value is derived from the "next" unconverted argument.
233 fn from_str(s: &str, arg: Option<&str>) -> Self {
234 if let Some(arg) = arg {
235 Num::Arg(arg.parse().expect(&format!("invalid format arg `{:?}`", arg)))
239 Num::Num(s.parse().expect(&format!("invalid format num `{:?}`", s)))
243 fn translate(&self, s: &mut String) -> ::std::fmt::Result {
246 Num::Num(n) => write!(s, "{}", n),
248 let n = try!(n.checked_sub(1).ok_or(::std::fmt::Error));
251 Num::Next => write!(s, "*"),
256 /// Returns an iterator over all substitutions in a given string.
257 pub fn iter_subs(s: &str) -> Substitutions {
263 /// Iterator over substitutions in a string.
264 pub struct Substitutions<'a> {
268 impl<'a> Iterator for Substitutions<'a> {
269 type Item = Substitution<'a>;
270 fn next(&mut self) -> Option<Self::Item> {
271 let (sub, tail) = parse_next_substitution(self.s)?;
276 fn size_hint(&self) -> (usize, Option<usize>) {
277 // Substitutions are at least 2 characters long.
278 (0, Some(self.s.len() / 2))
293 /// Parse the next substitution from the input string.
294 pub fn parse_next_substitution(s: &str) -> Option<(Substitution, &str)> {
298 let start = s.find('%')?;
299 match s[start+1..].chars().next()? {
300 '%' => return Some((Substitution::Escape, &s[start+2..])),
301 _ => {/* fall-through */},
304 Cur::new_at_start(&s[start..])
307 // This is meant to be a translation of the following regex:
312 // (?: (?P<parameter> \d+) \$ )?
313 // (?P<flags> [-+ 0\#']* )
314 // (?P<width> \d+ | \* (?: (?P<widtha> \d+) \$ )? )?
315 // (?: \. (?P<precision> \d+ | \* (?: (?P<precisiona> \d+) \$ )? ) )?
318 // hh | h | ll | l | L | z | j | t
321 // | I32 | I64 | I | q
326 // Used to establish the full span at the end.
328 // The current position within the string.
329 let mut at = at.at_next_cp()?;
330 // `c` is the next codepoint, `next` is a cursor after it.
331 let (mut c, mut next) = at.next_cp()?;
333 // Update `at`, `c`, and `next`, exiting if we're out of input.
334 macro_rules! move_to {
338 let (c_, next_) = at.next_cp()?;
345 // Constructs a result when parsing fails.
347 // Note: `move` used to capture copies of the cursors as they are *now*.
348 let fallback = move || {
350 Substitution::Format(Format {
351 span: start.slice_between(next).unwrap(),
357 type_: at.slice_between(next).unwrap(),
363 // Next parsing state.
364 let mut state = Start;
366 // Sadly, Rust isn't *quite* smart enough to know these *must* be initialised by the end.
367 let mut parameter: Option<u16> = None;
368 let mut flags: &str = "";
369 let mut width: Option<Num> = None;
370 let mut precision: Option<Num> = None;
371 let mut length: Option<&str> = None;
372 let mut type_: &str = "";
375 if let Start = state {
378 let end = at_next_cp_while(next, is_digit);
379 match end.next_cp() {
380 // Yes, this *is* the parameter.
381 Some(('$', end2)) => {
383 parameter = Some(at.slice_between(end).unwrap().parse().unwrap());
386 // Wait, no, actually, it's the width.
391 width = Some(Num::from_str(at.slice_between(end).unwrap(), None));
394 // It's invalid, is what it is.
395 None => return fallback(),
406 if let Flags = state {
407 let end = at_next_cp_while(at, is_flag);
409 flags = at.slice_between(end).unwrap();
413 if let Width = state {
420 let end = at_next_cp_while(next, is_digit);
422 width = Some(Num::from_str(at.slice_between(end).unwrap(), None));
433 if let WidthArg = state {
434 let end = at_next_cp_while(at, is_digit);
435 match end.next_cp() {
436 Some(('$', end2)) => {
438 width = Some(Num::from_str("", Some(at.slice_between(end).unwrap())));
443 width = Some(Num::Next);
449 if let Prec = state {
463 if let PrecInner = state {
466 let end = at_next_cp_while(next, is_digit);
467 match end.next_cp() {
468 Some(('$', end2)) => {
470 precision = Some(Num::from_str("*", next.slice_between(end)));
475 precision = Some(Num::Next);
481 let end = at_next_cp_while(next, is_digit);
483 precision = Some(Num::from_str(at.slice_between(end).unwrap(), None));
486 _ => return fallback(),
490 if let Length = state {
491 let c1_next1 = next.next_cp();
492 match (c, c1_next1) {
493 ('h', Some(('h', next1)))
494 | ('l', Some(('l', next1)))
497 length = Some(at.slice_between(next1).unwrap());
501 ('h', _) | ('l', _) | ('L', _)
502 | ('z', _) | ('j', _) | ('t', _)
506 length = Some(at.slice_between(next).unwrap());
511 let end = next.at_next_cp()
512 .and_then(|end| end.at_next_cp())
513 .map(|end| (next.slice_between(end).unwrap(), end));
514 let end = match end {
515 Some(("32", end)) => end,
516 Some(("64", end)) => end,
520 length = Some(at.slice_between(end).unwrap());
532 if let Type = state {
534 type_ = at.slice_between(next).unwrap();
536 // Don't use `move_to!` here, as we *can* be at the end of the input.
546 span: start.slice_between(end).unwrap(),
554 Some((Substitution::Format(f), end.slice_after()))
557 fn at_next_cp_while<F>(mut cur: Cur, mut pred: F) -> Cur
558 where F: FnMut(char) -> bool {
560 match cur.next_cp() {
561 Some((c, next)) => if pred(c) {
571 fn is_digit(c: char) -> bool {
578 fn is_flag(c: char) -> bool {
580 '0' | '-' | '+' | ' ' | '#' | '\'' => true,
592 parse_next_substitution as pns,
595 macro_rules! assert_eq_pnsat {
596 ($lhs:expr, $rhs:expr) => {
598 pns($lhs).and_then(|(s, _)| s.translate()),
599 $rhs.map(<String as From<&str>>::from)
606 assert_eq!(pns("has no escapes"), None);
607 assert_eq!(pns("has no escapes, either %"), None);
608 assert_eq!(pns("*so* has a %% escape"), Some((S::Escape," escape")));
609 assert_eq!(pns("%% leading escape"), Some((S::Escape, " leading escape")));
610 assert_eq!(pns("trailing escape %%"), Some((S::Escape, "")));
615 macro_rules! assert_pns_eq_sub {
617 $param:expr, $flags:expr,
618 $width:expr, $prec:expr, $len:expr, $type_:expr,
621 pns(concat!($in_, "!")),
638 assert_pns_eq_sub!("%!",
639 { None, "", None, None, None, "!", });
640 assert_pns_eq_sub!("%c",
641 { None, "", None, None, None, "c", });
642 assert_pns_eq_sub!("%s",
643 { None, "", None, None, None, "s", });
644 assert_pns_eq_sub!("%06d",
645 { None, "0", Some(N::Num(6)), None, None, "d", });
646 assert_pns_eq_sub!("%4.2f",
647 { None, "", Some(N::Num(4)), Some(N::Num(2)), None, "f", });
648 assert_pns_eq_sub!("%#x",
649 { None, "#", None, None, None, "x", });
650 assert_pns_eq_sub!("%-10s",
651 { None, "-", Some(N::Num(10)), None, None, "s", });
652 assert_pns_eq_sub!("%*s",
653 { None, "", Some(N::Next), None, None, "s", });
654 assert_pns_eq_sub!("%-10.*s",
655 { None, "-", Some(N::Num(10)), Some(N::Next), None, "s", });
656 assert_pns_eq_sub!("%-*.*s",
657 { None, "-", Some(N::Next), Some(N::Next), None, "s", });
658 assert_pns_eq_sub!("%.6i",
659 { None, "", None, Some(N::Num(6)), None, "i", });
660 assert_pns_eq_sub!("%+i",
661 { None, "+", None, None, None, "i", });
662 assert_pns_eq_sub!("%08X",
663 { None, "0", Some(N::Num(8)), None, None, "X", });
664 assert_pns_eq_sub!("%lu",
665 { None, "", None, None, Some("l"), "u", });
666 assert_pns_eq_sub!("%Iu",
667 { None, "", None, None, Some("I"), "u", });
668 assert_pns_eq_sub!("%I32u",
669 { None, "", None, None, Some("I32"), "u", });
670 assert_pns_eq_sub!("%I64u",
671 { None, "", None, None, Some("I64"), "u", });
672 assert_pns_eq_sub!("%'d",
673 { None, "'", None, None, None, "d", });
674 assert_pns_eq_sub!("%10s",
675 { None, "", Some(N::Num(10)), None, None, "s", });
676 assert_pns_eq_sub!("%-10.10s",
677 { None, "-", Some(N::Num(10)), Some(N::Num(10)), None, "s", });
678 assert_pns_eq_sub!("%1$d",
679 { Some(1), "", None, None, None, "d", });
680 assert_pns_eq_sub!("%2$.*3$d",
681 { Some(2), "", None, Some(N::Arg(3)), None, "d", });
682 assert_pns_eq_sub!("%1$*2$.*3$d",
683 { Some(1), "", Some(N::Arg(2)), Some(N::Arg(3)), None, "d", });
684 assert_pns_eq_sub!("%-8ld",
685 { None, "-", Some(N::Num(8)), None, Some("l"), "d", });
690 let s = "The %d'th word %% is: `%.*s` %!\n";
691 let subs: Vec<_> = iter_subs(s).map(|sub| sub.translate()).collect();
693 subs.iter().map(|ms| ms.as_ref().map(|s| &s[..])).collect::<Vec<_>>(),
694 vec![Some("{}"), None, Some("{:.*}"), None]
698 /// Check that the translations are what we expect.
700 fn test_translation() {
701 assert_eq_pnsat!("%c", Some("{}"));
702 assert_eq_pnsat!("%d", Some("{}"));
703 assert_eq_pnsat!("%u", Some("{}"));
704 assert_eq_pnsat!("%x", Some("{:x}"));
705 assert_eq_pnsat!("%X", Some("{:X}"));
706 assert_eq_pnsat!("%e", Some("{:e}"));
707 assert_eq_pnsat!("%E", Some("{:E}"));
708 assert_eq_pnsat!("%f", Some("{}"));
709 assert_eq_pnsat!("%g", Some("{:e}"));
710 assert_eq_pnsat!("%G", Some("{:E}"));
711 assert_eq_pnsat!("%s", Some("{}"));
712 assert_eq_pnsat!("%p", Some("{:p}"));
714 assert_eq_pnsat!("%06d", Some("{:06}"));
715 assert_eq_pnsat!("%4.2f", Some("{:4.2}"));
716 assert_eq_pnsat!("%#x", Some("{:#x}"));
717 assert_eq_pnsat!("%-10s", Some("{:<10}"));
718 assert_eq_pnsat!("%*s", None);
719 assert_eq_pnsat!("%-10.*s", Some("{:<10.*}"));
720 assert_eq_pnsat!("%-*.*s", None);
721 assert_eq_pnsat!("%.6i", Some("{:06}"));
722 assert_eq_pnsat!("%+i", Some("{:+}"));
723 assert_eq_pnsat!("%08X", Some("{:08X}"));
724 assert_eq_pnsat!("%lu", Some("{}"));
725 assert_eq_pnsat!("%Iu", Some("{}"));
726 assert_eq_pnsat!("%I32u", Some("{}"));
727 assert_eq_pnsat!("%I64u", Some("{}"));
728 assert_eq_pnsat!("%'d", None);
729 assert_eq_pnsat!("%10s", Some("{:>10}"));
730 assert_eq_pnsat!("%-10.10s", Some("{:<10.10}"));
731 assert_eq_pnsat!("%1$d", Some("{0}"));
732 assert_eq_pnsat!("%2$.*3$d", Some("{1:02$}"));
733 assert_eq_pnsat!("%1$*2$.*3$s", Some("{0:>1$.2$}"));
734 assert_eq_pnsat!("%-8ld", Some("{:<8}"));
740 use super::strcursor::StrCursor as Cur;
742 #[derive(Clone, Debug)]
743 pub enum Substitution<'a> {
749 impl<'a> Substitution<'a> {
750 pub fn as_str(&self) -> String {
752 Substitution::Ordinal(n) => format!("${}", n),
753 Substitution::Name(n) => format!("${}", n),
754 Substitution::Escape => "$$".into(),
758 pub fn translate(&self) -> Option<String> {
760 Substitution::Ordinal(n) => Some(format!("{{{}}}", n)),
761 Substitution::Name(n) => Some(format!("{{{}}}", n)),
762 Substitution::Escape => None,
767 /// Returns an iterator over all substitutions in a given string.
768 pub fn iter_subs(s: &str) -> Substitutions {
774 /// Iterator over substitutions in a string.
775 pub struct Substitutions<'a> {
779 impl<'a> Iterator for Substitutions<'a> {
780 type Item = Substitution<'a>;
781 fn next(&mut self) -> Option<Self::Item> {
782 match parse_next_substitution(self.s) {
783 Some((sub, tail)) => {
791 fn size_hint(&self) -> (usize, Option<usize>) {
792 (0, Some(self.s.len()))
796 /// Parse the next substitution from the input string.
797 pub fn parse_next_substitution(s: &str) -> Option<(Substitution, &str)> {
799 let start = s.find('$')?;
800 match s[start+1..].chars().next()? {
801 '$' => return Some((Substitution::Escape, &s[start+2..])),
803 let n = (c as u8) - b'0';
804 return Some((Substitution::Ordinal(n), &s[start+2..]));
806 _ => {/* fall-through */},
809 Cur::new_at_start(&s[start..])
812 let at = at.at_next_cp()?;
813 let (c, inner) = at.next_cp()?;
815 if !is_ident_head(c) {
818 let end = at_next_cp_while(inner, is_ident_tail);
819 Some((Substitution::Name(at.slice_between(end).unwrap()), end.slice_after()))
823 fn at_next_cp_while<F>(mut cur: Cur, mut pred: F) -> Cur
824 where F: FnMut(char) -> bool {
826 match cur.next_cp() {
827 Some((c, next)) => if pred(c) {
837 fn is_ident_head(c: char) -> bool {
839 'a' ..= 'z' | 'A' ..= 'Z' | '_' => true,
844 fn is_ident_tail(c: char) -> bool {
847 c => is_ident_head(c)
855 parse_next_substitution as pns,
858 macro_rules! assert_eq_pnsat {
859 ($lhs:expr, $rhs:expr) => {
861 pns($lhs).and_then(|(f, _)| f.translate()),
862 $rhs.map(<String as From<&str>>::from)
869 assert_eq!(pns("has no escapes"), None);
870 assert_eq!(pns("has no escapes, either $"), None);
871 assert_eq!(pns("*so* has a $$ escape"), Some((S::Escape, " escape")));
872 assert_eq!(pns("$$ leading escape"), Some((S::Escape, " leading escape")));
873 assert_eq!(pns("trailing escape $$"), Some((S::Escape, "")));
878 macro_rules! assert_pns_eq_sub {
879 ($in_:expr, $kind:ident($arg:expr)) => {
880 assert_eq!(pns(concat!($in_, "!")), Some((S::$kind($arg.into()), "!")))
884 assert_pns_eq_sub!("$0", Ordinal(0));
885 assert_pns_eq_sub!("$1", Ordinal(1));
886 assert_pns_eq_sub!("$9", Ordinal(9));
887 assert_pns_eq_sub!("$N", Name("N"));
888 assert_pns_eq_sub!("$NAME", Name("NAME"));
893 use super::iter_subs;
894 let s = "The $0'th word $$ is: `$WORD` $!\n";
895 let subs: Vec<_> = iter_subs(s).map(|sub| sub.translate()).collect();
897 subs.iter().map(|ms| ms.as_ref().map(|s| &s[..])).collect::<Vec<_>>(),
898 vec![Some("{0}"), None, Some("{WORD}")]
903 fn test_translation() {
904 assert_eq_pnsat!("$0", Some("{0}"));
905 assert_eq_pnsat!("$9", Some("{9}"));
906 assert_eq_pnsat!("$1", Some("{1}"));
907 assert_eq_pnsat!("$10", Some("{1}"));
908 assert_eq_pnsat!("$stuff", Some("{stuff}"));
909 assert_eq_pnsat!("$NAME", Some("{NAME}"));
910 assert_eq_pnsat!("$PREFIX/bin", Some("{PREFIX}"));
919 pub struct StrCursor<'a> {
924 impl<'a> StrCursor<'a> {
925 pub fn new_at_start(s: &'a str) -> StrCursor<'a> {
932 pub fn at_next_cp(mut self) -> Option<StrCursor<'a>> {
933 match self.try_seek_right_cp() {
939 pub fn next_cp(mut self) -> Option<(char, StrCursor<'a>)> {
940 let cp = self.cp_after()?;
941 self.seek_right(cp.len_utf8());
945 fn slice_before(&self) -> &'a str {
949 pub fn slice_after(&self) -> &'a str {
953 pub fn slice_between(&self, until: StrCursor<'a>) -> Option<&'a str> {
954 if !str_eq_literal(self.s, until.s) {
957 use std::cmp::{max, min};
958 let beg = min(self.at, until.at);
959 let end = max(self.at, until.at);
960 Some(&self.s[beg..end])
964 fn cp_after(&self) -> Option<char> {
965 self.slice_after().chars().next()
968 fn try_seek_right_cp(&mut self) -> bool {
969 match self.slice_after().chars().next() {
971 self.at += c.len_utf8();
978 fn seek_right(&mut self, bytes: usize) {
983 impl<'a> Copy for StrCursor<'a> {}
985 impl<'a> Clone for StrCursor<'a> {
986 fn clone(&self) -> StrCursor<'a> {
991 impl<'a> std::fmt::Debug for StrCursor<'a> {
992 fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
993 write!(fmt, "StrCursor({:?} | {:?})", self.slice_before(), self.slice_after())
997 fn str_eq_literal(a: &str, b: &str) -> bool {
998 a.as_bytes().as_ptr() == b.as_bytes().as_ptr()
999 && a.len() == b.len()