]>
Commit | Line | Data |
---|---|---|
f20569fa XL |
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("not enough elements on the stack"), | |
88 | TypeMismatch => f.write_str("type mismatch"), | |
89 | UnrecognizedFormatOption(_) => f.write_str("unrecognized format option"), | |
90 | InvalidVariableName(_) => f.write_str("invalid variable name"), | |
91 | InvalidParameterIndex(_) => f.write_str("invalid parameter index"), | |
92 | MalformedCharacterConstant => f.write_str("malformed character constant"), | |
93 | IntegerConstantOverflow => f.write_str("integer constant computation overflowed"), | |
94 | MalformedIntegerConstant => f.write_str("malformed integer constant"), | |
95 | FormatWidthOverflow => f.write_str("format width constant computation overflowed"), | |
96 | FormatPrecisionOverflow => { | |
97 | f.write_str("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 | } |