Skip to content

Support printf formats in terminfo strings #7161

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
274 changes: 243 additions & 31 deletions src/libextra/terminfo/parm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand All @@ -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),
Expand Down Expand Up @@ -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)| {
Expand Down Expand Up @@ -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)),
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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) => {
Expand Down Expand Up @@ -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::*;
Expand Down Expand Up @@ -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()));
}
}