2012-12-03 18:48:01 -06:00
|
|
|
// Copyright 2012 The Rust Project Developers. See the COPYRIGHT
|
|
|
|
// file at the top-level directory of this distribution and at
|
|
|
|
// http://rust-lang.org/COPYRIGHT.
|
|
|
|
//
|
|
|
|
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
|
|
|
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
|
|
|
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
|
|
|
// option. This file may not be copied, modified, or distributed
|
|
|
|
// except according to those terms.
|
|
|
|
|
2012-11-18 13:00:39 -06:00
|
|
|
//! Support for fmt! expressions.
|
|
|
|
//!
|
|
|
|
//! The syntax is close to that of Posix format strings:
|
2012-11-18 19:05:04 -06:00
|
|
|
//!
|
2012-11-18 13:00:39 -06:00
|
|
|
//! ~~~~~~
|
|
|
|
//! Format := '%' Parameter? Flag* Width? Precision? Type
|
|
|
|
//! Parameter := [0-9]+ '$'
|
|
|
|
//! Flag := [ 0#+-]
|
|
|
|
//! Width := Parameter | [0-9]+
|
|
|
|
//! Precision := '.' [0-9]+
|
|
|
|
//! Type := [bcdfiostuxX?]
|
|
|
|
//! ~~~~~~
|
2012-11-18 19:05:04 -06:00
|
|
|
//!
|
|
|
|
//! * Parameter is the 1-based argument to apply the format to. Currently not
|
|
|
|
//! implemented.
|
|
|
|
//! * Flag 0 causes leading zeros to be used for padding when converting
|
|
|
|
//! numbers.
|
|
|
|
//! * Flag # causes the conversion to be done in an *alternative* manner.
|
|
|
|
//! Currently not implemented.
|
|
|
|
//! * Flag + causes signed numbers to always be prepended with a sign
|
|
|
|
//! character.
|
2012-11-18 13:00:39 -06:00
|
|
|
//! * Flag - left justifies the result
|
2012-11-18 19:05:04 -06:00
|
|
|
//! * Width specifies the minimum field width of the result. By default
|
|
|
|
//! leading spaces are added.
|
|
|
|
//! * Precision specifies the minimum number of digits for integral types
|
|
|
|
//! and the minimum number
|
2012-11-18 13:00:39 -06:00
|
|
|
//! of decimal places for float.
|
2012-11-18 19:05:04 -06:00
|
|
|
//!
|
2012-11-18 13:00:39 -06:00
|
|
|
//! The types currently supported are:
|
2012-11-18 19:05:04 -06:00
|
|
|
//!
|
2012-11-18 13:00:39 -06:00
|
|
|
//! * b - bool
|
|
|
|
//! * c - char
|
|
|
|
//! * d - int
|
|
|
|
//! * f - float
|
|
|
|
//! * i - int (same as d)
|
|
|
|
//! * o - uint as octal
|
|
|
|
//! * t - uint as binary
|
|
|
|
//! * u - uint
|
|
|
|
//! * x - uint as lower-case hexadecimal
|
|
|
|
//! * X - uint as upper-case hexadecimal
|
|
|
|
//! * s - str (any flavor)
|
|
|
|
//! * ? - arbitrary type (does not use the to_str trait)
|
|
|
|
|
2011-12-13 18:25:51 -06:00
|
|
|
/*
|
|
|
|
Syntax Extension: fmt
|
|
|
|
|
|
|
|
Format a string
|
|
|
|
|
|
|
|
The 'fmt' extension is modeled on the posix printf system.
|
|
|
|
|
|
|
|
A posix conversion ostensibly looks like this
|
|
|
|
|
2012-06-29 18:26:56 -05:00
|
|
|
> %~[parameter]~[flags]~[width]~[.precision]~[length]type
|
2011-12-13 18:25:51 -06:00
|
|
|
|
|
|
|
Given the different numeric type bestiary we have, we omit the 'length'
|
|
|
|
parameter and support slightly different conversions for 'type'
|
|
|
|
|
2012-06-29 18:26:56 -05:00
|
|
|
> %~[parameter]~[flags]~[width]~[.precision]type
|
2011-12-13 18:25:51 -06:00
|
|
|
|
|
|
|
we also only support translating-to-rust a tiny subset of the possible
|
|
|
|
combinations at the moment.
|
|
|
|
|
|
|
|
Example:
|
|
|
|
|
2012-08-22 19:24:52 -05:00
|
|
|
debug!("hello, %s!", "world");
|
2011-12-13 18:25:51 -06:00
|
|
|
|
|
|
|
*/
|
|
|
|
|
2013-01-08 21:37:25 -06:00
|
|
|
use prelude::*;
|
2013-06-10 16:50:12 -05:00
|
|
|
use iterator::IteratorUtil;
|
2011-12-13 18:25:51 -06:00
|
|
|
|
|
|
|
/*
|
|
|
|
* We have a 'ct' (compile-time) module that parses format strings into a
|
|
|
|
* sequence of conversions. From those conversions AST fragments are built
|
|
|
|
* that call into properly-typed functions in the 'rt' (run-time) module.
|
|
|
|
* Each of those run-time conversion functions accepts another conversion
|
|
|
|
* description that specifies how to format its output.
|
|
|
|
*
|
|
|
|
* The building of the AST is currently done in a module inside the compiler,
|
|
|
|
* but should migrate over here as the plugin interface is defined.
|
|
|
|
*/
|
|
|
|
|
|
|
|
// Functions used by the fmt extension at compile time
|
2012-11-18 13:00:39 -06:00
|
|
|
#[doc(hidden)]
|
2012-10-04 15:49:37 -05:00
|
|
|
pub mod ct {
|
2012-12-23 16:41:37 -06:00
|
|
|
use char;
|
2013-06-18 11:39:16 -05:00
|
|
|
use container::Container;
|
2013-01-08 21:37:25 -06:00
|
|
|
use prelude::*;
|
2012-12-23 16:41:37 -06:00
|
|
|
use str;
|
|
|
|
|
2013-03-20 10:35:02 -05:00
|
|
|
#[deriving(Eq)]
|
2012-10-04 15:49:37 -05:00
|
|
|
pub enum Signedness { Signed, Unsigned, }
|
2013-01-07 14:06:59 -06:00
|
|
|
|
2013-03-20 10:35:02 -05:00
|
|
|
#[deriving(Eq)]
|
2012-10-04 15:49:37 -05:00
|
|
|
pub enum Caseness { CaseUpper, CaseLower, }
|
2013-01-07 14:06:59 -06:00
|
|
|
|
2013-03-20 10:35:02 -05:00
|
|
|
#[deriving(Eq)]
|
2012-10-04 15:49:37 -05:00
|
|
|
pub enum Ty {
|
2012-09-21 21:04:06 -05:00
|
|
|
TyBool,
|
|
|
|
TyStr,
|
|
|
|
TyChar,
|
|
|
|
TyInt(Signedness),
|
|
|
|
TyBits,
|
|
|
|
TyHex(Caseness),
|
|
|
|
TyOctal,
|
|
|
|
TyFloat,
|
2013-07-27 22:21:39 -05:00
|
|
|
TyPointer,
|
2012-09-21 21:04:06 -05:00
|
|
|
TyPoly,
|
2012-09-11 21:37:29 -05:00
|
|
|
}
|
2013-01-07 14:06:59 -06:00
|
|
|
|
2013-03-20 10:35:02 -05:00
|
|
|
#[deriving(Eq)]
|
2012-10-04 15:49:37 -05:00
|
|
|
pub enum Flag {
|
2012-09-21 21:04:06 -05:00
|
|
|
FlagLeftJustify,
|
|
|
|
FlagLeftZeroPad,
|
|
|
|
FlagSpaceForSign,
|
|
|
|
FlagSignAlways,
|
|
|
|
FlagAlternate,
|
2012-09-11 21:37:29 -05:00
|
|
|
}
|
2013-01-07 14:06:59 -06:00
|
|
|
|
2013-03-20 10:35:02 -05:00
|
|
|
#[deriving(Eq)]
|
2012-11-01 18:47:24 -05:00
|
|
|
pub enum Count {
|
|
|
|
CountIs(uint),
|
|
|
|
CountIsParam(uint),
|
|
|
|
CountIsNextParam,
|
|
|
|
CountImplied,
|
|
|
|
}
|
2011-12-13 18:25:51 -06:00
|
|
|
|
2013-03-20 10:35:02 -05:00
|
|
|
#[deriving(Eq)]
|
2013-01-07 13:30:34 -06:00
|
|
|
struct Parsed<T> {
|
|
|
|
val: T,
|
|
|
|
next: uint
|
|
|
|
}
|
|
|
|
|
2013-05-31 17:17:22 -05:00
|
|
|
impl<T> Parsed<T> {
|
|
|
|
pub fn new(val: T, next: uint) -> Parsed<T> {
|
2013-01-07 14:06:59 -06:00
|
|
|
Parsed {val: val, next: next}
|
2013-01-07 13:30:34 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-11-01 18:47:24 -05:00
|
|
|
// A formatted conversion from an expression to a string
|
2013-03-20 10:35:02 -05:00
|
|
|
#[deriving(Eq)]
|
2013-01-07 14:06:59 -06:00
|
|
|
pub struct Conv {
|
|
|
|
param: Option<uint>,
|
|
|
|
flags: ~[Flag],
|
|
|
|
width: Count,
|
|
|
|
precision: Count,
|
|
|
|
ty: Ty
|
|
|
|
}
|
2011-12-13 18:25:51 -06:00
|
|
|
|
|
|
|
// A fragment of the output sequence
|
2013-03-20 10:35:02 -05:00
|
|
|
#[deriving(Eq)]
|
2012-10-04 15:49:37 -05:00
|
|
|
pub enum Piece { PieceString(~str), PieceConv(Conv), }
|
2013-01-07 14:06:59 -06:00
|
|
|
|
|
|
|
pub type ErrorFn = @fn(&str) -> !;
|
2011-12-13 18:25:51 -06:00
|
|
|
|
2012-11-06 20:41:06 -06:00
|
|
|
pub fn parse_fmt_string(s: &str, err: ErrorFn) -> ~[Piece] {
|
2013-01-07 14:00:03 -06:00
|
|
|
fn push_slice(ps: &mut ~[Piece], s: &str, from: uint, to: uint) {
|
|
|
|
if to > from {
|
2013-03-21 06:36:21 -05:00
|
|
|
ps.push(PieceString(s.slice(from, to).to_owned()));
|
2011-12-13 18:25:51 -06:00
|
|
|
}
|
|
|
|
}
|
2013-01-07 14:00:03 -06:00
|
|
|
|
|
|
|
let lim = s.len();
|
|
|
|
let mut h = 0;
|
2012-09-10 18:31:00 -05:00
|
|
|
let mut i = 0;
|
2013-01-07 14:00:03 -06:00
|
|
|
let mut pieces = ~[];
|
|
|
|
|
2011-12-13 18:25:51 -06:00
|
|
|
while i < lim {
|
2013-01-07 14:00:03 -06:00
|
|
|
if s[i] == '%' as u8 {
|
2012-09-10 18:31:00 -05:00
|
|
|
i += 1;
|
2013-01-07 14:00:03 -06:00
|
|
|
|
2011-12-13 18:25:51 -06:00
|
|
|
if i >= lim {
|
2013-05-19 00:07:44 -05:00
|
|
|
err("unterminated conversion at end of string");
|
2013-01-07 14:00:03 -06:00
|
|
|
} else if s[i] == '%' as u8 {
|
|
|
|
push_slice(&mut pieces, s, h, i);
|
2012-09-10 18:31:00 -05:00
|
|
|
i += 1;
|
2011-12-13 18:25:51 -06:00
|
|
|
} else {
|
2013-01-07 14:00:03 -06:00
|
|
|
push_slice(&mut pieces, s, h, i - 1);
|
|
|
|
let Parsed {val, next} = parse_conversion(s, i, lim, err);
|
|
|
|
pieces.push(val);
|
|
|
|
i = next;
|
2011-12-13 18:25:51 -06:00
|
|
|
}
|
2013-01-07 14:00:03 -06:00
|
|
|
|
|
|
|
h = i;
|
|
|
|
} else {
|
|
|
|
i += str::utf8_char_width(s[i]);
|
|
|
|
}
|
2011-12-13 18:25:51 -06:00
|
|
|
}
|
2013-01-07 14:00:03 -06:00
|
|
|
|
|
|
|
push_slice(&mut pieces, s, h, i);
|
|
|
|
pieces
|
2011-12-13 18:25:51 -06:00
|
|
|
}
|
2013-01-07 14:06:59 -06:00
|
|
|
|
|
|
|
pub fn peek_num(s: &str, i: uint, lim: uint) -> Option<Parsed<uint>> {
|
|
|
|
let mut i = i;
|
|
|
|
let mut accum = 0;
|
2012-08-08 19:09:24 -05:00
|
|
|
let mut found = false;
|
2013-01-07 14:06:59 -06:00
|
|
|
|
|
|
|
while i < lim {
|
|
|
|
match char::to_digit(s[i] as char, 10) {
|
2012-08-20 14:23:37 -05:00
|
|
|
Some(x) => {
|
2012-08-08 19:09:24 -05:00
|
|
|
found = true;
|
|
|
|
accum *= 10;
|
|
|
|
accum += x;
|
2013-01-07 14:06:59 -06:00
|
|
|
i += 1;
|
|
|
|
}
|
2012-08-20 14:23:37 -05:00
|
|
|
None => break
|
2012-08-08 19:09:24 -05:00
|
|
|
}
|
|
|
|
}
|
2013-01-07 14:06:59 -06:00
|
|
|
|
2012-08-08 19:09:24 -05:00
|
|
|
if found {
|
2013-01-07 14:06:59 -06:00
|
|
|
Some(Parsed::new(accum, i))
|
2012-08-08 19:09:24 -05:00
|
|
|
} else {
|
2012-08-20 14:23:37 -05:00
|
|
|
None
|
2012-08-08 19:09:24 -05:00
|
|
|
}
|
2011-12-13 18:25:51 -06:00
|
|
|
}
|
2013-01-07 14:06:59 -06:00
|
|
|
|
|
|
|
pub fn parse_conversion(s: &str, i: uint, lim: uint, err: ErrorFn) ->
|
|
|
|
Parsed<Piece> {
|
|
|
|
let param = parse_parameter(s, i, lim);
|
2013-01-07 13:50:04 -06:00
|
|
|
// avoid copying ~[Flag] by destructuring
|
|
|
|
let Parsed {val: flags_val, next: flags_next} = parse_flags(s,
|
2013-01-07 14:06:59 -06:00
|
|
|
param.next, lim);
|
2013-01-07 13:50:04 -06:00
|
|
|
let width = parse_count(s, flags_next, lim);
|
2011-12-13 18:25:51 -06:00
|
|
|
let prec = parse_precision(s, width.next, lim);
|
2012-11-06 20:41:06 -06:00
|
|
|
let ty = parse_type(s, prec.next, lim, err);
|
2013-01-07 14:06:59 -06:00
|
|
|
|
|
|
|
Parsed::new(PieceConv(Conv {
|
|
|
|
param: param.val,
|
|
|
|
flags: flags_val,
|
|
|
|
width: width.val,
|
|
|
|
precision: prec.val,
|
|
|
|
ty: ty.val}), ty.next)
|
2011-12-13 18:25:51 -06:00
|
|
|
}
|
2013-01-07 14:06:59 -06:00
|
|
|
|
2012-11-01 18:47:24 -05:00
|
|
|
pub fn parse_parameter(s: &str, i: uint, lim: uint) ->
|
2013-01-07 14:06:59 -06:00
|
|
|
Parsed<Option<uint>> {
|
2013-01-07 13:30:34 -06:00
|
|
|
if i >= lim { return Parsed::new(None, i); }
|
2013-01-07 14:06:59 -06:00
|
|
|
|
2013-01-07 13:47:06 -06:00
|
|
|
match peek_num(s, i, lim) {
|
|
|
|
Some(num) if num.next < lim && s[num.next] == '$' as u8 =>
|
|
|
|
Parsed::new(Some(num.val), num.next + 1),
|
|
|
|
_ => Parsed::new(None, i)
|
|
|
|
}
|
2011-12-13 18:25:51 -06:00
|
|
|
}
|
2013-01-07 14:06:59 -06:00
|
|
|
|
|
|
|
pub fn parse_flags(s: &str, i: uint, lim: uint) -> Parsed<~[Flag]> {
|
2013-01-07 13:39:45 -06:00
|
|
|
let mut i = i;
|
|
|
|
let mut flags = ~[];
|
2011-12-13 18:25:51 -06:00
|
|
|
|
2013-01-07 13:39:45 -06:00
|
|
|
while i < lim {
|
2013-05-10 20:19:58 -05:00
|
|
|
let f = match s[i] as char {
|
|
|
|
'-' => FlagLeftJustify,
|
|
|
|
'0' => FlagLeftZeroPad,
|
|
|
|
' ' => FlagSpaceForSign,
|
|
|
|
'+' => FlagSignAlways,
|
|
|
|
'#' => FlagAlternate,
|
2013-01-07 13:39:45 -06:00
|
|
|
_ => break
|
|
|
|
};
|
|
|
|
|
|
|
|
flags.push(f);
|
|
|
|
i += 1;
|
2011-12-13 18:25:51 -06:00
|
|
|
}
|
2013-01-07 13:39:45 -06:00
|
|
|
|
|
|
|
Parsed::new(flags, i)
|
2011-12-13 18:25:51 -06:00
|
|
|
}
|
2013-01-07 14:06:59 -06:00
|
|
|
|
|
|
|
pub fn parse_count(s: &str, i: uint, lim: uint) -> Parsed<Count> {
|
|
|
|
if i >= lim {
|
|
|
|
Parsed::new(CountImplied, i)
|
|
|
|
} else if s[i] == '*' as u8 {
|
|
|
|
let param = parse_parameter(s, i + 1, lim);
|
|
|
|
let j = param.next;
|
|
|
|
|
|
|
|
match param.val {
|
|
|
|
None => Parsed::new(CountIsNextParam, j),
|
|
|
|
Some(n) => Parsed::new(CountIsParam(n), j)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
match peek_num(s, i, lim) {
|
|
|
|
None => Parsed::new(CountImplied, i),
|
|
|
|
Some(num) => Parsed::new(CountIs(num.val), num.next)
|
2013-01-07 13:41:37 -06:00
|
|
|
}
|
2013-01-07 14:06:59 -06:00
|
|
|
}
|
2012-11-01 18:47:24 -05:00
|
|
|
}
|
2011-12-13 18:25:51 -06:00
|
|
|
|
2013-01-07 14:06:59 -06:00
|
|
|
pub fn parse_precision(s: &str, i: uint, lim: uint) -> Parsed<Count> {
|
|
|
|
if i < lim && s[i] == '.' as u8 {
|
|
|
|
let count = parse_count(s, i + 1, lim);
|
2011-12-13 18:25:51 -06:00
|
|
|
|
2013-01-07 14:06:59 -06:00
|
|
|
// If there were no digits specified, i.e. the precision
|
|
|
|
// was ".", then the precision is 0
|
|
|
|
match count.val {
|
|
|
|
CountImplied => Parsed::new(CountIs(0), count.next),
|
|
|
|
_ => count
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
Parsed::new(CountImplied, i)
|
|
|
|
}
|
2011-12-13 18:25:51 -06:00
|
|
|
}
|
2013-01-07 14:06:59 -06:00
|
|
|
|
2012-11-06 20:41:06 -06:00
|
|
|
pub fn parse_type(s: &str, i: uint, lim: uint, err: ErrorFn) ->
|
2013-01-07 14:06:59 -06:00
|
|
|
Parsed<Ty> {
|
2013-05-19 00:07:44 -05:00
|
|
|
if i >= lim { err("missing type in conversion"); }
|
2013-01-07 14:06:59 -06:00
|
|
|
|
2012-07-05 16:17:16 -05:00
|
|
|
// FIXME (#2249): Do we really want two signed types here?
|
2011-12-13 18:25:51 -06:00
|
|
|
// How important is it to be printf compatible?
|
2013-05-10 20:19:58 -05:00
|
|
|
let t = match s[i] as char {
|
|
|
|
'b' => TyBool,
|
|
|
|
's' => TyStr,
|
|
|
|
'c' => TyChar,
|
|
|
|
'd' | 'i' => TyInt(Signed),
|
|
|
|
'u' => TyInt(Unsigned),
|
|
|
|
'x' => TyHex(CaseLower),
|
|
|
|
'X' => TyHex(CaseUpper),
|
|
|
|
't' => TyBits,
|
|
|
|
'o' => TyOctal,
|
|
|
|
'f' => TyFloat,
|
2013-07-27 22:21:39 -05:00
|
|
|
'p' => TyPointer,
|
2013-05-10 20:19:58 -05:00
|
|
|
'?' => TyPoly,
|
2013-06-11 06:37:22 -05:00
|
|
|
_ => err(fmt!("unknown type in conversion: %c", s.char_at(i)))
|
2013-01-07 13:36:46 -06:00
|
|
|
};
|
2013-01-07 14:06:59 -06:00
|
|
|
|
2013-01-07 13:36:46 -06:00
|
|
|
Parsed::new(t, i + 1)
|
2011-12-13 18:25:51 -06:00
|
|
|
}
|
2013-01-07 16:05:31 -06:00
|
|
|
|
|
|
|
#[cfg(test)]
|
2013-02-11 21:26:38 -06:00
|
|
|
fn die(s: &str) -> ! { fail!(s.to_owned()) }
|
2013-01-07 16:05:31 -06:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_parse_count() {
|
|
|
|
fn test(s: &str, count: Count, next: uint) -> bool {
|
|
|
|
parse_count(s, 0, s.len()) == Parsed::new(count, next)
|
|
|
|
}
|
|
|
|
|
2013-03-28 20:39:09 -05:00
|
|
|
assert!(test("", CountImplied, 0));
|
|
|
|
assert!(test("*", CountIsNextParam, 1));
|
|
|
|
assert!(test("*1", CountIsNextParam, 1));
|
|
|
|
assert!(test("*1$", CountIsParam(1), 3));
|
|
|
|
assert!(test("123", CountIs(123), 3));
|
2013-01-07 16:05:31 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_parse_flags() {
|
|
|
|
fn pack(fs: &[Flag]) -> uint {
|
2013-06-17 18:43:22 -05:00
|
|
|
fs.iter().fold(0, |p, &f| p | (1 << f as uint))
|
2013-01-07 16:05:31 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
fn test(s: &str, flags: &[Flag], next: uint) {
|
|
|
|
let f = parse_flags(s, 0, s.len());
|
2013-05-18 21:02:45 -05:00
|
|
|
assert_eq!(pack(f.val), pack(flags));
|
|
|
|
assert_eq!(f.next, next);
|
2013-01-07 16:05:31 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
test("", [], 0);
|
|
|
|
test("!#-+ 0", [], 0);
|
|
|
|
test("#-+", [FlagAlternate, FlagLeftJustify, FlagSignAlways], 3);
|
|
|
|
test(" 0", [FlagSpaceForSign, FlagLeftZeroPad], 2);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_parse_fmt_string() {
|
2013-03-28 20:39:09 -05:00
|
|
|
assert!(parse_fmt_string("foo %s bar", die) == ~[
|
2013-01-07 16:05:31 -06:00
|
|
|
PieceString(~"foo "),
|
2013-01-24 12:33:20 -06:00
|
|
|
PieceConv(Conv {
|
|
|
|
param: None,
|
|
|
|
flags: ~[],
|
|
|
|
width: CountImplied,
|
|
|
|
precision: CountImplied,
|
|
|
|
ty: TyStr,
|
|
|
|
}),
|
2013-03-06 15:58:02 -06:00
|
|
|
PieceString(~" bar")]);
|
2013-01-07 16:05:31 -06:00
|
|
|
|
2013-03-28 20:39:09 -05:00
|
|
|
assert!(parse_fmt_string("%s", die) == ~[
|
2013-01-24 12:33:20 -06:00
|
|
|
PieceConv(Conv {
|
|
|
|
param: None,
|
|
|
|
flags: ~[],
|
|
|
|
width: CountImplied,
|
|
|
|
precision: CountImplied,
|
|
|
|
ty: TyStr,
|
2013-03-06 15:58:02 -06:00
|
|
|
})]);
|
2013-01-07 16:05:31 -06:00
|
|
|
|
2013-03-28 20:39:09 -05:00
|
|
|
assert!(parse_fmt_string("%%%%", die) == ~[
|
2013-03-06 15:58:02 -06:00
|
|
|
PieceString(~"%"), PieceString(~"%")]);
|
2013-01-07 16:05:31 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_parse_parameter() {
|
|
|
|
fn test(s: &str, param: Option<uint>, next: uint) -> bool {
|
|
|
|
parse_parameter(s, 0, s.len()) == Parsed::new(param, next)
|
|
|
|
}
|
|
|
|
|
2013-03-28 20:39:09 -05:00
|
|
|
assert!(test("", None, 0));
|
|
|
|
assert!(test("foo", None, 0));
|
|
|
|
assert!(test("123", None, 0));
|
|
|
|
assert!(test("123$", Some(123), 4));
|
2013-01-07 16:05:31 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_parse_precision() {
|
|
|
|
fn test(s: &str, count: Count, next: uint) -> bool {
|
|
|
|
parse_precision(s, 0, s.len()) == Parsed::new(count, next)
|
|
|
|
}
|
|
|
|
|
2013-03-28 20:39:09 -05:00
|
|
|
assert!(test("", CountImplied, 0));
|
|
|
|
assert!(test(".", CountIs(0), 1));
|
|
|
|
assert!(test(".*", CountIsNextParam, 2));
|
|
|
|
assert!(test(".*1", CountIsNextParam, 2));
|
|
|
|
assert!(test(".*1$", CountIsParam(1), 4));
|
|
|
|
assert!(test(".123", CountIs(123), 4));
|
2013-01-07 16:05:31 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_parse_type() {
|
|
|
|
fn test(s: &str, ty: Ty) -> bool {
|
|
|
|
parse_type(s, 0, s.len(), die) == Parsed::new(ty, 1)
|
|
|
|
}
|
|
|
|
|
2013-03-28 20:39:09 -05:00
|
|
|
assert!(test("b", TyBool));
|
|
|
|
assert!(test("c", TyChar));
|
|
|
|
assert!(test("d", TyInt(Signed)));
|
|
|
|
assert!(test("f", TyFloat));
|
|
|
|
assert!(test("i", TyInt(Signed)));
|
|
|
|
assert!(test("o", TyOctal));
|
|
|
|
assert!(test("s", TyStr));
|
|
|
|
assert!(test("t", TyBits));
|
|
|
|
assert!(test("x", TyHex(CaseLower)));
|
|
|
|
assert!(test("X", TyHex(CaseUpper)));
|
2013-07-27 22:21:39 -05:00
|
|
|
assert!(test("p", TyPointer));
|
2013-03-28 20:39:09 -05:00
|
|
|
assert!(test("?", TyPoly));
|
2013-01-07 16:05:31 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
#[should_fail]
|
2013-01-09 10:16:31 -06:00
|
|
|
#[ignore(cfg(windows))]
|
2013-01-07 16:05:31 -06:00
|
|
|
fn test_parse_type_missing() {
|
|
|
|
parse_type("", 0, 0, die);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
#[should_fail]
|
2013-01-09 10:16:31 -06:00
|
|
|
#[ignore(cfg(windows))]
|
2013-01-07 16:05:31 -06:00
|
|
|
fn test_parse_type_unknown() {
|
|
|
|
parse_type("!", 0, 1, die);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_peek_num() {
|
|
|
|
let s1 = "";
|
2013-03-28 20:39:09 -05:00
|
|
|
assert!(peek_num(s1, 0, s1.len()).is_none());
|
2013-01-07 16:05:31 -06:00
|
|
|
|
|
|
|
let s2 = "foo";
|
2013-03-28 20:39:09 -05:00
|
|
|
assert!(peek_num(s2, 0, s2.len()).is_none());
|
2013-01-07 16:05:31 -06:00
|
|
|
|
|
|
|
let s3 = "123";
|
2013-05-18 21:02:45 -05:00
|
|
|
assert_eq!(peek_num(s3, 0, s3.len()), Some(Parsed::new(123, 3)));
|
2013-01-07 16:05:31 -06:00
|
|
|
|
|
|
|
let s4 = "123foo";
|
2013-05-18 21:02:45 -05:00
|
|
|
assert_eq!(peek_num(s4, 0, s4.len()), Some(Parsed::new(123, 3)));
|
2013-01-07 16:05:31 -06:00
|
|
|
}
|
2011-12-13 18:25:51 -06:00
|
|
|
}
|
|
|
|
|
2012-10-01 13:32:02 -05:00
|
|
|
// Functions used by the fmt extension at runtime. For now there are a lot of
|
|
|
|
// decisions made a runtime. If it proves worthwhile then some of these
|
|
|
|
// conditions can be evaluated at compile-time. For now though it's cleaner to
|
2013-01-24 11:49:48 -06:00
|
|
|
// implement it this way, I think.
|
2013-03-20 22:33:10 -05:00
|
|
|
#[doc(hidden)]
|
2013-06-30 22:51:13 -05:00
|
|
|
#[allow(non_uppercase_statics)]
|
2013-03-20 22:33:10 -05:00
|
|
|
pub mod rt {
|
|
|
|
use float;
|
|
|
|
use str;
|
|
|
|
use sys;
|
Replaces the free-standing functions in f32, &c.
The free-standing functions in f32, f64, i8, i16, i32, i64, u8, u16,
u32, u64, float, int, and uint are replaced with generic functions in
num instead.
If you were previously using any of those functions, just replace them
with the corresponding function with the same name in num.
Note: If you were using a function that corresponds to an operator, use
the operator instead.
2013-07-08 11:05:17 -05:00
|
|
|
use num;
|
2013-03-20 22:33:10 -05:00
|
|
|
use uint;
|
|
|
|
use vec;
|
|
|
|
use option::{Some, None, Option};
|
|
|
|
|
2013-03-22 19:24:26 -05:00
|
|
|
pub static flag_none : u32 = 0u32;
|
|
|
|
pub static flag_left_justify : u32 = 0b00000000000001u32;
|
|
|
|
pub static flag_left_zero_pad : u32 = 0b00000000000010u32;
|
|
|
|
pub static flag_space_for_sign : u32 = 0b00000000000100u32;
|
|
|
|
pub static flag_sign_always : u32 = 0b00000000001000u32;
|
|
|
|
pub static flag_alternate : u32 = 0b00000000010000u32;
|
2013-03-20 22:33:10 -05:00
|
|
|
|
|
|
|
pub enum Count { CountIs(uint), CountImplied, }
|
|
|
|
|
|
|
|
pub enum Ty { TyDefault, TyBits, TyHexUpper, TyHexLower, TyOctal, }
|
|
|
|
|
|
|
|
pub struct Conv {
|
|
|
|
flags: u32,
|
|
|
|
width: Count,
|
|
|
|
precision: Count,
|
|
|
|
ty: Ty,
|
|
|
|
}
|
|
|
|
|
2013-03-22 19:24:26 -05:00
|
|
|
pub fn conv_int(cv: Conv, i: int, buf: &mut ~str) {
|
2013-03-20 22:33:10 -05:00
|
|
|
let radix = 10;
|
|
|
|
let prec = get_int_precision(cv);
|
Replaces the free-standing functions in f32, &c.
The free-standing functions in f32, f64, i8, i16, i32, i64, u8, u16,
u32, u64, float, int, and uint are replaced with generic functions in
num instead.
If you were previously using any of those functions, just replace them
with the corresponding function with the same name in num.
Note: If you were using a function that corresponds to an operator, use
the operator instead.
2013-07-08 11:05:17 -05:00
|
|
|
let s : ~str = uint_to_str_prec(num::abs(i) as uint, radix, prec);
|
2013-03-20 22:33:10 -05:00
|
|
|
|
|
|
|
let head = if i >= 0 {
|
|
|
|
if have_flag(cv.flags, flag_sign_always) {
|
|
|
|
Some('+')
|
|
|
|
} else if have_flag(cv.flags, flag_space_for_sign) {
|
|
|
|
Some(' ')
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
|
|
|
} else { Some('-') };
|
2013-04-09 00:31:23 -05:00
|
|
|
pad(cv, s, head, PadSigned, buf);
|
2013-03-20 22:33:10 -05:00
|
|
|
}
|
2013-03-22 19:24:26 -05:00
|
|
|
pub fn conv_uint(cv: Conv, u: uint, buf: &mut ~str) {
|
2013-03-20 22:33:10 -05:00
|
|
|
let prec = get_int_precision(cv);
|
2013-04-30 13:10:21 -05:00
|
|
|
let rs =
|
2013-03-20 22:33:10 -05:00
|
|
|
match cv.ty {
|
|
|
|
TyDefault => uint_to_str_prec(u, 10, prec),
|
|
|
|
TyHexLower => uint_to_str_prec(u, 16, prec),
|
2013-04-23 04:08:13 -05:00
|
|
|
|
2013-04-23 11:20:55 -05:00
|
|
|
// FIXME: #4318 Instead of to_ascii and to_str_ascii, could use
|
|
|
|
// to_ascii_consume and to_str_consume to not do a unnecessary copy.
|
|
|
|
TyHexUpper => {
|
|
|
|
let s = uint_to_str_prec(u, 16, prec);
|
|
|
|
s.to_ascii().to_upper().to_str_ascii()
|
|
|
|
}
|
2013-03-20 22:33:10 -05:00
|
|
|
TyBits => uint_to_str_prec(u, 2, prec),
|
|
|
|
TyOctal => uint_to_str_prec(u, 8, prec)
|
|
|
|
};
|
2013-04-09 00:31:23 -05:00
|
|
|
pad(cv, rs, None, PadUnsigned, buf);
|
2013-03-20 22:33:10 -05:00
|
|
|
}
|
2013-03-22 19:24:26 -05:00
|
|
|
pub fn conv_bool(cv: Conv, b: bool, buf: &mut ~str) {
|
2013-03-20 22:33:10 -05:00
|
|
|
let s = if b { "true" } else { "false" };
|
|
|
|
// run the boolean conversion through the string conversion logic,
|
|
|
|
// giving it the same rules for precision, etc.
|
|
|
|
conv_str(cv, s, buf);
|
|
|
|
}
|
2013-03-22 19:24:26 -05:00
|
|
|
pub fn conv_char(cv: Conv, c: char, buf: &mut ~str) {
|
2013-04-09 00:31:23 -05:00
|
|
|
pad(cv, "", Some(c), PadNozero, buf);
|
2013-03-20 22:33:10 -05:00
|
|
|
}
|
2013-03-22 19:24:26 -05:00
|
|
|
pub fn conv_str(cv: Conv, s: &str, buf: &mut ~str) {
|
2013-03-20 22:33:10 -05:00
|
|
|
// For strings, precision is the maximum characters
|
|
|
|
// displayed
|
2013-04-12 00:10:01 -05:00
|
|
|
let unpadded = match cv.precision {
|
2013-03-20 22:33:10 -05:00
|
|
|
CountImplied => s,
|
2013-06-11 06:37:22 -05:00
|
|
|
CountIs(max) => if (max as uint) < s.char_len() {
|
2013-06-09 09:44:58 -05:00
|
|
|
s.slice(0, max as uint)
|
2013-03-20 22:33:10 -05:00
|
|
|
} else {
|
|
|
|
s
|
|
|
|
}
|
|
|
|
};
|
2013-04-09 00:31:23 -05:00
|
|
|
pad(cv, unpadded, None, PadNozero, buf);
|
2013-03-20 22:33:10 -05:00
|
|
|
}
|
2013-03-22 19:24:26 -05:00
|
|
|
pub fn conv_float(cv: Conv, f: float, buf: &mut ~str) {
|
2013-03-20 22:33:10 -05:00
|
|
|
let (to_str, digits) = match cv.precision {
|
|
|
|
CountIs(c) => (float::to_str_exact, c as uint),
|
|
|
|
CountImplied => (float::to_str_digits, 6u)
|
|
|
|
};
|
2013-04-30 13:10:21 -05:00
|
|
|
let s = to_str(f, digits);
|
2013-03-20 22:33:10 -05:00
|
|
|
let head = if 0.0 <= f {
|
|
|
|
if have_flag(cv.flags, flag_sign_always) {
|
|
|
|
Some('+')
|
|
|
|
} else if have_flag(cv.flags, flag_space_for_sign) {
|
|
|
|
Some(' ')
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
|
|
|
} else { None };
|
2013-04-09 00:31:23 -05:00
|
|
|
pad(cv, s, head, PadFloat, buf);
|
2013-03-20 22:33:10 -05:00
|
|
|
}
|
2013-07-27 22:21:39 -05:00
|
|
|
pub fn conv_pointer<T>(cv: Conv, ptr: *T, buf: &mut ~str) {
|
|
|
|
let s = ~"0x" + uint_to_str_prec(ptr as uint, 16, 1u);
|
|
|
|
pad(cv, s, None, PadNozero, buf);
|
|
|
|
}
|
2013-03-22 19:24:26 -05:00
|
|
|
pub fn conv_poly<T>(cv: Conv, v: &T, buf: &mut ~str) {
|
2013-03-20 22:33:10 -05:00
|
|
|
let s = sys::log_str(v);
|
|
|
|
conv_str(cv, s, buf);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Convert a uint to string with a minimum number of digits. If precision
|
|
|
|
// is 0 and num is 0 then the result is the empty string. Could move this
|
|
|
|
// to uint: but it doesn't seem all that useful.
|
2013-03-22 19:24:26 -05:00
|
|
|
pub fn uint_to_str_prec(num: uint, radix: uint, prec: uint) -> ~str {
|
2013-03-20 22:33:10 -05:00
|
|
|
return if prec == 0u && num == 0u {
|
|
|
|
~""
|
|
|
|
} else {
|
|
|
|
let s = uint::to_str_radix(num, radix);
|
2013-06-11 06:37:22 -05:00
|
|
|
let len = s.char_len();
|
2013-03-20 22:33:10 -05:00
|
|
|
if len < prec {
|
|
|
|
let diff = prec - len;
|
|
|
|
let pad = str::from_chars(vec::from_elem(diff, '0'));
|
|
|
|
pad + s
|
|
|
|
} else { s }
|
|
|
|
};
|
|
|
|
}
|
2013-03-22 19:24:26 -05:00
|
|
|
pub fn get_int_precision(cv: Conv) -> uint {
|
2013-03-20 22:33:10 -05:00
|
|
|
return match cv.precision {
|
|
|
|
CountIs(c) => c as uint,
|
|
|
|
CountImplied => 1u
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
#[deriving(Eq)]
|
|
|
|
pub enum PadMode { PadSigned, PadUnsigned, PadNozero, PadFloat }
|
|
|
|
|
2013-04-12 00:10:01 -05:00
|
|
|
pub fn pad(cv: Conv, s: &str, head: Option<char>, mode: PadMode,
|
2013-03-20 22:33:10 -05:00
|
|
|
buf: &mut ~str) {
|
|
|
|
let headsize = match head { Some(_) => 1, _ => 0 };
|
|
|
|
let uwidth : uint = match cv.width {
|
|
|
|
CountImplied => {
|
2013-06-10 16:50:12 -05:00
|
|
|
for head.iter().advance |&c| {
|
2013-03-20 22:33:10 -05:00
|
|
|
buf.push_char(c);
|
|
|
|
}
|
|
|
|
return buf.push_str(s);
|
|
|
|
}
|
|
|
|
CountIs(width) => { width as uint }
|
|
|
|
};
|
2013-06-11 06:37:22 -05:00
|
|
|
let strlen = s.char_len() + headsize;
|
2013-03-20 22:33:10 -05:00
|
|
|
if uwidth <= strlen {
|
2013-06-10 16:50:12 -05:00
|
|
|
for head.iter().advance |&c| {
|
2013-03-20 22:33:10 -05:00
|
|
|
buf.push_char(c);
|
|
|
|
}
|
|
|
|
return buf.push_str(s);
|
|
|
|
}
|
|
|
|
let mut padchar = ' ';
|
|
|
|
let diff = uwidth - strlen;
|
|
|
|
if have_flag(cv.flags, flag_left_justify) {
|
2013-06-10 16:50:12 -05:00
|
|
|
for head.iter().advance |&c| {
|
2013-03-20 22:33:10 -05:00
|
|
|
buf.push_char(c);
|
|
|
|
}
|
|
|
|
buf.push_str(s);
|
|
|
|
for diff.times {
|
|
|
|
buf.push_char(padchar);
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
let (might_zero_pad, signed) = match mode {
|
|
|
|
PadNozero => (false, true),
|
|
|
|
PadSigned => (true, true),
|
|
|
|
PadFloat => (true, true),
|
|
|
|
PadUnsigned => (true, false)
|
|
|
|
};
|
2013-03-22 19:24:26 -05:00
|
|
|
fn have_precision(cv: Conv) -> bool {
|
2013-03-20 22:33:10 -05:00
|
|
|
return match cv.precision { CountImplied => false, _ => true };
|
|
|
|
}
|
|
|
|
let zero_padding = {
|
|
|
|
if might_zero_pad && have_flag(cv.flags, flag_left_zero_pad) &&
|
|
|
|
(!have_precision(cv) || mode == PadFloat) {
|
|
|
|
padchar = '0';
|
|
|
|
true
|
|
|
|
} else {
|
|
|
|
false
|
|
|
|
}
|
|
|
|
};
|
|
|
|
let padstr = str::from_chars(vec::from_elem(diff, padchar));
|
|
|
|
// This is completely heinous. If we have a signed value then
|
|
|
|
// potentially rip apart the intermediate result and insert some
|
|
|
|
// zeros. It may make sense to convert zero padding to a precision
|
|
|
|
// instead.
|
|
|
|
|
|
|
|
if signed && zero_padding {
|
2013-06-10 16:50:12 -05:00
|
|
|
for head.iter().advance |&head| {
|
2013-03-20 22:33:10 -05:00
|
|
|
if head == '+' || head == '-' || head == ' ' {
|
|
|
|
buf.push_char(head);
|
|
|
|
buf.push_str(padstr);
|
|
|
|
buf.push_str(s);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
buf.push_str(padstr);
|
2013-06-10 16:50:12 -05:00
|
|
|
for head.iter().advance |&c| {
|
2013-03-20 22:33:10 -05:00
|
|
|
buf.push_char(c);
|
|
|
|
}
|
|
|
|
buf.push_str(s);
|
|
|
|
}
|
2013-06-18 16:45:18 -05:00
|
|
|
#[inline]
|
2013-03-22 19:24:26 -05:00
|
|
|
pub fn have_flag(flags: u32, f: u32) -> bool {
|
2013-03-20 22:33:10 -05:00
|
|
|
flags & f != 0
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-11-18 13:00:39 -06:00
|
|
|
// Bulk of the tests are in src/test/run-pass/syntax-extension-fmt.rs
|
2012-07-23 13:51:12 -05:00
|
|
|
#[cfg(test)]
|
|
|
|
mod test {
|
|
|
|
#[test]
|
|
|
|
fn fmt_slice() {
|
|
|
|
let s = "abc";
|
2012-08-22 19:24:52 -05:00
|
|
|
let _s = fmt!("%s", s);
|
2012-07-23 13:51:12 -05:00
|
|
|
}
|
|
|
|
}
|