about summary refs log tree commit diff
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2013-06-20 20:41:09 -0700
committerbors <bors@rust-lang.org>2013-06-20 20:41:09 -0700
commit77ae7ec8d874270af0741b5c1f52cf9d58248b86 (patch)
tree911665e9b0ebdcd40a0e3bc6f22c26b3db5aeeaf
parentf348465283d6cd85b69bcdc1711d14985d154c39 (diff)
parentc1b1091a4a63971875e760bbfe76f90fec23d208 (diff)
downloadrust-77ae7ec8d874270af0741b5c1f52cf9d58248b86.tar.gz
rust-77ae7ec8d874270af0741b5c1f52cf9d58248b86.zip
auto merge of #7161 : kballard/rust/terminfo-parm-format, r=thestinger
Introduce support for terminfo's subset of printf-style formatting on doxXs operations.

r? @thestinger
-rw-r--r--src/libextra/terminfo/parm.rs274
1 files changed, 243 insertions, 31 deletions
diff --git a/src/libextra/terminfo/parm.rs b/src/libextra/terminfo/parm.rs
index 11f0fc23be5..dca890ddf51 100644
--- a/src/libextra/terminfo/parm.rs
+++ b/src/libextra/terminfo/parm.rs
@@ -11,7 +11,8 @@
 //! Parameterized string expansion
 
 use core::prelude::*;
-use core::{char, int, vec};
+use core::{char, vec, util};
+use core::num::strconv::{SignNone,SignNeg,SignAll,DigAll,to_str_bytes_common};
 use core::iterator::IteratorUtil;
 
 #[deriving(Eq)]
@@ -23,13 +24,21 @@ enum States {
     PushParam,
     CharConstant,
     CharClose,
-    IntConstant,
+    IntConstant(int),
+    FormatPattern(Flags, FormatState),
     SeekIfElse(int),
     SeekIfElsePercent(int),
     SeekIfEnd(int),
     SeekIfEndPercent(int)
 }
 
+#[deriving(Eq)]
+enum FormatState {
+    FormatStateFlags,
+    FormatStateWidth,
+    FormatStatePrecision
+}
+
 /// Types of parameters a capability can use
 pub enum Param {
     String(~str),
@@ -71,8 +80,6 @@ pub fn expand(cap: &[u8], params: &[Param], vars: &mut Variables)
 
     let mut stack: ~[Param] = ~[];
 
-    let mut intstate = ~[];
-
     // Copy parameters into a local vector for mutability
     let mut mparams = [Number(0), ..9];
     for mparams.mut_iter().zip(params.iter()).advance |(dst, &src)| {
@@ -100,26 +107,11 @@ pub fn expand(cap: &[u8], params: &[Param], vars: &mut Variables)
                             _       => return Err(~"a non-char was used with %c")
                         }
                     } else { return Err(~"stack is empty") },
-                    's' => if stack.len() > 0 {
-                        match stack.pop() {
-                            String(s) => output.push_all(s.as_bytes()),
-                            _         => return Err(~"a non-str was used with %s")
-                        }
-                    } else { return Err(~"stack is empty") },
-                    'd' => if stack.len() > 0 {
-                        match stack.pop() {
-                            Number(x) => {
-                                let s = x.to_str();
-                                output.push_all(s.as_bytes())
-                            }
-                            _         => return Err(~"a non-number was used with %d")
-                        }
-                    } else { return Err(~"stack is empty") },
                     'p' => state = PushParam,
                     'P' => state = SetVar,
                     'g' => state = GetVar,
                     '\'' => state = CharConstant,
-                    '{' => state = IntConstant,
+                    '{' => state = IntConstant(0),
                     'l' => if stack.len() > 0 {
                         match stack.pop() {
                             String(s) => stack.push(Number(s.len() as int)),
@@ -231,6 +223,30 @@ pub fn expand(cap: &[u8], params: &[Param], vars: &mut Variables)
                         (_, _) => return Err(~"first two params not numbers with %i")
                     },
 
+                    // printf-style support for %doxXs
+                    'd'|'o'|'x'|'X'|'s' => if stack.len() > 0 {
+                        let flags = Flags::new();
+                        let res = format(stack.pop(), FormatOp::from_char(cur), flags);
+                        if res.is_err() { return res }
+                        output.push_all(res.unwrap())
+                    } else { return Err(~"stack is empty") },
+                    ':'|'#'|' '|'.'|'0'..'9' => {
+                        let mut flags = Flags::new();
+                        let mut fstate = FormatStateFlags;
+                        match cur {
+                            ':' => (),
+                            '#' => flags.alternate = true,
+                            ' ' => flags.space = true,
+                            '.' => fstate = FormatStatePrecision,
+                            '0'..'9' => {
+                                flags.width = (cur - '0') as uint;
+                                fstate = FormatStateWidth;
+                            }
+                            _ => util::unreachable()
+                        }
+                        state = FormatPattern(flags, fstate);
+                    }
+
                     // conditionals
                     '?' => (),
                     't' => if stack.len() > 0 {
@@ -288,17 +304,61 @@ pub fn expand(cap: &[u8], params: &[Param], vars: &mut Variables)
                     return Err(~"malformed character constant");
                 }
             },
