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