]> git.lizzy.rs Git - rust.git/blob - src/libsyntax_ext/format_foreign.rs
Rollup merge of #61077 - nnethercote:tweak-prefill, r=petrochenkov
[rust.git] / src / libsyntax_ext / format_foreign.rs
1 pub mod printf {
2     use super::strcursor::StrCursor as Cur;
3
4     /// Represents a single `printf`-style substitution.
5     #[derive(Clone, PartialEq, Debug)]
6     pub enum Substitution<'a> {
7         /// A formatted output substitution with its internal byte offset.
8         Format(Format<'a>),
9         /// A literal `%%` escape.
10         Escape,
11     }
12
13     impl<'a> Substitution<'a> {
14         pub fn as_str(&self) -> &str {
15             match *self {
16                 Substitution::Format(ref fmt) => fmt.span,
17                 Substitution::Escape => "%%",
18             }
19         }
20
21         pub fn position(&self) -> Option<(usize, usize)> {
22             match *self {
23                 Substitution::Format(ref fmt) => Some(fmt.position),
24                 _ => None,
25             }
26         }
27
28         pub fn set_position(&mut self, start: usize, end: usize) {
29             match self {
30                 Substitution::Format(ref mut fmt) => {
31                     fmt.position = (start, end);
32                 }
33                 _ => {}
34             }
35         }
36
37
38         /// Translate this substitution into an equivalent Rust formatting directive.
39         ///
40         /// This ignores cases where the substitution does not have an exact equivalent, or where
41         /// the substitution would be unnecessary.
42         pub fn translate(&self) -> Option<String> {
43             match *self {
44                 Substitution::Format(ref fmt) => fmt.translate(),
45                 Substitution::Escape => None,
46             }
47         }
48     }
49
50     #[derive(Clone, PartialEq, Debug)]
51     /// A single `printf`-style formatting directive.
52     pub struct Format<'a> {
53         /// The entire original formatting directive.
54         pub span: &'a str,
55         /// The (1-based) parameter to be converted.
56         pub parameter: Option<u16>,
57         /// Formatting flags.
58         pub flags: &'a str,
59         /// Minimum width of the output.
60         pub width: Option<Num>,
61         /// Precision of the conversion.
62         pub precision: Option<Num>,
63         /// Length modifier for the conversion.
64         pub length: Option<&'a str>,
65         /// Type of parameter being converted.
66         pub type_: &'a str,
67         /// Byte offset for the start and end of this formatting directive.
68         pub position: (usize, usize),
69     }
70
71     impl Format<'_> {
72         /// Translate this directive into an equivalent Rust formatting directive.
73         ///
74         /// Returns `None` in cases where the `printf` directive does not have an exact Rust
75         /// equivalent, rather than guessing.
76         pub fn translate(&self) -> Option<String> {
77             use std::fmt::Write;
78
79             let (c_alt, c_zero, c_left, c_plus) = {
80                 let mut c_alt = false;
81                 let mut c_zero = false;
82                 let mut c_left = false;
83                 let mut c_plus = false;
84                 for c in self.flags.chars() {
85                     match c {
86                         '#' => c_alt = true,
87                         '0' => c_zero = true,
88                         '-' => c_left = true,
89                         '+' => c_plus = true,
90                         _ => return None
91                     }
92                 }
93                 (c_alt, c_zero, c_left, c_plus)
94             };
95
96             // Has a special form in Rust for numbers.
97             let fill = if c_zero { Some("0") } else { None };
98
99             let align = if c_left { Some("<") } else { None };
100
101             // Rust doesn't have an equivalent to the `' '` flag.
102             let sign = if c_plus { Some("+") } else { None };
103
104             // Not *quite* the same, depending on the type...
105             let alt = c_alt;
106
107             let width = match self.width {
108                 Some(Num::Next) => {
109                     // NOTE: Rust doesn't support this.
110                     return None;
111                 }
112                 w @ Some(Num::Arg(_)) => w,
113                 w @ Some(Num::Num(_)) => w,
114                 None => None,
115             };
116
117             let precision = self.precision;
118
119             // NOTE: although length *can* have an effect, we can't duplicate the effect in Rust, so
120             // we just ignore it.
121
122             let (type_, use_zero_fill, is_int) = match self.type_ {
123                 "d" | "i" | "u" => (None, true, true),
124                 "f" | "F" => (None, false, false),
125                 "s" | "c" => (None, false, false),
126                 "e" | "E" => (Some(self.type_), true, false),
127                 "x" | "X" | "o" => (Some(self.type_), true, true),
128                 "p" => (Some(self.type_), false, true),
129                 "g" => (Some("e"), true, false),
130                 "G" => (Some("E"), true, false),
131                 _ => return None,
132             };
133
134             let (fill, width, precision) = match (is_int, width, precision) {
135                 (true, Some(_), Some(_)) => {
136                     // Rust can't duplicate this insanity.
137                     return None;
138                 },
139                 (true, None, Some(p)) => (Some("0"), Some(p), None),
140                 (true, w, None) => (fill, w, None),
141                 (false, w, p) => (fill, w, p),
142             };
143
144             let align = match (self.type_, width.is_some(), align.is_some()) {
145                 ("s", true, false) => Some(">"),
146                 _ => align,
147             };
148
149             let (fill, zero_fill) = match (fill, use_zero_fill) {
150                 (Some("0"), true) => (None, true),
151                 (fill, _) => (fill, false),
152             };
153
154             let alt = match type_ {
155                 Some("x") | Some("X") => alt,
156                 _ => false,
157             };
158
159             let has_options = fill.is_some()
160                 || align.is_some()
161                 || sign.is_some()
162                 || alt
163                 || zero_fill
164                 || width.is_some()
165                 || precision.is_some()
166                 || type_.is_some()
167                 ;
168
169             // Initialise with a rough guess.
170             let cap = self.span.len() + if has_options { 2 } else { 0 };
171             let mut s = String::with_capacity(cap);
172
173             s.push_str("{");
174
175             if let Some(arg) = self.parameter {
176                 write!(s, "{}", arg.checked_sub(1)?).ok()?;
177             }
178
179             if has_options {
180                 s.push_str(":");
181
182                 let align = if let Some(fill) = fill {
183                     s.push_str(fill);
184                     align.or(Some(">"))
185                 } else {
186                     align
187                 };
188
189                 if let Some(align) = align {
190                     s.push_str(align);
191                 }
192
193                 if let Some(sign) = sign {
194                     s.push_str(sign);
195                 }
196
197                 if alt {
198                     s.push_str("#");
199                 }
200
201                 if zero_fill {
202                     s.push_str("0");
203                 }
204
205                 if let Some(width) = width {
206                     width.translate(&mut s).ok()?;
207                 }
208
209                 if let Some(precision) = precision {
210                     s.push_str(".");
211                     precision.translate(&mut s).ok()?;
212                 }
213
214                 if let Some(type_) = type_ {
215                     s.push_str(type_);
216                 }
217             }
218
219             s.push_str("}");
220             Some(s)
221         }
222     }
223
224     /// A general number used in a `printf` formatting directive.
225     #[derive(Copy, Clone, PartialEq, Debug)]
226     pub enum Num {
227         // The range of these values is technically bounded by `NL_ARGMAX`... but, at least for GNU
228         // libc, it apparently has no real fixed limit.  A `u16` is used here on the basis that it
229         // is *vanishingly* unlikely that *anyone* is going to try formatting something wider, or
230         // with more precision, than 32 thousand positions which is so wide it couldn't possibly fit
231         // on a screen.
232
233         /// A specific, fixed value.
234         Num(u16),
235         /// The value is derived from a positional argument.
236         Arg(u16),
237         /// The value is derived from the "next" unconverted argument.
238         Next,
239     }
240
241     impl Num {
242         fn from_str(s: &str, arg: Option<&str>) -> Self {
243             if let Some(arg) = arg {
244                 Num::Arg(arg.parse().unwrap_or_else(|_| panic!("invalid format arg `{:?}`", arg)))
245             } else if s == "*" {
246                 Num::Next
247             } else {
248                 Num::Num(s.parse().unwrap_or_else(|_| panic!("invalid format num `{:?}`", s)))
249             }
250         }
251
252         fn translate(&self, s: &mut String) -> std::fmt::Result {
253             use std::fmt::Write;
254             match *self {
255                 Num::Num(n) => write!(s, "{}", n),
256                 Num::Arg(n) => {
257                     let n = n.checked_sub(1).ok_or(std::fmt::Error)?;
258                     write!(s, "{}$", n)
259                 },
260                 Num::Next => write!(s, "*"),
261             }
262         }
263     }
264
265     /// Returns an iterator over all substitutions in a given string.
266     pub fn iter_subs(s: &str) -> Substitutions<'_> {
267         Substitutions {
268             s,
269             pos: 0,
270         }
271     }
272
273     /// Iterator over substitutions in a string.
274     pub struct Substitutions<'a> {
275         s: &'a str,
276         pos: usize,
277     }
278
279     impl<'a> Iterator for Substitutions<'a> {
280         type Item = Substitution<'a>;
281         fn next(&mut self) -> Option<Self::Item> {
282             let (mut sub, tail) = parse_next_substitution(self.s)?;
283             self.s = tail;
284             match sub {
285                 Substitution::Format(_) => if let Some((start, end)) = sub.position() {
286                     sub.set_position(start + self.pos, end + self.pos);
287                     self.pos += end;
288                 }
289                 Substitution::Escape => self.pos += 2,
290             }
291             Some(sub)
292         }
293
294         fn size_hint(&self) -> (usize, Option<usize>) {
295             // Substitutions are at least 2 characters long.
296             (0, Some(self.s.len() / 2))
297         }
298     }
299
300     enum State {
301         Start,
302         Flags,
303         Width,
304         WidthArg,
305         Prec,
306         PrecInner,
307         Length,
308         Type,
309     }
310
311     /// Parse the next substitution from the input string.
312     pub fn parse_next_substitution(s: &str) -> Option<(Substitution<'_>, &str)> {
313         use self::State::*;
314
315         let at = {
316             let start = s.find('%')?;
317             match s[start+1..].chars().next()? {
318                 '%' => return Some((Substitution::Escape, &s[start+2..])),
319                 _ => {/* fall-through */},
320             }
321
322             Cur::new_at(&s[..], start)
323         };
324
325         // This is meant to be a translation of the following regex:
326         //
327         // ```regex
328         // (?x)
329         // ^ %
330         // (?: (?P<parameter> \d+) \$ )?
331         // (?P<flags> [-+ 0\#']* )
332         // (?P<width> \d+ | \* (?: (?P<widtha> \d+) \$ )? )?
333         // (?: \. (?P<precision> \d+ | \* (?: (?P<precisiona> \d+) \$ )? ) )?
334         // (?P<length>
335         //     # Standard
336         //     hh | h | ll | l | L | z | j | t
337         //
338         //     # Other
339         //     | I32 | I64 | I | q
340         // )?
341         // (?P<type> . )
342         // ```
343
344         // Used to establish the full span at the end.
345         let start = at;
346         // The current position within the string.
347         let mut at = at.at_next_cp()?;
348         // `c` is the next codepoint, `next` is a cursor after it.
349         let (mut c, mut next) = at.next_cp()?;
350
351         // Update `at`, `c`, and `next`, exiting if we're out of input.
352         macro_rules! move_to {
353             ($cur:expr) => {
354                 {
355                     at = $cur;
356                     let (c_, next_) = at.next_cp()?;
357                     c = c_;
358                     next = next_;
359                 }
360             };
361         }
362
363         // Constructs a result when parsing fails.
364         //
365         // Note: `move` used to capture copies of the cursors as they are *now*.
366         let fallback = move || {
367             return Some((
368                 Substitution::Format(Format {
369                     span: start.slice_between(next).unwrap(),
370                     parameter: None,
371                     flags: "",
372                     width: None,
373                     precision: None,
374                     length: None,
375                     type_: at.slice_between(next).unwrap(),
376                     position: (start.at, next.at),
377                 }),
378                 next.slice_after()
379             ));
380         };
381
382         // Next parsing state.
383         let mut state = Start;
384
385         // Sadly, Rust isn't *quite* smart enough to know these *must* be initialised by the end.
386         let mut parameter: Option<u16> = None;
387         let mut flags: &str = "";
388         let mut width: Option<Num> = None;
389         let mut precision: Option<Num> = None;
390         let mut length: Option<&str> = None;
391         let mut type_: &str = "";
392         let end: Cur<'_>;
393
394         if let Start = state {
395             match c {
396                 '1'..='9' => {
397                     let end = at_next_cp_while(next, is_digit);
398                     match end.next_cp() {
399                         // Yes, this *is* the parameter.
400                         Some(('$', end2)) => {
401                             state = Flags;
402                             parameter = Some(at.slice_between(end).unwrap().parse().unwrap());
403                             move_to!(end2);
404                         },
405                         // Wait, no, actually, it's the width.
406                         Some(_) => {
407                             state = Prec;
408                             parameter = None;
409                             flags = "";
410                             width = Some(Num::from_str(at.slice_between(end).unwrap(), None));
411                             move_to!(end);
412                         },
413                         // It's invalid, is what it is.
414                         None => return fallback(),
415                     }
416                 },
417                 _ => {
418                     state = Flags;
419                     parameter = None;
420                     move_to!(at);
421                 }
422             }
423         }
424
425         if let Flags = state {
426             let end = at_next_cp_while(at, is_flag);
427             state = Width;
428             flags = at.slice_between(end).unwrap();
429             move_to!(end);
430         }
431
432         if let Width = state {
433             match c {
434                 '*' => {
435                     state = WidthArg;
436                     move_to!(next);
437                 },
438                 '1' ..= '9' => {
439                     let end = at_next_cp_while(next, is_digit);
440                     state = Prec;
441                     width = Some(Num::from_str(at.slice_between(end).unwrap(), None));
442                     move_to!(end);
443                 },
444                 _ => {
445                     state = Prec;
446                     width = None;
447                     move_to!(at);
448                 }
449             }
450         }
451
452         if let WidthArg = state {
453             let end = at_next_cp_while(at, is_digit);
454             match end.next_cp() {
455                 Some(('$', end2)) => {
456                     state = Prec;
457                     width = Some(Num::from_str("", Some(at.slice_between(end).unwrap())));
458                     move_to!(end2);
459                 },
460                 _ => {
461                     state = Prec;
462                     width = Some(Num::Next);
463                     move_to!(end);
464                 }
465             }
466         }
467
468         if let Prec = state {
469             match c {
470                 '.' => {
471                     state = PrecInner;
472                     move_to!(next);
473                 },
474                 _ => {
475                     state = Length;
476                     precision = None;
477                     move_to!(at);
478                 }
479             }
480         }
481
482         if let PrecInner = state {
483             match c {
484                 '*' => {
485                     let end = at_next_cp_while(next, is_digit);
486                     match end.next_cp() {
487                         Some(('$', end2)) => {
488                             state = Length;
489                             precision = Some(Num::from_str("*", next.slice_between(end)));
490                             move_to!(end2);
491                         },
492                         _ => {
493                             state = Length;
494                             precision = Some(Num::Next);
495                             move_to!(end);
496                         }
497                     }
498                 },
499                 '0' ..= '9' => {
500                     let end = at_next_cp_while(next, is_digit);
501                     state = Length;
502                     precision = Some(Num::from_str(at.slice_between(end).unwrap(), None));
503                     move_to!(end);
504                 },
505                 _ => return fallback(),
506             }
507         }
508
509         if let Length = state {
510             let c1_next1 = next.next_cp();
511             match (c, c1_next1) {
512                 ('h', Some(('h', next1)))
513                 | ('l', Some(('l', next1)))
514                 => {
515                     state = Type;
516                     length = Some(at.slice_between(next1).unwrap());
517                     move_to!(next1);
518                 },
519
520                 ('h', _) | ('l', _) | ('L', _)
521                 | ('z', _) | ('j', _) | ('t', _)
522                 | ('q', _)
523                 => {
524                     state = Type;
525                     length = Some(at.slice_between(next).unwrap());
526                     move_to!(next);
527                 },
528
529                 ('I', _) => {
530                     let end = next.at_next_cp()
531                         .and_then(|end| end.at_next_cp())
532                         .map(|end| (next.slice_between(end).unwrap(), end));
533                     let end = match end {
534                         Some(("32", end)) => end,
535                         Some(("64", end)) => end,
536                         _ => next
537                     };
538                     state = Type;
539                     length = Some(at.slice_between(end).unwrap());
540                     move_to!(end);
541                 },
542
543                 _ => {
544                     state = Type;
545                     length = None;
546                     move_to!(at);
547                 }
548             }
549         }
550
551         if let Type = state {
552             drop(c);
553             type_ = at.slice_between(next).unwrap();
554
555             // Don't use `move_to!` here, as we *can* be at the end of the input.
556             at = next;
557         }
558
559         drop(c);
560         drop(next);
561
562         end = at;
563         let position = (start.at, end.at);
564
565         let f = Format {
566             span: start.slice_between(end).unwrap(),
567             parameter,
568             flags,
569             width,
570             precision,
571             length,
572             type_,
573             position,
574         };
575         Some((Substitution::Format(f), end.slice_after()))
576     }
577
578     fn at_next_cp_while<F>(mut cur: Cur<'_>, mut pred: F) -> Cur<'_>
579     where F: FnMut(char) -> bool {
580         loop {
581             match cur.next_cp() {
582                 Some((c, next)) => if pred(c) {
583                     cur = next;
584                 } else {
585                     return cur;
586                 },
587                 None => return cur,
588             }
589         }
590     }
591
592     fn is_digit(c: char) -> bool {
593         match c {
594             '0' ..= '9' => true,
595             _ => false
596         }
597     }
598
599     fn is_flag(c: char) -> bool {
600         match c {
601             '0' | '-' | '+' | ' ' | '#' | '\'' => true,
602             _ => false
603         }
604     }
605
606     #[cfg(test)]
607     mod tests {
608         use super::{
609             Format as F,
610             Num as N,
611             Substitution as S,
612             iter_subs,
613             parse_next_substitution as pns,
614         };
615
616         macro_rules! assert_eq_pnsat {
617             ($lhs:expr, $rhs:expr) => {
618                 assert_eq!(
619                     pns($lhs).and_then(|(s, _)| s.translate()),
620                     $rhs.map(<String as From<&str>>::from)
621                 )
622             };
623         }
624
625         #[test]
626         fn test_escape() {
627             assert_eq!(pns("has no escapes"), None);
628             assert_eq!(pns("has no escapes, either %"), None);
629             assert_eq!(pns("*so* has a %% escape"), Some((S::Escape," escape")));
630             assert_eq!(pns("%% leading escape"), Some((S::Escape, " leading escape")));
631             assert_eq!(pns("trailing escape %%"), Some((S::Escape, "")));
632         }
633
634         #[test]
635         fn test_parse() {
636             macro_rules! assert_pns_eq_sub {
637                 ($in_:expr, {
638                     $param:expr, $flags:expr,
639                     $width:expr, $prec:expr, $len:expr, $type_:expr,
640                     $pos:expr,
641                 }) => {
642                     assert_eq!(
643                         pns(concat!($in_, "!")),
644                         Some((
645                             S::Format(F {
646                                 span: $in_,
647                                 parameter: $param,
648                                 flags: $flags,
649                                 width: $width,
650                                 precision: $prec,
651                                 length: $len,
652                                 type_: $type_,
653                                 position: $pos,
654                             }),
655                             "!"
656                         ))
657                     )
658                 };
659             }
660
661             assert_pns_eq_sub!("%!",
662                 { None, "", None, None, None, "!", (0, 2), });
663             assert_pns_eq_sub!("%c",
664                 { None, "", None, None, None, "c", (0, 2), });
665             assert_pns_eq_sub!("%s",
666                 { None, "", None, None, None, "s", (0, 2), });
667             assert_pns_eq_sub!("%06d",
668                 { None, "0", Some(N::Num(6)), None, None, "d", (0, 4), });
669             assert_pns_eq_sub!("%4.2f",
670                 { None, "", Some(N::Num(4)), Some(N::Num(2)), None, "f", (0, 5), });
671             assert_pns_eq_sub!("%#x",
672                 { None, "#", None, None, None, "x", (0, 3), });
673             assert_pns_eq_sub!("%-10s",
674                 { None, "-", Some(N::Num(10)), None, None, "s", (0, 5), });
675             assert_pns_eq_sub!("%*s",
676                 { None, "", Some(N::Next), None, None, "s", (0, 3), });
677             assert_pns_eq_sub!("%-10.*s",
678                 { None, "-", Some(N::Num(10)), Some(N::Next), None, "s", (0, 7), });
679             assert_pns_eq_sub!("%-*.*s",
680                 { None, "-", Some(N::Next), Some(N::Next), None, "s", (0, 6), });
681             assert_pns_eq_sub!("%.6i",
682                 { None, "", None, Some(N::Num(6)), None, "i", (0, 4), });
683             assert_pns_eq_sub!("%+i",
684                 { None, "+", None, None, None, "i", (0, 3), });
685             assert_pns_eq_sub!("%08X",
686                 { None, "0", Some(N::Num(8)), None, None, "X", (0, 4), });
687             assert_pns_eq_sub!("%lu",
688                 { None, "", None, None, Some("l"), "u", (0, 3), });
689             assert_pns_eq_sub!("%Iu",
690                 { None, "", None, None, Some("I"), "u", (0, 3), });
691             assert_pns_eq_sub!("%I32u",
692                 { None, "", None, None, Some("I32"), "u", (0, 5), });
693             assert_pns_eq_sub!("%I64u",
694                 { None, "", None, None, Some("I64"), "u", (0, 5), });
695             assert_pns_eq_sub!("%'d",
696                 { None, "'", None, None, None, "d", (0, 3), });
697             assert_pns_eq_sub!("%10s",
698                 { None, "", Some(N::Num(10)), None, None, "s", (0, 4), });
699             assert_pns_eq_sub!("%-10.10s",
700                 { None, "-", Some(N::Num(10)), Some(N::Num(10)), None, "s", (0, 8), });
701             assert_pns_eq_sub!("%1$d",
702                 { Some(1), "", None, None, None, "d", (0, 4), });
703             assert_pns_eq_sub!("%2$.*3$d",
704                 { Some(2), "", None, Some(N::Arg(3)), None, "d", (0, 8), });
705             assert_pns_eq_sub!("%1$*2$.*3$d",
706                 { Some(1), "", Some(N::Arg(2)), Some(N::Arg(3)), None, "d", (0, 11), });
707             assert_pns_eq_sub!("%-8ld",
708                 { None, "-", Some(N::Num(8)), None, Some("l"), "d", (0, 5), });
709         }
710
711         #[test]
712         fn test_iter() {
713             let s = "The %d'th word %% is: `%.*s` %!\n";
714             let subs: Vec<_> = iter_subs(s).map(|sub| sub.translate()).collect();
715             assert_eq!(
716                 subs.iter().map(|ms| ms.as_ref().map(|s| &s[..])).collect::<Vec<_>>(),
717                 vec![Some("{}"), None, Some("{:.*}"), None]
718             );
719         }
720
721         /// Checks that the translations are what we expect.
722         #[test]
723         fn test_translation() {
724             assert_eq_pnsat!("%c", Some("{}"));
725             assert_eq_pnsat!("%d", Some("{}"));
726             assert_eq_pnsat!("%u", Some("{}"));
727             assert_eq_pnsat!("%x", Some("{:x}"));
728             assert_eq_pnsat!("%X", Some("{:X}"));
729             assert_eq_pnsat!("%e", Some("{:e}"));
730             assert_eq_pnsat!("%E", Some("{:E}"));
731             assert_eq_pnsat!("%f", Some("{}"));
732             assert_eq_pnsat!("%g", Some("{:e}"));
733             assert_eq_pnsat!("%G", Some("{:E}"));
734             assert_eq_pnsat!("%s", Some("{}"));
735             assert_eq_pnsat!("%p", Some("{:p}"));
736
737             assert_eq_pnsat!("%06d",        Some("{:06}"));
738             assert_eq_pnsat!("%4.2f",       Some("{:4.2}"));
739             assert_eq_pnsat!("%#x",         Some("{:#x}"));
740             assert_eq_pnsat!("%-10s",       Some("{:<10}"));
741             assert_eq_pnsat!("%*s",         None);
742             assert_eq_pnsat!("%-10.*s",     Some("{:<10.*}"));
743             assert_eq_pnsat!("%-*.*s",      None);
744             assert_eq_pnsat!("%.6i",        Some("{:06}"));
745             assert_eq_pnsat!("%+i",         Some("{:+}"));
746             assert_eq_pnsat!("%08X",        Some("{:08X}"));
747             assert_eq_pnsat!("%lu",         Some("{}"));
748             assert_eq_pnsat!("%Iu",         Some("{}"));
749             assert_eq_pnsat!("%I32u",       Some("{}"));
750             assert_eq_pnsat!("%I64u",       Some("{}"));
751             assert_eq_pnsat!("%'d",         None);
752             assert_eq_pnsat!("%10s",        Some("{:>10}"));
753             assert_eq_pnsat!("%-10.10s",    Some("{:<10.10}"));
754             assert_eq_pnsat!("%1$d",        Some("{0}"));
755             assert_eq_pnsat!("%2$.*3$d",    Some("{1:02$}"));
756             assert_eq_pnsat!("%1$*2$.*3$s", Some("{0:>1$.2$}"));
757             assert_eq_pnsat!("%-8ld",       Some("{:<8}"));
758         }
759     }
760 }
761
762 pub mod shell {
763     use super::strcursor::StrCursor as Cur;
764
765     #[derive(Clone, PartialEq, Debug)]
766     pub enum Substitution<'a> {
767         Ordinal(u8, (usize, usize)),
768         Name(&'a str, (usize, usize)),
769         Escape((usize, usize)),
770     }
771
772     impl Substitution<'_> {
773         pub fn as_str(&self) -> String {
774             match self {
775                 Substitution::Ordinal(n, _) => format!("${}", n),
776                 Substitution::Name(n, _) => format!("${}", n),
777                 Substitution::Escape(_) => "$$".into(),
778             }
779         }
780
781         pub fn position(&self) -> Option<(usize, usize)> {
782             match self {
783                 Substitution::Ordinal(_, pos) |
784                 Substitution::Name(_, pos) |
785                 Substitution::Escape(pos) => Some(*pos),
786             }
787         }
788
789         pub fn set_position(&mut self, start: usize, end: usize) {
790             match self {
791                 Substitution::Ordinal(_, ref mut pos) |
792                 Substitution::Name(_, ref mut pos) |
793                 Substitution::Escape(ref mut pos) => *pos = (start, end),
794             }
795         }
796
797         pub fn translate(&self) -> Option<String> {
798             match *self {
799                 Substitution::Ordinal(n, _) => Some(format!("{{{}}}", n)),
800                 Substitution::Name(n, _) => Some(format!("{{{}}}", n)),
801                 Substitution::Escape(_) => None,
802             }
803         }
804     }
805
806     /// Returns an iterator over all substitutions in a given string.
807     pub fn iter_subs(s: &str) -> Substitutions<'_> {
808         Substitutions {
809             s,
810             pos: 0,
811         }
812     }
813
814     /// Iterator over substitutions in a string.
815     pub struct Substitutions<'a> {
816         s: &'a str,
817         pos: usize,
818     }
819
820     impl<'a> Iterator for Substitutions<'a> {
821         type Item = Substitution<'a>;
822         fn next(&mut self) -> Option<Self::Item> {
823             match parse_next_substitution(self.s) {
824                 Some((mut sub, tail)) => {
825                     self.s = tail;
826                     if let Some((start, end)) = sub.position() {
827                         sub.set_position(start + self.pos, end + self.pos);
828                         self.pos += end;
829                     }
830                     Some(sub)
831                 },
832                 None => None,
833             }
834         }
835
836         fn size_hint(&self) -> (usize, Option<usize>) {
837             (0, Some(self.s.len()))
838         }
839     }
840
841     /// Parse the next substitution from the input string.
842     pub fn parse_next_substitution(s: &str) -> Option<(Substitution<'_>, &str)> {
843         let at = {
844             let start = s.find('$')?;
845             match s[start+1..].chars().next()? {
846                 '$' => return Some((Substitution::Escape((start, start+2)), &s[start+2..])),
847                 c @ '0' ..= '9' => {
848                     let n = (c as u8) - b'0';
849                     return Some((Substitution::Ordinal(n, (start, start+2)), &s[start+2..]));
850                 },
851                 _ => {/* fall-through */},
852             }
853
854             Cur::new_at(&s[..], start)
855         };
856
857         let at = at.at_next_cp()?;
858         let (c, inner) = at.next_cp()?;
859
860         if !is_ident_head(c) {
861             None
862         } else {
863             let end = at_next_cp_while(inner, is_ident_tail);
864             let slice = at.slice_between(end).unwrap();
865             let start = at.at - 1;
866             let end_pos = at.at + slice.len();
867             Some((Substitution::Name(slice, (start, end_pos)), end.slice_after()))
868         }
869     }
870
871     fn at_next_cp_while<F>(mut cur: Cur<'_>, mut pred: F) -> Cur<'_>
872     where F: FnMut(char) -> bool {
873         loop {
874             match cur.next_cp() {
875                 Some((c, next)) => if pred(c) {
876                     cur = next;
877                 } else {
878                     return cur;
879                 },
880                 None => return cur,
881             }
882         }
883     }
884
885     fn is_ident_head(c: char) -> bool {
886         match c {
887             'a' ..= 'z' | 'A' ..= 'Z' | '_' => true,
888             _ => false
889         }
890     }
891
892     fn is_ident_tail(c: char) -> bool {
893         match c {
894             '0' ..= '9' => true,
895             c => is_ident_head(c)
896         }
897     }
898
899     #[cfg(test)]
900     mod tests {
901         use super::{
902             Substitution as S,
903             parse_next_substitution as pns,
904         };
905
906         macro_rules! assert_eq_pnsat {
907             ($lhs:expr, $rhs:expr) => {
908                 assert_eq!(
909                     pns($lhs).and_then(|(f, _)| f.translate()),
910                     $rhs.map(<String as From<&str>>::from)
911                 )
912             };
913         }
914
915         #[test]
916         fn test_escape() {
917             assert_eq!(pns("has no escapes"), None);
918             assert_eq!(pns("has no escapes, either $"), None);
919             assert_eq!(pns("*so* has a $$ escape"), Some((S::Escape((11, 13)), " escape")));
920             assert_eq!(pns("$$ leading escape"), Some((S::Escape((0, 2)), " leading escape")));
921             assert_eq!(pns("trailing escape $$"), Some((S::Escape((16, 18)), "")));
922         }
923
924         #[test]
925         fn test_parse() {
926             macro_rules! assert_pns_eq_sub {
927                 ($in_:expr, $kind:ident($arg:expr, $pos:expr)) => {
928                     assert_eq!(pns(concat!($in_, "!")), Some((S::$kind($arg.into(), $pos), "!")))
929                 };
930             }
931
932             assert_pns_eq_sub!("$0", Ordinal(0, (0, 2)));
933             assert_pns_eq_sub!("$1", Ordinal(1, (0, 2)));
934             assert_pns_eq_sub!("$9", Ordinal(9, (0, 2)));
935             assert_pns_eq_sub!("$N", Name("N", (0, 2)));
936             assert_pns_eq_sub!("$NAME", Name("NAME", (0, 5)));
937         }
938
939         #[test]
940         fn test_iter() {
941             use super::iter_subs;
942             let s = "The $0'th word $$ is: `$WORD` $!\n";
943             let subs: Vec<_> = iter_subs(s).map(|sub| sub.translate()).collect();
944             assert_eq!(
945                 subs.iter().map(|ms| ms.as_ref().map(|s| &s[..])).collect::<Vec<_>>(),
946                 vec![Some("{0}"), None, Some("{WORD}")]
947             );
948         }
949
950         #[test]
951         fn test_translation() {
952             assert_eq_pnsat!("$0", Some("{0}"));
953             assert_eq_pnsat!("$9", Some("{9}"));
954             assert_eq_pnsat!("$1", Some("{1}"));
955             assert_eq_pnsat!("$10", Some("{1}"));
956             assert_eq_pnsat!("$stuff", Some("{stuff}"));
957             assert_eq_pnsat!("$NAME", Some("{NAME}"));
958             assert_eq_pnsat!("$PREFIX/bin", Some("{PREFIX}"));
959         }
960
961     }
962 }
963
964 mod strcursor {
965     pub struct StrCursor<'a> {
966         s: &'a str,
967         pub at: usize,
968     }
969
970     impl<'a> StrCursor<'a> {
971         pub fn new_at(s: &'a str, at: usize) -> StrCursor<'a> {
972             StrCursor {
973                 s,
974                 at,
975             }
976         }
977
978         pub fn at_next_cp(mut self) -> Option<StrCursor<'a>> {
979             match self.try_seek_right_cp() {
980                 true => Some(self),
981                 false => None
982             }
983         }
984
985         pub fn next_cp(mut self) -> Option<(char, StrCursor<'a>)> {
986             let cp = self.cp_after()?;
987             self.seek_right(cp.len_utf8());
988             Some((cp, self))
989         }
990
991         fn slice_before(&self) -> &'a str {
992             &self.s[0..self.at]
993         }
994
995         pub fn slice_after(&self) -> &'a str {
996             &self.s[self.at..]
997         }
998
999         pub fn slice_between(&self, until: StrCursor<'a>) -> Option<&'a str> {
1000             if !str_eq_literal(self.s, until.s) {
1001                 None
1002             } else {
1003                 use std::cmp::{max, min};
1004                 let beg = min(self.at, until.at);
1005                 let end = max(self.at, until.at);
1006                 Some(&self.s[beg..end])
1007             }
1008         }
1009
1010         fn cp_after(&self) -> Option<char> {
1011             self.slice_after().chars().next()
1012         }
1013
1014         fn try_seek_right_cp(&mut self) -> bool {
1015             match self.slice_after().chars().next() {
1016                 Some(c) => {
1017                     self.at += c.len_utf8();
1018                     true
1019                 },
1020                 None => false,
1021             }
1022         }
1023
1024         fn seek_right(&mut self, bytes: usize) {
1025             self.at += bytes;
1026         }
1027     }
1028
1029     impl Copy for StrCursor<'_> {}
1030
1031     impl<'a> Clone for StrCursor<'a> {
1032         fn clone(&self) -> StrCursor<'a> {
1033             *self
1034         }
1035     }
1036
1037     impl std::fmt::Debug for StrCursor<'_> {
1038         fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1039             write!(fmt, "StrCursor({:?} | {:?})", self.slice_before(), self.slice_after())
1040         }
1041     }
1042
1043     fn str_eq_literal(a: &str, b: &str) -> bool {
1044         a.as_bytes().as_ptr() == b.as_bytes().as_ptr()
1045             && a.len() == b.len()
1046     }
1047 }