]> git.lizzy.rs Git - rust.git/blob - library/test/src/term/terminfo/parm.rs
Rollup merge of #87428 - GuillaumeGomez:union-highlighting, r=notriddle
[rust.git] / library / test / src / term / terminfo / parm.rs
1 //! Parameterized string expansion
2
3 use self::Param::*;
4 use self::States::*;
5
6 use std::iter::repeat;
7
8 #[cfg(test)]
9 mod tests;
10
11 #[derive(Clone, Copy, PartialEq)]
12 enum States {
13     Nothing,
14     Percent,
15     SetVar,
16     GetVar,
17     PushParam,
18     CharConstant,
19     CharClose,
20     IntConstant(i32),
21     FormatPattern(Flags, FormatState),
22     SeekIfElse(usize),
23     SeekIfElsePercent(usize),
24     SeekIfEnd(usize),
25     SeekIfEndPercent(usize),
26 }
27
28 #[derive(Copy, PartialEq, Clone)]
29 enum FormatState {
30     Flags,
31     Width,
32     Precision,
33 }
34
35 /// Types of parameters a capability can use
36 #[allow(missing_docs)]
37 #[derive(Clone)]
38 pub(crate) enum Param {
39     Number(i32),
40 }
41
42 /// Container for static and dynamic variable arrays
43 pub(crate) struct Variables {
44     /// Static variables A-Z
45     sta_va: [Param; 26],
46     /// Dynamic variables a-z
47     dyn_va: [Param; 26],
48 }
49
50 impl Variables {
51     /// Returns a new zero-initialized Variables
52     pub(crate) fn new() -> Variables {
53         Variables {
54             sta_va: [
55                 Number(0),
56                 Number(0),
57                 Number(0),
58                 Number(0),
59                 Number(0),
60                 Number(0),
61                 Number(0),
62                 Number(0),
63                 Number(0),
64                 Number(0),
65                 Number(0),
66                 Number(0),
67                 Number(0),
68                 Number(0),
69                 Number(0),
70                 Number(0),
71                 Number(0),
72                 Number(0),
73                 Number(0),
74                 Number(0),
75                 Number(0),
76                 Number(0),
77                 Number(0),
78                 Number(0),
79                 Number(0),
80                 Number(0),
81             ],
82             dyn_va: [
83                 Number(0),
84                 Number(0),
85                 Number(0),
86                 Number(0),
87                 Number(0),
88                 Number(0),
89                 Number(0),
90                 Number(0),
91                 Number(0),
92                 Number(0),
93                 Number(0),
94                 Number(0),
95                 Number(0),
96                 Number(0),
97                 Number(0),
98                 Number(0),
99                 Number(0),
100                 Number(0),
101                 Number(0),
102                 Number(0),
103                 Number(0),
104                 Number(0),
105                 Number(0),
106                 Number(0),
107                 Number(0),
108                 Number(0),
109             ],
110         }
111     }
112 }
113
114 /// Expand a parameterized capability
115 ///
116 /// # Arguments
117 /// * `cap`    - string to expand
118 /// * `params` - vector of params for %p1 etc
119 /// * `vars`   - Variables struct for %Pa etc
120 ///
121 /// To be compatible with ncurses, `vars` should be the same between calls to `expand` for
122 /// multiple capabilities for the same terminal.
123 pub(crate) fn expand(
124     cap: &[u8],
125     params: &[Param],
126     vars: &mut Variables,
127 ) -> Result<Vec<u8>, String> {
128     let mut state = Nothing;
129
130     // expanded cap will only rarely be larger than the cap itself
131     let mut output = Vec::with_capacity(cap.len());
132
133     let mut stack: Vec<Param> = Vec::new();
134
135     // Copy parameters into a local vector for mutability
136     let mut mparams = [
137         Number(0),
138         Number(0),
139         Number(0),
140         Number(0),
141         Number(0),
142         Number(0),
143         Number(0),
144         Number(0),
145         Number(0),
146     ];
147     for (dst, src) in mparams.iter_mut().zip(params.iter()) {
148         *dst = (*src).clone();
149     }
150
151     for &c in cap.iter() {
152         let cur = c as char;
153         let mut old_state = state;
154         match state {
155             Nothing => {
156                 if cur == '%' {
157                     state = Percent;
158                 } else {
159                     output.push(c);
160                 }
161             }
162             Percent => {
163                 match cur {
164                     '%' => {
165                         output.push(c);
166                         state = Nothing
167                     }
168                     'c' => {
169                         match stack.pop() {
170                             // if c is 0, use 0200 (128) for ncurses compatibility
171                             Some(Number(0)) => output.push(128u8),
172                             // Don't check bounds. ncurses just casts and truncates.
173                             Some(Number(c)) => output.push(c as u8),
174                             None => return Err("stack is empty".to_string()),
175                         }
176                     }
177                     'p' => state = PushParam,
178                     'P' => state = SetVar,
179                     'g' => state = GetVar,
180                     '\'' => state = CharConstant,
181                     '{' => state = IntConstant(0),
182                     'l' => match stack.pop() {
183                         Some(_) => return Err("a non-str was used with %l".to_string()),
184                         None => return Err("stack is empty".to_string()),
185                     },
186                     '+' | '-' | '/' | '*' | '^' | '&' | '|' | 'm' => {
187                         match (stack.pop(), stack.pop()) {
188                             (Some(Number(y)), Some(Number(x))) => stack.push(Number(match cur {
189                                 '+' => x + y,
190                                 '-' => x - y,
191                                 '*' => x * y,
192                                 '/' => x / y,
193                                 '|' => x | y,
194                                 '&' => x & y,
195                                 '^' => x ^ y,
196                                 'm' => x % y,
197                                 _ => unreachable!("All cases handled"),
198                             })),
199                             _ => return Err("stack is empty".to_string()),
200                         }
201                     }
202                     '=' | '>' | '<' | 'A' | 'O' => match (stack.pop(), stack.pop()) {
203                         (Some(Number(y)), Some(Number(x))) => stack.push(Number(
204                             if match cur {
205                                 '=' => x == y,
206                                 '<' => x < y,
207                                 '>' => x > y,
208                                 'A' => x > 0 && y > 0,
209                                 'O' => x > 0 || y > 0,
210                                 _ => unreachable!(),
211                             } {
212                                 1
213                             } else {
214                                 0
215                             },
216                         )),
217                         _ => return Err("stack is empty".to_string()),
218                     },
219                     '!' | '~' => match stack.pop() {
220                         Some(Number(x)) => stack.push(Number(match cur {
221                             '!' if x > 0 => 0,
222                             '!' => 1,
223                             '~' => !x,
224                             _ => unreachable!(),
225                         })),
226                         None => return Err("stack is empty".to_string()),
227                     },
228                     'i' => match (&mparams[0], &mparams[1]) {
229                         (&Number(x), &Number(y)) => {
230                             mparams[0] = Number(x + 1);
231                             mparams[1] = Number(y + 1);
232                         }
233                     },
234
235                     // printf-style support for %doxXs
236                     'd' | 'o' | 'x' | 'X' | 's' => {
237                         if let Some(arg) = stack.pop() {
238                             let flags = Flags::new();
239                             let res = format(arg, FormatOp::from_char(cur), flags)?;
240                             output.extend(res.iter().cloned());
241                         } else {
242                             return Err("stack is empty".to_string());
243                         }
244                     }
245                     ':' | '#' | ' ' | '.' | '0'..='9' => {
246                         let mut flags = Flags::new();
247                         let mut fstate = FormatState::Flags;
248                         match cur {
249                             ':' => (),
250                             '#' => flags.alternate = true,
251                             ' ' => flags.space = true,
252                             '.' => fstate = FormatState::Precision,
253                             '0'..='9' => {
254                                 flags.width = cur as usize - '0' as usize;
255                                 fstate = FormatState::Width;
256                             }
257                             _ => unreachable!(),
258                         }
259                         state = FormatPattern(flags, fstate);
260                     }
261
262                     // conditionals
263                     '?' => (),
264                     't' => match stack.pop() {
265                         Some(Number(0)) => state = SeekIfElse(0),
266                         Some(Number(_)) => (),
267                         None => return Err("stack is empty".to_string()),
268                     },
269                     'e' => state = SeekIfEnd(0),
270                     ';' => (),
271                     _ => return Err(format!("unrecognized format option {}", cur)),
272                 }
273             }
274             PushParam => {
275                 // params are 1-indexed
276                 stack.push(
277                     mparams[match cur.to_digit(10) {
278                         Some(d) => d as usize - 1,
279                         None => return Err("bad param number".to_string()),
280                     }]
281                     .clone(),
282                 );
283             }
284             SetVar => {
285                 if cur >= 'A' && cur <= 'Z' {
286                     if let Some(arg) = stack.pop() {
287                         let idx = (cur as u8) - b'A';
288                         vars.sta_va[idx as usize] = arg;
289                     } else {
290                         return Err("stack is empty".to_string());
291                     }
292                 } else if cur >= 'a' && cur <= 'z' {
293                     if let Some(arg) = stack.pop() {
294                         let idx = (cur as u8) - b'a';
295                         vars.dyn_va[idx as usize] = arg;
296                     } else {
297                         return Err("stack is empty".to_string());
298                     }
299                 } else {
300                     return Err("bad variable name in %P".to_string());
301                 }
302             }
303             GetVar => {
304                 if cur >= 'A' && cur <= 'Z' {
305                     let idx = (cur as u8) - b'A';
306                     stack.push(vars.sta_va[idx as usize].clone());
307                 } else if cur >= 'a' && cur <= 'z' {
308                     let idx = (cur as u8) - b'a';
309                     stack.push(vars.dyn_va[idx as usize].clone());
310                 } else {
311                     return Err("bad variable name in %g".to_string());
312                 }
313             }
314             CharConstant => {
315                 stack.push(Number(c as i32));
316                 state = CharClose;
317             }
318             CharClose => {
319                 if cur != '\'' {
320                     return Err("malformed character constant".to_string());
321                 }
322             }
323             IntConstant(i) => {
324                 if cur == '}' {
325                     stack.push(Number(i));
326                     state = Nothing;
327                 } else if let Some(digit) = cur.to_digit(10) {
328                     match i.checked_mul(10).and_then(|i_ten| i_ten.checked_add(digit as i32)) {
329                         Some(i) => {
330                             state = IntConstant(i);
331                             old_state = Nothing;
332                         }
333                         None => return Err("int constant too large".to_string()),
334                     }
335                 } else {
336                     return Err("bad int constant".to_string());
337                 }
338             }
339             FormatPattern(ref mut flags, ref mut fstate) => {
340                 old_state = Nothing;
341                 match (*fstate, cur) {
342                     (_, 'd') | (_, 'o') | (_, 'x') | (_, 'X') | (_, 's') => {
343                         if let Some(arg) = stack.pop() {
344                             let res = format(arg, FormatOp::from_char(cur), *flags)?;
345                             output.extend(res.iter().cloned());
346                             // will cause state to go to Nothing
347                             old_state = FormatPattern(*flags, *fstate);
348                         } else {
349                             return Err("stack is empty".to_string());
350                         }
351                     }
352                     (FormatState::Flags, '#') => {
353                         flags.alternate = true;
354                     }
355                     (FormatState::Flags, '-') => {
356                         flags.left = true;
357                     }
358                     (FormatState::Flags, '+') => {
359                         flags.sign = true;
360                     }
361                     (FormatState::Flags, ' ') => {
362                         flags.space = true;
363                     }
364                     (FormatState::Flags, '0'..='9') => {
365                         flags.width = cur as usize - '0' as usize;
366                         *fstate = FormatState::Width;
367                     }
368                     (FormatState::Flags, '.') => {
369                         *fstate = FormatState::Precision;
370                     }
371                     (FormatState::Width, '0'..='9') => {
372                         let old = flags.width;
373                         flags.width = flags.width * 10 + (cur as usize - '0' as usize);
374                         if flags.width < old {
375                             return Err("format width overflow".to_string());
376                         }
377                     }
378                     (FormatState::Width, '.') => {
379                         *fstate = FormatState::Precision;
380                     }
381                     (FormatState::Precision, '0'..='9') => {
382                         let old = flags.precision;
383                         flags.precision = flags.precision * 10 + (cur as usize - '0' as usize);
384                         if flags.precision < old {
385                             return Err("format precision overflow".to_string());
386                         }
387                     }
388                     _ => return Err("invalid format specifier".to_string()),
389                 }
390             }
391             SeekIfElse(level) => {
392                 if cur == '%' {
393                     state = SeekIfElsePercent(level);
394                 }
395                 old_state = Nothing;
396             }
397             SeekIfElsePercent(level) => {
398                 if cur == ';' {
399                     if level == 0 {
400                         state = Nothing;
401                     } else {
402                         state = SeekIfElse(level - 1);
403                     }
404                 } else if cur == 'e' && level == 0 {
405                     state = Nothing;
406                 } else if cur == '?' {
407                     state = SeekIfElse(level + 1);
408                 } else {
409                     state = SeekIfElse(level);
410                 }
411             }
412             SeekIfEnd(level) => {
413                 if cur == '%' {
414                     state = SeekIfEndPercent(level);
415                 }
416                 old_state = Nothing;
417             }
418             SeekIfEndPercent(level) => {
419                 if cur == ';' {
420                     if level == 0 {
421                         state = Nothing;
422                     } else {
423                         state = SeekIfEnd(level - 1);
424                     }
425                 } else if cur == '?' {
426                     state = SeekIfEnd(level + 1);
427                 } else {
428                     state = SeekIfEnd(level);
429                 }
430             }
431         }
432         if state == old_state {
433             state = Nothing;
434         }
435     }
436     Ok(output)
437 }
438
439 #[derive(Copy, PartialEq, Clone)]
440 struct Flags {
441     width: usize,
442     precision: usize,
443     alternate: bool,
444     left: bool,
445     sign: bool,
446     space: bool,
447 }
448
449 impl Flags {
450     fn new() -> Flags {
451         Flags { width: 0, precision: 0, alternate: false, left: false, sign: false, space: false }
452     }
453 }
454
455 #[derive(Copy, Clone)]
456 enum FormatOp {
457     Digit,
458     Octal,
459     LowerHex,
460     UpperHex,
461     String,
462 }
463
464 impl FormatOp {
465     fn from_char(c: char) -> FormatOp {
466         match c {
467             'd' => FormatOp::Digit,
468             'o' => FormatOp::Octal,
469             'x' => FormatOp::LowerHex,
470             'X' => FormatOp::UpperHex,
471             's' => FormatOp::String,
472             _ => panic!("bad FormatOp char"),
473         }
474     }
475 }
476
477 fn format(val: Param, op: FormatOp, flags: Flags) -> Result<Vec<u8>, String> {
478     let mut s = match val {
479         Number(d) => {
480             match op {
481                 FormatOp::Digit => {
482                     if flags.sign {
483                         format!("{:+01$}", d, flags.precision)
484                     } else if d < 0 {
485                         // C doesn't take sign into account in precision calculation.
486                         format!("{:01$}", d, flags.precision + 1)
487                     } else if flags.space {
488                         format!(" {:01$}", d, flags.precision)
489                     } else {
490                         format!("{:01$}", d, flags.precision)
491                     }
492                 }
493                 FormatOp::Octal => {
494                     if flags.alternate {
495                         // Leading octal zero counts against precision.
496                         format!("0{:01$o}", d, flags.precision.saturating_sub(1))
497                     } else {
498                         format!("{:01$o}", d, flags.precision)
499                     }
500                 }
501                 FormatOp::LowerHex => {
502                     if flags.alternate && d != 0 {
503                         format!("0x{:01$x}", d, flags.precision)
504                     } else {
505                         format!("{:01$x}", d, flags.precision)
506                     }
507                 }
508                 FormatOp::UpperHex => {
509                     if flags.alternate && d != 0 {
510                         format!("0X{:01$X}", d, flags.precision)
511                     } else {
512                         format!("{:01$X}", d, flags.precision)
513                     }
514                 }
515                 FormatOp::String => return Err("non-number on stack with %s".to_string()),
516             }
517             .into_bytes()
518         }
519     };
520     if flags.width > s.len() {
521         let n = flags.width - s.len();
522         if flags.left {
523             s.extend(repeat(b' ').take(n));
524         } else {
525             let mut s_ = Vec::with_capacity(flags.width);
526             s_.extend(repeat(b' ').take(n));
527             s_.extend(s.into_iter());
528             s = s_;
529         }
530     }
531     Ok(s)
532 }