auto merge of #9153 : alexcrichton/rust/simplify-format, r=huonw

This follows from the discussion in #9012.

* All macros are now defined in terms of `format_args!` allowing for removal of a good bit of code in the syntax extension
* The syntax extension is now in a more aptly-named file, `format.rs`
* Documentation was added for the `format!`-related macros.
This commit is contained in:
bors 2013-09-15 01:50:50 -07:00
commit 4ecb0a372d
6 changed files with 171 additions and 141 deletions

View File

@ -133,7 +133,7 @@ is `?` which is defined for all types by default.
When implementing a format trait for your own time, you will have to implement a
method of the signature:
~~~
~~~{.rust}
fn fmt(value: &T, f: &mut std::fmt::Formatter);
~~~
@ -144,6 +144,78 @@ values of these parameters will be listed in the fields of the `Formatter`
struct. In order to help with this, the `Formatter` struct also provides some
helper methods.
### Related macros
There are a number of related macros in the `format!` family. The ones that are
currently implemented are:
~~~{.rust}
format! // described above
write! // first argument is a &mut rt::io::Writer, the destination
writeln! // same as write but appends a newline
print! // the format string is printed to the standard output
println! // same as print but appends a newline
format_args! // described below.
~~~
#### `write!`
This and `writeln` are two macros which are used to emit the format string to a
specified stream. This is used to prevent intermediate allocations of format
strings and instead directly write the output. Under the hood, this function is
actually invoking the `write` function defined in this module. Example usage is:
~~~{.rust}
use std::rt::io;
let mut w = io::mem::MemWriter::new();
write!(&mut w as &mut io::Writer, "Hello {}!", "world");
~~~
#### `print!`
This and `println` emit their output to stdout. Similarly to the `write!` macro,
the goal of these macros is to avoid intermediate allocations when printing
output. Example usage is:
~~~{.rust}
print!("Hello {}!", "world");
println!("I have a newline {}", "character at the end");
~~~
#### `format_args!`
This is a curious macro which is used to safely pass around
an opaque object describing the format string. This object
does not require any heap allocations to create, and it only
references information on the stack. Under the hood, all of
the related macros are implemented in terms of this. First
off, some example usage is:
~~~{.rust}
use std::fmt;
format_args!(fmt::format, "this returns {}", "~str");
format_args!(|args| { fmt::write(my_writer, args) }, "some {}", "args");
format_args!(my_fn, "format {}", "string");
~~~
The first argument of the `format_args!` macro is a function (or closure) which
takes one argument of type `&fmt::Arguments`. This structure can then be
passed to the `write` and `format` functions inside this module in order to
process the format string. The goal of this macro is to even further prevent
intermediate allocations when dealing formatting strings.
For example, a logging library could use the standard formatting syntax, but it
would internally pass around this structure until it has been determined where
output should go to.
It is unsafe to programmatically create an instance of `fmt::Arguments` because
the operations performed when executing a format string require the compile-time
checks provided by the compiler. The `format_args!` macro is the only method of
safely creating these structures, but they can be unsafely created with the
constructor provided.
## Internationalization
The formatting syntax supported by the `format!` extension supports
@ -163,7 +235,7 @@ Furthermore, whenever a case is running, the special character `#` can be used
to reference the string value of the argument which was selected upon. As an
example:
~~~
~~~{.rust}
format!("{0, select, other{#}}", "hello") // => ~"hello"
~~~
@ -452,6 +524,13 @@ pub fn write(output: &mut io::Writer, args: &Arguments) {
unsafe { write_unsafe(output, args.fmt, args.args) }
}
/// The `writeln` function takes the same arguments as `write`, except that it
/// will also write a newline (`\n`) character at the end of the format string.
pub fn writeln(output: &mut io::Writer, args: &Arguments) {
unsafe { write_unsafe(output, args.fmt, args.args) }
output.write(['\n' as u8]);
}
/// The `write_unsafe` function takes an output stream, a precompiled format
/// string, and a list of arguments. The arguments will be formatted according
/// to the specified format string into the output stream provided.

View File

@ -155,14 +155,8 @@ pub fn syntax_expander_table() -> SyntaxEnv {
@SE(IdentTT(ext::tt::macro_rules::add_new_extension, None)));
syntax_expanders.insert(intern(&"fmt"),
builtin_normal_tt_no_ctxt(ext::fmt::expand_syntax_ext));
syntax_expanders.insert(intern(&"format"),
builtin_normal_tt_no_ctxt(ext::ifmt::expand_format));
syntax_expanders.insert(intern(&"write"),
builtin_normal_tt_no_ctxt(ext::ifmt::expand_write));
syntax_expanders.insert(intern(&"writeln"),
builtin_normal_tt_no_ctxt(ext::ifmt::expand_writeln));
syntax_expanders.insert(intern(&"format_args"),
builtin_normal_tt_no_ctxt(ext::ifmt::expand_format_args));
builtin_normal_tt_no_ctxt(ext::format::expand_args));
syntax_expanders.insert(
intern(&"auto_encode"),
@SE(ItemDecorator(ext::auto_encode::expand_auto_encode)));

