Support printf formats in terminfo strings
terminfo parameterized strings supports a limited subset of printf-style formatting operations, such as %#5.3d.
This commit is contained in:
parent
d084d9e7df
commit
c1b1091a4a
@ -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()));
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user