Skip to content

Commit c1b1091

Browse files
committed
Support printf formats in terminfo strings
terminfo parameterized strings supports a limited subset of printf-style formatting operations, such as %rust-lang#5.3d.
1 parent d084d9e commit c1b1091

File tree

1 file changed

+243
-31
lines changed

1 file changed

+243
-31
lines changed

src/libextra/terminfo/parm.rs

+243-31
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@
1111
//! Parameterized string expansion
1212
1313
use core::prelude::*;
14-
use core::{char, int, vec};
14+
use core::{char, vec, util};
15+
use core::num::strconv::{SignNone,SignNeg,SignAll,DigAll,to_str_bytes_common};
1516
use core::iterator::IteratorUtil;
1617

1718
#[deriving(Eq)]
@@ -23,13 +24,21 @@ enum States {
2324
PushParam,
2425
CharConstant,
2526
CharClose,
26-
IntConstant,
27+
IntConstant(int),
28+
FormatPattern(Flags, FormatState),
2729
SeekIfElse(int),
2830
SeekIfElsePercent(int),
2931
SeekIfEnd(int),
3032
SeekIfEndPercent(int)
3133
}
3234

35+
#[deriving(Eq)]
36+
enum FormatState {
37+
FormatStateFlags,
38+
FormatStateWidth,
39+
FormatStatePrecision
40+
}
41+
3342
/// Types of parameters a capability can use
3443
pub enum Param {
3544
String(~str),
@@ -71,8 +80,6 @@ pub fn expand(cap: &[u8], params: &[Param], vars: &mut Variables)
7180

7281
let mut stack: ~[Param] = ~[];
7382

74-
let mut intstate = ~[];
75-
7683
// Copy parameters into a local vector for mutability
7784
let mut mparams = [Number(0), ..9];
7885
for mparams.mut_iter().zip(params.iter()).advance |(dst, &src)| {
@@ -100,26 +107,11 @@ pub fn expand(cap: &[u8], params: &[Param], vars: &mut Variables)
100107
_ => return Err(~"a non-char was used with %c")
101108
}
102109
} else { return Err(~"stack is empty") },
103-
's' => if stack.len() > 0 {
104-
match stack.pop() {
105-
String(s) => output.push_all(s.as_bytes()),
106-
_ => return Err(~"a non-str was used with %s")
107-
}
108-
} else { return Err(~"stack is empty") },
109-
'd' => if stack.len() > 0 {
110-
match stack.pop() {
111-
Number(x) => {
112-
let s = x.to_str();
113-
output.push_all(s.as_bytes())
114-
}
115-
_ => return Err(~"a non-number was used with %d")
116-
}
117-
} else { return Err(~"stack is empty") },
118110
'p' => state = PushParam,
119111
'P' => state = SetVar,
120112
'g' => state = GetVar,
121113
'\'' => state = CharConstant,
122-
'{' => state = IntConstant,
114+
'{' => state = IntConstant(0),
123115
'l' => if stack.len() > 0 {
124116
match stack.pop() {
125117
String(s) => stack.push(Number(s.len() as int)),
@@ -231,6 +223,30 @@ pub fn expand(cap: &[u8], params: &[Param], vars: &mut Variables)
231223
(_, _) => return Err(~"first two params not numbers with %i")
232224
},
233225

226+
// printf-style support for %doxXs
227+
'd'|'o'|'x'|'X'|'s' => if stack.len() > 0 {
228+
let flags = Flags::new();
229+
let res = format(stack.pop(), FormatOp::from_char(cur), flags);
230+
if res.is_err() { return res }
231+
output.push_all(res.unwrap())
232+
} else { return Err(~"stack is empty") },
233+
':'|'#'|' '|'.'|'0'..'9' => {
234+
let mut flags = Flags::new();
235+
let mut fstate = FormatStateFlags;
236+
match cur {
237+
':' => (),
238+
'#' => flags.alternate = true,
239+
' ' => flags.space = true,
240+
'.' => fstate = FormatStatePrecision,
241+
'0'..'9' => {
242+
flags.width = (cur - '0') as uint;
243+
fstate = FormatStateWidth;
244+
}
245+
_ => util::unreachable()
246+
}
247+
state = FormatPattern(flags, fstate);
248+
}
249+
234250
// conditionals
235251
'?' => (),
236252
't' => if stack.len() > 0 {
@@ -288,17 +304,61 @@ pub fn expand(cap: &[u8], params: &[Param], vars: &mut Variables)
288304
return Err(~"malformed character constant");
289305
}
290306
},
291-
IntConstant => {
292-
if cur == '}' {
293-
stack.push(match int::parse_bytes(intstate, 10) {
294-
Some(n) => Number(n),
295-
None => return Err(~"bad int constant")
296-
});
297-
intstate.clear();
298-
state = Nothing;
299-
} else {
300-
intstate.push(cur as u8);
301-
old_state = Nothing;
307+
IntConstant(i) => {
308+
match cur {
309+
'}' => {
310+
stack.push(Number(i));
311+
state = Nothing;
312+
}
313+
'0'..'9' => {
314+
state = IntConstant(i*10 + ((cur - '0') as int));
315+
old_state = Nothing;
316+
}
317+
_ => return Err(~"bad int constant")
318+
}
319+
}
320+
FormatPattern(ref mut flags, ref mut fstate) => {
321+
old_state = Nothing;
322+
match (*fstate, cur) {
323+
(_,'d')|(_,'o')|(_,'x')|(_,'X')|(_,'s') => if stack.len() > 0 {
324+
let res = format(stack.pop(), FormatOp::from_char(cur), *flags);
325+
if res.is_err() { return res }
326+
output.push_all(res.unwrap());
327+
old_state = state; // will cause state to go to Nothing
328+
} else { return Err(~"stack is empty") },
329+
(FormatStateFlags,'#') => {
330+
flags.alternate = true;
331+
}
332+
(FormatStateFlags,'-') => {
333+
flags.left = true;
334+
}
335+
(FormatStateFlags,'+') => {
336+
flags.sign = true;
337+
}
338+
(FormatStateFlags,' ') => {
339+
flags.space = true;
340+
}
341+
(FormatStateFlags,'0'..'9') => {
342+
flags.width = (cur - '0') as uint;
343+
*fstate = FormatStateWidth;
344+
}
345+
(FormatStateFlags,'.') => {
346+
*fstate = FormatStatePrecision;
347+
}
348+
(FormatStateWidth,'0'..'9') => {
349+
let old = flags.width;
350+
flags.width = flags.width * 10 + ((cur - '0') as uint);
351+
if flags.width < old { return Err(~"format width overflow") }
352+
}
353+
(FormatStateWidth,'.') => {
354+
*fstate = FormatStatePrecision;
355+
}
356+
(FormatStatePrecision,'0'..'9') => {
357+
let old = flags.precision;
358+
flags.precision = flags.precision * 10 + ((cur - '0') as uint);
359+
if flags.precision < old { return Err(~"format precision overflow") }
360+
}
361+
_ => return Err(~"invalid format specifier")
302362
}
303363
}
304364
SeekIfElse(level) => {
@@ -349,6 +409,142 @@ pub fn expand(cap: &[u8], params: &[Param], vars: &mut Variables)
349409
Ok(output)
350410
}
351411

412+
#[deriving(Eq)]
413+
priv struct Flags {
414+
width: uint,
415+
precision: uint,
416+
alternate: bool,
417+
left: bool,
418+
sign: bool,
419+
space: bool
420+
}
421+
422+
impl Flags {
423+
priv fn new() -> Flags {
424+
Flags{ width: 0, precision: 0, alternate: false,
425+
left: false, sign: false, space: false }
426+
}
427+
}
428+
429+
priv enum FormatOp {
430+
FormatDigit,
431+
FormatOctal,
432+
FormatHex,
433+
FormatHEX,
434+
FormatString
435+
}
436+
437+
impl FormatOp {
438+
priv fn from_char(c: char) -> FormatOp {
439+
match c {
440+
'd' => FormatDigit,
441+
'o' => FormatOctal,
442+
'x' => FormatHex,
443+
'X' => FormatHEX,
444+
's' => FormatString,
445+
_ => fail!("bad FormatOp char")
446+
}
447+
}
448+
priv fn to_char(self) -> char {
449+
match self {
450+
FormatDigit => 'd',
451+
FormatOctal => 'o',
452+
FormatHex => 'x',
453+
FormatHEX => 'X',
454+
FormatString => 's'
455+
}
456+
}
457+
}
458+
459+
priv fn format(val: Param, op: FormatOp, flags: Flags) -> Result<~[u8],~str> {
460+
let mut s = match val {
461+
Number(d) => {
462+
match op {
463+
FormatString => {
464+
return Err(~"non-number on stack with %s")
465+
}
466+
_ => {
467+
let radix = match op {
468+
FormatDigit => 10,
469+
FormatOctal => 8,
470+
FormatHex|FormatHEX => 16,
471+
FormatString => util::unreachable()
472+
};
473+
let mut (s,_) = match op {
474+
FormatDigit => {
475+
let sign = if flags.sign { SignAll } else { SignNeg };
476+
to_str_bytes_common(&d, radix, false, sign, DigAll)
477+
}
478+
_ => to_str_bytes_common(&(d as uint), radix, false, SignNone, DigAll)
479+
};
480+
if flags.precision > s.len() {
481+
let mut s_ = vec::with_capacity(flags.precision);
482+
let n = flags.precision - s.len();
483+
s_.grow(n, &('0' as u8));
484+
s_.push_all_move(s);
485+
s = s_;
486+
}
487+
assert!(!s.is_empty(), "string conversion produced empty result");
488+
match op {
489+
FormatDigit => {
490+
if flags.space && !(s[0] == '-' as u8 || s[0] == '+' as u8) {
491+
s.unshift(' ' as u8);
492+
}
493+
}
494+
FormatOctal => {
495+
if flags.alternate && s[0] != '0' as u8 {
496+
s.unshift('0' as u8);
497+
}
498+
}
499+
FormatHex => {
500+
if flags.alternate {
501+
let s_ = util::replace(&mut s, ~['0' as u8, 'x' as u8]);
502+
s.push_all_move(s_);
503+
}
504+
}
505+
FormatHEX => {
506+
s = s.into_ascii().to_upper().into_bytes();
507+
if flags.alternate {
508+
let s_ = util::replace(&mut s, ~['0' as u8, 'X' as u8]);
509+
s.push_all_move(s_);
510+
}
511+
}
512+
FormatString => util::unreachable()
513+
}
514+
s
515+
}
516+
}
517+
}
518+
String(s) => {
519+
match op {
520+
FormatString => {
521+
let mut s = s.as_bytes_with_null_consume();
522+
s.pop(); // remove the null
523+
if flags.precision > 0 && flags.precision < s.len() {
524+
s.truncate(flags.precision);
525+
}
526+
s
527+
}
528+
_ => {
529+
return Err(fmt!("non-string on stack with %%%c", op.to_char()))
530+
}
531+
}
532+
}
533+
};
534+
if flags.width > s.len() {
535+
let n = flags.width - s.len();
536+
if flags.left {
537+
s.grow(n, &(' ' as u8));
538+
} else {
539+
let mut s_ = vec::with_capacity(flags.width);
540+
s_.grow(n, &(' ' as u8));
541+
s_.push_all_move(s);
542+
s = s_;
543+
}
544+
}
545+
Ok(s)
546+
}
547+
352548
#[cfg(test)]
353549
mod test {
354550
use super::*;
@@ -443,4 +639,20 @@ mod test {
443639
assert!(res.is_ok(), res.unwrap_err());
444640
assert_eq!(res.unwrap(), bytes!("\\E[38;5;42m").to_owned());
445641
}
642+
643+
#[test]
644+
fn test_format() {
645+
let mut varstruct = Variables::new();
646+
let vars = &mut varstruct;
647+
assert_eq!(expand(bytes!("%p1%s%p2%2s%p3%2s%p4%.2s"),
648+
[String(~"foo"), String(~"foo"), String(~"f"), String(~"foo")], vars),
649+
Ok(bytes!("foofoo ffo").to_owned()));
650+
assert_eq!(expand(bytes!("%p1%:-4.2s"), [String(~"foo")], vars),
651+
Ok(bytes!("fo ").to_owned()));
652+
653+
assert_eq!(expand(bytes!("%p1%d%p1%.3d%p1%5d%p1%:+d"), [Number(1)], vars),
654+
Ok(bytes!("1001 1+1").to_owned()));
655+
assert_eq!(expand(bytes!("%p1%o%p1%#o%p2%6.4x%p2%#6.4X"), [Number(15), Number(27)], vars),
656+
Ok(bytes!("17017 001b0X001B").to_owned()));
657+
}
446658
}

0 commit comments

Comments
 (0)