1 //! Parameterized string expansion
8 #[derive(Clone, Copy, PartialEq)]
18 FormatPattern(Flags, FormatState),
20 SeekIfElsePercent(usize),
22 SeekIfEndPercent(usize),
25 #[derive(Copy, PartialEq, Clone)]
32 /// Types of parameters a capability can use
33 #[allow(missing_docs)]
40 /// Container for static and dynamic variable arrays
41 pub struct Variables {
42 /// Static variables A-Z
44 /// Dynamic variables a-z
49 /// Returns a new zero-initialized Variables
50 pub fn new() -> Variables {
53 Number(0), Number(0), Number(0), Number(0), Number(0), Number(0), Number(0),
54 Number(0), Number(0), Number(0), Number(0), Number(0), Number(0), Number(0),
55 Number(0), Number(0), Number(0), Number(0), Number(0), Number(0), Number(0),
56 Number(0), Number(0), Number(0), Number(0), Number(0)
59 Number(0), Number(0), Number(0), Number(0), Number(0), Number(0), Number(0),
60 Number(0), Number(0), Number(0), Number(0), Number(0), Number(0), Number(0),
61 Number(0), Number(0), Number(0), Number(0), Number(0), Number(0), Number(0),
62 Number(0), Number(0), Number(0), Number(0), Number(0)
68 /// Expand a parameterized capability
71 /// * `cap` - string to expand
72 /// * `params` - vector of params for %p1 etc
73 /// * `vars` - Variables struct for %Pa etc
75 /// To be compatible with ncurses, `vars` should be the same between calls to `expand` for
76 /// multiple capabilities for the same terminal.
77 pub fn expand(cap: &[u8], params: &[Param], vars: &mut Variables) -> Result<Vec<u8>, String> {
78 let mut state = Nothing;
80 // expanded cap will only rarely be larger than the cap itself
81 let mut output = Vec::with_capacity(cap.len());
83 let mut stack: Vec<Param> = Vec::new();
85 // Copy parameters into a local vector for mutability
86 let mut mparams = [Number(0), Number(0), Number(0), Number(0), Number(0), Number(0),
87 Number(0), Number(0), Number(0)];
88 for (dst, src) in mparams.iter_mut().zip(params.iter()) {
89 *dst = (*src).clone();
92 for &c in cap.iter() {
94 let mut old_state = state;
111 // if c is 0, use 0200 (128) for ncurses compatibility
112 Some(Number(0)) => output.push(128u8),
113 // Don't check bounds. ncurses just casts and truncates.
114 Some(Number(c)) => output.push(c as u8),
115 Some(_) => return Err("a non-char was used with %c".to_string()),
116 None => return Err("stack is empty".to_string()),
119 'p' => state = PushParam,
120 'P' => state = SetVar,
121 'g' => state = GetVar,
122 '\'' => state = CharConstant,
123 '{' => state = IntConstant(0),
126 Some(Words(s)) => stack.push(Number(s.len() as i32)),
127 Some(_) => return Err("a non-str was used with %l".to_string()),
128 None => return Err("stack is empty".to_string()),
131 '+' | '-' | '/' | '*' | '^' | '&' | '|' | 'm' => {
132 match (stack.pop(), stack.pop()) {
133 (Some(Number(y)), Some(Number(x))) => {
134 stack.push(Number(match cur {
143 _ => unreachable!("All cases handled"),
146 (Some(_), Some(_)) => {
147 return Err(format!("non-numbers on stack with {}", cur))
149 _ => return Err("stack is empty".to_string()),
152 '=' | '>' | '<' | 'A' | 'O' => {
153 match (stack.pop(), stack.pop()) {
154 (Some(Number(y)), Some(Number(x))) => {
155 stack.push(Number(if match cur {
159 'A' => x > 0 && y > 0,
160 'O' => x > 0 || y > 0,
168 (Some(_), Some(_)) => {
169 return Err(format!("non-numbers on stack with {}", cur))
171 _ => return Err("stack is empty".to_string()),
177 stack.push(Number(match cur {
184 Some(_) => return Err(format!("non-numbers on stack with {}", cur)),
185 None => return Err("stack is empty".to_string()),
189 match (&mparams[0], &mparams[1]) {
190 (&Number(x), &Number(y)) => {
191 mparams[0] = Number(x + 1);
192 mparams[1] = Number(y + 1);
195 return Err("first two params not numbers with %i".to_string())
200 // printf-style support for %doxXs
201 'd' | 'o' | 'x' | 'X' | 's' => {
202 if let Some(arg) = stack.pop() {
203 let flags = Flags::new();
204 let res = format(arg, FormatOp::from_char(cur), flags)?;
205 output.extend(res.iter().cloned());
207 return Err("stack is empty".to_string());
210 ':' | '#' | ' ' | '.' | '0'..='9' => {
211 let mut flags = Flags::new();
212 let mut fstate = FormatState::Flags;
215 '#' => flags.alternate = true,
216 ' ' => flags.space = true,
217 '.' => fstate = FormatState::Precision,
219 flags.width = cur as usize - '0' as usize;
220 fstate = FormatState::Width;
224 state = FormatPattern(flags, fstate);
231 Some(Number(0)) => state = SeekIfElse(0),
232 Some(Number(_)) => (),
234 return Err("non-number on stack with conditional".to_string())
236 None => return Err("stack is empty".to_string()),
239 'e' => state = SeekIfEnd(0),
241 _ => return Err(format!("unrecognized format option {}", cur)),
245 // params are 1-indexed
246 stack.push(mparams[match cur.to_digit(10) {
247 Some(d) => d as usize - 1,
248 None => return Err("bad param number".to_string()),
253 if cur >= 'A' && cur <= 'Z' {
254 if let Some(arg) = stack.pop() {
255 let idx = (cur as u8) - b'A';
256 vars.sta_va[idx as usize] = arg;
258 return Err("stack is empty".to_string());
260 } else if cur >= 'a' && cur <= 'z' {
261 if let Some(arg) = stack.pop() {
262 let idx = (cur as u8) - b'a';
263 vars.dyn_va[idx as usize] = arg;
265 return Err("stack is empty".to_string());
268 return Err("bad variable name in %P".to_string());
272 if cur >= 'A' && cur <= 'Z' {
273 let idx = (cur as u8) - b'A';
274 stack.push(vars.sta_va[idx as usize].clone());
275 } else if cur >= 'a' && cur <= 'z' {
276 let idx = (cur as u8) - b'a';
277 stack.push(vars.dyn_va[idx as usize].clone());
279 return Err("bad variable name in %g".to_string());
283 stack.push(Number(c as i32));
288 return Err("malformed character constant".to_string());
293 stack.push(Number(i));
295 } else if let Some(digit) = cur.to_digit(10) {
296 match i.checked_mul(10).and_then(|i_ten| i_ten.checked_add(digit as i32)) {
298 state = IntConstant(i);
301 None => return Err("int constant too large".to_string()),
304 return Err("bad int constant".to_string());
307 FormatPattern(ref mut flags, ref mut fstate) => {
309 match (*fstate, cur) {
310 (_, 'd') | (_, 'o') | (_, 'x') | (_, 'X') | (_, 's') => {
311 if let Some(arg) = stack.pop() {
312 let res = format(arg, FormatOp::from_char(cur), *flags)?;
313 output.extend(res.iter().cloned());
314 // will cause state to go to Nothing
315 old_state = FormatPattern(*flags, *fstate);
317 return Err("stack is empty".to_string());
320 (FormatState::Flags, '#') => {
321 flags.alternate = true;
323 (FormatState::Flags, '-') => {
326 (FormatState::Flags, '+') => {
329 (FormatState::Flags, ' ') => {
332 (FormatState::Flags, '0'..='9') => {
333 flags.width = cur as usize - '0' as usize;
334 *fstate = FormatState::Width;
336 (FormatState::Flags, '.') => {
337 *fstate = FormatState::Precision;
339 (FormatState::Width, '0'..='9') => {
340 let old = flags.width;
341 flags.width = flags.width * 10 + (cur as usize - '0' as usize);
342 if flags.width < old {
343 return Err("format width overflow".to_string());
346 (FormatState::Width, '.') => {
347 *fstate = FormatState::Precision;
349 (FormatState::Precision, '0'..='9') => {
350 let old = flags.precision;
351 flags.precision = flags.precision * 10 + (cur as usize - '0' as usize);
352 if flags.precision < old {
353 return Err("format precision overflow".to_string());
356 _ => return Err("invalid format specifier".to_string()),
359 SeekIfElse(level) => {
361 state = SeekIfElsePercent(level);
365 SeekIfElsePercent(level) => {
370 state = SeekIfElse(level - 1);
372 } else if cur == 'e' && level == 0 {
374 } else if cur == '?' {
375 state = SeekIfElse(level + 1);
377 state = SeekIfElse(level);
380 SeekIfEnd(level) => {
382 state = SeekIfEndPercent(level);
386 SeekIfEndPercent(level) => {
391 state = SeekIfEnd(level - 1);
393 } else if cur == '?' {
394 state = SeekIfEnd(level + 1);
396 state = SeekIfEnd(level);
400 if state == old_state {
407 #[derive(Copy, PartialEq, Clone)]
430 #[derive(Copy, Clone)]
440 fn from_char(c: char) -> FormatOp {
442 'd' => FormatOp::Digit,
443 'o' => FormatOp::Octal,
444 'x' => FormatOp::LowerHex,
445 'X' => FormatOp::UpperHex,
446 's' => FormatOp::String,
447 _ => panic!("bad FormatOp char"),
450 fn to_char(self) -> char {
452 FormatOp::Digit => 'd',
453 FormatOp::Octal => 'o',
454 FormatOp::LowerHex => 'x',
455 FormatOp::UpperHex => 'X',
456 FormatOp::String => 's',
461 fn format(val: Param, op: FormatOp, flags: Flags) -> Result<Vec<u8>, String> {
462 let mut s = match val {
467 format!("{:+01$}", d, flags.precision)
469 // C doesn't take sign into account in precision calculation.
470 format!("{:01$}", d, flags.precision + 1)
471 } else if flags.space {
472 format!(" {:01$}", d, flags.precision)
474 format!("{:01$}", d, flags.precision)
479 // Leading octal zero counts against precision.
480 format!("0{:01$o}", d, flags.precision.saturating_sub(1))
482 format!("{:01$o}", d, flags.precision)
485 FormatOp::LowerHex => {
486 if flags.alternate && d != 0 {
487 format!("0x{:01$x}", d, flags.precision)
489 format!("{:01$x}", d, flags.precision)
492 FormatOp::UpperHex => {
493 if flags.alternate && d != 0 {
494 format!("0X{:01$X}", d, flags.precision)
496 format!("{:01$X}", d, flags.precision)
499 FormatOp::String => return Err("non-number on stack with %s".to_string()),
505 FormatOp::String => {
506 let mut s = s.into_bytes();
507 if flags.precision > 0 && flags.precision < s.len() {
508 s.truncate(flags.precision);
512 _ => return Err(format!("non-string on stack with %{}", op.to_char())),
516 if flags.width > s.len() {
517 let n = flags.width - s.len();
519 s.extend(repeat(b' ').take(n));
521 let mut s_ = Vec::with_capacity(flags.width);
522 s_.extend(repeat(b' ').take(n));
523 s_.extend(s.into_iter());
532 use super::{expand, Variables};
533 use super::Param::{self, Words, Number};
534 use std::result::Result::Ok;
537 fn test_basic_setabf() {
538 let s = b"\\E[48;5;%p1%dm";
539 assert_eq!(expand(s, &[Number(1)], &mut Variables::new()).unwrap(),
540 "\\E[48;5;1m".bytes().collect::<Vec<_>>());
544 fn test_multiple_int_constants() {
545 assert_eq!(expand(b"%{1}%{2}%d%d", &[], &mut Variables::new()).unwrap(),
546 "21".bytes().collect::<Vec<_>>());
551 let mut vars = Variables::new();
552 assert_eq!(expand(b"%p1%d%p2%d%p3%d%i%p1%d%p2%d%p3%d",
553 &[Number(1), Number(2), Number(3)],
555 Ok("123233".bytes().collect::<Vec<_>>()));
556 assert_eq!(expand(b"%p1%d%p2%d%i%p1%d%p2%d", &[], &mut vars),
557 Ok("0011".bytes().collect::<Vec<_>>()));
561 fn test_param_stack_failure_conditions() {
562 let mut varstruct = Variables::new();
563 let vars = &mut varstruct;
564 fn get_res(fmt: &str,
567 vars: &mut Variables)
568 -> Result<Vec<u8>, String> {
569 let mut u8v: Vec<_> = fmt.bytes().collect();
570 u8v.extend(cap.as_bytes().iter().map(|&b| b));
571 expand(&u8v, params, vars)
574 let caps = ["%d", "%c", "%s", "%Pa", "%l", "%!", "%~"];
575 for &cap in caps.iter() {
576 let res = get_res("", cap, &[], vars);
577 assert!(res.is_err(),
578 "Op {} succeeded incorrectly with 0 stack entries",
580 let p = if cap == "%s" || cap == "%l" {
581 Words("foo".to_string())
585 let res = get_res("%p1", cap, &[p], vars);
587 "Op {} failed with 1 stack entry: {}",
591 let caps = ["%+", "%-", "%*", "%/", "%m", "%&", "%|", "%A", "%O"];
592 for &cap in caps.iter() {
593 let res = expand(cap.as_bytes(), &[], vars);
594 assert!(res.is_err(),
595 "Binop {} succeeded incorrectly with 0 stack entries",
597 let res = get_res("%{1}", cap, &[], vars);
598 assert!(res.is_err(),
599 "Binop {} succeeded incorrectly with 1 stack entry",
601 let res = get_res("%{1}%{2}", cap, &[], vars);
603 "Binop {} failed with 2 stack entries: {}",
610 fn test_push_bad_param() {
611 assert!(expand(b"%pa", &[], &mut Variables::new()).is_err());
615 fn test_comparison_ops() {
616 let v = [('<', [1u8, 0u8, 0u8]), ('=', [0u8, 1u8, 0u8]), ('>', [0u8, 0u8, 1u8])];
617 for &(op, bs) in v.iter() {
618 let s = format!("%{{1}}%{{2}}%{}%d", op);
619 let res = expand(s.as_bytes(), &[], &mut Variables::new());
620 assert!(res.is_ok(), res.unwrap_err());
621 assert_eq!(res.unwrap(), vec![b'0' + bs[0]]);
622 let s = format!("%{{1}}%{{1}}%{}%d", op);
623 let res = expand(s.as_bytes(), &[], &mut Variables::new());
624 assert!(res.is_ok(), res.unwrap_err());
625 assert_eq!(res.unwrap(), vec![b'0' + bs[1]]);
626 let s = format!("%{{2}}%{{1}}%{}%d", op);
627 let res = expand(s.as_bytes(), &[], &mut Variables::new());
628 assert!(res.is_ok(), res.unwrap_err());
629 assert_eq!(res.unwrap(), vec![b'0' + bs[2]]);
634 fn test_conditionals() {
635 let mut vars = Variables::new();
636 let s = b"\\E[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;m";
637 let res = expand(s, &[Number(1)], &mut vars);
638 assert!(res.is_ok(), res.unwrap_err());
639 assert_eq!(res.unwrap(), "\\E[31m".bytes().collect::<Vec<_>>());
640 let res = expand(s, &[Number(8)], &mut vars);
641 assert!(res.is_ok(), res.unwrap_err());
642 assert_eq!(res.unwrap(), "\\E[90m".bytes().collect::<Vec<_>>());
643 let res = expand(s, &[Number(42)], &mut vars);
644 assert!(res.is_ok(), res.unwrap_err());
645 assert_eq!(res.unwrap(), "\\E[38;5;42m".bytes().collect::<Vec<_>>());
650 let mut varstruct = Variables::new();
651 let vars = &mut varstruct;
652 assert_eq!(expand(b"%p1%s%p2%2s%p3%2s%p4%.2s",
653 &[Words("foo".to_string()),
654 Words("foo".to_string()),
655 Words("f".to_string()),
656 Words("foo".to_string())],
658 Ok("foofoo ffo".bytes().collect::<Vec<_>>()));
659 assert_eq!(expand(b"%p1%:-4.2s", &[Words("foo".to_string())], vars),
660 Ok("fo ".bytes().collect::<Vec<_>>()));
662 assert_eq!(expand(b"%p1%d%p1%.3d%p1%5d%p1%:+d", &[Number(1)], vars),
663 Ok("1001 1+1".bytes().collect::<Vec<_>>()));
664 assert_eq!(expand(b"%p1%o%p1%#o%p2%6.4x%p2%#6.4X",
665 &[Number(15), Number(27)],
667 Ok("17017 001b0X001B".bytes().collect::<Vec<_>>()));