]> git.lizzy.rs Git - rust.git/blob - src/libterm/terminfo/parm.rs
Removed some unnecessary RefCells from resolve
[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     Words(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.iter_mut().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) => {
127                                 output.push(if c == 0 {
128                                     128u8
129                                 } else {
130                                     c as u8
131                                 })
132                             }
133                             _       => return Err("a non-char was used with %c".to_string())
134                         }
135                     } else { return Err("stack is empty".to_string()) },
136                     'p' => state = PushParam,
137                     'P' => state = SetVar,
138                     'g' => state = GetVar,
139                     '\'' => state = CharConstant,
140                     '{' => state = IntConstant(0),
141                     'l' => if stack.len() > 0 {
142                         match stack.pop().unwrap() {
143                             Words(s) => stack.push(Number(s.len() as int)),
144                             _        => return Err("a non-str was used with %l".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                     '/' => 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                     'm' => 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(x ^ y)),
192                             _ => return Err("non-numbers on stack with ^".to_string())
193                         }
194                     } else { return Err("stack is empty".to_string()) },
195                     '=' => if stack.len() > 1 {
196                         match (stack.pop().unwrap(), stack.pop().unwrap()) {
197                             (Number(y), Number(x)) => stack.push(Number(if x == y { 1 }
198                                                                         else { 0 })),
199                             _ => return Err("non-numbers on stack with =".to_string())
200                         }
201                     } else { return Err("stack is empty".to_string()) },
202                     '>' => if stack.len() > 1 {
203                         match (stack.pop().unwrap(), stack.pop().unwrap()) {
204                             (Number(y), Number(x)) => stack.push(Number(if x > y { 1 }
205                                                                         else { 0 })),
206                             _ => return Err("non-numbers on stack with >".to_string())
207                         }
208                     } else { return Err("stack is empty".to_string()) },
209                     '<' => if stack.len() > 1 {
210                         match (stack.pop().unwrap(), stack.pop().unwrap()) {
211                             (Number(y), Number(x)) => stack.push(Number(if x < y { 1 }
212                                                                         else { 0 })),
213                             _ => return Err("non-numbers on stack with <".to_string())
214                         }
215                     } else { return Err("stack is empty".to_string()) },
216                     'A' => if stack.len() > 1 {
217                         match (stack.pop().unwrap(), stack.pop().unwrap()) {
218                             (Number(0), Number(_)) => stack.push(Number(0)),
219                             (Number(_), Number(0)) => stack.push(Number(0)),
220                             (Number(_), Number(_)) => stack.push(Number(1)),
221                             _ => return Err("non-numbers on stack with logical and".to_string())
222                         }
223                     } else { return Err("stack is empty".to_string()) },
224                     'O' => if stack.len() > 1 {
225                         match (stack.pop().unwrap(), stack.pop().unwrap()) {
226                             (Number(0), Number(0)) => stack.push(Number(0)),
227                             (Number(_), Number(_)) => stack.push(Number(1)),
228                             _ => return Err("non-numbers on stack with logical or".to_string())
229                         }
230                     } else { return Err("stack is empty".to_string()) },
231                     '!' => if stack.len() > 0 {
232                         match stack.pop().unwrap() {
233                             Number(0) => stack.push(Number(1)),
234                             Number(_) => stack.push(Number(0)),
235                             _ => return Err("non-number on stack with logical not".to_string())
236                         }
237                     } else { return Err("stack is empty".to_string()) },
238                     '~' => if stack.len() > 0 {
239                         match stack.pop().unwrap() {
240                             Number(x) => stack.push(Number(!x)),
241                             _         => return Err("non-number on stack with %~".to_string())
242                         }
243                     } else { return Err("stack is empty".to_string()) },
244                     'i' => match (mparams[0].clone(), mparams[1].clone()) {
245                         (Number(x), Number(y)) => {
246                             mparams[0] = Number(x+1);
247                             mparams[1] = Number(y+1);
248                         },
249                         (_, _) => return Err("first two params not numbers with %i".to_string())
250                     },
251
252                     // printf-style support for %doxXs
253                     'd'|'o'|'x'|'X'|'s' => if stack.len() > 0 {
254                         let flags = Flags::new();
255                         let res = format(stack.pop().unwrap(), FormatOp::from_char(cur), flags);
256                         if res.is_err() { return res }
257                         output.push_all(res.unwrap().as_slice())
258                     } else { return Err("stack is empty".to_string()) },
259                     ':'|'#'|' '|'.'|'0'..'9' => {
260                         let mut flags = Flags::new();
261                         let mut fstate = FormatStateFlags;
262                         match cur {
263                             ':' => (),
264                             '#' => flags.alternate = true,
265                             ' ' => flags.space = true,
266                             '.' => fstate = FormatStatePrecision,
267                             '0'..'9' => {
268                                 flags.width = cur as uint - '0' as uint;
269                                 fstate = FormatStateWidth;
270                             }
271                             _ => unreachable!()
272                         }
273                         state = FormatPattern(flags, fstate);
274                     }
275
276                     // conditionals
277                     '?' => (),
278                     't' => if stack.len() > 0 {
279                         match stack.pop().unwrap() {
280                             Number(0) => state = SeekIfElse(0),
281                             Number(_) => (),
282                             _         => return Err("non-number on stack \
283                                                     with conditional".to_string())
284                         }
285                     } else { return Err("stack is empty".to_string()) },
286                     'e' => state = SeekIfEnd(0),
287                     ';' => (),
288
289                     _ => {
290                         return Err(format!("unrecognized format option {}", cur))
291                     }
292                 }
293             },
294             PushParam => {
295                 // params are 1-indexed
296                 stack.push(mparams[match char::to_digit(cur, 10) {
297                     Some(d) => d - 1,
298                     None => return Err("bad param number".to_string())
299                 }].clone());
300             },
301             SetVar => {
302                 if cur >= 'A' && cur <= 'Z' {
303                     if stack.len() > 0 {
304                         let idx = (cur as u8) - b'A';
305                         vars.sta[idx as uint] = stack.pop().unwrap();
306                     } else { return Err("stack is empty".to_string()) }
307                 } else if cur >= 'a' && cur <= 'z' {
308                     if stack.len() > 0 {
309                         let idx = (cur as u8) - b'a';
310                         vars.dyn[idx as uint] = stack.pop().unwrap();
311                     } else { return Err("stack is empty".to_string()) }
312                 } else {
313                     return Err("bad variable name in %P".to_string());
314                 }
315             },
316             GetVar => {
317                 if cur >= 'A' && cur <= 'Z' {
318                     let idx = (cur as u8) - b'A';
319                     stack.push(vars.sta[idx as uint].clone());
320                 } else if cur >= 'a' && cur <= 'z' {
321                     let idx = (cur as u8) - b'a';
322                     stack.push(vars.dyn[idx as uint].clone());
323                 } else {
324                     return Err("bad variable name in %g".to_string());
325                 }
326             },
327             CharConstant => {
328                 stack.push(Number(c as int));
329                 state = CharClose;
330             },
331             CharClose => {
332                 if cur != '\'' {
333                     return Err("malformed character constant".to_string());
334                 }
335             },
336             IntConstant(i) => {
337                 match cur {
338                     '}' => {
339                         stack.push(Number(i));
340                         state = Nothing;
341                     }
342                     '0'..'9' => {
343                         state = IntConstant(i*10 + (cur as int - '0' as int));
344                         old_state = Nothing;
345                     }
346                     _ => return Err("bad int constant".to_string())
347                 }
348             }
349             FormatPattern(ref mut flags, ref mut fstate) => {
350                 old_state = Nothing;
351                 match (*fstate, cur) {
352                     (_,'d')|(_,'o')|(_,'x')|(_,'X')|(_,'s') => if stack.len() > 0 {
353                         let res = format(stack.pop().unwrap(), FormatOp::from_char(cur), *flags);
354                         if res.is_err() { return res }
355                         output.push_all(res.unwrap().as_slice());
356                         // will cause state to go to Nothing
357                         old_state = FormatPattern(*flags, *fstate);
358                     } else { return Err("stack is empty".to_string()) },
359                     (FormatStateFlags,'#') => {
360                         flags.alternate = true;
361                     }
362                     (FormatStateFlags,'-') => {
363                         flags.left = true;
364                     }
365                     (FormatStateFlags,'+') => {
366                         flags.sign = true;
367                     }
368                     (FormatStateFlags,' ') => {
369                         flags.space = true;
370                     }
371                     (FormatStateFlags,'0'..'9') => {
372                         flags.width = cur as uint - '0' as uint;
373                         *fstate = FormatStateWidth;
374                     }
375                     (FormatStateFlags,'.') => {
376                         *fstate = FormatStatePrecision;
377                     }
378                     (FormatStateWidth,'0'..'9') => {
379                         let old = flags.width;
380                         flags.width = flags.width * 10 + (cur as uint - '0' as uint);
381                         if flags.width < old { return Err("format width overflow".to_string()) }
382                     }
383                     (FormatStateWidth,'.') => {
384                         *fstate = FormatStatePrecision;
385                     }
386                     (FormatStatePrecision,'0'..'9') => {
387                         let old = flags.precision;
388                         flags.precision = flags.precision * 10 + (cur as uint - '0' as uint);
389                         if flags.precision < old {
390                             return Err("format precision overflow".to_string())
391                         }
392                     }
393                     _ => return Err("invalid format specifier".to_string())
394                 }
395             }
396             SeekIfElse(level) => {
397                 if cur == '%' {
398                     state = SeekIfElsePercent(level);
399                 }
400                 old_state = Nothing;
401             }
402             SeekIfElsePercent(level) => {
403                 if cur == ';' {
404                     if level == 0 {
405                         state = Nothing;
406                     } else {
407                         state = SeekIfElse(level-1);
408                     }
409                 } else if cur == 'e' && level == 0 {
410                     state = Nothing;
411                 } else if cur == '?' {
412                     state = SeekIfElse(level+1);
413                 } else {
414                     state = SeekIfElse(level);
415                 }
416             }
417             SeekIfEnd(level) => {
418                 if cur == '%' {
419                     state = SeekIfEndPercent(level);
420                 }
421                 old_state = Nothing;
422             }
423             SeekIfEndPercent(level) => {
424                 if cur == ';' {
425                     if level == 0 {
426                         state = Nothing;
427                     } else {
428                         state = SeekIfEnd(level-1);
429                     }
430                 } else if cur == '?' {
431                     state = SeekIfEnd(level+1);
432                 } else {
433                     state = SeekIfEnd(level);
434                 }
435             }
436         }
437         if state == old_state {
438             state = Nothing;
439         }
440     }
441     Ok(output)
442 }
443
444 #[deriving(PartialEq)]
445 struct Flags {
446     width: uint,
447     precision: uint,
448     alternate: bool,
449     left: bool,
450     sign: bool,
451     space: bool
452 }
453
454 impl Flags {
455     fn new() -> Flags {
456         Flags{ width: 0, precision: 0, alternate: false,
457                left: false, sign: false, space: false }
458     }
459 }
460
461 enum FormatOp {
462     FormatDigit,
463     FormatOctal,
464     FormatHex,
465     FormatHEX,
466     FormatString
467 }
468
469 impl FormatOp {
470     fn from_char(c: char) -> FormatOp {
471         match c {
472             'd' => FormatDigit,
473             'o' => FormatOctal,
474             'x' => FormatHex,
475             'X' => FormatHEX,
476             's' => FormatString,
477             _ => fail!("bad FormatOp char")
478         }
479     }
480     fn to_char(self) -> char {
481         match self {
482             FormatDigit => 'd',
483             FormatOctal => 'o',
484             FormatHex => 'x',
485             FormatHEX => 'X',
486             FormatString => 's'
487         }
488     }
489 }
490
491 fn format(val: Param, op: FormatOp, flags: Flags) -> Result<Vec<u8> ,String> {
492     let mut s = match val {
493         Number(d) => {
494             let s = match (op, flags.sign) {
495                 (FormatDigit, true)  => format!("{:+d}", d).into_bytes(),
496                 (FormatDigit, false) => format!("{:d}", d).into_bytes(),
497                 (FormatOctal, _)     => format!("{:o}", d).into_bytes(),
498                 (FormatHex, _)       => format!("{:x}", d).into_bytes(),
499                 (FormatHEX, _)       => format!("{:X}", d).into_bytes(),
500                 (FormatString, _)    => {
501                     return Err("non-number on stack with %s".to_string())
502                 }
503             };
504             let mut s: Vec<u8> = s.into_iter().collect();
505             if flags.precision > s.len() {
506                 let mut s_ = Vec::with_capacity(flags.precision);
507                 let n = flags.precision - s.len();
508                 s_.grow(n, b'0');
509                 s_.extend(s.into_iter());
510                 s = s_;
511             }
512             assert!(!s.is_empty(), "string conversion produced empty result");
513             match op {
514                 FormatDigit => {
515                     if flags.space && !(s[0] == b'-' || s[0] == b'+' ) {
516                         s.insert(0, b' ');
517                     }
518                 }
519                 FormatOctal => {
520                     if flags.alternate && s[0] != b'0' {
521                         s.insert(0, b'0');
522                     }
523                 }
524                 FormatHex => {
525                     if flags.alternate {
526                         let s_ = replace(&mut s, vec!(b'0', b'x'));
527                         s.extend(s_.into_iter());
528                     }
529                 }
530                 FormatHEX => {
531                     s = s.as_slice()
532                          .to_ascii()
533                          .to_upper()
534                          .into_bytes()
535                          .into_iter()
536                          .collect();
537                     if flags.alternate {
538                         let s_ = replace(&mut s, vec!(b'0', b'X'));
539                         s.extend(s_.into_iter());
540                     }
541                 }
542                 FormatString => unreachable!()
543             }
544             s
545         }
546         Words(s) => {
547             match op {
548                 FormatString => {
549                     let mut s = s.as_bytes().to_vec();
550                     if flags.precision > 0 && flags.precision < s.len() {
551                         s.truncate(flags.precision);
552                     }
553                     s
554                 }
555                 _ => {
556                     return Err(format!("non-string on stack with %{}",
557                                        op.to_char()))
558                 }
559             }
560         }
561     };
562     if flags.width > s.len() {
563         let n = flags.width - s.len();
564         if flags.left {
565             s.grow(n, b' ');
566         } else {
567             let mut s_ = Vec::with_capacity(flags.width);
568             s_.grow(n, b' ');
569             s_.extend(s.into_iter());
570             s = s_;
571         }
572     }
573     Ok(s)
574 }
575
576 #[cfg(test)]
577 mod test {
578     use super::{expand,Words,Variables,Number};
579     use std::result::Ok;
580
581     #[test]
582     fn test_basic_setabf() {
583         let s = b"\\E[48;5;%p1%dm";
584         assert_eq!(expand(s, [Number(1)], &mut Variables::new()).unwrap(),
585                    "\\E[48;5;1m".bytes().collect());
586     }
587
588     #[test]
589     fn test_multiple_int_constants() {
590         assert_eq!(expand(b"%{1}%{2}%d%d", [], &mut Variables::new()).unwrap(),
591                    "21".bytes().collect());
592     }
593
594     #[test]
595     fn test_op_i() {
596         let mut vars = Variables::new();
597         assert_eq!(expand(b"%p1%d%p2%d%p3%d%i%p1%d%p2%d%p3%d",
598                           [Number(1),Number(2),Number(3)], &mut vars),
599                    Ok("123233".bytes().collect()));
600         assert_eq!(expand(b"%p1%d%p2%d%i%p1%d%p2%d", [], &mut vars),
601                    Ok("0011".bytes().collect()));
602     }
603
604     #[test]
605     fn test_param_stack_failure_conditions() {
606         let mut varstruct = Variables::new();
607         let vars = &mut varstruct;
608         let caps = ["%d", "%c", "%s", "%Pa", "%l", "%!", "%~"];
609         for cap in caps.iter() {
610             let res = expand(cap.as_bytes(), [], vars);
611             assert!(res.is_err(),
612                     "Op {} succeeded incorrectly with 0 stack entries", *cap);
613             let p = if *cap == "%s" || *cap == "%l" {
614                 Words("foo".to_string())
615             } else {
616                 Number(97)
617             };
618             let res = expand("%p1".bytes().collect::<Vec<_>>()
619                              .append(cap.as_bytes()).as_slice(),
620                              [p],
621                              vars);
622             assert!(res.is_ok(),
623                     "Op {} failed with 1 stack entry: {}", *cap, res.unwrap_err());
624         }
625         let caps = ["%+", "%-", "%*", "%/", "%m", "%&", "%|", "%A", "%O"];
626         for cap in caps.iter() {
627             let res = expand(cap.as_bytes(), [], vars);
628             assert!(res.is_err(),
629                     "Binop {} succeeded incorrectly with 0 stack entries", *cap);
630             let res = expand("%{1}".bytes().collect::<Vec<_>>()
631                              .append(cap.as_bytes()).as_slice(),
632                               [],
633                               vars);
634             assert!(res.is_err(),
635                     "Binop {} succeeded incorrectly with 1 stack entry", *cap);
636             let res = expand("%{1}%{2}".bytes().collect::<Vec<_>>()
637                              .append(cap.as_bytes()).as_slice(),
638                              [],
639                              vars);
640             assert!(res.is_ok(),
641                     "Binop {} failed with 2 stack entries: {}", *cap, res.unwrap_err());
642         }
643     }
644
645     #[test]
646     fn test_push_bad_param() {
647         assert!(expand(b"%pa", [], &mut Variables::new()).is_err());
648     }
649
650     #[test]
651     fn test_comparison_ops() {
652         let v = [('<', [1u8, 0u8, 0u8]), ('=', [0u8, 1u8, 0u8]), ('>', [0u8, 0u8, 1u8])];
653         for &(op, bs) in v.iter() {
654             let s = format!("%{{1}}%{{2}}%{}%d", op);
655             let res = expand(s.as_bytes(), [], &mut Variables::new());
656             assert!(res.is_ok(), res.unwrap_err());
657             assert_eq!(res.unwrap(), vec!(b'0' + bs[0]));
658             let s = format!("%{{1}}%{{1}}%{}%d", op);
659             let res = expand(s.as_bytes(), [], &mut Variables::new());
660             assert!(res.is_ok(), res.unwrap_err());
661             assert_eq!(res.unwrap(), vec!(b'0' + bs[1]));
662             let s = format!("%{{2}}%{{1}}%{}%d", op);
663             let res = expand(s.as_bytes(), [], &mut Variables::new());
664             assert!(res.is_ok(), res.unwrap_err());
665             assert_eq!(res.unwrap(), vec!(b'0' + bs[2]));
666         }
667     }
668
669     #[test]
670     fn test_conditionals() {
671         let mut vars = Variables::new();
672         let s = b"\\E[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;m";
673         let res = expand(s, [Number(1)], &mut vars);
674         assert!(res.is_ok(), res.unwrap_err());
675         assert_eq!(res.unwrap(),
676                    "\\E[31m".bytes().collect());
677         let res = expand(s, [Number(8)], &mut vars);
678         assert!(res.is_ok(), res.unwrap_err());
679         assert_eq!(res.unwrap(),
680                    "\\E[90m".bytes().collect());
681         let res = expand(s, [Number(42)], &mut vars);
682         assert!(res.is_ok(), res.unwrap_err());
683         assert_eq!(res.unwrap(),
684                    "\\E[38;5;42m".bytes().collect());
685     }
686
687     #[test]
688     fn test_format() {
689         let mut varstruct = Variables::new();
690         let vars = &mut varstruct;
691         assert_eq!(expand(b"%p1%s%p2%2s%p3%2s%p4%.2s",
692                           [Words("foo".to_string()),
693                            Words("foo".to_string()),
694                            Words("f".to_string()),
695                            Words("foo".to_string())], vars),
696                    Ok("foofoo ffo".bytes().collect()));
697         assert_eq!(expand(b"%p1%:-4.2s", [Words("foo".to_string())], vars),
698                    Ok("fo  ".bytes().collect()));
699
700         assert_eq!(expand(b"%p1%d%p1%.3d%p1%5d%p1%:+d", [Number(1)], vars),
701                    Ok("1001    1+1".bytes().collect()));
702         assert_eq!(expand(b"%p1%o%p1%#o%p2%6.4x%p2%#6.4X", [Number(15), Number(27)], vars),
703                    Ok("17017  001b0X001B".bytes().collect()));
704     }
705 }