Remove the fmt! syntax extension

It lived a good life, but its time has come. The groundwork is set for the
official transition after the next snapshot (removal of XXX2 macros)
This commit is contained in:
Alex Crichton 2013-10-18 16:01:40 -07:00
parent d773a024a2
commit 29c58c473f
18 changed files with 26 additions and 1533 deletions

View File

@ -88,7 +88,7 @@ ifneq ($(wildcard $(NON_BUILD_TARGET_TRIPLES)),)
CFG_INFO := $(info cfg: non-build target triples $(NON_BUILD_TARGET_TRIPLES))
endif
CFG_RUSTC_FLAGS := $(RUSTFLAGS) --cfg nofmt
CFG_RUSTC_FLAGS := $(RUSTFLAGS)
CFG_GCCISH_CFLAGS :=
CFG_GCCISH_LINK_FLAGS :=

View File

@ -1,703 +0,0 @@
// 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.
//! Support for fmt! expressions.
//!
//! The syntax is close to that of Posix format strings:
//!
//! ~~~~~~
//! Format := '%' Parameter? Flag* Width? Precision? Type
//! Parameter := [0-9]+ '$'
//! Flag := [ 0#+-]
//! Width := Parameter | [0-9]+
//! Precision := '.' [0-9]+
//! Type := [bcdfiostuxX?]
//! ~~~~~~
//!
//! * 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.
//! * Flag - left justifies the result
//! * 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
//! of decimal places for float.
//!
//! The types currently supported are:
//!
//! * 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)
/*
Syntax Extension: fmt
Format a string
The 'fmt' extension is modeled on the posix printf system.
A posix conversion ostensibly looks like this
> %~[parameter]~[flags]~[width]~[.precision]~[length]type
Given the different numeric type bestiary we have, we omit the 'length'
parameter and support slightly different conversions for 'type'
> %~[parameter]~[flags]~[width]~[.precision]type
we also only support translating-to-rust a tiny subset of the possible
combinations at the moment.
Example:
debug!("hello, %s!", "world");
*/
use prelude::*;
/*
* 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
#[doc(hidden)]
pub mod ct {
use char;
use container::Container;
use prelude::*;
use str;
#[deriving(Eq)]
pub enum Signedness { Signed, Unsigned, }
#[deriving(Eq)]
pub enum Caseness { CaseUpper, CaseLower, }
#[deriving(Eq)]
pub enum Ty {
TyBool,
TyStr,
TyChar,
TyInt(Signedness),
TyBits,
TyHex(Caseness),
TyOctal,
TyFloat,
TyPointer,
TyPoly,
}
#[deriving(Eq)]
pub enum Flag {
FlagLeftJustify,
FlagLeftZeroPad,
FlagSpaceForSign,
FlagSignAlways,
FlagAlternate,
}
#[deriving(Eq)]
pub enum Count {
CountIs(uint),
CountIsParam(uint),
CountIsNextParam,
CountImplied,
}
#[deriving(Eq)]
struct Parsed<T> {
val: T,
next: uint
}
impl<T> Parsed<T> {
pub fn new(val: T, next: uint) -> Parsed<T> {
Parsed {val: val, next: next}
}
}
// A formatted conversion from an expression to a string
#[deriving(Eq)]
pub struct Conv {
param: Option<uint>,
flags: ~[Flag],
width: Count,
precision: Count,
ty: Ty
}
// A fragment of the output sequence
#[deriving(Eq)]
pub enum Piece {
PieceString(~str),
PieceConv(Conv),
}
pub type ErrorFn<'self> = &'self fn(&str) -> !;
pub fn parse_fmt_string<'a>(s: &str, err: ErrorFn<'a>) -> ~[Piece] {
fn push_slice(ps: &mut ~[Piece], s: &str, from: uint, to: uint) {
if to > from {
ps.push(PieceString(s.slice(from, to).to_owned()));
}
}
let lim = s.len();
let mut h = 0;
let mut i = 0;
let mut pieces = ~[];
while i < lim {
if s[i] == '%' as u8 {
i += 1;
if i >= lim {
err("unterminated conversion at end of string");
} else if s[i] == '%' as u8 {
push_slice(&mut pieces, s, h, i);
i += 1;
} else {
push_slice(&mut pieces, s, h, i - 1);
let Parsed {
val,
next
} = parse_conversion(s, i, lim, |s| err(s));
pieces.push(val);
i = next;
}
h = i;
} else {
i += str::utf8_char_width(s[i]);
}
}
push_slice(&mut pieces, s, h, i);
pieces
}
pub fn peek_num(s: &str, i: uint, lim: uint) -> Option<Parsed<uint>> {
let mut i = i;
let mut accum = 0;
let mut found = false;
while i < lim {
match char::to_digit(s[i] as char, 10) {
Some(x) => {
found = true;
accum *= 10;
accum += x;
i += 1;
}
None => break
}
}
if found {
Some(Parsed::new(accum, i))
} else {
None
}
}
pub fn parse_conversion<'a>(s: &str, i: uint, lim: uint, err: ErrorFn<'a>)
-> Parsed<Piece> {
let param = parse_parameter(s, i, lim);
// avoid copying ~[Flag] by destructuring
let Parsed {val: flags_val, next: flags_next} = parse_flags(s,
param.next, lim);
let width = parse_count(s, flags_next, lim);
let prec = parse_precision(s, width.next, lim);
let ty = parse_type(s, prec.next, lim, err);
Parsed::new(PieceConv(Conv {
param: param.val,
flags: flags_val,
width: width.val,
precision: prec.val,
ty: ty.val}), ty.next)
}
pub fn parse_parameter(s: &str, i: uint, lim: uint) ->
Parsed<Option<uint>> {
if i >= lim { return Parsed::new(None, i); }
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)
}
}
pub fn parse_flags(s: &str, i: uint, lim: uint) -> Parsed<~[Flag]> {
let mut i = i;
let mut flags = ~[];
while i < lim {
let f = match s[i] as char {
'-' => FlagLeftJustify,
'0' => FlagLeftZeroPad,
' ' => FlagSpaceForSign,
'+' => FlagSignAlways,
'#' => FlagAlternate,
_ => break
};
flags.push(f);
i += 1;
}
Parsed::new(flags, i)
}
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)
}
}
}
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);
// 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)
}
}
pub fn parse_type<'a>(s: &str, i: uint, lim: uint, err: ErrorFn<'a>)
-> Parsed<Ty> {
if i >= lim { err("missing type in conversion"); }
// FIXME (#2249): Do we really want two signed types here?
// How important is it to be printf compatible?
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,
'p' => TyPointer,
'?' => TyPoly,
_ => err(format!("unknown type in conversion: {}", s.char_at(i)))
};
Parsed::new(t, i + 1)
}
#[cfg(test)]
fn die(s: &str) -> ! { fail2!(s.to_owned()) }
#[test]
fn test_parse_count() {
fn test(s: &str, count: Count, next: uint) -> bool {
parse_count(s, 0, s.len()) == Parsed::new(count, next)
}
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));
}
#[test]
fn test_parse_flags() {
fn pack(fs: &[Flag]) -> uint {
fs.iter().fold(0, |p, &f| p | (1 << f as uint))
}
fn test(s: &str, flags: &[Flag], next: uint) {
let f = parse_flags(s, 0, s.len());
assert_eq!(pack(f.val), pack(flags));
assert_eq!(f.next, next);
}
test("", [], 0);
test("!#-+ 0", [], 0);
test("#-+", [FlagAlternate, FlagLeftJustify, FlagSignAlways], 3);
test(" 0", [FlagSpaceForSign, FlagLeftZeroPad], 2);
}
#[test]
fn test_parse_fmt_string() {
assert!(parse_fmt_string("foo %s bar", die) == ~[
PieceString(~"foo "),
PieceConv(Conv {
param: None,
flags: ~[],
width: CountImplied,
precision: CountImplied,
ty: TyStr,
}),
PieceString(~" bar")]);
assert!(parse_fmt_string("%s", die) == ~[
PieceConv(Conv {
param: None,
flags: ~[],
width: CountImplied,
precision: CountImplied,
ty: TyStr,
})]);
assert!(parse_fmt_string("%%%%", die) == ~[
PieceString(~"%"), PieceString(~"%")]);
}
#[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)
}
assert!(test("", None, 0));
assert!(test("foo", None, 0));
assert!(test("123", None, 0));
assert!(test("123$", Some(123), 4));
}
#[test]
fn test_parse_precision() {
fn test(s: &str, count: Count, next: uint) -> bool {
parse_precision(s, 0, s.len()) == Parsed::new(count, next)
}
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));
}
#[test]
fn test_parse_type() {
fn test(s: &str, ty: Ty) -> bool {
parse_type(s, 0, s.len(), die) == Parsed::new(ty, 1)
}
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)));
assert!(test("p", TyPointer));
assert!(test("?", TyPoly));
}
#[test]
#[should_fail]
fn test_parse_type_missing() {
parse_type("", 0, 0, die);
}
#[test]
#[should_fail]
fn test_parse_type_unknown() {
parse_type("!", 0, 1, die);
}
#[test]
fn test_peek_num() {
let s1 = "";
assert!(peek_num(s1, 0, s1.len()).is_none());
let s2 = "foo";
assert!(peek_num(s2, 0, s2.len()).is_none());
let s3 = "123";
assert_eq!(peek_num(s3, 0, s3.len()), Some(Parsed::new(123, 3)));
let s4 = "123foo";
assert_eq!(peek_num(s4, 0, s4.len()), Some(Parsed::new(123, 3)));
}
}
// 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
// implement it this way, I think.
#[doc(hidden)]
#[allow(non_uppercase_statics)]
pub mod rt {
use f64;
use str;
use sys;
use num;
use vec;
use option::{Some, None, Option};
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;
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,
}
pub fn conv_int(cv: Conv, i: int, buf: &mut ~str) {
let radix = 10;
let prec = get_int_precision(cv);
let s : ~str = uint_to_str_prec(num::abs(i) as uint, radix, prec);
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('-') };
pad(cv, s, head, PadSigned, buf);
}
pub fn conv_uint(cv: Conv, u: uint, buf: &mut ~str) {
let prec = get_int_precision(cv);
let rs =
match cv.ty {
TyDefault => uint_to_str_prec(u, 10, prec),
TyHexLower => uint_to_str_prec(u, 16, prec),
// FIXME: #4318 Instead of to_ascii and to_str_ascii, could use
// to_ascii_move and to_str_move to not do a unnecessary copy.
TyHexUpper => {
let s = uint_to_str_prec(u, 16, prec);
s.to_ascii().to_upper().to_str_ascii()
}
TyBits => uint_to_str_prec(u, 2, prec),
TyOctal => uint_to_str_prec(u, 8, prec)
};
pad(cv, rs, None, PadUnsigned, buf);
}
pub fn conv_bool(cv: Conv, b: bool, buf: &mut ~str) {
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);
}
pub fn conv_char(cv: Conv, c: char, buf: &mut ~str) {
pad(cv, "", Some(c), PadNozero, buf);
}
pub fn conv_str(cv: Conv, s: &str, buf: &mut ~str) {
// For strings, precision is the maximum characters
// displayed
let unpadded = match cv.precision {
CountImplied => s,
CountIs(max) => {
if (max as uint) < s.char_len() {
s.slice(0, max as uint)
} else {
s
}
}
};
pad(cv, unpadded, None, PadNozero, buf);
}
pub fn conv_float(cv: Conv, f: f64, buf: &mut ~str) {
let (to_str, digits) = match cv.precision {
CountIs(c) => (f64::to_str_exact, c as uint),
CountImplied => (f64::to_str_digits, 6u)
};
let s = to_str(f, digits);
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 };
pad(cv, s, head, PadFloat, buf);
}
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);
}
pub fn conv_poly<T>(cv: Conv, v: &T, buf: &mut ~str) {
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.
pub fn uint_to_str_prec(num: uint, radix: uint, prec: uint) -> ~str {
return if prec == 0u && num == 0u {
~""
} else {
let s = num.to_str_radix(radix);
let len = s.char_len();
if len < prec {
let diff = prec - len;
let pad = str::from_chars(vec::from_elem(diff, '0'));
pad + s
} else { s }
};
}
pub fn get_int_precision(cv: Conv) -> uint {
return match cv.precision {
CountIs(c) => c as uint,
CountImplied => 1u
};
}
#[deriving(Eq)]
pub enum PadMode { PadSigned, PadUnsigned, PadNozero, PadFloat }
pub fn pad(cv: Conv, s: &str, head: Option<char>, mode: PadMode,
buf: &mut ~str) {
let headsize = match head { Some(_) => 1, _ => 0 };
let uwidth : uint = match cv.width {
CountImplied => {
for &c in head.iter() {
buf.push_char(c);
}
return buf.push_str(s);
}
CountIs(width) => { width as uint }
};
let strlen = s.char_len() + headsize;
if uwidth <= strlen {
for &c in head.iter() {
buf.push_char(c);
}
return buf.push_str(s);
}
let mut padchar = ' ';
let diff = uwidth - strlen;
if have_flag(cv.flags, flag_left_justify) {
for &c in head.iter() {
buf.push_char(c);
}
buf.push_str(s);
do 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)
};
fn have_precision(cv: Conv) -> bool {
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 {
for &head in head.iter() {
if head == '+' || head == '-' || head == ' ' {
buf.push_char(head);
buf.push_str(padstr);
buf.push_str(s);
return;
}
}
}
buf.push_str(padstr);
for &c in head.iter() {
buf.push_char(c);
}
buf.push_str(s);
}
#[inline]
pub fn have_flag(flags: u32, f: u32) -> bool {
flags & f != 0
}
}
// Bulk of the tests are in src/test/run-pass/syntax-extension-fmt.rs
#[cfg(test)]
mod test {
#[test]
fn fmt_slice() {
let s = "abc";
let _s = format!("{}", s);
}
}

View File

@ -21,7 +21,6 @@ pub mod dynamic_lib;
pub mod finally;
pub mod intrinsics;
pub mod simd;
pub mod extfmt;
#[cfg(not(test))]
pub mod lang;
pub mod sync;

View File

@ -222,7 +222,7 @@ pub fn syntax_expander_table() -> SyntaxEnv {
span: None,
} as @SyntaxExpanderTTItemTrait,
None)));
syntax_expanders.insert(intern(&"oldfmt"),
syntax_expanders.insert(intern(&"fmt"),
builtin_normal_tt_no_ctxt(
ext::fmt::expand_syntax_ext));
syntax_expanders.insert(intern(&"format_args"),

View File

@ -809,51 +809,7 @@ pub fn std_macros() -> @str {
macro_rules! ignore (($($x:tt)*) => (()))
#[cfg(not(nofmt))]
mod fmt_extension {
#[macro_escape];
macro_rules! fmt(($($arg:tt)*) => (oldfmt!($($arg)*)))
macro_rules! log(
($lvl:expr, $arg:expr) => ({
let lvl = $lvl;
if lvl <= __log_level() {
format_args!(|args| {
::std::logging::log(lvl, args)
}, \"{}\", fmt!(\"%?\", $arg))
}
});
($lvl:expr, $($arg:expr),+) => ({
let lvl = $lvl;
if lvl <= __log_level() {
format_args!(|args| {
::std::logging::log(lvl, args)
}, \"{}\", fmt!($($arg),+))
}
})
)
macro_rules! error( ($($arg:tt)*) => (log!(1u32, $($arg)*)) )
macro_rules! warn ( ($($arg:tt)*) => (log!(2u32, $($arg)*)) )
macro_rules! info ( ($($arg:tt)*) => (log!(3u32, $($arg)*)) )
macro_rules! debug( ($($arg:tt)*) => (
if cfg!(not(ndebug)) { log!(4u32, $($arg)*) }
))
macro_rules! fail(
() => (
fail2!(\"explicit failure\")
);
($msg:expr) => (
::std::sys::FailWithCause::fail_with($msg, file!(), line!())
);
($( $arg:expr ),+) => (
::std::sys::FailWithCause::fail_with(fmt!( $($arg),+ ), file!(), line!())
)
)
}
macro_rules! log2(
macro_rules! log(
($lvl:expr, $($arg:tt)+) => ({
let lvl = $lvl;
if lvl <= __log_level() {
@ -863,16 +819,16 @@ pub fn std_macros() -> @str {
}
})
)
macro_rules! error2( ($($arg:tt)*) => (log2!(1u32, $($arg)*)) )
macro_rules! warn2 ( ($($arg:tt)*) => (log2!(2u32, $($arg)*)) )
macro_rules! info2 ( ($($arg:tt)*) => (log2!(3u32, $($arg)*)) )
macro_rules! debug2( ($($arg:tt)*) => (
if cfg!(not(ndebug)) { log2!(4u32, $($arg)*) }
macro_rules! error( ($($arg:tt)*) => (log!(1u32, $($arg)*)) )
macro_rules! warn ( ($($arg:tt)*) => (log!(2u32, $($arg)*)) )
macro_rules! info ( ($($arg:tt)*) => (log!(3u32, $($arg)*)) )
macro_rules! debug( ($($arg:tt)*) => (
if cfg!(not(ndebug)) { log!(4u32, $($arg)*) }
))
macro_rules! fail2(
macro_rules! fail(
() => (
fail2!(\"explicit failure\")
fail!(\"explicit failure\")
);
($fmt:expr) => (
::std::sys::FailWithCause::fail_with($fmt, file!(), line!())
@ -882,6 +838,14 @@ pub fn std_macros() -> @str {
)
)
// NOTE (acrichto): remove these after the next snapshot
macro_rules! log2( ($($arg:tt)*) => (log!($($arg)*)) )
macro_rules! error2( ($($arg:tt)*) => (error!($($arg)*)) )
macro_rules! warn2 ( ($($arg:tt)*) => (warn!($($arg)*)) )
macro_rules! info2 ( ($($arg:tt)*) => (info!($($arg)*)) )
macro_rules! debug2( ($($arg:tt)*) => (debug!($($arg)*)) )
macro_rules! fail2( ($($arg:tt)*) => (fail!($($arg)*)) )
macro_rules! assert(
($cond:expr) => {
if !$cond {

View File

@ -8,320 +8,19 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.
/*
* The compiler code necessary to support the fmt! extension. Eventually this
* should all get sucked into either the standard library extfmt module or the
* compiler syntax extension plugin interface.
*/
/// Deprecated fmt! syntax extension
use ast;
use codemap::Span;
use ext::base::*;
use ext::base;
use ext::build::AstBuilder;
use std::option;
use std::unstable::extfmt::ct::*;
use parse::token::{str_to_ident};
pub fn expand_syntax_ext(ecx: @base::ExtCtxt, sp: Span,
_tts: &[ast::token_tree]) -> base::MacResult {
ecx.span_err(sp, "`fmt!` is deprecated, use `format!` instead");
ecx.parse_sess.span_diagnostic.span_note(sp,
"see http://static.rust-lang.org/doc/master/std/fmt/index.html \
for documentation");
pub fn expand_syntax_ext(cx: @ExtCtxt, sp: Span, tts: &[ast::token_tree])
-> base::MacResult {
let args = get_exprs_from_tts(cx, sp, tts);
if args.len() == 0 {
cx.span_fatal(sp, "fmt! takes at least 1 argument.");
}
let (fmt, _fmt_str_style) =
expr_to_str(cx, args[0],
"first argument to fmt! must be a string literal.");
let fmtspan = args[0].span;
debug2!("Format string: {}", fmt);
fn parse_fmt_err_(cx: @ExtCtxt, sp: Span, msg: &str) -> ! {
cx.span_fatal(sp, msg);
}
let parse_fmt_err: &fn(&str) -> ! = |s| parse_fmt_err_(cx, fmtspan, s);
let pieces = parse_fmt_string(fmt, parse_fmt_err);
MRExpr(pieces_to_expr(cx, sp, pieces, args))
}
// FIXME (#2249): A lot of these functions for producing expressions can
// probably be factored out in common with other code that builds
// expressions. Also: Cleanup the naming of these functions.
// Note: Moved many of the common ones to build.rs --kevina
fn pieces_to_expr(cx: @ExtCtxt, sp: Span,
pieces: ~[Piece], args: ~[@ast::Expr])
-> @ast::Expr {
fn make_path_vec(ident: &str) -> ~[ast::Ident] {
return ~[str_to_ident("std"),
str_to_ident("unstable"),
str_to_ident("extfmt"),
str_to_ident("rt"),
str_to_ident(ident)];
}
fn make_rt_path_expr(cx: @ExtCtxt, sp: Span, nm: &str) -> @ast::Expr {
let path = make_path_vec(nm);
cx.expr_path(cx.path_global(sp, path))
}
// Produces an AST expression that represents a RT::conv record,
// which tells the RT::conv* functions how to perform the conversion
fn make_rt_conv_expr(cx: @ExtCtxt, sp: Span, cnv: &Conv) -> @ast::Expr {
fn make_flags(cx: @ExtCtxt, sp: Span, flags: &[Flag]) -> @ast::Expr {
let mut tmp_expr = make_rt_path_expr(cx, sp, "flag_none");
for f in flags.iter() {
let fstr = match *f {
FlagLeftJustify => "flag_left_justify",
FlagLeftZeroPad => "flag_left_zero_pad",
FlagSpaceForSign => "flag_space_for_sign",
FlagSignAlways => "flag_sign_always",
FlagAlternate => "flag_alternate"
};
tmp_expr = cx.expr_binary(sp, ast::BiBitOr, tmp_expr,
make_rt_path_expr(cx, sp, fstr));
}
return tmp_expr;
}
fn make_count(cx: @ExtCtxt, sp: Span, cnt: Count) -> @ast::Expr {
match cnt {
CountImplied => {
return make_rt_path_expr(cx, sp, "CountImplied");
}
CountIs(c) => {
let count_lit = cx.expr_uint(sp, c as uint);
let count_is_path = make_path_vec("CountIs");
let count_is_args = ~[count_lit];
return cx.expr_call_global(sp, count_is_path, count_is_args);
}
_ => cx.span_unimpl(sp, "unimplemented fmt! conversion")
}
}
fn make_ty(cx: @ExtCtxt, sp: Span, t: Ty) -> @ast::Expr {
let rt_type = match t {
TyHex(c) => match c {
CaseUpper => "TyHexUpper",
CaseLower => "TyHexLower"
},
TyBits => "TyBits",
TyOctal => "TyOctal",
_ => "TyDefault"
};
return make_rt_path_expr(cx, sp, rt_type);
}
fn make_conv_struct(cx: @ExtCtxt, sp: Span, flags_expr: @ast::Expr,
width_expr: @ast::Expr, precision_expr: @ast::Expr,
ty_expr: @ast::Expr) -> @ast::Expr {
cx.expr_struct(
sp,
cx.path_global(sp, make_path_vec("Conv")),
~[
cx.field_imm(sp, str_to_ident("flags"), flags_expr),
cx.field_imm(sp, str_to_ident("width"), width_expr),
cx.field_imm(sp, str_to_ident("precision"), precision_expr),
cx.field_imm(sp, str_to_ident("ty"), ty_expr)
]
)
}
let rt_conv_flags = make_flags(cx, sp, cnv.flags);
let rt_conv_width = make_count(cx, sp, cnv.width);
let rt_conv_precision = make_count(cx, sp, cnv.precision);
let rt_conv_ty = make_ty(cx, sp, cnv.ty);
make_conv_struct(cx, sp, rt_conv_flags, rt_conv_width,
rt_conv_precision, rt_conv_ty)
}
fn make_conv_call(cx: @ExtCtxt, sp: Span, conv_type: &str, cnv: &Conv,
arg: @ast::Expr, buf: @ast::Expr) -> @ast::Expr {
let fname = ~"conv_" + conv_type;
let path = make_path_vec(fname);
let cnv_expr = make_rt_conv_expr(cx, sp, cnv);
let args = ~[cnv_expr, arg, buf];
cx.expr_call_global(arg.span, path, args)
}
fn make_new_conv(cx: @ExtCtxt, sp: Span, cnv: &Conv,
arg: @ast::Expr, buf: @ast::Expr) -> @ast::Expr {
fn is_signed_type(cnv: &Conv) -> bool {
match cnv.ty {
TyInt(s) => match s {
Signed => return true,
Unsigned => return false
},
TyFloat => return true,
_ => return false
}
}
let unsupported = ~"conversion not supported in fmt! string";
match cnv.param {
option::None => (),
_ => cx.span_unimpl(sp, unsupported)
}
for f in cnv.flags.iter() {
match *f {
FlagLeftJustify => (),
FlagSignAlways => {
if !is_signed_type(cnv) {
cx.span_fatal(sp,
"+ flag only valid in \
signed fmt! conversion");
}
}
FlagSpaceForSign => {
if !is_signed_type(cnv) {
cx.span_fatal(sp,
"space flag only valid in \
signed fmt! conversions");
}
}
FlagLeftZeroPad => (),
_ => cx.span_unimpl(sp, unsupported)
}
}
match cnv.width {
CountImplied => (),
CountIs(_) => (),
_ => cx.span_unimpl(sp, unsupported)
}
match cnv.precision {
CountImplied => (),
CountIs(_) => (),
_ => cx.span_unimpl(sp, unsupported)
}
let (name, actual_arg) = match cnv.ty {
TyStr => ("str", arg),
TyInt(Signed) => ("int", arg),
TyBool => ("bool", arg),
TyChar => ("char", arg),
TyBits | TyOctal | TyHex(_) | TyInt(Unsigned) => ("uint", arg),
TyFloat => ("float", arg),
TyPointer => ("pointer", arg),
TyPoly => ("poly", cx.expr_addr_of(sp, arg))
};
return make_conv_call(cx, arg.span, name, cnv, actual_arg,
cx.expr_mut_addr_of(arg.span, buf));
}
fn log_conv(c: &Conv) {
debug2!("Building conversion:");
match c.param {
Some(p) => { debug2!("param: {}", p.to_str()); }
_ => debug2!("param: none")
}
for f in c.flags.iter() {
match *f {
FlagLeftJustify => debug2!("flag: left justify"),
FlagLeftZeroPad => debug2!("flag: left zero pad"),
FlagSpaceForSign => debug2!("flag: left space pad"),
FlagSignAlways => debug2!("flag: sign always"),
FlagAlternate => debug2!("flag: alternate")
}
}
match c.width {
CountIs(i) =>
debug2!("width: count is {}", i.to_str()),
CountIsParam(i) =>
debug2!("width: count is param {}", i.to_str()),
CountIsNextParam => debug2!("width: count is next param"),
CountImplied => debug2!("width: count is implied")
}
match c.precision {
CountIs(i) =>
debug2!("prec: count is {}", i.to_str()),
CountIsParam(i) =>
debug2!("prec: count is param {}", i.to_str()),
CountIsNextParam => debug2!("prec: count is next param"),
CountImplied => debug2!("prec: count is implied")
}
match c.ty {
TyBool => debug2!("type: bool"),
TyStr => debug2!("type: str"),
TyChar => debug2!("type: char"),
TyInt(s) => match s {
Signed => debug2!("type: signed"),
Unsigned => debug2!("type: unsigned")
},
TyBits => debug2!("type: bits"),
TyHex(cs) => match cs {
CaseUpper => debug2!("type: uhex"),
CaseLower => debug2!("type: lhex"),
},
TyOctal => debug2!("type: octal"),
TyFloat => debug2!("type: float"),
TyPointer => debug2!("type: pointer"),
TyPoly => debug2!("type: poly")
}
}
/* Short circuit an easy case up front (won't work otherwise) */
if pieces.len() == 0 {
return cx.expr_str_uniq(args[0].span, @"");
}
let fmt_sp = args[0].span;
let mut n = 0u;
let nargs = args.len();
/* 'ident' is the local buffer building up the result of fmt! */
let ident = str_to_ident("__fmtbuf");
let buf = || cx.expr_ident(fmt_sp, ident);
let core_ident = str_to_ident("std");
let str_ident = str_to_ident("str");
let push_ident = str_to_ident("push_str");
let mut stms = ~[];
/* Translate each piece (portion of the fmt expression) by invoking the
corresponding function in std::unstable::extfmt. Each function takes a
buffer to insert data into along with the data being formatted. */
let npieces = pieces.len();
for (i, pc) in pieces.move_iter().enumerate() {
match pc {
/* Raw strings get appended via str::push_str */
PieceString(s) => {
/* If this is the first portion, then initialize the local
buffer with it directly. If it's actually the only piece,
then there's no need for it to be mutable */
if i == 0 {
stms.push(cx.stmt_let(fmt_sp, npieces > 1,
ident, cx.expr_str_uniq(fmt_sp, s.to_managed())));
} else {
// we call the push_str function because the
// bootstrap doesnt't seem to work if we call the
// method.
let args = ~[cx.expr_mut_addr_of(fmt_sp, buf()),
cx.expr_str(fmt_sp, s.to_managed())];
let call = cx.expr_call_global(fmt_sp,
~[core_ident,
str_ident,
push_ident],
args);
stms.push(cx.stmt_expr(call));
}
}
/* Invoke the correct conv function in extfmt */
PieceConv(ref conv) => {
n += 1u;
if n >= nargs {
cx.span_fatal(sp,
"not enough arguments to fmt! \
for the given format string");
}
log_conv(conv);
/* If the first portion is a conversion, then the local buffer
must be initialized as an empty string */
if i == 0 {
stms.push(cx.stmt_let(fmt_sp, true, ident,
cx.expr_str_uniq(fmt_sp, @"")));
}
stms.push(cx.stmt_expr(make_new_conv(cx, fmt_sp, conv,
args[n], buf())));
}
}
}
let expected_nargs = n + 1u; // n conversions + the fmt string
if expected_nargs < nargs {
cx.span_fatal
(sp, format!("too many arguments to fmt!. found {}, expected {}",
nargs, expected_nargs));
}
cx.expr_block(cx.block(fmt_sp, stms, Some(buf())))
base::MRExpr(ecx.expr_uint(sp, 2))
}

View File

@ -1,13 +0,0 @@
// 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.
// error-pattern:missing type
fn main() { oldfmt!("%+"); }

View File

@ -1,13 +0,0 @@
// 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.
// error-pattern:fmt! takes at least 1 argument
fn main() { oldfmt!(); }

View File

@ -1,18 +0,0 @@
// 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.
// error-pattern: literal
fn main() {
// fmt!'s first argument must be a literal. Hopefully this
// restriction can be eased eventually to just require a
// compile-time constant.
let x = oldfmt!("a" + "b");
}

View File

@ -1,18 +0,0 @@
// 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.
// error-pattern: literal
fn main() {
// fmt!'s first argument must be a literal. Hopefully this
// restriction can be eased eventually to just require a
// compile-time constant.
let x = oldfmt!(20);
}

View File

@ -1,15 +0,0 @@
// 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.
// error-pattern:not enough arguments
extern mod extra;
fn main() { let s = oldfmt!("%s%s%s", "test", "test"); }

View File

@ -1,15 +0,0 @@
// 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.
// error-pattern:too many arguments
extern mod extra;
fn main() { let s = oldfmt!("%s", "test", "test"); }

View File

@ -1,13 +0,0 @@
// 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.
// error-pattern:unknown type
fn main() { oldfmt!("%w"); }

View File

@ -1,16 +0,0 @@
// 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.
// error-pattern:only valid in signed fmt! conversion
fn main() {
// Can't use a sign on unsigned conversions
oldfmt!("%+u", 10u);
}

View File

@ -1,16 +0,0 @@
// 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.
// error-pattern:only valid in signed fmt! conversion
fn main() {
// Can't use a space on unsigned conversions
oldfmt!("% u", 10u);
}

View File

@ -1,13 +0,0 @@
// 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.
// error-pattern:unterminated conversion
fn main() { oldfmt!("%"); }

View File

@ -1,34 +0,0 @@
// 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.
// Testing that calling fmt! (via info2!) doesn't complain about impure borrows
struct Big { b: @~str, c: uint, d: int, e: char,
f: f64, g: bool }
fn foo() {
let a = Big {
b: @~"hi",
c: 0,
d: 1,
e: 'a',
f: 0.0,
g: true
};
info2!("test {:?}", a.b);
info2!("test {:u}", a.c);
info2!("test {:i}", a.d);
info2!("test {:c}", a.e);
info2!("test {:f}", a.f);
info2!("test {:b}", a.g);
}
pub fn main() {
}

View File

@ -1,282 +0,0 @@
// 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.
#[feature(macro_rules)];
// compile-flags: --cfg nofmt
extern mod extra;
macro_rules! fmt(($($arg:tt)*) => (oldfmt!($($arg)*)))
fn test(actual: ~str, expected: ~str) {
info2!("{}", actual.clone());
info2!("{}", expected.clone());
assert_eq!(actual, expected);
}
pub fn main() {
test(fmt!("hello %d friends and %s things", 10, "formatted"),
~"hello 10 friends and formatted things");
test(fmt!("test"), ~"test");
// a quadratic optimization in LLVM (jump-threading) makes this test a
// bit slow to compile unless we break it up
part1();
part2();
part3();
part4();
part5();
part6();
percent();
more_floats();
pointer();
}
fn part1() {
// Simple tests for types
test(fmt!("%d", 1), ~"1");
test(fmt!("%i", 2), ~"2");
test(fmt!("%i", -1), ~"-1");
test(fmt!("%u", 10u), ~"10");
test(fmt!("%s", "test"), ~"test");
test(fmt!("%b", true), ~"true");
test(fmt!("%b", false), ~"false");
test(fmt!("%c", 'A'), ~"A");
test(fmt!("%x", 0xff_u), ~"ff");
test(fmt!("%X", 0x12ab_u), ~"12AB");
test(fmt!("%o", 10u), ~"12");
test(fmt!("%t", 0b11010101_u), ~"11010101");
test(fmt!("%f", 5.82), ~"5.82");
// 32-bit limits
test(fmt!("%i", -2147483648), ~"-2147483648");
test(fmt!("%i", 2147483647), ~"2147483647");
test(fmt!("%u", 4294967295u), ~"4294967295");
test(fmt!("%x", 0xffffffff_u), ~"ffffffff");
test(fmt!("%o", 0xffffffff_u), ~"37777777777");
test(fmt!("%t", 0xffffffff_u), ~"11111111111111111111111111111111");
// Don't result in a compilation error
test(fmt!(""), ~"");
}
fn part2() {
// Widths
test(fmt!("%1d", 500), ~"500");
test(fmt!("%10d", 500), ~" 500");
test(fmt!("%10d", -500), ~" -500");
test(fmt!("%10u", 500u), ~" 500");
test(fmt!("%10s", "test"), ~" test");
test(fmt!("%10b", true), ~" true");
test(fmt!("%10x", 0xff_u), ~" ff");
test(fmt!("%10X", 0xff_u), ~" FF");
test(fmt!("%10o", 10u), ~" 12");
test(fmt!("%10t", 0xff_u), ~" 11111111");
test(fmt!("%10c", 'A'), ~" A");
test(fmt!("%10f", 5.82), ~" 5.82");
// Left justify
test(fmt!("%-10d", 500), ~"500 ");
test(fmt!("%-10d", -500), ~"-500 ");
test(fmt!("%-10u", 500u), ~"500 ");
test(fmt!("%-10s", "test"), ~"test ");
test(fmt!("%-10b", true), ~"true ");
test(fmt!("%-10x", 0xff_u), ~"ff ");
test(fmt!("%-10X", 0xff_u), ~"FF ");
test(fmt!("%-10o", 10u), ~"12 ");
test(fmt!("%-10t", 0xff_u), ~"11111111 ");
test(fmt!("%-10c", 'A'), ~"A ");
test(fmt!("%-10f", 5.82), ~"5.82 ");
}
fn part3() {
// Precision
test(fmt!("%.d", 0), ~"");
test(fmt!("%.u", 0u), ~"");
test(fmt!("%.x", 0u), ~"");
test(fmt!("%.t", 0u), ~"");
test(fmt!("%.d", 10), ~"10");
test(fmt!("%.d", -10), ~"-10");
test(fmt!("%.u", 10u), ~"10");
test(fmt!("%.s", "test"), ~"");
test(fmt!("%.x", 127u), ~"7f");
test(fmt!("%.o", 10u), ~"12");
test(fmt!("%.t", 3u), ~"11");
test(fmt!("%.c", 'A'), ~"A");
test(fmt!("%.f", 5.82), ~"6");
test(fmt!("%.0d", 0), ~"");
test(fmt!("%.0u", 0u), ~"");
test(fmt!("%.0x", 0u), ~"");
test(fmt!("%.0t", 0u), ~"");
test(fmt!("%.0d", 10), ~"10");
test(fmt!("%.0d", -10), ~"-10");
test(fmt!("%.0u", 10u), ~"10");
test(fmt!("%.0s", "test"), ~"");
test(fmt!("%.0x", 127u), ~"7f");
test(fmt!("%.0o", 10u), ~"12");
test(fmt!("%.0t", 3u), ~"11");
test(fmt!("%.0c", 'A'), ~"A");
test(fmt!("%.0f", 5.892), ~"6");
test(fmt!("%.1d", 0), ~"0");
test(fmt!("%.1u", 0u), ~"0");
test(fmt!("%.1x", 0u), ~"0");
test(fmt!("%.1t", 0u), ~"0");
test(fmt!("%.1d", 10), ~"10");
test(fmt!("%.1d", -10), ~"-10");
test(fmt!("%.1u", 10u), ~"10");
test(fmt!("%.1s", "test"), ~"t");
test(fmt!("%.1x", 127u), ~"7f");
test(fmt!("%.1o", 10u), ~"12");
test(fmt!("%.1t", 3u), ~"11");
test(fmt!("%.1c", 'A'), ~"A");
test(fmt!("%.1f", 5.82), ~"5.8");
}
fn part4() {
test(fmt!("%.5d", 0), ~"00000");
test(fmt!("%.5u", 0u), ~"00000");
test(fmt!("%.5x", 0u), ~"00000");
test(fmt!("%.5t", 0u), ~"00000");
test(fmt!("%.5d", 10), ~"00010");
test(fmt!("%.5d", -10), ~"-00010");
test(fmt!("%.5u", 10u), ~"00010");
test(fmt!("%.5s", "test"), ~"test");
test(fmt!("%.5x", 127u), ~"0007f");
test(fmt!("%.5o", 10u), ~"00012");
test(fmt!("%.5t", 3u), ~"00011");
test(fmt!("%.5c", 'A'), ~"A");
test(fmt!("%.5f", 5.82), ~"5.82000");
test(fmt!("%.5f", 5.0), ~"5.00000");
test(fmt!("%.100f", 1.1), ~"1.1000000000000000888178419700125232338905334472656250000000000000000000000000000000000000000000000000");
// Bool precision. I'm not sure if it's good or bad to have bool
// conversions support precision - it's not standard printf so we
// can do whatever. For now I'm making it behave the same as string
// conversions.
test(fmt!("%.b", true), ~"");
test(fmt!("%.0b", true), ~"");
test(fmt!("%.1b", true), ~"t");
}
fn part5() {
// Explicit + sign. Only for signed conversions
test(fmt!("%+d", 0), ~"+0");
test(fmt!("%+d", 1), ~"+1");
test(fmt!("%+d", -1), ~"-1");
test(fmt!("%+f", 0.0), ~"+0");
// Leave space for sign
test(fmt!("% d", 0), ~" 0");
test(fmt!("% d", 1), ~" 1");
test(fmt!("% d", -1), ~"-1");
test(fmt!("% f", 0.0), ~" 0");
// Plus overrides space
test(fmt!("% +d", 0), ~"+0");
test(fmt!("%+ d", 0), ~"+0");
test(fmt!("% +f", 0.0), ~"+0");
test(fmt!("%+ f", 0.0), ~"+0");
// 0-padding
test(fmt!("%05d", 0), ~"00000");
test(fmt!("%05d", 1), ~"00001");
test(fmt!("%05d", -1), ~"-0001");
test(fmt!("%05u", 1u), ~"00001");
test(fmt!("%05x", 127u), ~"0007f");
test(fmt!("%05X", 127u), ~"0007F");
test(fmt!("%05o", 10u), ~"00012");
test(fmt!("%05t", 3u), ~"00011");
test(fmt!("%05f", 5.82), ~"05.82");
// 0-padding a string is undefined but glibc does this:
test(fmt!("%05s", "test"), ~" test");
test(fmt!("%05c", 'A'), ~" A");
test(fmt!("%05b", true), ~" true");
// Left-justify overrides 0-padding
test(fmt!("%-05d", 0), ~"0 ");
test(fmt!("%-05d", 1), ~"1 ");
test(fmt!("%-05d", -1), ~"-1 ");
test(fmt!("%-05u", 1u), ~"1 ");
test(fmt!("%-05x", 127u), ~"7f ");
test(fmt!("%-05X", 127u), ~"7F ");
test(fmt!("%-05o", 10u), ~"12 ");
test(fmt!("%-05t", 3u), ~"11 ");
test(fmt!("%-05s", "test"), ~"test ");
test(fmt!("%-05c", 'A'), ~"A ");
test(fmt!("%-05b", true), ~"true ");
test(fmt!("%-05f", 5.82), ~"5.82 ");
}
fn part6() {
// Precision overrides 0-padding
// FIXME #2481: Recent gcc's report some of these as warnings
test(fmt!("%06.5d", 0), ~" 00000");
test(fmt!("%06.5u", 0u), ~" 00000");
test(fmt!("%06.5x", 0u), ~" 00000");
test(fmt!("%06.5d", 10), ~" 00010");
test(fmt!("%06.5d", -10), ~"-00010");
test(fmt!("%06.5u", 10u), ~" 00010");
test(fmt!("%06.5s", "test"), ~" test");
test(fmt!("%06.5c", 'A'), ~" A");
test(fmt!("%06.5x", 127u), ~" 0007f");
test(fmt!("%06.5X", 127u), ~" 0007F");
test(fmt!("%06.5o", 10u), ~" 00012");
// Precision does not override zero-padding for floats
test(fmt!("%08.5f", 5.82), ~"05.82000");
// Signed combinations
test(fmt!("% 5d", 1), ~" 1");
test(fmt!("% 5d", -1), ~" -1");
test(fmt!("%+5d", 1), ~" +1");
test(fmt!("%+5d", -1), ~" -1");
test(fmt!("% 05d", 1), ~" 0001");
test(fmt!("% 05d", -1), ~"-0001");
test(fmt!("%+05d", 1), ~"+0001");
test(fmt!("%+05d", -1), ~"-0001");
test(fmt!("%- 5d", 1), ~" 1 ");
test(fmt!("%- 5d", -1), ~"-1 ");
test(fmt!("%-+5d", 1), ~"+1 ");
test(fmt!("%-+5d", -1), ~"-1 ");
test(fmt!("%- 05d", 1), ~" 1 ");
test(fmt!("%- 05d", -1), ~"-1 ");
test(fmt!("%-+05d", 1), ~"+1 ");
test(fmt!("%-+05d", -1), ~"-1 ");
}
fn percent() {
let s = fmt!("ab%%cd");
assert_eq!(s, ~"ab%cd");
}
fn more_floats() {
assert_eq!(~"3.1416", fmt!("%.4f", 3.14159));
assert_eq!(~"3", fmt!("%.0f", 3.14159));
assert_eq!(~"99", fmt!("%.0f", 98.5));
assert_eq!(~"7.0000", fmt!("%.4f", 6.999999999));
assert_eq!(~"3.141590000", fmt!("%.9f", 3.14159));
}
fn pointer() {
do 10.times {
let x: uint = ::std::rand::random();
assert_eq!(fmt!("%p", x as *uint), fmt!("0x%x", x));
}
let i = &1;
assert_eq!(fmt!("%p", i), fmt!("0x%x", i as *uint as uint));
}