-            IntConstant => {
-                if cur == '}' {
-                    stack.push(match int::parse_bytes(intstate, 10) {
-                        Some(n) => Number(n),
-                        None => return Err(~"bad int constant")
-                    });
-                    intstate.clear();
-                    state = Nothing;
-                } else {
-                    intstate.push(cur as u8);
-                    old_state = Nothing;
+            IntConstant(i) => {
+                match cur {
+                    '}' => {
+                        stack.push(Number(i));
+                        state = Nothing;
+                    }
+                    '0'..'9' => {
+                        state = IntConstant(i*10 + ((cur - '0') as int));
+                        old_state = Nothing;
+                    }
+                    _ => return Err(~"bad int constant")
+                }
+            }
+            FormatPattern(ref mut flags, ref mut fstate) => {
+                old_state = Nothing;
+                match (*fstate, cur) {
+                    (_,'d')|(_,'o')|(_,'x')|(_,'X')|(_,'s') => if stack.len() > 0 {
+                        let res = format(stack.pop(), FormatOp::from_char(cur), *flags);
+                        if res.is_err() { return res }
+                        output.push_all(res.unwrap());
+                        old_state = state; // will cause state to go to Nothing
+                    } else { return Err(~"stack is empty") },
+                    (FormatStateFlags,'#') => {
+                        flags.alternate = true;
+                    }
+                    (FormatStateFlags,'-') => {
+                        flags.left = true;
+                    }
+                    (FormatStateFlags,'+') => {
+                        flags.sign = true;
+                    }
+                    (FormatStateFlags,' ') => {
+                        flags.space = true;
+                    }
+                    (FormatStateFlags,'0'..'9') => {
+                        flags.width = (cur - '0') as uint;
+                        *fstate = FormatStateWidth;
+                    }
+                    (FormatStateFlags,'.') => {
+                        *fstate = FormatStatePrecision;
+                    }
+                    (FormatStateWidth,'0'..'9') => {
+                        let old = flags.width;
+                        flags.width = flags.width * 10 + ((cur - '0') as uint);
+                        if flags.width < old { return Err(~"format width overflow") }
+                    }
+                    (FormatStateWidth,'.') => {
+                        *fstate = FormatStatePrecision;
+                    }
+                    (FormatStatePrecision,'0'..'9') => {
+                        let old = flags.precision;
+                        flags.precision = flags.precision * 10 + ((cur - '0') as uint);
+                        if flags.precision < old { return Err(~"format precision overflow") }
+                    }
+                    _ => return Err(~"invalid format specifier")
                 }
             }
             SeekIfElse(level) => {
@@ -349,6 +409,142 @@ pub fn expand(cap: &[u8], params: &[Param], vars: &mut Variables)
     Ok(output)
 }
 
+#[deriving(Eq)]
+priv struct Flags {
+    width: uint,
+    precision: uint,
+    alternate: bool,
+    left: bool,
+    sign: bool,
+    space: bool
+}
+
+impl Flags {
+    priv fn new() -> Flags {
+        Flags{ width: 0, precision: 0, alternate: false,
+               left: false, sign: false, space: false }
+    }
+}
+
+priv enum FormatOp {
+    FormatDigit,
+    FormatOctal,
+    FormatHex,
+    FormatHEX,
+    FormatString
+}
+
+impl FormatOp {
+    priv fn from_char(c: char) -> FormatOp {
+        match c {
+            'd' => FormatDigit,
+            'o' => FormatOctal,
+            'x' => FormatHex,
+            'X' => FormatHEX,
+            's' => FormatString,
+            _ => fail!("bad FormatOp char")
+        }
+    }
+    priv fn to_char(self) -> char {
+        match self {
+            FormatDigit => 'd',
+            FormatOctal => 'o',
+            FormatHex => 'x',
+            FormatHEX => 'X',
+            FormatString => 's'
+        }
+    }
+}
+
+priv fn format(val: Param, op: FormatOp, flags: Flags) -> Result<~[u8],~str> {
+    let mut s = match val {
+        Number(d) => {
+            match op {
+                FormatString => {
+                    return Err(~"non-number on stack with %s")
+                }
+                _ => {
+                    let radix = match op {
+                        FormatDigit => 10,
+                        FormatOctal => 8,
+                        FormatHex|FormatHEX => 16,
+                        FormatString => util::unreachable()
+                    };
+                    let mut (s,_) = match op {
+                        FormatDigit => {
+                            let sign = if flags.sign { SignAll } else { SignNeg };
+                            to_str_bytes_common(&d, radix, false, sign, DigAll)
+                        }
+                        _ => to_str_bytes_common(&(d as uint), radix, false, SignNone, DigAll)
+                    };
+                    if flags.precision > s.len() {
+                        let mut s_ = vec::with_capacity(flags.precision);
+                        let n = flags.precision - s.len();
+                        s_.grow(n, &('0' as u8));
+                        s_.push_all_move(s);
+                        s = s_;
+                    }
+                    assert!(!s.is_empty(), "string conversion produced empty result");
+                    match op {
+                        FormatDigit => {
+                            if flags.space && !(s[0] == '-' as u8 || s[0] == '+' as u8) {
+                                s.unshift(' ' as u8);
+                            }
+                        }
+                        FormatOctal => {
+                            if flags.alternate && s[0] != '0' as u8 {
+                                s.unshift('0' as u8);
+                            }
+                        }
+                        FormatHex => {
+                            if flags.alternate {
+                                let s_ = util::replace(&mut s, ~['0' as u8, 'x' as u8]);
+                                s.push_all_move(s_);
+                            }
+                        }
+                        FormatHEX => {
+                            s = s.into_ascii().to_upper().into_bytes();
+                            if flags.alternate {
+                                let s_ = util::replace(&mut s, ~['0' as u8, 'X' as u8]);
+                                s.push_all_move(s_);
+                            }
+                        }
+                        FormatString => util::unreachable()
+                    }
+                    s
+                }
+            }
+        }
+        String(s) => {
+            match op {
+                FormatString => {
+                    let mut s = s.as_bytes_with_null_consume();
+                    s.pop(); // remove the null
+                    if flags.precision > 0 && flags.precision < s.len() {
+                        s.truncate(flags.precision);
+                    }
+                    s
+                }
+                _ => {
+                    return Err(fmt!("non-string on stack with %%%c", op.to_char()))
+                }
+            }
+        }
+    };
+    if flags.width > s.len() {
+        let n = flags.width - s.len();
+        if flags.left {
+            s.grow(n, &(' ' as u8));
+        } else {
+            let mut s_ = vec::with_capacity(flags.width);
+            s_.grow(n, &(' ' as u8));
+            s_.push_all_move(s);
+            s = s_;
+        }
+    }
+    Ok(s)
+}
+
 #[cfg(test)]
 mod test {
     use super::*;
@@ -443,4 +639,20 @@ mod test {
         assert!(res.is_ok(), res.unwrap_err());
         assert_eq!(res.unwrap(), bytes!("\\E[38;5;42m").to_owned());
     }
+
+    #[test]
+    fn test_format() {
+        let mut varstruct = Variables::new();
+        let vars = &mut varstruct;
+        assert_eq!(expand(bytes!("%p1%s%p2%2s%p3%2s%p4%.2s"),
+                          [String(~"foo"), String(~"foo"), String(~"f"), String(~"foo")], vars),
+                   Ok(bytes!("foofoo ffo").to_owned()));
+        assert_eq!(expand(bytes!("%p1%:-4.2s"), [String(~"foo")], vars),
+                   Ok(bytes!("fo  ").to_owned()));
+
+        assert_eq!(expand(bytes!("%p1%d%p1%.3d%p1%5d%p1%:+d"), [Number(1)], vars),
+                   Ok(bytes!("1001    1+1").to_owned()));
+        assert_eq!(expand(bytes!("%p1%o%p1%#o%p2%6.4x%p2%#6.4X"), [Number(15), Number(27)], vars),
+                   Ok(bytes!("17017  001b0X001B").to_owned()));
+    }
 }