test/term/terminfo/
parm.rs

1//! Parameterized string expansion
2
3use std::iter::repeat;
4
5use self::Param::*;
6use self::States::*;
7
8#[cfg(test)]
9mod tests;
10
11#[derive(Clone, Copy, PartialEq)]
12enum 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)]
29enum FormatState {
30    Flags,
31    Width,
32    Precision,
33}
34
35/// Types of parameters a capability can use
36#[allow(missing_docs)]
37#[derive(Clone)]
38pub(crate) enum Param {
39    Number(i32),
40}
41
42/// Container for static and dynamic variable arrays
43pub(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
50impl 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.
123pub(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.is_ascii_uppercase() {
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.is_ascii_lowercase() {
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.is_ascii_uppercase() {
305                    let idx = (cur as u8) - b'A';
306                    stack.push(vars.sta_va[idx as usize].clone());
307                } else if cur.is_ascii_lowercase() {
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)]
440struct Flags {
441    width: usize,
442    precision: usize,
443    alternate: bool,
444    left: bool,
445    sign: bool,
446    space: bool,
447}
448
449impl 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)]
456enum FormatOp {
457    Digit,
458    Octal,
459    LowerHex,
460    UpperHex,
461    String,
462}
463
464impl 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
477fn 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);
528            s = s_;
529        }
530    }
531    Ok(s)
532}