about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--src/libstd/fmt/mod.rs91
-rw-r--r--src/libsyntax/ext/base.rs2
-rw-r--r--src/libsyntax/ext/ifmt.rs167
-rw-r--r--src/test/compile-fail/ifmt-bad-format-args.rs14
-rw-r--r--src/test/compile-fail/ifmt-bad-format-args2.rs13
-rw-r--r--src/test/run-pass/ifmt.rs29
6 files changed, 234 insertions, 82 deletions
diff --git a/src/libstd/fmt/mod.rs b/src/libstd/fmt/mod.rs
index 7d5033e3a6a..c7ab508ea6e 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<T> Pointer for *T {
         }
     }
 }
-
 impl<T> 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<T> Default for *T {
     fn fmt(me: &*T, f: &mut Formatter) { Pointer::fmt(me, f) }
 }
-
 impl<T> 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 48a13646686..284801ab123 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 486069db4f0..2f040ef2519 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<uint, @str>,
-                  ident: ast::Ident) -> @ast::Expr {
-        let ty = match arg {
+    fn format_arg(&self, sp: Span, argno: Either<uint, @str>,
+                  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 00000000000..d2a3fe2a9b1
--- /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 <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.
+
+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 00000000000..7bb8365bc12
--- /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 <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.
+
+fn main() {
+    format_args!("{}", ""); //~ ERROR: expected function
+}
diff --git a/src/test/run-pass/ifmt.rs b/src/test/run-pass/ifmt.rs
index ab66bfc1011..08d5ac5c1fb 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");
+}