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