1//! Parameterized string expansion
2
3use self::Param::*;
4use self::States::*;
5
6use std::iter::repeat;
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.into_iter());
528 s = s_;
529 }
530 }
531 Ok(s)
532}
533