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.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)] |
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 | } |
533 | |