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