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