]> git.lizzy.rs Git - rust.git/blob - src/libsyntax_ext/format_foreign.rs
1cf21b5c3ae3a854f601b751fc6affc4c34b8e86
[rust.git] / src / libsyntax_ext / format_foreign.rs
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.
4 //
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.
10
11 pub mod printf {
12     use super::strcursor::StrCursor as Cur;
13
14     /// Represents a single `printf`-style substitution.
15     #[derive(Clone, Debug)]
16     pub enum Substitution<'a> {
17         /// A formatted output substitution.
18         Format(Format<'a>),
19         /// A literal `%%` escape.
20         Escape,
21     }
22
23     impl<'a> Substitution<'a> {
24         pub fn as_str(&self) -> &str {
25             match *self {
26                 Substitution::Format(ref fmt) => fmt.span,
27                 Substitution::Escape => "%%",
28             }
29         }
30
31         /// Translate this substitution into an equivalent Rust formatting directive.
32         ///
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> {
36             match *self {
37                 Substitution::Format(ref fmt) => fmt.translate(),
38                 Substitution::Escape => None,
39             }
40         }
41     }
42
43     #[derive(Clone, Debug)]
44     /// A single `printf`-style formatting directive.
45     pub struct Format<'a> {
46         /// The entire original formatting directive.
47         pub span: &'a str,
48         /// The (1-based) parameter to be converted.
49         pub parameter: Option<u16>,
50         /// Formatting flags.
51         pub flags: &'a str,
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.
59         pub type_: &'a str,
60     }
61
62     impl<'a> Format<'a> {
63         /// Translate this directive into an equivalent Rust formatting directive.
64         ///
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> {
68             use std::fmt::Write;
69
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() {
76                     match c {
77                         '#' => c_alt = true,
78                         '0' => c_zero = true,
79                         '-' => c_left = true,
80                         '+' => c_plus = true,
81                         _ => return None
82                     }
83                 }
84                 (c_alt, c_zero, c_left, c_plus)
85             };
86
87             // Has a special form in Rust for numbers.
88             let fill = if c_zero { Some("0") } else { None };
89
90             let align = if c_left { Some("<") } else { None };
91
92             // Rust doesn't have an equivalent to the `' '` flag.
93             let sign = if c_plus { Some("+") } else { None };
94
95             // Not *quite* the same, depending on the type...
96             let alt = c_alt;
97
98             let width = match self.width {
99                 Some(Num::Next) => {
100                     // NOTE: Rust doesn't support this.
101                     return None;
102                 }
103                 w @ Some(Num::Arg(_)) => w,
104                 w @ Some(Num::Num(_)) => w,
105                 None => None,
106             };
107
108             let precision = self.precision;
109
110             // NOTE: although length *can* have an effect, we can't duplicate the effect in Rust, so
111             // we just ignore it.
112
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),
122                 _ => return None,
123             };
124
125             let (fill, width, precision) = match (is_int, width, precision) {
126                 (true, Some(_), Some(_)) => {
127                     // Rust can't duplicate this insanity.
128                     return None;
129                 },
130                 (true, None, Some(p)) => (Some("0"), Some(p), None),
131                 (true, w, None) => (fill, w, None),
132                 (false, w, p) => (fill, w, p),
133             };
134
135             let align = match (self.type_, width.is_some(), align.is_some()) {
136                 ("s", true, false) => Some(">"),
137                 _ => align,
138             };
139
140             let (fill, zero_fill) = match (fill, use_zero_fill) {
141                 (Some("0"), true) => (None, true),
142                 (fill, _) => (fill, false),
143             };
144
145             let alt = match type_ {
146                 Some("x") | Some("X") => alt,
147                 _ => false,
148             };
149
150             let has_options = fill.is_some()
151                 || align.is_some()
152                 || sign.is_some()
153                 || alt
154                 || zero_fill
155                 || width.is_some()
156                 || precision.is_some()
157                 || type_.is_some()
158                 ;
159
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);
163
164             s.push_str("{");
165
166             if let Some(arg) = self.parameter {
167                 write!(s, "{}", arg.checked_sub(1)?).ok()?;
168             }
169
170             if has_options {
171                 s.push_str(":");
172
173                 let align = if let Some(fill) = fill {
174                     s.push_str(fill);
175                     align.or(Some(">"))
176                 } else {
177                     align
178                 };
179
180                 if let Some(align) = align {
181                     s.push_str(align);
182                 }
183
184                 if let Some(sign) = sign {
185                     s.push_str(sign);
186                 }
187
188                 if alt {
189                     s.push_str("#");
190                 }
191
192                 if zero_fill {
193                     s.push_str("0");
194                 }
195
196                 if let Some(width) = width {
197                     width.translate(&mut s).ok()?;
198                 }
199
200                 if let Some(precision) = precision {
201                     s.push_str(".");
202                     precision.translate(&mut s).ok()?;
203                 }
204
205                 if let Some(type_) = type_ {
206                     s.push_str(type_);
207                 }
208             }
209
210             s.push_str("}");
211             Some(s)
212         }
213     }
214
215     /// A general number used in a `printf` formatting directive.
216     #[derive(Copy, Clone, Debug)]
217     pub enum Num {
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
222         // on a screen.
223
224         /// A specific, fixed value.
225         Num(u16),
226         /// The value is derived from a positional argument.
227         Arg(u16),
228         /// The value is derived from the "next" unconverted argument.
229         Next,
230     }
231
232     impl Num {
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)))
236             } else if s == "*" {
237                 Num::Next
238             } else {
239                 Num::Num(s.parse().expect(&format!("invalid format num `{:?}`", s)))
240             }
241         }
242
243         fn translate(&self, s: &mut String) -> ::std::fmt::Result {
244             use std::fmt::Write;
245             match *self {
246                 Num::Num(n) => write!(s, "{}", n),
247                 Num::Arg(n) => {
248                     let n = try!(n.checked_sub(1).ok_or(::std::fmt::Error));
249                     write!(s, "{}$", n)
250                 },
251                 Num::Next => write!(s, "*"),
252             }
253         }
254     }
255
256     /// Returns an iterator over all substitutions in a given string.
257     pub fn iter_subs(s: &str) -> Substitutions {
258         Substitutions {
259             s,
260         }
261     }
262
263     /// Iterator over substitutions in a string.
264     pub struct Substitutions<'a> {
265         s: &'a str,
266     }
267
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)?;
272             self.s = tail;
273             Some(sub)
274         }
275
276         fn size_hint(&self) -> (usize, Option<usize>) {
277             // Substitutions are at least 2 characters long.
278             (0, Some(self.s.len() / 2))
279         }
280     }
281
282     enum State {
283         Start,
284         Flags,
285         Width,
286         WidthArg,
287         Prec,
288         PrecInner,
289         Length,
290         Type,
291     }
292
293     /// Parse the next substitution from the input string.
294     pub fn parse_next_substitution(s: &str) -> Option<(Substitution, &str)> {
295         use self::State::*;
296
297         let at = {
298             let start = s.find('%')?;
299             match s[start+1..].chars().next()? {
300                 '%' => return Some((Substitution::Escape, &s[start+2..])),
301                 _ => {/* fall-through */},
302             }
303
304             Cur::new_at_start(&s[start..])
305         };
306
307         // This is meant to be a translation of the following regex:
308         //
309         // ```regex
310         // (?x)
311         // ^ %
312         // (?: (?P<parameter> \d+) \$ )?
313         // (?P<flags> [-+ 0\#']* )
314         // (?P<width> \d+ | \* (?: (?P<widtha> \d+) \$ )? )?
315         // (?: \. (?P<precision> \d+ | \* (?: (?P<precisiona> \d+) \$ )? ) )?
316         // (?P<length>
317         //     # Standard
318         //     hh | h | ll | l | L | z | j | t
319         //
320         //     # Other
321         //     | I32 | I64 | I | q
322         // )?
323         // (?P<type> . )
324         // ```
325
326         // Used to establish the full span at the end.
327         let start = at;
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()?;
332
333         // Update `at`, `c`, and `next`, exiting if we're out of input.
334         macro_rules! move_to {
335             ($cur:expr) => {
336                 {
337                     at = $cur;
338                     let (c_, next_) = at.next_cp()?;
339                     c = c_;
340                     next = next_;
341                 }
342             };
343         }
344
345         // Constructs a result when parsing fails.
346         //
347         // Note: `move` used to capture copies of the cursors as they are *now*.
348         let fallback = move || {
349             return Some((
350                 Substitution::Format(Format {
351                     span: start.slice_between(next).unwrap(),
352                     parameter: None,
353                     flags: "",
354                     width: None,
355                     precision: None,
356                     length: None,
357                     type_: at.slice_between(next).unwrap(),
358                 }),
359                 next.slice_after()
360             ));
361         };
362
363         // Next parsing state.
364         let mut state = Start;
365
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 = "";
373         let end: Cur;
374
375         if let Start = state {
376             match c {
377                 '1'..='9' => {
378                     let end = at_next_cp_while(next, is_digit);
379                     match end.next_cp() {
380                         // Yes, this *is* the parameter.
381                         Some(('$', end2)) => {
382                             state = Flags;
383                             parameter = Some(at.slice_between(end).unwrap().parse().unwrap());
384                             move_to!(end2);
385                         },
386                         // Wait, no, actually, it's the width.
387                         Some(_) => {
388                             state = Prec;
389                             parameter = None;
390                             flags = "";
391                             width = Some(Num::from_str(at.slice_between(end).unwrap(), None));
392                             move_to!(end);
393                         },
394                         // It's invalid, is what it is.
395                         None => return fallback(),
396                     }
397                 },
398                 _ => {
399                     state = Flags;
400                     parameter = None;
401                     move_to!(at);
402                 }
403             }
404         }
405
406         if let Flags = state {
407             let end = at_next_cp_while(at, is_flag);
408             state = Width;
409             flags = at.slice_between(end).unwrap();
410             move_to!(end);
411         }
412
413         if let Width = state {
414             match c {
415                 '*' => {
416                     state = WidthArg;
417                     move_to!(next);
418                 },
419                 '1' ..= '9' => {
420                     let end = at_next_cp_while(next, is_digit);
421                     state = Prec;
422                     width = Some(Num::from_str(at.slice_between(end).unwrap(), None));
423                     move_to!(end);
424                 },
425                 _ => {
426                     state = Prec;
427                     width = None;
428                     move_to!(at);
429                 }
430             }
431         }
432
433         if let WidthArg = state {
434             let end = at_next_cp_while(at, is_digit);
435             match end.next_cp() {
436                 Some(('$', end2)) => {
437                     state = Prec;
438                     width = Some(Num::from_str("", Some(at.slice_between(end).unwrap())));
439                     move_to!(end2);
440                 },
441                 _ => {
442                     state = Prec;
443                     width = Some(Num::Next);
444                     move_to!(end);
445                 }
446             }
447         }
448
449         if let Prec = state {
450             match c {
451                 '.' => {
452                     state = PrecInner;
453                     move_to!(next);
454                 },
455                 _ => {
456                     state = Length;
457                     precision = None;
458                     move_to!(at);
459                 }
460             }
461         }
462
463         if let PrecInner = state {
464             match c {
465                 '*' => {
466                     let end = at_next_cp_while(next, is_digit);
467                     match end.next_cp() {
468                         Some(('$', end2)) => {
469                             state = Length;
470                             precision = Some(Num::from_str("*", next.slice_between(end)));
471                             move_to!(end2);
472                         },
473                         _ => {
474                             state = Length;
475                             precision = Some(Num::Next);
476                             move_to!(end);
477                         }
478                     }
479                 },
480                 '0' ..= '9' => {
481                     let end = at_next_cp_while(next, is_digit);
482                     state = Length;
483                     precision = Some(Num::from_str(at.slice_between(end).unwrap(), None));
484                     move_to!(end);
485                 },
486                 _ => return fallback(),
487             }
488         }
489
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)))
495                 => {
496                     state = Type;
497                     length = Some(at.slice_between(next1).unwrap());
498                     move_to!(next1);
499                 },
500
501                 ('h', _) | ('l', _) | ('L', _)
502                 | ('z', _) | ('j', _) | ('t', _)
503                 | ('q', _)
504                 => {
505                     state = Type;
506                     length = Some(at.slice_between(next).unwrap());
507                     move_to!(next);
508                 },
509
510                 ('I', _) => {
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,
517                         _ => next
518                     };
519                     state = Type;
520                     length = Some(at.slice_between(end).unwrap());
521                     move_to!(end);
522                 },
523
524                 _ => {
525                     state = Type;
526                     length = None;
527                     move_to!(at);
528                 }
529             }
530         }
531
532         if let Type = state {
533             drop(c);
534             type_ = at.slice_between(next).unwrap();
535
536             // Don't use `move_to!` here, as we *can* be at the end of the input.
537             at = next;
538         }
539
540         drop(c);
541         drop(next);
542
543         end = at;
544
545         let f = Format {
546             span: start.slice_between(end).unwrap(),
547             parameter,
548             flags,
549             width,
550             precision,
551             length,
552             type_,
553         };
554         Some((Substitution::Format(f), end.slice_after()))
555     }
556
557     fn at_next_cp_while<F>(mut cur: Cur, mut pred: F) -> Cur
558     where F: FnMut(char) -> bool {
559         loop {
560             match cur.next_cp() {
561                 Some((c, next)) => if pred(c) {
562                     cur = next;
563                 } else {
564                     return cur;
565                 },
566                 None => return cur,
567             }
568         }
569     }
570
571     fn is_digit(c: char) -> bool {
572         match c {
573             '0' ..= '9' => true,
574             _ => false
575         }
576     }
577
578     fn is_flag(c: char) -> bool {
579         match c {
580             '0' | '-' | '+' | ' ' | '#' | '\'' => true,
581             _ => false
582         }
583     }
584
585     #[cfg(test)]
586     mod tests {
587         use super::{
588             Format as F,
589             Num as N,
590             Substitution as S,
591             iter_subs,
592             parse_next_substitution as pns,
593         };
594
595         macro_rules! assert_eq_pnsat {
596             ($lhs:expr, $rhs:expr) => {
597                 assert_eq!(
598                     pns($lhs).and_then(|(s, _)| s.translate()),
599                     $rhs.map(<String as From<&str>>::from)
600                 )
601             };
602         }
603
604         #[test]
605         fn test_escape() {
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, "")));
611         }
612
613         #[test]
614         fn test_parse() {
615             macro_rules! assert_pns_eq_sub {
616                 ($in_:expr, {
617                     $param:expr, $flags:expr,
618                     $width:expr, $prec:expr, $len:expr, $type_:expr,
619                 }) => {
620                     assert_eq!(
621                         pns(concat!($in_, "!")),
622                         Some((
623                             S::Format(F {
624                                 span: $in_,
625                                 parameter: $param,
626                                 flags: $flags,
627                                 width: $width,
628                                 precision: $prec,
629                                 length: $len,
630                                 type_: $type_,
631                             }),
632                             "!"
633                         ))
634                     )
635                 };
636             }
637
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", });
686         }
687
688         #[test]
689         fn test_iter() {
690             let s = "The %d'th word %% is: `%.*s` %!\n";
691             let subs: Vec<_> = iter_subs(s).map(|sub| sub.translate()).collect();
692             assert_eq!(
693                 subs.iter().map(|ms| ms.as_ref().map(|s| &s[..])).collect::<Vec<_>>(),
694                 vec![Some("{}"), None, Some("{:.*}"), None]
695             );
696         }
697
698         /// Check that the translations are what we expect.
699         #[test]
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}"));
713
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}"));
735         }
736     }
737 }
738
739 pub mod shell {
740     use super::strcursor::StrCursor as Cur;
741
742     #[derive(Clone, Debug)]
743     pub enum Substitution<'a> {
744         Ordinal(u8),
745         Name(&'a str),
746         Escape,
747     }
748
749     impl<'a> Substitution<'a> {
750         pub fn as_str(&self) -> String {
751             match *self {
752                 Substitution::Ordinal(n) => format!("${}", n),
753                 Substitution::Name(n) => format!("${}", n),
754                 Substitution::Escape => "$$".into(),
755             }
756         }
757
758         pub fn translate(&self) -> Option<String> {
759             match *self {
760                 Substitution::Ordinal(n) => Some(format!("{{{}}}", n)),
761                 Substitution::Name(n) => Some(format!("{{{}}}", n)),
762                 Substitution::Escape => None,
763             }
764         }
765     }
766
767     /// Returns an iterator over all substitutions in a given string.
768     pub fn iter_subs(s: &str) -> Substitutions {
769         Substitutions {
770             s,
771         }
772     }
773
774     /// Iterator over substitutions in a string.
775     pub struct Substitutions<'a> {
776         s: &'a str,
777     }
778
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)) => {
784                     self.s = tail;
785                     Some(sub)
786                 },
787                 None => None,
788             }
789         }
790
791         fn size_hint(&self) -> (usize, Option<usize>) {
792             (0, Some(self.s.len()))
793         }
794     }
795
796     /// Parse the next substitution from the input string.
797     pub fn parse_next_substitution(s: &str) -> Option<(Substitution, &str)> {
798         let at = {
799             let start = s.find('$')?;
800             match s[start+1..].chars().next()? {
801                 '$' => return Some((Substitution::Escape, &s[start+2..])),
802                 c @ '0' ..= '9' => {
803                     let n = (c as u8) - b'0';
804                     return Some((Substitution::Ordinal(n), &s[start+2..]));
805                 },
806                 _ => {/* fall-through */},
807             }
808
809             Cur::new_at_start(&s[start..])
810         };
811
812         let at = at.at_next_cp()?;
813         let (c, inner) = at.next_cp()?;
814
815         if !is_ident_head(c) {
816             None
817         } else {
818             let end = at_next_cp_while(inner, is_ident_tail);
819             Some((Substitution::Name(at.slice_between(end).unwrap()), end.slice_after()))
820         }
821     }
822
823     fn at_next_cp_while<F>(mut cur: Cur, mut pred: F) -> Cur
824     where F: FnMut(char) -> bool {
825         loop {
826             match cur.next_cp() {
827                 Some((c, next)) => if pred(c) {
828                     cur = next;
829                 } else {
830                     return cur;
831                 },
832                 None => return cur,
833             }
834         }
835     }
836
837     fn is_ident_head(c: char) -> bool {
838         match c {
839             'a' ..= 'z' | 'A' ..= 'Z' | '_' => true,
840             _ => false
841         }
842     }
843
844     fn is_ident_tail(c: char) -> bool {
845         match c {
846             '0' ..= '9' => true,
847             c => is_ident_head(c)
848         }
849     }
850
851     #[cfg(test)]
852     mod tests {
853         use super::{
854             Substitution as S,
855             parse_next_substitution as pns,
856         };
857
858         macro_rules! assert_eq_pnsat {
859             ($lhs:expr, $rhs:expr) => {
860                 assert_eq!(
861                     pns($lhs).and_then(|(f, _)| f.translate()),
862                     $rhs.map(<String as From<&str>>::from)
863                 )
864             };
865         }
866
867         #[test]
868         fn test_escape() {
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, "")));
874         }
875
876         #[test]
877         fn test_parse() {
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()), "!")))
881                 };
882             }
883
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"));
889         }
890
891         #[test]
892         fn test_iter() {
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();
896             assert_eq!(
897                 subs.iter().map(|ms| ms.as_ref().map(|s| &s[..])).collect::<Vec<_>>(),
898                 vec![Some("{0}"), None, Some("{WORD}")]
899             );
900         }
901
902         #[test]
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}"));
911         }
912
913     }
914 }
915
916 mod strcursor {
917     use std;
918
919     pub struct StrCursor<'a> {
920         s: &'a str,
921         at: usize,
922     }
923
924     impl<'a> StrCursor<'a> {
925         pub fn new_at_start(s: &'a str) -> StrCursor<'a> {
926             StrCursor {
927                 s,
928                 at: 0,
929             }
930         }
931
932         pub fn at_next_cp(mut self) -> Option<StrCursor<'a>> {
933             match self.try_seek_right_cp() {
934                 true => Some(self),
935                 false => None
936             }
937         }
938
939         pub fn next_cp(mut self) -> Option<(char, StrCursor<'a>)> {
940             let cp = self.cp_after()?;
941             self.seek_right(cp.len_utf8());
942             Some((cp, self))
943         }
944
945         fn slice_before(&self) -> &'a str {
946             &self.s[0..self.at]
947         }
948
949         pub fn slice_after(&self) -> &'a str {
950             &self.s[self.at..]
951         }
952
953         pub fn slice_between(&self, until: StrCursor<'a>) -> Option<&'a str> {
954             if !str_eq_literal(self.s, until.s) {
955                 None
956             } else {
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])
961             }
962         }
963
964         fn cp_after(&self) -> Option<char> {
965             self.slice_after().chars().next()
966         }
967
968         fn try_seek_right_cp(&mut self) -> bool {
969             match self.slice_after().chars().next() {
970                 Some(c) => {
971                     self.at += c.len_utf8();
972                     true
973                 },
974                 None => false,
975             }
976         }
977
978         fn seek_right(&mut self, bytes: usize) {
979             self.at += bytes;
980         }
981     }
982
983     impl<'a> Copy for StrCursor<'a> {}
984
985     impl<'a> Clone for StrCursor<'a> {
986         fn clone(&self) -> StrCursor<'a> {
987             *self
988         }
989     }
990
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())
994         }
995     }
996
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()
1000     }
1001 }