| 1 | // Copyright 2019 The Rust Project Developers. See the COPYRIGHT |
| 2 | // file at the top-level directory of this distribution and at |
| 3 | // http://rust-lang.org/COPYRIGHT. |
| 4 | // |
| 5 | // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or |
| 6 | // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license |
| 7 | // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your |
| 8 | // option. This file may not be copied, modified, or distributed |
| 9 | // except according to those terms. |
| 10 | |
| 11 | //! Parameterized string expansion |
| 12 | |
| 13 | use self::Param::*; |
| 14 | use self::States::*; |
| 15 | |
| 16 | use std::iter::repeat; |
| 17 | |
| 18 | #[derive (Clone, Copy, PartialEq)] |
| 19 | enum States { |
| 20 | Nothing, |
| 21 | Delay, |
| 22 | Percent, |
| 23 | SetVar, |
| 24 | GetVar, |
| 25 | PushParam, |
| 26 | CharConstant, |
| 27 | CharClose, |
| 28 | IntConstant(i32), |
| 29 | FormatPattern(Flags, FormatState), |
| 30 | SeekIfElse(usize), |
| 31 | SeekIfElsePercent(usize), |
| 32 | SeekIfEnd(usize), |
| 33 | SeekIfEndPercent(usize), |
| 34 | } |
| 35 | |
| 36 | #[derive (Copy, PartialEq, Clone)] |
| 37 | enum FormatState { |
| 38 | Flags, |
| 39 | Width, |
| 40 | Precision, |
| 41 | } |
| 42 | |
| 43 | /// Types of parameters a capability can use |
| 44 | #[allow (missing_docs)] |
| 45 | #[derive (Clone)] |
| 46 | pub enum Param { |
| 47 | Number(i32), |
| 48 | Words(String), |
| 49 | } |
| 50 | |
| 51 | impl Default for Param { |
| 52 | fn default() -> Self { |
| 53 | Param::Number(0) |
| 54 | } |
| 55 | } |
| 56 | |
| 57 | /// An error from interpreting a parameterized string. |
| 58 | #[derive (Debug, Eq, PartialEq)] |
| 59 | pub enum Error { |
| 60 | /// Data was requested from the stack, but the stack didn't have enough elements. |
| 61 | StackUnderflow, |
| 62 | /// The type of the element(s) on top of the stack did not match the type that the operator |
| 63 | /// wanted. |
| 64 | TypeMismatch, |
| 65 | /// An unrecognized format option was used. |
| 66 | UnrecognizedFormatOption(char), |
| 67 | /// An invalid variable name was used. |
| 68 | InvalidVariableName(char), |
| 69 | /// An invalid parameter index was used. |
| 70 | InvalidParameterIndex(char), |
| 71 | /// A malformed character constant was used. |
| 72 | MalformedCharacterConstant, |
| 73 | /// An integer constant was too large (overflowed an i32) |
| 74 | IntegerConstantOverflow, |
| 75 | /// A malformed integer constant was used. |
| 76 | MalformedIntegerConstant, |
| 77 | /// A format width constant was too large (overflowed a usize) |
| 78 | FormatWidthOverflow, |
| 79 | /// A format precision constant was too large (overflowed a usize) |
| 80 | FormatPrecisionOverflow, |
| 81 | } |
| 82 | |
| 83 | impl ::std::fmt::Display for Error { |
| 84 | fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { |
| 85 | use self::Error::*; |
| 86 | match *self { |
| 87 | StackUnderflow => f.write_str(data:"not enough elements on the stack" ), |
| 88 | TypeMismatch => f.write_str(data:"type mismatch" ), |
| 89 | UnrecognizedFormatOption(_) => f.write_str(data:"unrecognized format option" ), |
| 90 | InvalidVariableName(_) => f.write_str(data:"invalid variable name" ), |
| 91 | InvalidParameterIndex(_) => f.write_str(data:"invalid parameter index" ), |
| 92 | MalformedCharacterConstant => f.write_str(data:"malformed character constant" ), |
| 93 | IntegerConstantOverflow => f.write_str(data:"integer constant computation overflowed" ), |
| 94 | MalformedIntegerConstant => f.write_str(data:"malformed integer constant" ), |
| 95 | FormatWidthOverflow => f.write_str(data:"format width constant computation overflowed" ), |
| 96 | FormatPrecisionOverflow => { |
| 97 | f.write_str(data:"format precision constant computation overflowed" ) |
| 98 | } |
| 99 | } |
| 100 | } |
| 101 | } |
| 102 | |
| 103 | impl ::std::error::Error for Error {} |
| 104 | |
| 105 | /// Container for static and dynamic variable arrays |
| 106 | #[derive (Default)] |
| 107 | pub struct Variables { |
| 108 | /// Static variables A-Z |
| 109 | sta_vars: [Param; 26], |
| 110 | /// Dynamic variables a-z |
| 111 | dyn_vars: [Param; 26], |
| 112 | } |
| 113 | |
| 114 | impl Variables { |
| 115 | /// Return a new zero-initialized Variables |
| 116 | pub fn new() -> Variables { |
| 117 | Default::default() |
| 118 | } |
| 119 | } |
| 120 | |
| 121 | /// Expand a parameterized capability |
| 122 | /// |
| 123 | /// # Arguments |
| 124 | /// * `cap` - string to expand |
| 125 | /// * `params` - vector of params for %p1 etc |
| 126 | /// * `vars` - Variables struct for %Pa etc |
| 127 | /// |
| 128 | /// To be compatible with ncurses, `vars` should be the same between calls to `expand` for |
| 129 | /// multiple capabilities for the same terminal. |
| 130 | pub fn expand(cap: &[u8], params: &[Param], vars: &mut Variables) -> Result<Vec<u8>, Error> { |
| 131 | let mut state = Nothing; |
| 132 | |
| 133 | // expanded cap will only rarely be larger than the cap itself |
| 134 | let mut output = Vec::with_capacity(cap.len()); |
| 135 | |
| 136 | let mut stack: Vec<Param> = Vec::new(); |
| 137 | |
| 138 | // Copy parameters into a local vector for mutability |
| 139 | let mut mparams = [ |
| 140 | Number(0), |
| 141 | Number(0), |
| 142 | Number(0), |
| 143 | Number(0), |
| 144 | Number(0), |
| 145 | Number(0), |
| 146 | Number(0), |
| 147 | Number(0), |
| 148 | Number(0), |
| 149 | ]; |
| 150 | for (dst, src) in mparams.iter_mut().zip(params.iter()) { |
| 151 | *dst = (*src).clone(); |
| 152 | } |
| 153 | |
| 154 | for &c in cap.iter() { |
| 155 | let cur = c as char; |
| 156 | let mut old_state = state; |
| 157 | match state { |
| 158 | Nothing => { |
| 159 | if cur == '%' { |
| 160 | state = Percent; |
| 161 | } else if cur == '$' { |
| 162 | state = Delay; |
| 163 | } else { |
| 164 | output.push(c); |
| 165 | } |
| 166 | } |
| 167 | Delay => { |
| 168 | old_state = Nothing; |
| 169 | if cur == '>' { |
| 170 | state = Nothing; |
| 171 | } |
| 172 | } |
| 173 | Percent => { |
| 174 | match cur { |
| 175 | '%' => { |
| 176 | output.push(c); |
| 177 | state = Nothing |
| 178 | } |
| 179 | 'c' => { |
| 180 | match stack.pop() { |
| 181 | // if c is 0, use 0200 (128) for ncurses compatibility |
| 182 | Some(Number(0)) => output.push(128u8), |
| 183 | // Don't check bounds. ncurses just casts and truncates. |
| 184 | Some(Number(c)) => output.push(c as u8), |
| 185 | Some(_) => return Err(Error::TypeMismatch), |
| 186 | None => return Err(Error::StackUnderflow), |
| 187 | } |
| 188 | } |
| 189 | 'p' => state = PushParam, |
| 190 | 'P' => state = SetVar, |
| 191 | 'g' => state = GetVar, |
| 192 | ' \'' => state = CharConstant, |
| 193 | '{' => state = IntConstant(0), |
| 194 | 'l' => match stack.pop() { |
| 195 | Some(Words(s)) => stack.push(Number(s.len() as i32)), |
| 196 | Some(_) => return Err(Error::TypeMismatch), |
| 197 | None => return Err(Error::StackUnderflow), |
| 198 | }, |
| 199 | '+' | '-' | '/' | '*' | '^' | '&' | '|' | 'm' => { |
| 200 | match (stack.pop(), stack.pop()) { |
| 201 | (Some(Number(y)), Some(Number(x))) => stack.push(Number(match cur { |
| 202 | '+' => x + y, |
| 203 | '-' => x - y, |
| 204 | '*' => x * y, |
| 205 | '/' => x / y, |
| 206 | '|' => x | y, |
| 207 | '&' => x & y, |
| 208 | '^' => x ^ y, |
| 209 | 'm' => x % y, |
| 210 | _ => unreachable!("logic error" ), |
| 211 | })), |
| 212 | (Some(_), Some(_)) => return Err(Error::TypeMismatch), |
| 213 | _ => return Err(Error::StackUnderflow), |
| 214 | } |
| 215 | } |
| 216 | '=' | '>' | '<' | 'A' | 'O' => match (stack.pop(), stack.pop()) { |
| 217 | (Some(Number(y)), Some(Number(x))) => stack.push(Number( |
| 218 | if match cur { |
| 219 | '=' => x == y, |
| 220 | '<' => x < y, |
| 221 | '>' => x > y, |
| 222 | 'A' => x > 0 && y > 0, |
| 223 | 'O' => x > 0 || y > 0, |
| 224 | _ => unreachable!("logic error" ), |
| 225 | } { |
| 226 | 1 |
| 227 | } else { |
| 228 | 0 |
| 229 | }, |
| 230 | )), |
| 231 | (Some(_), Some(_)) => return Err(Error::TypeMismatch), |
| 232 | _ => return Err(Error::StackUnderflow), |
| 233 | }, |
| 234 | '!' | '~' => match stack.pop() { |
| 235 | Some(Number(x)) => stack.push(Number(match cur { |
| 236 | '!' if x > 0 => 0, |
| 237 | '!' => 1, |
| 238 | '~' => !x, |
| 239 | _ => unreachable!("logic error" ), |
| 240 | })), |
| 241 | Some(_) => return Err(Error::TypeMismatch), |
| 242 | None => return Err(Error::StackUnderflow), |
| 243 | }, |
| 244 | 'i' => match (&mparams[0], &mparams[1]) { |
| 245 | (&Number(x), &Number(y)) => { |
| 246 | mparams[0] = Number(x + 1); |
| 247 | mparams[1] = Number(y + 1); |
| 248 | } |
| 249 | (_, _) => return Err(Error::TypeMismatch), |
| 250 | }, |
| 251 | |
| 252 | // printf-style support for %doxXs |
| 253 | 'd' | 'o' | 'x' | 'X' | 's' => { |
| 254 | if let Some(arg) = stack.pop() { |
| 255 | let flags = Flags::default(); |
| 256 | let res = format(arg, FormatOp::from_char(cur), flags)?; |
| 257 | output.extend(res); |
| 258 | } else { |
| 259 | return Err(Error::StackUnderflow); |
| 260 | } |
| 261 | } |
| 262 | ':' | '#' | ' ' | '.' | '0' ..='9' => { |
| 263 | let mut flags = Flags::default(); |
| 264 | let mut fstate = FormatState::Flags; |
| 265 | match cur { |
| 266 | ':' => (), |
| 267 | '#' => flags.alternate = true, |
| 268 | ' ' => flags.space = true, |
| 269 | '.' => fstate = FormatState::Precision, |
| 270 | '0' ..='9' => { |
| 271 | flags.width = cur as usize - '0' as usize; |
| 272 | fstate = FormatState::Width; |
| 273 | } |
| 274 | _ => unreachable!("logic error" ), |
| 275 | } |
| 276 | state = FormatPattern(flags, fstate); |
| 277 | } |
| 278 | |
| 279 | // conditionals |
| 280 | '?' | ';' => (), |
| 281 | 't' => match stack.pop() { |
| 282 | Some(Number(0)) => state = SeekIfElse(0), |
| 283 | Some(Number(_)) => (), |
| 284 | Some(_) => return Err(Error::TypeMismatch), |
| 285 | None => return Err(Error::StackUnderflow), |
| 286 | }, |
| 287 | 'e' => state = SeekIfEnd(0), |
| 288 | c => return Err(Error::UnrecognizedFormatOption(c)), |
| 289 | } |
| 290 | } |
| 291 | PushParam => { |
| 292 | // params are 1-indexed |
| 293 | stack.push( |
| 294 | mparams[match cur.to_digit(10) { |
| 295 | Some(d) => d as usize - 1, |
| 296 | None => return Err(Error::InvalidParameterIndex(cur)), |
| 297 | }] |
| 298 | .clone(), |
| 299 | ); |
| 300 | } |
| 301 | SetVar => { |
| 302 | if cur >= 'A' && cur <= 'Z' { |
| 303 | if let Some(arg) = stack.pop() { |
| 304 | let idx = (cur as u8) - b'A' ; |
| 305 | vars.sta_vars[idx as usize] = arg; |
| 306 | } else { |
| 307 | return Err(Error::StackUnderflow); |
| 308 | } |
| 309 | } else if cur >= 'a' && cur <= 'z' { |
| 310 | if let Some(arg) = stack.pop() { |
| 311 | let idx = (cur as u8) - b'a' ; |
| 312 | vars.dyn_vars[idx as usize] = arg; |
| 313 | } else { |
| 314 | return Err(Error::StackUnderflow); |
| 315 | } |
| 316 | } else { |
| 317 | return Err(Error::InvalidVariableName(cur)); |
| 318 | } |
| 319 | } |
| 320 | GetVar => { |
| 321 | if cur >= 'A' && cur <= 'Z' { |
| 322 | let idx = (cur as u8) - b'A' ; |
| 323 | stack.push(vars.sta_vars[idx as usize].clone()); |
| 324 | } else if cur >= 'a' && cur <= 'z' { |
| 325 | let idx = (cur as u8) - b'a' ; |
| 326 | stack.push(vars.dyn_vars[idx as usize].clone()); |
| 327 | } else { |
| 328 | return Err(Error::InvalidVariableName(cur)); |
| 329 | } |
| 330 | } |
| 331 | CharConstant => { |
| 332 | stack.push(Number(i32::from(c))); |
| 333 | state = CharClose; |
| 334 | } |
| 335 | CharClose => { |
| 336 | if cur != ' \'' { |
| 337 | return Err(Error::MalformedCharacterConstant); |
| 338 | } |
| 339 | } |
| 340 | IntConstant(i) => { |
| 341 | if cur == '}' { |
| 342 | stack.push(Number(i)); |
| 343 | state = Nothing; |
| 344 | } else if let Some(digit) = cur.to_digit(10) { |
| 345 | match i |
| 346 | .checked_mul(10) |
| 347 | .and_then(|i_ten| i_ten.checked_add(digit as i32)) |
| 348 | { |
| 349 | Some(i) => { |
| 350 | state = IntConstant(i); |
| 351 | old_state = Nothing; |
| 352 | } |
| 353 | None => return Err(Error::IntegerConstantOverflow), |
| 354 | } |
| 355 | } else { |
| 356 | return Err(Error::MalformedIntegerConstant); |
| 357 | } |
| 358 | } |
| 359 | FormatPattern(ref mut flags, ref mut fstate) => { |
| 360 | old_state = Nothing; |
| 361 | match (*fstate, cur) { |
| 362 | (_, 'd' ) | (_, 'o' ) | (_, 'x' ) | (_, 'X' ) | (_, 's' ) => { |
| 363 | if let Some(arg) = stack.pop() { |
| 364 | let res = format(arg, FormatOp::from_char(cur), *flags)?; |
| 365 | output.extend(res); |
| 366 | // will cause state to go to Nothing |
| 367 | old_state = FormatPattern(*flags, *fstate); |
| 368 | } else { |
| 369 | return Err(Error::StackUnderflow); |
| 370 | } |
| 371 | } |
| 372 | (FormatState::Flags, '#' ) => { |
| 373 | flags.alternate = true; |
| 374 | } |
| 375 | (FormatState::Flags, '-' ) => { |
| 376 | flags.left = true; |
| 377 | } |
| 378 | (FormatState::Flags, '+' ) => { |
| 379 | flags.sign = true; |
| 380 | } |
| 381 | (FormatState::Flags, ' ' ) => { |
| 382 | flags.space = true; |
| 383 | } |
| 384 | (FormatState::Flags, '0' ..='9' ) => { |
| 385 | flags.width = cur as usize - '0' as usize; |
| 386 | *fstate = FormatState::Width; |
| 387 | } |
| 388 | (FormatState::Width, '0' ..='9' ) => { |
| 389 | flags.width = match flags |
| 390 | .width |
| 391 | .checked_mul(10) |
| 392 | .and_then(|w| w.checked_add(cur as usize - '0' as usize)) |
| 393 | { |
| 394 | Some(width) => width, |
| 395 | None => return Err(Error::FormatWidthOverflow), |
| 396 | } |
| 397 | } |
| 398 | (FormatState::Width, '.' ) | (FormatState::Flags, '.' ) => { |
| 399 | *fstate = FormatState::Precision; |
| 400 | } |
| 401 | (FormatState::Precision, '0' ..='9' ) => { |
| 402 | flags.precision = match flags |
| 403 | .precision |
| 404 | .checked_mul(10) |
| 405 | .and_then(|w| w.checked_add(cur as usize - '0' as usize)) |
| 406 | { |
| 407 | Some(precision) => precision, |
| 408 | None => return Err(Error::FormatPrecisionOverflow), |
| 409 | } |
| 410 | } |
| 411 | _ => return Err(Error::UnrecognizedFormatOption(cur)), |
| 412 | } |
| 413 | } |
| 414 | SeekIfElse(level) => { |
| 415 | if cur == '%' { |
| 416 | state = SeekIfElsePercent(level); |
| 417 | } |
| 418 | old_state = Nothing; |
| 419 | } |
| 420 | SeekIfElsePercent(level) => { |
| 421 | if cur == ';' { |
| 422 | if level == 0 { |
| 423 | state = Nothing; |
| 424 | } else { |
| 425 | state = SeekIfElse(level - 1); |
| 426 | } |
| 427 | } else if cur == 'e' && level == 0 { |
| 428 | state = Nothing; |
| 429 | } else if cur == '?' { |
| 430 | state = SeekIfElse(level + 1); |
| 431 | } else { |
| 432 | state = SeekIfElse(level); |
| 433 | } |
| 434 | } |
| 435 | SeekIfEnd(level) => { |
| 436 | if cur == '%' { |
| 437 | state = SeekIfEndPercent(level); |
| 438 | } |
| 439 | old_state = Nothing; |
| 440 | } |
| 441 | SeekIfEndPercent(level) => { |
| 442 | if cur == ';' { |
| 443 | if level == 0 { |
| 444 | state = Nothing; |
| 445 | } else { |
| 446 | state = SeekIfEnd(level - 1); |
| 447 | } |
| 448 | } else if cur == '?' { |
| 449 | state = SeekIfEnd(level + 1); |
| 450 | } else { |
| 451 | state = SeekIfEnd(level); |
| 452 | } |
| 453 | } |
| 454 | } |
| 455 | if state == old_state { |
| 456 | state = Nothing; |
| 457 | } |
| 458 | } |
| 459 | Ok(output) |
| 460 | } |
| 461 | |
| 462 | #[derive (Copy, PartialEq, Clone, Default)] |
| 463 | struct Flags { |
| 464 | width: usize, |
| 465 | precision: usize, |
| 466 | alternate: bool, |
| 467 | left: bool, |
| 468 | sign: bool, |
| 469 | space: bool, |
| 470 | } |
| 471 | |
| 472 | #[derive (Copy, Clone)] |
| 473 | enum FormatOp { |
| 474 | Digit, |
| 475 | Octal, |
| 476 | Hex, |
| 477 | HEX, |
| 478 | String, |
| 479 | } |
| 480 | |
| 481 | impl FormatOp { |
| 482 | fn from_char(c: char) -> FormatOp { |
| 483 | use self::FormatOp::*; |
| 484 | match c { |
| 485 | 'd' => Digit, |
| 486 | 'o' => Octal, |
| 487 | 'x' => Hex, |
| 488 | 'X' => HEX, |
| 489 | 's' => String, |
| 490 | _ => panic!("bad FormatOp char" ), |
| 491 | } |
| 492 | } |
| 493 | } |
| 494 | |
| 495 | fn format(val: Param, op: FormatOp, flags: Flags) -> Result<Vec<u8>, Error> { |
| 496 | use self::FormatOp::*; |
| 497 | let mut s = match val { |
| 498 | Number(d) => { |
| 499 | match op { |
| 500 | Digit => { |
| 501 | if flags.sign { |
| 502 | format!(" {:+01$}" , d, flags.precision) |
| 503 | } else if d < 0 { |
| 504 | // C doesn't take sign into account in precision calculation. |
| 505 | format!(" {:01$}" , d, flags.precision + 1) |
| 506 | } else if flags.space { |
| 507 | format!(" {:01$}" , d, flags.precision) |
| 508 | } else { |
| 509 | format!(" {:01$}" , d, flags.precision) |
| 510 | } |
| 511 | } |
| 512 | Octal => { |
| 513 | if flags.alternate { |
| 514 | // Leading octal zero counts against precision. |
| 515 | format!("0 {:01$o}" , d, flags.precision.saturating_sub(1)) |
| 516 | } else { |
| 517 | format!(" {:01$o}" , d, flags.precision) |
| 518 | } |
| 519 | } |
| 520 | Hex => { |
| 521 | if flags.alternate && d != 0 { |
| 522 | format!("0x {:01$x}" , d, flags.precision) |
| 523 | } else { |
| 524 | format!(" {:01$x}" , d, flags.precision) |
| 525 | } |
| 526 | } |
| 527 | HEX => { |
| 528 | if flags.alternate && d != 0 { |
| 529 | format!("0X {:01$X}" , d, flags.precision) |
| 530 | } else { |
| 531 | format!(" {:01$X}" , d, flags.precision) |
| 532 | } |
| 533 | } |
| 534 | String => return Err(Error::TypeMismatch), |
| 535 | } |
| 536 | .into_bytes() |
| 537 | } |
| 538 | Words(s) => match op { |
| 539 | String => { |
| 540 | let mut s = s.into_bytes(); |
| 541 | if flags.precision > 0 && flags.precision < s.len() { |
| 542 | s.truncate(flags.precision); |
| 543 | } |
| 544 | s |
| 545 | } |
| 546 | _ => return Err(Error::TypeMismatch), |
| 547 | }, |
| 548 | }; |
| 549 | if flags.width > s.len() { |
| 550 | let n = flags.width - s.len(); |
| 551 | if flags.left { |
| 552 | s.extend(repeat(b' ' ).take(n)); |
| 553 | } else { |
| 554 | let mut s_ = Vec::with_capacity(flags.width); |
| 555 | s_.extend(repeat(b' ' ).take(n)); |
| 556 | s_.extend(s.into_iter()); |
| 557 | s = s_; |
| 558 | } |
| 559 | } |
| 560 | Ok(s) |
| 561 | } |
| 562 | |
| 563 | #[cfg (test)] |
| 564 | mod test { |
| 565 | use super::Param::{self, Number, Words}; |
| 566 | use super::{expand, Variables}; |
| 567 | use std::result::Result::Ok; |
| 568 | |
| 569 | #[test ] |
| 570 | fn test_basic_setabf() { |
| 571 | let s = b" \\E[48;5;%p1%dm" ; |
| 572 | assert_eq!( |
| 573 | expand(s, &[Number(1)], &mut Variables::new()).unwrap(), |
| 574 | " \\E[48;5;1m" .bytes().collect::<Vec<_>>() |
| 575 | ); |
| 576 | } |
| 577 | |
| 578 | #[test ] |
| 579 | fn test_multiple_int_constants() { |
| 580 | assert_eq!( |
| 581 | expand(b"%{1}%{2}%d%d" , &[], &mut Variables::new()).unwrap(), |
| 582 | "21" .bytes().collect::<Vec<_>>() |
| 583 | ); |
| 584 | } |
| 585 | |
| 586 | #[test ] |
| 587 | fn test_op_i() { |
| 588 | let mut vars = Variables::new(); |
| 589 | assert_eq!( |
| 590 | expand( |
| 591 | b"%p1%d%p2%d%p3%d%i%p1%d%p2%d%p3%d" , |
| 592 | &[Number(1), Number(2), Number(3)], |
| 593 | &mut vars |
| 594 | ), |
| 595 | Ok("123233" .bytes().collect::<Vec<_>>()) |
| 596 | ); |
| 597 | assert_eq!( |
| 598 | expand(b"%p1%d%p2%d%i%p1%d%p2%d" , &[], &mut vars), |
| 599 | Ok("0011" .bytes().collect::<Vec<_>>()) |
| 600 | ); |
| 601 | } |
| 602 | |
| 603 | #[test ] |
| 604 | fn test_param_stack_failure_conditions() { |
| 605 | let mut varstruct = Variables::new(); |
| 606 | let vars = &mut varstruct; |
| 607 | fn get_res( |
| 608 | fmt: &str, |
| 609 | cap: &str, |
| 610 | params: &[Param], |
| 611 | vars: &mut Variables, |
| 612 | ) -> Result<Vec<u8>, super::Error> { |
| 613 | let mut u8v: Vec<_> = fmt.bytes().collect(); |
| 614 | u8v.extend(cap.as_bytes().iter().cloned()); |
| 615 | expand(&u8v, params, vars) |
| 616 | } |
| 617 | |
| 618 | let caps = ["%d" , "%c" , "%s" , "%Pa" , "%l" , "%!" , "%~" ]; |
| 619 | for &cap in &caps { |
| 620 | let res = get_res("" , cap, &[], vars); |
| 621 | assert!( |
| 622 | res.is_err(), |
| 623 | "Op {} succeeded incorrectly with 0 stack entries" , |
| 624 | cap |
| 625 | ); |
| 626 | let p = if cap == "%s" || cap == "%l" { |
| 627 | Words("foo" .to_owned()) |
| 628 | } else { |
| 629 | Number(97) |
| 630 | }; |
| 631 | let res = get_res("%p1" , cap, &[p], vars); |
| 632 | assert!( |
| 633 | res.is_ok(), |
| 634 | "Op {} failed with 1 stack entry: {}" , |
| 635 | cap, |
| 636 | res.err().unwrap() |
| 637 | ); |
| 638 | } |
| 639 | let caps = ["%+" , "%-" , "%*" , "%/" , "%m" , "%&" , "%|" , "%A" , "%O" ]; |
| 640 | for &cap in &caps { |
| 641 | let res = expand(cap.as_bytes(), &[], vars); |
| 642 | assert!( |
| 643 | res.is_err(), |
| 644 | "Binop {} succeeded incorrectly with 0 stack entries" , |
| 645 | cap |
| 646 | ); |
| 647 | let res = get_res("%{1}" , cap, &[], vars); |
| 648 | assert!( |
| 649 | res.is_err(), |
| 650 | "Binop {} succeeded incorrectly with 1 stack entry" , |
| 651 | cap |
| 652 | ); |
| 653 | let res = get_res("%{1}%{2}" , cap, &[], vars); |
| 654 | assert!( |
| 655 | res.is_ok(), |
| 656 | "Binop {} failed with 2 stack entries: {}" , |
| 657 | cap, |
| 658 | res.err().unwrap() |
| 659 | ); |
| 660 | } |
| 661 | } |
| 662 | |
| 663 | #[test ] |
| 664 | fn test_push_bad_param() { |
| 665 | assert!(expand(b"%pa" , &[], &mut Variables::new()).is_err()); |
| 666 | } |
| 667 | |
| 668 | #[test ] |
| 669 | fn test_comparison_ops() { |
| 670 | let v = [ |
| 671 | ('<' , [1u8, 0u8, 0u8]), |
| 672 | ('=' , [0u8, 1u8, 0u8]), |
| 673 | ('>' , [0u8, 0u8, 1u8]), |
| 674 | ]; |
| 675 | for &(op, bs) in &v { |
| 676 | let s = format!("%{{1}}%{{2}}%{}%d" , op); |
| 677 | let res = expand(s.as_bytes(), &[], &mut Variables::new()); |
| 678 | assert!(res.is_ok(), res.err().unwrap()); |
| 679 | assert_eq!(res.unwrap(), vec![b'0' + bs[0]]); |
| 680 | let s = format!("%{{1}}%{{1}}%{}%d" , op); |
| 681 | let res = expand(s.as_bytes(), &[], &mut Variables::new()); |
| 682 | assert!(res.is_ok(), res.err().unwrap()); |
| 683 | assert_eq!(res.unwrap(), vec![b'0' + bs[1]]); |
| 684 | let s = format!("%{{2}}%{{1}}%{}%d" , op); |
| 685 | let res = expand(s.as_bytes(), &[], &mut Variables::new()); |
| 686 | assert!(res.is_ok(), res.err().unwrap()); |
| 687 | assert_eq!(res.unwrap(), vec![b'0' + bs[2]]); |
| 688 | } |
| 689 | } |
| 690 | |
| 691 | #[test ] |
| 692 | fn test_conditionals() { |
| 693 | let mut vars = Variables::new(); |
| 694 | let s = b" \\E[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;m" ; |
| 695 | let res = expand(s, &[Number(1)], &mut vars); |
| 696 | assert!(res.is_ok(), res.err().unwrap()); |
| 697 | assert_eq!(res.unwrap(), " \\E[31m" .bytes().collect::<Vec<_>>()); |
| 698 | let res = expand(s, &[Number(8)], &mut vars); |
| 699 | assert!(res.is_ok(), res.err().unwrap()); |
| 700 | assert_eq!(res.unwrap(), " \\E[90m" .bytes().collect::<Vec<_>>()); |
| 701 | let res = expand(s, &[Number(42)], &mut vars); |
| 702 | assert!(res.is_ok(), res.err().unwrap()); |
| 703 | assert_eq!(res.unwrap(), " \\E[38;5;42m" .bytes().collect::<Vec<_>>()); |
| 704 | } |
| 705 | |
| 706 | #[test ] |
| 707 | fn test_format() { |
| 708 | let mut varstruct = Variables::new(); |
| 709 | let vars = &mut varstruct; |
| 710 | assert_eq!( |
| 711 | expand( |
| 712 | b"%p1%s%p2%2s%p3%2s%p4%.2s" , |
| 713 | &[ |
| 714 | Words("foo" .to_owned()), |
| 715 | Words("foo" .to_owned()), |
| 716 | Words("f" .to_owned()), |
| 717 | Words("foo" .to_owned()) |
| 718 | ], |
| 719 | vars |
| 720 | ), |
| 721 | Ok("foofoo ffo" .bytes().collect::<Vec<_>>()) |
| 722 | ); |
| 723 | assert_eq!( |
| 724 | expand(b"%p1%:-4.2s" , &[Words("foo" .to_owned())], vars), |
| 725 | Ok("fo " .bytes().collect::<Vec<_>>()) |
| 726 | ); |
| 727 | |
| 728 | assert_eq!( |
| 729 | expand(b"%p1%d%p1%.3d%p1%5d%p1%:+d" , &[Number(1)], vars), |
| 730 | Ok("1001 1+1" .bytes().collect::<Vec<_>>()) |
| 731 | ); |
| 732 | assert_eq!( |
| 733 | expand( |
| 734 | b"%p1%o%p1%#o%p2%6.4x%p2%#6.4X" , |
| 735 | &[Number(15), Number(27)], |
| 736 | vars |
| 737 | ), |
| 738 | Ok("17017 001b0X001B" .bytes().collect::<Vec<_>>()) |
| 739 | ); |
| 740 | } |
| 741 | } |
| 742 | |