]> git.lizzy.rs Git - rust.git/blob - src/libterm/terminfo/parm.rs
std: Rename Show/String to Debug/Display
[rust.git] / src / libterm / terminfo / parm.rs
1 // Copyright 2012 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 //! Parameterized string expansion
12
13 pub use self::Param::*;
14 use self::States::*;
15 use self::FormatState::*;
16 use self::FormatOp::*;
17 use std::ascii::OwnedAsciiExt;
18 use std::mem::replace;
19 use std::iter::repeat;
20
21 #[derive(Copy, PartialEq)]
22 enum States {
23     Nothing,
24     Percent,
25     SetVar,
26     GetVar,
27     PushParam,
28     CharConstant,
29     CharClose,
30     IntConstant(int),
31     FormatPattern(Flags, FormatState),
32     SeekIfElse(int),
33     SeekIfElsePercent(int),
34     SeekIfEnd(int),
35     SeekIfEndPercent(int)
36 }
37
38 #[derive(Copy, PartialEq)]
39 enum FormatState {
40     FormatStateFlags,
41     FormatStateWidth,
42     FormatStatePrecision
43 }
44
45 /// Types of parameters a capability can use
46 #[allow(missing_docs)]
47 #[derive(Clone)]
48 pub enum Param {
49     Words(String),
50     Number(int)
51 }
52
53 /// Container for static and dynamic variable arrays
54 pub struct Variables {
55     /// Static variables A-Z
56     sta: [Param; 26],
57     /// Dynamic variables a-z
58     dyn: [Param; 26]
59 }
60
61 impl Variables {
62     /// Return a new zero-initialized Variables
63     pub fn new() -> Variables {
64         Variables {
65             sta: [
66                 Number(0), Number(0), Number(0), Number(0), Number(0),
67                 Number(0), Number(0), Number(0), Number(0), Number(0),
68                 Number(0), Number(0), Number(0), Number(0), Number(0),
69                 Number(0), Number(0), Number(0), Number(0), Number(0),
70                 Number(0), Number(0), Number(0), Number(0), Number(0),
71                 Number(0),
72             ],
73             dyn: [
74                 Number(0), Number(0), Number(0), Number(0), Number(0),
75                 Number(0), Number(0), Number(0), Number(0), Number(0),
76                 Number(0), Number(0), Number(0), Number(0), Number(0),
77                 Number(0), Number(0), Number(0), Number(0), Number(0),
78                 Number(0), Number(0), Number(0), Number(0), Number(0),
79                 Number(0),
80             ],
81         }
82     }
83 }
84
85 /// Expand a parameterized capability
86 ///
87 /// # Arguments
88 /// * `cap`    - string to expand
89 /// * `params` - vector of params for %p1 etc
90 /// * `vars`   - Variables struct for %Pa etc
91 ///
92 /// To be compatible with ncurses, `vars` should be the same between calls to `expand` for
93 /// multiple capabilities for the same terminal.
94 pub fn expand(cap: &[u8], params: &[Param], vars: &mut Variables)
95     -> Result<Vec<u8> , String> {
96     let mut state = Nothing;
97
98     // expanded cap will only rarely be larger than the cap itself
99     let mut output = Vec::with_capacity(cap.len());
100
101     let mut stack: Vec<Param> = Vec::new();
102
103     // Copy parameters into a local vector for mutability
104     let mut mparams = [
105         Number(0), Number(0), Number(0), Number(0), Number(0),
106         Number(0), Number(0), Number(0), Number(0),
107     ];
108     for (dst, src) in mparams.iter_mut().zip(params.iter()) {
109         *dst = (*src).clone();
110     }
111
112     for &c in cap.iter() {
113         let cur = c as char;
114         let mut old_state = state;
115         match state {
116             Nothing => {
117                 if cur == '%' {
118                     state = Percent;
119                 } else {
120                     output.push(c);
121                 }
122             },
123             Percent => {
124                 match cur {
125                     '%' => { output.push(c); state = Nothing },
126                     'c' => if stack.len() > 0 {
127                         match stack.pop().unwrap() {
128                             // if c is 0, use 0200 (128) for ncurses compatibility
129                             Number(c) => {
130                                 output.push(if c == 0 {
131                                     128u8
132                                 } else {
133                                     c as u8
134                                 })
135                             }
136                             _       => return Err("a non-char was used with %c".to_string())
137                         }
138                     } else { return Err("stack is empty".to_string()) },
139                     'p' => state = PushParam,
140                     'P' => state = SetVar,
141                     'g' => state = GetVar,
142                     '\'' => state = CharConstant,
143                     '{' => state = IntConstant(0),
144                     'l' => if stack.len() > 0 {
145                         match stack.pop().unwrap() {
146                             Words(s) => stack.push(Number(s.len() as int)),
147                             _        => return Err("a non-str was used with %l".to_string())
148                         }
149                     } else { return Err("stack is empty".to_string()) },
150                     '+' => if stack.len() > 1 {
151                         match (stack.pop().unwrap(), stack.pop().unwrap()) {
152                             (Number(y), Number(x)) => stack.push(Number(x + y)),
153                             _ => return Err("non-numbers on stack with +".to_string())
154                         }
155                     } else { return Err("stack is empty".to_string()) },
156                     '-' => if stack.len() > 1 {
157                         match (stack.pop().unwrap(), stack.pop().unwrap()) {
158                             (Number(y), Number(x)) => stack.push(Number(x - y)),
159                             _ => return Err("non-numbers on stack with -".to_string())
160                         }
161                     } else { return Err("stack is empty".to_string()) },
162                     '*' => if stack.len() > 1 {
163                         match (stack.pop().unwrap(), stack.pop().unwrap()) {
164                             (Number(y), Number(x)) => stack.push(Number(x * y)),
165                             _ => return Err("non-numbers on stack with *".to_string())
166                         }
167                     } else { return Err("stack is empty".to_string()) },
168                     '/' => if stack.len() > 1 {
169                         match (stack.pop().unwrap(), stack.pop().unwrap()) {
170                             (Number(y), Number(x)) => stack.push(Number(x / y)),
171                             _ => return Err("non-numbers on stack with /".to_string())
172                         }
173                     } else { return Err("stack is empty".to_string()) },
174                     'm' => if stack.len() > 1 {
175                         match (stack.pop().unwrap(), stack.pop().unwrap()) {
176                             (Number(y), Number(x)) => stack.push(Number(x % y)),
177                             _ => return Err("non-numbers on stack with %".to_string())
178                         }
179                     } else { return Err("stack is empty".to_string()) },
180                     '&' => if stack.len() > 1 {
181                         match (stack.pop().unwrap(), stack.pop().unwrap()) {
182                             (Number(y), Number(x)) => stack.push(Number(x & y)),
183                             _ => return Err("non-numbers on stack with &".to_string())
184                         }
185                     } else { return Err("stack is empty".to_string()) },
186                     '|' => if stack.len() > 1 {
187                         match (stack.pop().unwrap(), stack.pop().unwrap()) {
188                             (Number(y), Number(x)) => stack.push(Number(x | y)),
189                             _ => return Err("non-numbers on stack with |".to_string())
190                         }
191                     } else { return Err("stack is empty".to_string()) },
192                     '^' => if stack.len() > 1 {
193                         match (stack.pop().unwrap(), stack.pop().unwrap()) {
194                             (Number(y), Number(x)) => stack.push(Number(x ^ y)),
195                             _ => return Err("non-numbers on stack with ^".to_string())
196                         }
197                     } else { return Err("stack is empty".to_string()) },
198                     '=' => if stack.len() > 1 {
199                         match (stack.pop().unwrap(), stack.pop().unwrap()) {
200                             (Number(y), Number(x)) => stack.push(Number(if x == y { 1 }
201                                                                         else { 0 })),
202                             _ => return Err("non-numbers on stack with =".to_string())
203                         }
204                     } else { return Err("stack is empty".to_string()) },
205                     '>' => if stack.len() > 1 {
206                         match (stack.pop().unwrap(), stack.pop().unwrap()) {
207                             (Number(y), Number(x)) => stack.push(Number(if x > y { 1 }
208                                                                         else { 0 })),
209                             _ => return Err("non-numbers on stack with >".to_string())
210                         }
211                     } else { return Err("stack is empty".to_string()) },
212                     '<' => if stack.len() > 1 {
213                         match (stack.pop().unwrap(), stack.pop().unwrap()) {
214                             (Number(y), Number(x)) => stack.push(Number(if x < y { 1 }
215                                                                         else { 0 })),
216                             _ => return Err("non-numbers on stack with <".to_string())
217                         }
218                     } else { return Err("stack is empty".to_string()) },
219                     'A' => if stack.len() > 1 {
220                         match (stack.pop().unwrap(), stack.pop().unwrap()) {
221                             (Number(0), Number(_)) => stack.push(Number(0)),
222                             (Number(_), Number(0)) => stack.push(Number(0)),
223                             (Number(_), Number(_)) => stack.push(Number(1)),
224                             _ => return Err("non-numbers on stack with logical and".to_string())
225                         }
226                     } else { return Err("stack is empty".to_string()) },
227                     'O' => if stack.len() > 1 {
228                         match (stack.pop().unwrap(), stack.pop().unwrap()) {
229                             (Number(0), Number(0)) => stack.push(Number(0)),
230                             (Number(_), Number(_)) => stack.push(Number(1)),
231                             _ => return Err("non-numbers on stack with logical or".to_string())
232                         }
233                     } else { return Err("stack is empty".to_string()) },
234                     '!' => if stack.len() > 0 {
235                         match stack.pop().unwrap() {
236                             Number(0) => stack.push(Number(1)),
237                             Number(_) => stack.push(Number(0)),
238                             _ => return Err("non-number on stack with logical not".to_string())
239                         }
240                     } else { return Err("stack is empty".to_string()) },
241                     '~' => if stack.len() > 0 {
242                         match stack.pop().unwrap() {
243                             Number(x) => stack.push(Number(!x)),
244                             _         => return Err("non-number on stack with %~".to_string())
245                         }
246                     } else { return Err("stack is empty".to_string()) },
247                     'i' => match (mparams[0].clone(), mparams[1].clone()) {
248                         (Number(x), Number(y)) => {
249                             mparams[0] = Number(x+1);
250                             mparams[1] = Number(y+1);
251                         },
252                         (_, _) => return Err("first two params not numbers with %i".to_string())
253                     },
254
255                     // printf-style support for %doxXs
256                     'd'|'o'|'x'|'X'|'s' => if stack.len() > 0 {
257                         let flags = Flags::new();
258                         let res = format(stack.pop().unwrap(), FormatOp::from_char(cur), flags);
259                         if res.is_err() { return res }
260                         output.push_all(res.unwrap().as_slice())
261                     } else { return Err("stack is empty".to_string()) },
262                     ':'|'#'|' '|'.'|'0'...'9' => {
263                         let mut flags = Flags::new();
264                         let mut fstate = FormatStateFlags;
265                         match cur {
266                             ':' => (),
267                             '#' => flags.alternate = true,
268                             ' ' => flags.space = true,
269                             '.' => fstate = FormatStatePrecision,
270                             '0'...'9' => {
271                                 flags.width = cur as uint - '0' as uint;
272                                 fstate = FormatStateWidth;
273                             }
274                             _ => unreachable!()
275                         }
276                         state = FormatPattern(flags, fstate);
277                     }
278
279                     // conditionals
280                     '?' => (),
281                     't' => if stack.len() > 0 {
282                         match stack.pop().unwrap() {
283                             Number(0) => state = SeekIfElse(0),
284                             Number(_) => (),
285                             _         => return Err("non-number on stack \
286                                                     with conditional".to_string())
287                         }
288                     } else { return Err("stack is empty".to_string()) },
289                     'e' => state = SeekIfEnd(0),
290                     ';' => (),
291
292                     _ => {
293                         return Err(format!("unrecognized format option {:?}", cur))
294                     }
295                 }
296             },
297             PushParam => {
298                 // params are 1-indexed
299                 stack.push(mparams[match cur.to_digit(10) {
300                     Some(d) => d - 1,
301                     None => return Err("bad param number".to_string())
302                 }].clone());
303             },
304             SetVar => {
305                 if cur >= 'A' && cur <= 'Z' {
306                     if stack.len() > 0 {
307                         let idx = (cur as u8) - b'A';
308                         vars.sta[idx as uint] = stack.pop().unwrap();
309                     } else { return Err("stack is empty".to_string()) }
310                 } else if cur >= 'a' && cur <= 'z' {
311                     if stack.len() > 0 {
312                         let idx = (cur as u8) - b'a';
313                         vars.dyn[idx as uint] = stack.pop().unwrap();
314                     } else { return Err("stack is empty".to_string()) }
315                 } else {
316                     return Err("bad variable name in %P".to_string());
317                 }
318             },
319             GetVar => {
320                 if cur >= 'A' && cur <= 'Z' {
321                     let idx = (cur as u8) - b'A';
322                     stack.push(vars.sta[idx as uint].clone());
323                 } else if cur >= 'a' && cur <= 'z' {
324                     let idx = (cur as u8) - b'a';
325                     stack.push(vars.dyn[idx as uint].clone());
326                 } else {
327                     return Err("bad variable name in %g".to_string());
328                 }
329             },
330             CharConstant => {
331                 stack.push(Number(c as int));
332                 state = CharClose;
333             },
334             CharClose => {
335                 if cur != '\'' {
336                     return Err("malformed character constant".to_string());
337                 }
338             },
339             IntConstant(i) => {
340                 match cur {
341                     '}' => {
342                         stack.push(Number(i));
343                         state = Nothing;
344                     }
345                     '0'...'9' => {
346                         state = IntConstant(i*10 + (cur as int - '0' as int));
347                         old_state = Nothing;
348                     }
349                     _ => return Err("bad int constant".to_string())
350                 }
351             }
352             FormatPattern(ref mut flags, ref mut fstate) => {
353                 old_state = Nothing;
354                 match (*fstate, cur) {
355                     (_,'d')|(_,'o')|(_,'x')|(_,'X')|(_,'s') => if stack.len() > 0 {
356                         let res = format(stack.pop().unwrap(), FormatOp::from_char(cur), *flags);
357                         if res.is_err() { return res }
358                         output.push_all(res.unwrap().as_slice());
359                         // will cause state to go to Nothing
360                         old_state = FormatPattern(*flags, *fstate);
361                     } else { return Err("stack is empty".to_string()) },
362                     (FormatStateFlags,'#') => {
363                         flags.alternate = true;
364                     }
365                     (FormatStateFlags,'-') => {
366                         flags.left = true;
367                     }
368                     (FormatStateFlags,'+') => {
369                         flags.sign = true;
370                     }
371                     (FormatStateFlags,' ') => {
372                         flags.space = true;
373                     }
374                     (FormatStateFlags,'0'...'9') => {
375                         flags.width = cur as uint - '0' as uint;
376                         *fstate = FormatStateWidth;
377                     }
378                     (FormatStateFlags,'.') => {
379                         *fstate = FormatStatePrecision;
380                     }
381                     (FormatStateWidth,'0'...'9') => {
382                         let old = flags.width;
383                         flags.width = flags.width * 10 + (cur as uint - '0' as uint);
384                         if flags.width < old { return Err("format width overflow".to_string()) }
385                     }
386                     (FormatStateWidth,'.') => {
387                         *fstate = FormatStatePrecision;
388                     }
389                     (FormatStatePrecision,'0'...'9') => {
390                         let old = flags.precision;
391                         flags.precision = flags.precision * 10 + (cur as uint - '0' as uint);
392                         if flags.precision < old {
393                             return Err("format precision overflow".to_string())
394                         }
395                     }
396                     _ => return Err("invalid format specifier".to_string())
397                 }
398             }
399             SeekIfElse(level) => {
400                 if cur == '%' {
401                     state = SeekIfElsePercent(level);
402                 }
403                 old_state = Nothing;
404             }
405             SeekIfElsePercent(level) => {
406                 if cur == ';' {
407                     if level == 0 {
408                         state = Nothing;
409                     } else {
410                         state = SeekIfElse(level-1);
411                     }
412                 } else if cur == 'e' && level == 0 {
413                     state = Nothing;
414                 } else if cur == '?' {
415                     state = SeekIfElse(level+1);
416                 } else {
417                     state = SeekIfElse(level);
418                 }
419             }
420             SeekIfEnd(level) => {
421                 if cur == '%' {
422                     state = SeekIfEndPercent(level);
423                 }
424                 old_state = Nothing;
425             }
426             SeekIfEndPercent(level) => {
427                 if cur == ';' {
428                     if level == 0 {
429                         state = Nothing;
430                     } else {
431                         state = SeekIfEnd(level-1);
432                     }
433                 } else if cur == '?' {
434                     state = SeekIfEnd(level+1);
435                 } else {
436                     state = SeekIfEnd(level);
437                 }
438             }
439         }
440         if state == old_state {
441             state = Nothing;
442         }
443     }
444     Ok(output)
445 }
446
447 #[derive(Copy, PartialEq)]
448 struct Flags {
449     width: uint,
450     precision: uint,
451     alternate: bool,
452     left: bool,
453     sign: bool,
454     space: bool
455 }
456
457 impl Flags {
458     fn new() -> Flags {
459         Flags{ width: 0, precision: 0, alternate: false,
460                left: false, sign: false, space: false }
461     }
462 }
463
464 #[derive(Copy)]
465 enum FormatOp {
466     FormatDigit,
467     FormatOctal,
468     FormatHex,
469     FormatHEX,
470     FormatString
471 }
472
473 impl FormatOp {
474     fn from_char(c: char) -> FormatOp {
475         match c {
476             'd' => FormatDigit,
477             'o' => FormatOctal,
478             'x' => FormatHex,
479             'X' => FormatHEX,
480             's' => FormatString,
481             _ => panic!("bad FormatOp char")
482         }
483     }
484     fn to_char(self) -> char {
485         match self {
486             FormatDigit => 'd',
487             FormatOctal => 'o',
488             FormatHex => 'x',
489             FormatHEX => 'X',
490             FormatString => 's'
491         }
492     }
493 }
494
495 fn format(val: Param, op: FormatOp, flags: Flags) -> Result<Vec<u8> ,String> {
496     let mut s = match val {
497         Number(d) => {
498             let s = match (op, flags.sign) {
499                 (FormatDigit, true)  => format!("{:+}", d).into_bytes(),
500                 (FormatDigit, false) => format!("{}", d).into_bytes(),
501                 (FormatOctal, _)     => format!("{:o}", d).into_bytes(),
502                 (FormatHex, _)       => format!("{:x}", d).into_bytes(),
503                 (FormatHEX, _)       => format!("{:X}", d).into_bytes(),
504                 (FormatString, _)    => {
505                     return Err("non-number on stack with %s".to_string())
506                 }
507             };
508             let mut s: Vec<u8> = s.into_iter().collect();
509             if flags.precision > s.len() {
510                 let mut s_ = Vec::with_capacity(flags.precision);
511                 let n = flags.precision - s.len();
512                 s_.extend(repeat(b'0').take(n));
513                 s_.extend(s.into_iter());
514                 s = s_;
515             }
516             assert!(!s.is_empty(), "string conversion produced empty result");
517             match op {
518                 FormatDigit => {
519                     if flags.space && !(s[0] == b'-' || s[0] == b'+' ) {
520                         s.insert(0, b' ');
521                     }
522                 }
523                 FormatOctal => {
524                     if flags.alternate && s[0] != b'0' {
525                         s.insert(0, b'0');
526                     }
527                 }
528                 FormatHex => {
529                     if flags.alternate {
530                         let s_ = replace(&mut s, vec!(b'0', b'x'));
531                         s.extend(s_.into_iter());
532                     }
533                 }
534                 FormatHEX => {
535                     s = s.into_ascii_uppercase();
536                     if flags.alternate {
537                         let s_ = replace(&mut s, vec!(b'0', b'X'));
538                         s.extend(s_.into_iter());
539                     }
540                 }
541                 FormatString => unreachable!()
542             }
543             s
544         }
545         Words(s) => {
546             match op {
547                 FormatString => {
548                     let mut s = s.as_bytes().to_vec();
549                     if flags.precision > 0 && flags.precision < s.len() {
550                         s.truncate(flags.precision);
551                     }
552                     s
553                 }
554                 _ => {
555                     return Err(format!("non-string on stack with %{:?}",
556                                        op.to_char()))
557                 }
558             }
559         }
560     };
561     if flags.width > s.len() {
562         let n = flags.width - s.len();
563         if flags.left {
564             s.extend(repeat(b' ').take(n));
565         } else {
566             let mut s_ = Vec::with_capacity(flags.width);
567             s_.extend(repeat(b' ').take(n));
568             s_.extend(s.into_iter());
569             s = s_;
570         }
571     }
572     Ok(s)
573 }
574
575 #[cfg(test)]
576 mod test {
577     use super::{expand,Param,Words,Variables,Number};
578     use std::result::Result::Ok;
579
580     #[test]
581     fn test_basic_setabf() {
582         let s = b"\\E[48;5;%p1%dm";
583         assert_eq!(expand(s, &[Number(1)], &mut Variables::new()).unwrap(),
584                    "\\E[48;5;1m".bytes().collect::<Vec<_>>());
585     }
586
587     #[test]
588     fn test_multiple_int_constants() {
589         assert_eq!(expand(b"%{1}%{2}%d%d", &[], &mut Variables::new()).unwrap(),
590                    "21".bytes().collect::<Vec<_>>());
591     }
592
593     #[test]
594     fn test_op_i() {
595         let mut vars = Variables::new();
596         assert_eq!(expand(b"%p1%d%p2%d%p3%d%i%p1%d%p2%d%p3%d",
597                           &[Number(1),Number(2),Number(3)], &mut vars),
598                    Ok("123233".bytes().collect::<Vec<_>>()));
599         assert_eq!(expand(b"%p1%d%p2%d%i%p1%d%p2%d", &[], &mut vars),
600                    Ok("0011".bytes().collect::<Vec<_>>()));
601     }
602
603     #[test]
604     fn test_param_stack_failure_conditions() {
605         let mut varstruct = Variables::new();
606         let vars = &mut varstruct;
607         fn get_res(fmt: &str, cap: &str, params: &[Param], vars: &mut Variables) ->
608             Result<Vec<u8>, String>
609         {
610             let mut u8v: Vec<_> = fmt.bytes().collect();
611             u8v.extend(cap.as_bytes().iter().map(|&b| b));
612             expand(u8v.as_slice(), params, vars)
613         }
614
615         let caps = ["%d", "%c", "%s", "%Pa", "%l", "%!", "%~"];
616         for &cap in caps.iter() {
617             let res = get_res("", cap, &[], vars);
618             assert!(res.is_err(),
619                     "Op {} succeeded incorrectly with 0 stack entries", cap);
620             let p = if cap == "%s" || cap == "%l" {
621                 Words("foo".to_string())
622             } else {
623                 Number(97)
624             };
625             let res = get_res("%p1", cap, &[p], vars);
626             assert!(res.is_ok(),
627                     "Op {} failed with 1 stack entry: {}", cap, res.err().unwrap());
628         }
629         let caps = ["%+", "%-", "%*", "%/", "%m", "%&", "%|", "%A", "%O"];
630         for &cap in caps.iter() {
631             let res = expand(cap.as_bytes(), &[], vars);
632             assert!(res.is_err(),
633                     "Binop {} succeeded incorrectly with 0 stack entries", cap);
634             let res = get_res("%{1}", cap, &[], vars);
635             assert!(res.is_err(),
636                     "Binop {} succeeded incorrectly with 1 stack entry", cap);
637             let res = get_res("%{1}%{2}", cap, &[], vars);
638             assert!(res.is_ok(),
639                     "Binop {} failed with 2 stack entries: {:?}", cap, res.err().unwrap());
640         }
641     }
642
643     #[test]
644     fn test_push_bad_param() {
645         assert!(expand(b"%pa", &[], &mut Variables::new()).is_err());
646     }
647
648     #[test]
649     fn test_comparison_ops() {
650         let v = [('<', [1u8, 0u8, 0u8]), ('=', [0u8, 1u8, 0u8]), ('>', [0u8, 0u8, 1u8])];
651         for &(op, bs) in v.iter() {
652             let s = format!("%{{1}}%{{2}}%{}%d", op);
653             let res = expand(s.as_bytes(), &[], &mut Variables::new());
654             assert!(res.is_ok(), res.err().unwrap());
655             assert_eq!(res.unwrap(), vec!(b'0' + bs[0]));
656             let s = format!("%{{1}}%{{1}}%{}%d", op);
657             let res = expand(s.as_bytes(), &[], &mut Variables::new());
658             assert!(res.is_ok(), res.err().unwrap());
659             assert_eq!(res.unwrap(), vec!(b'0' + bs[1]));
660             let s = format!("%{{2}}%{{1}}%{}%d", op);
661             let res = expand(s.as_bytes(), &[], &mut Variables::new());
662             assert!(res.is_ok(), res.err().unwrap());
663             assert_eq!(res.unwrap(), vec!(b'0' + bs[2]));
664         }
665     }
666
667     #[test]
668     fn test_conditionals() {
669         let mut vars = Variables::new();
670         let s = b"\\E[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;m";
671         let res = expand(s, &[Number(1)], &mut vars);
672         assert!(res.is_ok(), res.err().unwrap());
673         assert_eq!(res.unwrap(),
674                    "\\E[31m".bytes().collect::<Vec<_>>());
675         let res = expand(s, &[Number(8)], &mut vars);
676         assert!(res.is_ok(), res.err().unwrap());
677         assert_eq!(res.unwrap(),
678                    "\\E[90m".bytes().collect::<Vec<_>>());
679         let res = expand(s, &[Number(42)], &mut vars);
680         assert!(res.is_ok(), res.err().unwrap());
681         assert_eq!(res.unwrap(),
682                    "\\E[38;5;42m".bytes().collect::<Vec<_>>());
683     }
684
685     #[test]
686     fn test_format() {
687         let mut varstruct = Variables::new();
688         let vars = &mut varstruct;
689         assert_eq!(expand(b"%p1%s%p2%2s%p3%2s%p4%.2s",
690                           &[Words("foo".to_string()),
691                             Words("foo".to_string()),
692                             Words("f".to_string()),
693                             Words("foo".to_string())], vars),
694                    Ok("foofoo ffo".bytes().collect::<Vec<_>>()));
695         assert_eq!(expand(b"%p1%:-4.2s", &[Words("foo".to_string())], vars),
696                    Ok("fo  ".bytes().collect::<Vec<_>>()));
697
698         assert_eq!(expand(b"%p1%d%p1%.3d%p1%5d%p1%:+d", &[Number(1)], vars),
699                    Ok("1001    1+1".bytes().collect::<Vec<_>>()));
700         assert_eq!(expand(b"%p1%o%p1%#o%p2%6.4x%p2%#6.4X", &[Number(15), Number(27)], vars),
701                    Ok("17017  001b0X001B".bytes().collect::<Vec<_>>()));
702     }
703 }