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