View File

@ -81,20 +81,10 @@ pub fn expand_expr(extsbox: @mut SyntaxEnv,
// be the root of the call stack. That's the most
// relevant span and it's the actual invocation of
// the macro.
let mut relevant_info = cx.backtrace();
let mut einfo = relevant_info.unwrap();
loop {
match relevant_info {
None => { break }
Some(e) => {
einfo = e;
relevant_info = einfo.call_site.expn_info;
}
}
}
let mac_span = original_span(cx);
let expanded =
match expandfun(cx, einfo.call_site,
match expandfun(cx, mac_span.call_site,
marked_before, marked_ctxt) {
MRExpr(e) => e,
MRAny(expr_maker,_,_) => expr_maker(),
@ -400,11 +390,11 @@ pub fn expand_stmt(extsbox: @mut SyntaxEnv,
-> (Option<Stmt_>, Span) {
// why the copying here and not in expand_expr?
// looks like classic changed-in-only-one-place
let (mac, pth, tts, semi, ctxt) = match *s {
let (pth, tts, semi, ctxt) = match *s {
StmtMac(ref mac, semi) => {
match mac.node {
mac_invoc_tt(ref pth, ref tts, ctxt) => {
((*mac).clone(), pth, (*tts).clone(), semi, ctxt)
(pth, (*tts).clone(), semi, ctxt)
}
}
}
@ -431,7 +421,13 @@ pub fn expand_stmt(extsbox: @mut SyntaxEnv,
// mark before expansion:
let marked_tts = mark_tts(tts,fm);
let marked_ctxt = new_mark(fm,ctxt);
let expanded = match expandfun(cx, mac.span, marked_tts, marked_ctxt) {
// See the comment in expand_expr for why we want the original span,
// not the current mac.span.
let mac_span = original_span(cx);
let expanded = match expandfun(cx, mac_span.call_site,
marked_tts, marked_ctxt) {
MRExpr(e) =>
@codemap::Spanned { node: StmtExpr(e, ast::DUMMY_NODE_ID),
span: e.span},
@ -715,11 +711,11 @@ pub fn std_macros() -> @str {
}
})
)
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!(debug) { log!(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!(debug) { log!(4u32, $($arg)*) }
))
macro_rules! log2(
@ -730,11 +726,11 @@ 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!(debug) { log2!(4u32, $($arg)+) }
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!(debug) { log2!(4u32, $($arg)*) }
))
macro_rules! fail(
@ -753,8 +749,8 @@ pub fn std_macros() -> @str {
() => (
fail!(\"explicit failure\")
);
($($arg:tt)+) => (
::std::sys::FailWithCause::fail_with(format!($($arg)+), file!(), line!())
($($arg:tt)*) => (
::std::sys::FailWithCause::fail_with(format!($($arg)*), file!(), line!())
)
)
@ -958,17 +954,25 @@ pub fn std_macros() -> @str {
)
)
macro_rules! format(($($arg:tt)*) => (
format_args!(::std::fmt::format, $($arg)*)
))
macro_rules! write(($dst:expr, $($arg:tt)*) => (
format_args!(|args| { ::std::fmt::write($dst, args) }, $($arg)*)
))
macro_rules! writeln(($dst:expr, $($arg:tt)*) => (
format_args!(|args| { ::std::fmt::writeln($dst, args) }, $($arg)*)
))
// FIXME(#6846) once stdio is redesigned, this shouldn't perform an
// allocation but should rather delegate to an invocation of
// write! instead of format!
macro_rules! print (
($($arg:tt)+) => (::std::io::print(format!($($arg)+)))
($($arg:tt)*) => (::std::io::print(format!($($arg)*)))
)
// FIXME(#6846) once stdio is redesigned, this shouldn't perform an
// allocation but should rather delegate to an io::Writer
macro_rules! println (
($($arg:tt)+) => (::std::io::println(format!($($arg)+)))
($($arg:tt)*) => (::std::io::println(format!($($arg)*)))
)
// NOTE: use this after a snapshot lands to abstract the details
@ -1262,6 +1266,20 @@ pub fn mtwt_cancel_outer_mark(tts: &[ast::token_tree], ctxt: ast::SyntaxContext)
mark_tts(tts,outer_mark)
}
fn original_span(cx: @ExtCtxt) -> @codemap::ExpnInfo {
let mut relevant_info = cx.backtrace();
let mut einfo = relevant_info.unwrap();
loop {
match relevant_info {
None => { break }
Some(e) => {
einfo = e;
relevant_info = einfo.call_site.expn_info;
}
}
}
return einfo;
}
#[cfg(test)]
mod test {

View File

@ -54,21 +54,16 @@ impl Context {
/// Parses the arguments from the given list of tokens, returning None if
/// there's a parse error so we can continue parsing other fmt! expressions.
fn parse_args(&mut self, sp: Span,
leading_expr: bool,
tts: &[ast::token_tree]) -> (Option<@ast::Expr>,
Option<@ast::Expr>) {
tts: &[ast::token_tree]) -> (@ast::Expr, Option<@ast::Expr>) {
let p = rsparse::new_parser_from_tts(self.ecx.parse_sess(),
self.ecx.cfg(),
tts.to_owned());
// If we want a leading expression, parse it here
let extra = if leading_expr {
let e = Some(p.parse_expr());
if !p.eat(&token::COMMA) {
self.ecx.span_err(sp, "expected token: `,`");
return (e, None);
}
e
} else { None };
// Parse the leading function expression (maybe a block, maybe a path)
let extra = p.parse_expr();
if !p.eat(&token::COMMA) {
self.ecx.span_err(sp, "expected token: `,`");
return (extra, None);
}
if *p.token == token::EOF {
self.ecx.span_err(sp, "requires at least a format string argument");
@ -547,7 +542,7 @@ impl Context {
/// Actually builds the expression which the ifmt! block will be expanded
/// to
fn to_expr(&self, extra: Option<@ast::Expr>, f: Option<&str>) -> @ast::Expr {
fn to_expr(&self, extra: @ast::Expr) -> @ast::Expr {
let mut lets = ~[];
let mut locals = ~[];
let mut names = vec::from_fn(self.name_positions.len(), |_| None);
@ -614,68 +609,33 @@ impl Context {
self.ecx.expr_ident(e.span, lname)));
}
// Now create the fmt::Arguments struct with all our locals we created.
let args = names.move_iter().map(|a| a.unwrap());
let mut args = locals.move_iter().chain(args);
let fmt = self.ecx.expr_ident(self.fmtsp, static_name);
let args = self.ecx.expr_vec_slice(self.fmtsp, args.collect());
let result = self.ecx.expr_call_global(self.fmtsp, ~[
self.ecx.ident_of("std"),
self.ecx.ident_of("fmt"),
self.ecx.ident_of("Arguments"),
self.ecx.ident_of("new"),
], ~[fmt, args]);
let result = match f {
// Invocation of write!()/format!(), call the function and we're
// done.
Some(f) => {
let mut fmt_args = match extra {
Some(e) => ~[e], None => ~[]
};
fmt_args.push(self.ecx.expr_ident(self.fmtsp, static_name));
fmt_args.push(self.ecx.expr_vec_slice(self.fmtsp,
args.collect()));
let result = self.ecx.expr_call_global(self.fmtsp, ~[
self.ecx.ident_of("std"),
self.ecx.ident_of("fmt"),
self.ecx.ident_of(f),
], fmt_args);
// sprintf is unsafe, but we just went through a lot of work to
// validate that our call is save, so inject the unsafe block
// for the user.
self.ecx.expr_block(ast::Block {
view_items: ~[],
stmts: ~[],
expr: Some(result),
id: ast::DUMMY_NODE_ID,
rules: ast::UnsafeBlock(ast::CompilerGenerated),
span: self.fmtsp,
})
}
// Invocation of format_args!()
None => {
let fmt = self.ecx.expr_ident(self.fmtsp, static_name);
let args = self.ecx.expr_vec_slice(self.fmtsp, args.collect());
let result = self.ecx.expr_call_global(self.fmtsp, ~[
self.ecx.ident_of("std"),
self.ecx.ident_of("fmt"),
self.ecx.ident_of("Arguments"),
self.ecx.ident_of("new"),
], ~[fmt, args]);
// We did all the work of making sure that the arguments
// structure is safe, so we can safely have an unsafe block.
let result = self.ecx.expr_block(ast::Block {
view_items: ~[],
stmts: ~[],
expr: Some(result),
id: ast::DUMMY_NODE_ID,
rules: ast::UnsafeBlock(ast::CompilerGenerated),
span: self.fmtsp,
});
let extra = extra.unwrap();
let resname = self.ecx.ident_of("__args");
lets.push(self.ecx.stmt_let(self.fmtsp, false, resname, result));
let res = self.ecx.expr_ident(self.fmtsp, resname);
self.ecx.expr_call(extra.span, extra, ~[
self.ecx.expr_addr_of(extra.span, res)])
}
};
// We did all the work of making sure that the arguments
// structure is safe, so we can safely have an unsafe block.
let result = self.ecx.expr_block(ast::Block {
view_items: ~[],
stmts: ~[],
expr: Some(result),
id: ast::DUMMY_NODE_ID,
rules: ast::UnsafeBlock(ast::CompilerGenerated),
span: self.fmtsp,
});
let resname = self.ecx.ident_of("__args");
lets.push(self.ecx.stmt_let(self.fmtsp, false, resname, result));
let res = self.ecx.expr_ident(self.fmtsp, resname);
let result = self.ecx.expr_call(extra.span, extra, ~[
self.ecx.expr_addr_of(extra.span, res)]);
self.ecx.expr_block(self.ecx.block(self.fmtsp, lets,
Some(result)))
}
@ -740,29 +700,8 @@ impl Context {
}
}
pub fn expand_format(ecx: @ExtCtxt, sp: Span,
tts: &[ast::token_tree]) -> base::MacResult {
expand_ifmt(ecx, sp, tts, false, false, Some("format_unsafe"))
}
pub fn expand_write(ecx: @ExtCtxt, sp: Span,
tts: &[ast::token_tree]) -> base::MacResult {
expand_ifmt(ecx, sp, tts, true, false, Some("write_unsafe"))
}
pub fn expand_writeln(ecx: @ExtCtxt, sp: Span,
tts: &[ast::token_tree]) -> base::MacResult {
expand_ifmt(ecx, sp, tts, true, true, Some("write_unsafe"))
}
pub fn expand_format_args(ecx: @ExtCtxt, sp: Span,
tts: &[ast::token_tree]) -> base::MacResult {
expand_ifmt(ecx, sp, tts, true, false, None)
}
fn expand_ifmt(ecx: @ExtCtxt, sp: Span, tts: &[ast::token_tree],
leading_arg: bool, append_newline: bool,
function: Option<&str>) -> base::MacResult {
pub fn expand_args(ecx: @ExtCtxt, sp: Span,
tts: &[ast::token_tree]) -> base::MacResult {
let mut cx = Context {
ecx: ecx,
args: ~[],
@ -776,14 +715,13 @@ fn expand_ifmt(ecx: @ExtCtxt, sp: Span, tts: &[ast::token_tree],
method_statics: ~[],
fmtsp: sp,
};
let (extra, efmt) = match cx.parse_args(sp, leading_arg, tts) {
let (extra, efmt) = match cx.parse_args(sp, tts) {
(extra, Some(e)) => (extra, e),
(_, None) => { return MRExpr(ecx.expr_uint(sp, 2)); }
};
cx.fmtsp = efmt.span;
let fmt = expr_to_str(ecx, efmt,
"format argument must be a string literal.");
let fmt = if append_newline { fmt + "\n" } else { fmt.to_owned() };
let mut err = false;
do parse::parse_error::cond.trap(|m| {
@ -814,5 +752,5 @@ fn expand_ifmt(ecx: @ExtCtxt, sp: Span, tts: &[ast::token_tree],
}
}
MRExpr(cx.to_expr(extra, function))
MRExpr(cx.to_expr(extra))
}

View File

@ -72,7 +72,7 @@ pub mod ext {
pub mod cfg;
pub mod fmt;
pub mod ifmt;
pub mod format;
pub mod env;
pub mod bytes;
pub mod concat_idents;

View File

@ -11,7 +11,6 @@
fn main() {
// bad arguments to the format! call
format!(); //~ ERROR: requires at least a format string
format!("{}"); //~ ERROR: invalid reference to argument
format!("{1}", 1); //~ ERROR: invalid reference to argument `1`
@ -30,8 +29,6 @@ fn main() {
format!("{foo}", foo=1, foo=2); //~ ERROR: duplicate argument
format!("#"); //~ ERROR: `#` reference used
format!("", foo=1, 2); //~ ERROR: positional arguments cannot follow
format!("" 1); //~ ERROR: expected token: `,`
format!("", 1 1); //~ ERROR: expected token: `,`
format!("{0, select, a{} a{} other{}}", "a"); //~ ERROR: duplicate selector
format!("{0, plural, =1{} =1{} other{}}", 1u); //~ ERROR: duplicate selector
@ -74,4 +71,8 @@ fn main() {
format!("foo } bar"); //~ ERROR: unmatched `}` found
format!("foo }"); //~ ERROR: unmatched `}` found
format!(); //~ ERROR: requires at least a format string argument
format!("" 1); //~ ERROR: expected token: `,`
format!("", 1 1); //~ ERROR: expected token: `,`
}