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