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)]
38 pub(crate) enum Param {
42 /// Container for static and dynamic variable arrays
43 pub(crate) struct Variables {
44 /// Static variables A-Z
46 /// Dynamic variables a-z
51 /// Returns a new zero-initialized Variables
52 pub(crate) fn new() -> Variables {
114 /// Expand a parameterized capability
117 /// * `cap` - string to expand
118 /// * `params` - vector of params for %p1 etc
119 /// * `vars` - Variables struct for %Pa etc
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(
126 vars: &mut Variables,
127 ) -> Result<Vec<u8>, String> {
128 let mut state = Nothing;
130 // expanded cap will only rarely be larger than the cap itself
131 let mut output = Vec::with_capacity(cap.len());
133 let mut stack: Vec<Param> = Vec::new();
135 // Copy parameters into a local vector for mutability
147 for (dst, src) in mparams.iter_mut().zip(params.iter()) {
148 *dst = (*src).clone();
151 for &c in cap.iter() {
153 let mut old_state = state;
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()),
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()),
186 '+' | '-' | '/' | '*' | '^' | '&' | '|' | 'm' => {
187 match (stack.pop(), stack.pop()) {
188 (Some(Number(y)), Some(Number(x))) => stack.push(Number(match cur {
197 _ => unreachable!("All cases handled"),
199 _ => return Err("stack is empty".to_string()),
202 '=' | '>' | '<' | 'A' | 'O' => match (stack.pop(), stack.pop()) {
203 (Some(Number(y)), Some(Number(x))) => stack.push(Number(
208 'A' => x > 0 && y > 0,
209 'O' => x > 0 || y > 0,
217 _ => return Err("stack is empty".to_string()),
219 '!' | '~' => match stack.pop() {
220 Some(Number(x)) => stack.push(Number(match cur {
226 None => return Err("stack is empty".to_string()),
228 'i' => match (&mparams[0], &mparams[1]) {
229 (&Number(x), &Number(y)) => {
230 mparams[0] = Number(x + 1);
231 mparams[1] = Number(y + 1);
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());
242 return Err("stack is empty".to_string());
245 ':' | '#' | ' ' | '.' | '0'..='9' => {
246 let mut flags = Flags::new();
247 let mut fstate = FormatState::Flags;
250 '#' => flags.alternate = true,
251 ' ' => flags.space = true,
252 '.' => fstate = FormatState::Precision,
254 flags.width = cur as usize - '0' as usize;
255 fstate = FormatState::Width;
259 state = FormatPattern(flags, fstate);
264 't' => match stack.pop() {
265 Some(Number(0)) => state = SeekIfElse(0),
266 Some(Number(_)) => (),
267 None => return Err("stack is empty".to_string()),
269 'e' => state = SeekIfEnd(0),
271 _ => return Err(format!("unrecognized format option {}", cur)),
275 // params are 1-indexed
277 mparams[match cur.to_digit(10) {
278 Some(d) => d as usize - 1,
279 None => return Err("bad param number".to_string()),
285 if cur >= 'A' && cur <= 'Z' {
286 if let Some(arg) = stack.pop() {
287 let idx = (cur as u8) - b'A';
288 vars.sta_va[idx as usize] = arg;
290 return Err("stack is empty".to_string());
292 } else if cur >= 'a' && cur <= 'z' {
293 if let Some(arg) = stack.pop() {
294 let idx = (cur as u8) - b'a';
295 vars.dyn_va[idx as usize] = arg;
297 return Err("stack is empty".to_string());
300 return Err("bad variable name in %P".to_string());
304 if cur >= 'A' && cur <= 'Z' {
305 let idx = (cur as u8) - b'A';
306 stack.push(vars.sta_va[idx as usize].clone());
307 } else if cur >= 'a' && cur <= 'z' {
308 let idx = (cur as u8) - b'a';
309 stack.push(vars.dyn_va[idx as usize].clone());
311 return Err("bad variable name in %g".to_string());
315 stack.push(Number(c as i32));
320 return Err("malformed character constant".to_string());
325 stack.push(Number(i));
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)) {
330 state = IntConstant(i);
333 None => return Err("int constant too large".to_string()),
336 return Err("bad int constant".to_string());
339 FormatPattern(ref mut flags, ref mut fstate) => {
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);
349 return Err("stack is empty".to_string());
352 (FormatState::Flags, '#') => {
353 flags.alternate = true;
355 (FormatState::Flags, '-') => {
358 (FormatState::Flags, '+') => {
361 (FormatState::Flags, ' ') => {
364 (FormatState::Flags, '0'..='9') => {
365 flags.width = cur as usize - '0' as usize;
366 *fstate = FormatState::Width;
368 (FormatState::Flags, '.') => {
369 *fstate = FormatState::Precision;
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());
378 (FormatState::Width, '.') => {
379 *fstate = FormatState::Precision;
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());
388 _ => return Err("invalid format specifier".to_string()),
391 SeekIfElse(level) => {
393 state = SeekIfElsePercent(level);
397 SeekIfElsePercent(level) => {
402 state = SeekIfElse(level - 1);
404 } else if cur == 'e' && level == 0 {
406 } else if cur == '?' {
407 state = SeekIfElse(level + 1);
409 state = SeekIfElse(level);
412 SeekIfEnd(level) => {
414 state = SeekIfEndPercent(level);
418 SeekIfEndPercent(level) => {
423 state = SeekIfEnd(level - 1);
425 } else if cur == '?' {
426 state = SeekIfEnd(level + 1);
428 state = SeekIfEnd(level);
432 if state == old_state {
439 #[derive(Copy, PartialEq, Clone)]
451 Flags { width: 0, precision: 0, alternate: false, left: false, sign: false, space: false }
455 #[derive(Copy, Clone)]
465 fn from_char(c: char) -> FormatOp {
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"),
477 fn format(val: Param, op: FormatOp, flags: Flags) -> Result<Vec<u8>, String> {
478 let mut s = match val {
483 format!("{:+01$}", d, flags.precision)
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)
490 format!("{:01$}", d, flags.precision)
495 // Leading octal zero counts against precision.
496 format!("0{:01$o}", d, flags.precision.saturating_sub(1))
498 format!("{:01$o}", d, flags.precision)
501 FormatOp::LowerHex => {
502 if flags.alternate && d != 0 {
503 format!("0x{:01$x}", d, flags.precision)
505 format!("{:01$x}", d, flags.precision)
508 FormatOp::UpperHex => {
509 if flags.alternate && d != 0 {
510 format!("0X{:01$X}", d, flags.precision)
512 format!("{:01$X}", d, flags.precision)
515 FormatOp::String => return Err("non-number on stack with %s".to_string()),
520 if flags.width > s.len() {
521 let n = flags.width - s.len();
523 s.extend(repeat(b' ').take(n));
525 let mut s_ = Vec::with_capacity(flags.width);
526 s_.extend(repeat(b' ').take(n));
527 s_.extend(s.into_iter());