diff --git a/src/libstd/fmt/mod.rs b/src/libstd/fmt/mod.rs index 7d5033e3a6ad1..c7ab508ea6ec6 100644 --- a/src/libstd/fmt/mod.rs +++ b/src/libstd/fmt/mod.rs @@ -36,7 +36,7 @@ Some examples of the `format!` extension are: format!("Hello") // => ~"Hello" format!("Hello, {:s}!", "world") // => ~"Hello, world!" format!("The number is {:d}", 1) // => ~"The number is 1" -format!("{}", ~[3, 4]) // => ~"~[3, 4]" +format!("{:?}", ~[3, 4]) // => ~"~[3, 4]" format!("{value}", value=4) // => ~"4" format!("{} {}", 1, 2) // => ~"1 2" ~~~ @@ -363,6 +363,32 @@ pub struct Argument<'self> { priv value: &'self util::Void, } +impl<'self> Arguments<'self> { + /// When using the format_args!() macro, this function is used to generate the + /// Arguments structure. The compiler inserts an `unsafe` block to call this, + /// which is valid because the compiler performs all necessary validation to + /// ensure that the resulting call to format/write would be safe. + #[doc(hidden)] #[inline] + pub unsafe fn new<'a>(fmt: &'static [rt::Piece<'static>], + args: &'a [Argument<'a>]) -> Arguments<'a> { + Arguments{ fmt: cast::transmute(fmt), args: args } + } +} + +/// This structure represents a safely precompiled version of a format string +/// and its arguments. This cannot be generated at runtime because it cannot +/// safely be done so, so no constructors are given and the fields are private +/// to prevent modification. +/// +/// The `format_args!` macro will safely create an instance of this structure +/// and pass it to a user-supplied function. The macro validates the format +/// string at compile-time so usage of the `write` and `format` functions can +/// be safely performed. +pub struct Arguments<'self> { + priv fmt: &'self [rt::Piece<'self>], + priv args: &'self [Argument<'self>], +} + /// When a format is not otherwise specified, types are formatted by ascribing /// to this trait. There is not an explicit way of selecting this trait to be /// used for formatting, it is only if no other format is specified. @@ -410,6 +436,26 @@ pub trait Float { fn fmt(&Self, &mut Formatter); } /// and a list of arguments. The arguments will be formatted according to the /// specified format string into the output stream provided. /// +/// # Arguments +/// +/// * output - the buffer to write output to +/// * args - the precompiled arguments generated by `format_args!` +/// +/// # Example +/// +/// ~~~{.rust} +/// use std::fmt; +/// let w: &mut io::Writer = ...; +/// format_args!(|args| { fmt::write(w, args) }, "Hello, {}!", "world"); +/// ~~~ +pub fn write(output: &mut io::Writer, args: &Arguments) { + unsafe { write_unsafe(output, args.fmt, args.args) } +} + +/// 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. +/// /// See the documentation for `format` for why this function is unsafe and care /// should be taken if calling it manually. /// @@ -426,8 +472,9 @@ pub trait Float { fn fmt(&Self, &mut Formatter); } /// /// Note that this function assumes that there are enough arguments for the /// format string. -pub unsafe fn write(output: &mut io::Writer, - fmt: &[rt::Piece], args: &[Argument]) { +pub unsafe fn write_unsafe(output: &mut io::Writer, + fmt: &[rt::Piece], + args: &[Argument]) { let mut formatter = Formatter { flags: 0, width: None, @@ -446,6 +493,25 @@ pub unsafe fn write(output: &mut io::Writer, /// The format function takes a precompiled format string and a list of /// arguments, to return the resulting formatted string. /// +/// # Arguments +/// +/// * args - a structure of arguments generated via the `format_args!` macro. +/// Because this structure can only be safely generated at +/// compile-time, this function is safe. +/// +/// # Example +/// +/// ~~~{.rust} +/// use std::fmt; +/// let s = format_args!(fmt::format, "Hello, {}!", "world"); +/// assert_eq!(s, "Hello, world!"); +/// ~~~ +pub fn format(args: &Arguments) -> ~str { + unsafe { format_unsafe(args.fmt, args.args) } +} + +/// The unsafe version of the formatting function. +/// /// This is currently an unsafe function because the types of all arguments /// aren't verified by immediate callers of this function. This currently does /// not validate that the correct types of arguments are specified for each @@ -465,9 +531,9 @@ pub unsafe fn write(output: &mut io::Writer, /// /// Note that this function assumes that there are enough arguments for the /// format string. -pub unsafe fn format(fmt: &[rt::Piece], args: &[Argument]) -> ~str { +pub unsafe fn format_unsafe(fmt: &[rt::Piece], args: &[Argument]) -> ~str { let mut output = MemWriter::new(); - write(&mut output as &mut io::Writer, fmt, args); + write_unsafe(&mut output as &mut io::Writer, fmt, args); return str::from_utf8_owned(output.inner()); } @@ -740,7 +806,7 @@ impl<'self> Formatter<'self> { /// This is a function which calls are emitted to by the compiler itself to /// create the Argument structures that are passed into the `format` function. -#[doc(hidden)] +#[doc(hidden)] #[inline] pub fn argument<'a, T>(f: extern "Rust" fn(&T, &mut Formatter), t: &'a T) -> Argument<'a> { unsafe { @@ -753,14 +819,14 @@ pub fn argument<'a, T>(f: extern "Rust" fn(&T, &mut Formatter), /// When the compiler determines that the type of an argument *must* be a string /// (such as for select), then it invokes this method. -#[doc(hidden)] +#[doc(hidden)] #[inline] pub fn argumentstr<'a>(s: &'a &str) -> Argument<'a> { argument(String::fmt, s) } /// When the compiler determines that the type of an argument *must* be a uint /// (such as for plural), then it invokes this method. -#[doc(hidden)] +#[doc(hidden)] #[inline] pub fn argumentuint<'a>(s: &'a uint) -> Argument<'a> { argument(Unsigned::fmt, s) } @@ -899,14 +965,8 @@ impl Pointer for *T { } } } - impl Pointer for *mut T { - fn fmt(t: &*mut T, f: &mut Formatter) { - f.flags |= 1 << (parse::FlagAlternate as uint); - do ::uint::to_str_bytes(*t as uint, 16) |buf| { - f.pad_integral(buf, "0x", true); - } - } + fn fmt(t: &*mut T, f: &mut Formatter) { Pointer::fmt(&(*t as *T), f) } } // Implementation of Default for various core types @@ -940,7 +1000,6 @@ delegate!(f64 to Float) impl Default for *T { fn fmt(me: &*T, f: &mut Formatter) { Pointer::fmt(me, f) } } - impl Default for *mut T { fn fmt(me: &*mut T, f: &mut Formatter) { Pointer::fmt(me, f) } } diff --git a/src/libsyntax/ext/base.rs b/src/libsyntax/ext/base.rs index 48a1364668688..284801ab123c9 100644 --- a/src/libsyntax/ext/base.rs +++ b/src/libsyntax/ext/base.rs @@ -161,6 +161,8 @@ pub fn syntax_expander_table() -> SyntaxEnv { 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)); syntax_expanders.insert( intern(&"auto_encode"), @SE(ItemDecorator(ext::auto_encode::expand_auto_encode))); diff --git a/src/libsyntax/ext/ifmt.rs b/src/libsyntax/ext/ifmt.rs index 486069db4f0c4..2f040ef251923 100644 --- a/src/libsyntax/ext/ifmt.rs +++ b/src/libsyntax/ext/ifmt.rs @@ -60,7 +60,7 @@ impl Context { let p = rsparse::new_parser_from_tts(self.ecx.parse_sess(), self.ecx.cfg(), tts.to_owned()); - // If we want a leading expression (for ifmtf), parse it here + // 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) { @@ -341,12 +341,18 @@ impl Context { ~[self.ecx.ident_of("std"), self.ecx.ident_of("fmt"), self.ecx.ident_of("parse"), self.ecx.ident_of(s)] }; - let none = || { - let p = self.ecx.path(sp, ~[self.ecx.ident_of("None")]); - self.ecx.expr_path(p) - }; + let none = self.ecx.path_global(sp, ~[ + self.ecx.ident_of("std"), + self.ecx.ident_of("option"), + self.ecx.ident_of("None")]); + let none = self.ecx.expr_path(none); let some = |e: @ast::Expr| { - self.ecx.expr_call_ident(sp, self.ecx.ident_of("Some"), ~[e]) + let p = self.ecx.path_global(sp, ~[ + self.ecx.ident_of("std"), + self.ecx.ident_of("option"), + self.ecx.ident_of("Some")]); + let p = self.ecx.expr_path(p); + self.ecx.expr_call(sp, p, ~[e]) }; let trans_count = |c: parse::Count| { match c { @@ -397,7 +403,7 @@ impl Context { parse::Plural(offset, ref arms, ref default) => { let offset = match offset { Some(i) => { some(self.ecx.expr_uint(sp, i)) } - None => { none() } + None => { none.clone() } }; let arms = arms.iter().map(|arm| { let p = self.ecx.path_global(sp, rtpath("PluralArm")); @@ -522,7 +528,7 @@ impl Context { // Translate the method (if any) let method = match arg.method { - None => { none() } + None => { none.clone() } Some(ref m) => { let m = trans_method(*m); some(self.ecx.expr_addr_of(sp, m)) @@ -541,7 +547,7 @@ impl Context { /// Actually builds the expression which the ifmt! block will be expanded /// to - fn to_expr(&self, extra: Option<@ast::Expr>, f: &str) -> @ast::Expr { + fn to_expr(&self, extra: Option<@ast::Expr>, f: Option<&str>) -> @ast::Expr { let mut lets = ~[]; let mut locals = ~[]; let mut names = vec::from_fn(self.name_positions.len(), |_| None); @@ -556,21 +562,19 @@ impl Context { // Next, build up the static array which will become our precompiled // format "string" let fmt = self.ecx.expr_vec(self.fmtsp, self.pieces.clone()); + let piece_ty = self.ecx.ty_path(self.ecx.path_all( + self.fmtsp, + true, ~[ + self.ecx.ident_of("std"), + self.ecx.ident_of("fmt"), + self.ecx.ident_of("rt"), + self.ecx.ident_of("Piece"), + ], + Some(self.ecx.lifetime(self.fmtsp, self.ecx.ident_of("static"))), + ~[] + ), None); let ty = ast::ty_fixed_length_vec( - self.ecx.ty_mt( - self.ecx.ty_path(self.ecx.path_all( - self.fmtsp, - true, ~[ - self.ecx.ident_of("std"), - self.ecx.ident_of("fmt"), - self.ecx.ident_of("rt"), - self.ecx.ident_of("Piece"), - ], - Some(self.ecx.lifetime(self.fmtsp, self.ecx.ident_of("static"))), - ~[] - ), None), - ast::MutImmutable - ), + self.ecx.ty_mt(piece_ty.clone(), ast::MutImmutable), self.ecx.expr_uint(self.fmtsp, self.pieces.len()) ); let ty = self.ecx.ty(self.fmtsp, ty); @@ -596,7 +600,8 @@ impl Context { let name = self.ecx.ident_of(fmt!("__arg%u", i)); let e = self.ecx.expr_addr_of(e.span, e); lets.push(self.ecx.stmt_let(e.span, false, name, e)); - locals.push(self.format_arg(e.span, Left(i), name)); + locals.push(self.format_arg(e.span, Left(i), + self.ecx.expr_ident(e.span, name))); } for (&name, &e) in self.names.iter() { if !self.name_types.contains_key(&name) { loop } @@ -605,48 +610,83 @@ impl Context { let e = self.ecx.expr_addr_of(e.span, e); lets.push(self.ecx.stmt_let(e.span, false, lname, e)); names[*self.name_positions.get(&name)] = - Some(self.format_arg(e.span, Right(name), lname)); + Some(self.format_arg(e.span, Right(name), + self.ecx.expr_ident(e.span, lname))); } let args = names.move_iter().map(|a| a.unwrap()); let mut args = locals.move_iter().chain(args); - 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(self.fmtsp, args.collect())); + 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())); - // Next, build up the actual call to the {s,f}printf function. - 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. - 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, - }); - - self.ecx.expr_block(self.ecx.block(self.fmtsp, lets, Some(result))) + 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)]) + } + }; + self.ecx.expr_block(self.ecx.block(self.fmtsp, lets, + Some(result))) } - fn format_arg(&self, sp: Span, arg: Either, - ident: ast::Ident) -> @ast::Expr { - let ty = match arg { + fn format_arg(&self, sp: Span, argno: Either, + arg: @ast::Expr) -> @ast::Expr { + let ty = match argno { Left(i) => self.arg_types[i].unwrap(), Right(s) => *self.name_types.get(&s) }; - let argptr = self.ecx.expr_ident(sp, ident); let fmt_trait = match ty { Unknown => "Default", Known(tyname) => { @@ -675,14 +715,14 @@ impl Context { self.ecx.ident_of("std"), self.ecx.ident_of("fmt"), self.ecx.ident_of("argumentstr"), - ], ~[argptr]) + ], ~[arg]) } Unsigned => { return self.ecx.expr_call_global(sp, ~[ self.ecx.ident_of("std"), self.ecx.ident_of("fmt"), self.ecx.ident_of("argumentuint"), - ], ~[argptr]) + ], ~[arg]) } }; @@ -696,28 +736,33 @@ impl Context { self.ecx.ident_of("std"), self.ecx.ident_of("fmt"), self.ecx.ident_of("argument"), - ], ~[self.ecx.expr_path(format_fn), argptr]) + ], ~[self.ecx.expr_path(format_fn), arg]) } } pub fn expand_format(ecx: @ExtCtxt, sp: Span, tts: &[ast::token_tree]) -> base::MacResult { - expand_ifmt(ecx, sp, tts, false, false, "format") + 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, "write") + 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, "write") + 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: &str) -> base::MacResult { + function: Option<&str>) -> base::MacResult { let mut cx = Context { ecx: ecx, args: ~[], diff --git a/src/test/compile-fail/ifmt-bad-format-args.rs b/src/test/compile-fail/ifmt-bad-format-args.rs new file mode 100644 index 0000000000000..d2a3fe2a9b1c2 --- /dev/null +++ b/src/test/compile-fail/ifmt-bad-format-args.rs @@ -0,0 +1,14 @@ +// Copyright 2013 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 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +fn main() { + format_args!("test"); //~ ERROR: expected token + format_args!("", || {}); //~ ERROR: must be a string literal +} diff --git a/src/test/compile-fail/ifmt-bad-format-args2.rs b/src/test/compile-fail/ifmt-bad-format-args2.rs new file mode 100644 index 0000000000000..7bb8365bc128e --- /dev/null +++ b/src/test/compile-fail/ifmt-bad-format-args2.rs @@ -0,0 +1,13 @@ +// Copyright 2013 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 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +fn main() { + format_args!("{}", ""); //~ ERROR: expected function +} diff --git a/src/test/run-pass/ifmt.rs b/src/test/run-pass/ifmt.rs index ab66bfc101110..08d5ac5c1fb8e 100644 --- a/src/test/run-pass/ifmt.rs +++ b/src/test/run-pass/ifmt.rs @@ -13,6 +13,11 @@ #[deny(warnings)]; use std::fmt; +use std::rt::io::Decorator; +use std::rt::io::mem::MemWriter; +use std::rt::io; +use std::rt::io::Writer; +use std::str; struct A; struct B; @@ -235,16 +240,13 @@ pub fn main() { let a: int = ::std::cast::transmute(3u); format!("{}", a); } + + test_format_args(); } // Basic test to make sure that we can invoke the `write!` macro with an // io::Writer instance. fn test_write() { - use std::rt::io::Decorator; - use std::rt::io::mem::MemWriter; - use std::rt::io; - use std::str; - let mut buf = MemWriter::new(); write!(&mut buf as &mut io::Writer, "{}", 3); { @@ -268,3 +270,20 @@ fn test_print() { println!("this is a {}", "test"); println!("{foo}", foo="bar"); } + +// Just make sure that the macros are defined, there's not really a lot that we +// can do with them just yet (to test the output) +fn test_format_args() { + let mut buf = MemWriter::new(); + { + let w = &mut buf as &mut io::Writer; + format_args!(|args| { fmt::write(w, args) }, "{}", 1); + format_args!(|args| { fmt::write(w, args) }, "test"); + format_args!(|args| { fmt::write(w, args) }, "{test}", test=3); + } + let s = str::from_utf8_owned(buf.inner()); + t!(s, "1test3"); + + let s = format_args!(fmt::format, "hello {}", "world"); + t!(s, "hello world"); +}