1 //! Parameterized string expansion
11 #[derive(Clone, Copy, PartialEq)]
21 FormatPattern(Flags, FormatState),
23 SeekIfElsePercent(usize),
25 SeekIfEndPercent(usize),
28 #[derive(Copy, PartialEq, Clone)]
35 /// Types of parameters a capability can use
36 #[allow(missing_docs)]
43 /// Container for static and dynamic variable arrays
44 pub struct Variables {
45 /// Static variables A-Z
47 /// Dynamic variables a-z
52 /// Returns a new zero-initialized Variables
53 pub fn new() -> Variables {
115 /// Expand a parameterized capability
118 /// * `cap` - string to expand
119 /// * `params` - vector of params for %p1 etc
120 /// * `vars` - Variables struct for %Pa etc
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;
127 // expanded cap will only rarely be larger than the cap itself
128 let mut output = Vec::with_capacity(cap.len());
130 let mut stack: Vec<Param> = Vec::new();
132 // Copy parameters into a local vector for mutability
144 for (dst, src) in mparams.iter_mut().zip(params.iter()) {
145 *dst = (*src).clone();
148 for &c in cap.iter() {
150 let mut old_state = state;
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()),
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()),
185 '+' | '-' | '/' | '*' | '^' | '&' | '|' | 'm' => {
186 match (stack.pop(), stack.pop()) {
187 (Some(Number(y)), Some(Number(x))) => stack.push(Number(match cur {
196 _ => unreachable!("All cases handled"),
198 (Some(_), Some(_)) => {
199 return Err(format!("non-numbers on stack with {}", cur));
201 _ => return Err("stack is empty".to_string()),
204 '=' | '>' | '<' | 'A' | 'O' => match (stack.pop(), stack.pop()) {
205 (Some(Number(y)), Some(Number(x))) => stack.push(Number(
210 'A' => x > 0 && y > 0,
211 'O' => x > 0 || y > 0,
219 (Some(_), Some(_)) => {
220 return Err(format!("non-numbers on stack with {}", cur));
222 _ => return Err("stack is empty".to_string()),
224 '!' | '~' => match stack.pop() {
225 Some(Number(x)) => stack.push(Number(match cur {
231 Some(_) => return Err(format!("non-numbers on stack with {}", cur)),
232 None => return Err("stack is empty".to_string()),
234 'i' => match (&mparams[0], &mparams[1]) {
235 (&Number(x), &Number(y)) => {
236 mparams[0] = Number(x + 1);
237 mparams[1] = Number(y + 1);
239 _ => return Err("first two params not numbers with %i".to_string()),
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());
249 return Err("stack is empty".to_string());
252 ':' | '#' | ' ' | '.' | '0'..='9' => {
253 let mut flags = Flags::new();
254 let mut fstate = FormatState::Flags;
257 '#' => flags.alternate = true,
258 ' ' => flags.space = true,
259 '.' => fstate = FormatState::Precision,
261 flags.width = cur as usize - '0' as usize;
262 fstate = FormatState::Width;
266 state = FormatPattern(flags, fstate);
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()),
277 'e' => state = SeekIfEnd(0),
279 _ => return Err(format!("unrecognized format option {}", cur)),
283 // params are 1-indexed
285 mparams[match cur.to_digit(10) {
286 Some(d) => d as usize - 1,
287 None => return Err("bad param number".to_string()),
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;
298 return Err("stack is empty".to_string());
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;
305 return Err("stack is empty".to_string());
308 return Err("bad variable name in %P".to_string());
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());
319 return Err("bad variable name in %g".to_string());
323 stack.push(Number(c as i32));
328 return Err("malformed character constant".to_string());
333 stack.push(Number(i));
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)) {
338 state = IntConstant(i);
341 None => return Err("int constant too large".to_string()),
344 return Err("bad int constant".to_string());
347 FormatPattern(ref mut flags, ref mut fstate) => {
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);
357 return Err("stack is empty".to_string());
360 (FormatState::Flags, '#') => {
361 flags.alternate = true;
363 (FormatState::Flags, '-') => {
366 (FormatState::Flags, '+') => {
369 (FormatState::Flags, ' ') => {
372 (FormatState::Flags, '0'..='9') => {
373 flags.width = cur as usize - '0' as usize;
374 *fstate = FormatState::Width;
376 (FormatState::Flags, '.') => {
377 *fstate = FormatState::Precision;
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());
386 (FormatState::Width, '.') => {
387 *fstate = FormatState::Precision;
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());
396 _ => return Err("invalid format specifier".to_string()),
399 SeekIfElse(level) => {
401 state = SeekIfElsePercent(level);
405 SeekIfElsePercent(level) => {
410 state = SeekIfElse(level - 1);
412 } else if cur == 'e' && level == 0 {
414 } else if cur == '?' {
415 state = SeekIfElse(level + 1);
417 state = SeekIfElse(level);
420 SeekIfEnd(level) => {
422 state = SeekIfEndPercent(level);
426 SeekIfEndPercent(level) => {
431 state = SeekIfEnd(level - 1);
433 } else if cur == '?' {
434 state = SeekIfEnd(level + 1);
436 state = SeekIfEnd(level);
440 if state == old_state {
447 #[derive(Copy, PartialEq, Clone)]
459 Flags { width: 0, precision: 0, alternate: false, left: false, sign: false, space: false }
463 #[derive(Copy, Clone)]
473 fn from_char(c: char) -> FormatOp {
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"),
483 fn to_char(self) -> char {
485 FormatOp::Digit => 'd',
486 FormatOp::Octal => 'o',
487 FormatOp::LowerHex => 'x',
488 FormatOp::UpperHex => 'X',
489 FormatOp::String => 's',
494 fn format(val: Param, op: FormatOp, flags: Flags) -> Result<Vec<u8>, String> {
495 let mut s = match val {
500 format!("{:+01$}", d, flags.precision)
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)
507 format!("{:01$}", d, flags.precision)
512 // Leading octal zero counts against precision.
513 format!("0{:01$o}", d, flags.precision.saturating_sub(1))
515 format!("{:01$o}", d, flags.precision)
518 FormatOp::LowerHex => {
519 if flags.alternate && d != 0 {
520 format!("0x{:01$x}", d, flags.precision)
522 format!("{:01$x}", d, flags.precision)
525 FormatOp::UpperHex => {
526 if flags.alternate && d != 0 {
527 format!("0X{:01$X}", d, flags.precision)
529 format!("{:01$X}", d, flags.precision)
532 FormatOp::String => return Err("non-number on stack with %s".to_string()),
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);
544 _ => return Err(format!("non-string on stack with %{}", op.to_char())),
547 if flags.width > s.len() {
548 let n = flags.width - s.len();
550 s.extend(repeat(b' ').take(n));
552 let mut s_ = Vec::with_capacity(flags.width);
553 s_.extend(repeat(b' ').take(n));
554 s_.extend(s.into_iter());