]> git.lizzy.rs Git - rust.git/commitdiff
Implement a concat!() format extension
authorAlex Crichton <alex@alexcrichton.com>
Sun, 6 Oct 2013 04:15:46 +0000 (21:15 -0700)
committerAlex Crichton <alex@alexcrichton.com>
Thu, 31 Oct 2013 20:46:10 +0000 (13:46 -0700)
This extension can be used to concatenate string literals at compile time. C has
this useful ability when placing string literals lexically next to one another,
but this needs to be handled at the syntax extension level to recursively expand
macros.

The major use case for this is something like:

    macro_rules! mylog( ($fmt:expr $($arg:tt)*) => {
        error2!(concat!(file!(), ":", line!(), " - ", $fmt) $($arg)*);
    })

Where the mylog macro will automatically prepend the filename/line number to the
beginning of every log message.

doc/rust.md
src/libsyntax/ext/base.rs
src/libsyntax/ext/concat.rs [new file with mode: 0644]
src/libsyntax/ext/expand.rs
src/libsyntax/ext/format.rs
src/libsyntax/syntax.rs
src/test/compile-fail/concat.rs [new file with mode: 0644]
src/test/run-pass/concat.rs [new file with mode: 0644]

index 40a3bc12798f75e390a647171587048d46266a2e..0023e65cd83917a97cb2f5cc4855c0c979c921dd 100644 (file)
@@ -568,6 +568,7 @@ This requirement most often affects name-designator pairs when they occur at the
 * `log_syntax!` : print out the arguments at compile time
 * `trace_macros!` : supply `true` or `false` to enable or disable macro expansion logging
 * `stringify!` : turn the identifier argument into a string literal
+* `concat!` : concatenates a comma-separated list of literals
 * `concat_idents!` : create a new identifier by concatenating the arguments
 
 # Crates and source files
index 1f9fe28a46d5b5088de46c10906da6774f3645af..7ce73a4afef70b04fbdfa06ab47db30a054a84c8 100644 (file)
@@ -14,6 +14,7 @@
 use codemap::{CodeMap, Span, ExpnInfo};
 use diagnostic::span_handler;
 use ext;
+use ext::expand;
 use parse;
 use parse::token;
 use parse::token::{ident_to_str, intern, str_to_ident};
@@ -246,6 +247,9 @@ fn builtin_item_tt_no_ctxt(f: SyntaxExpanderTTItemFunNoCtxt) -> @Transformer {
     syntax_expanders.insert(intern("concat_idents"),
                             builtin_normal_tt_no_ctxt(
                                     ext::concat_idents::expand_syntax_ext));
+    syntax_expanders.insert(intern("concat"),
+                            builtin_normal_tt_no_ctxt(
+                                    ext::concat::expand_syntax_ext));
     syntax_expanders.insert(intern(&"log_syntax"),
                             builtin_normal_tt_no_ctxt(
                                     ext::log_syntax::expand_syntax_ext));
@@ -338,6 +342,22 @@ pub fn new(parse_sess: @mut parse::ParseSess, cfg: ast::CrateConfig)
         }
     }
 
+    pub fn expand_expr(@self, mut e: @ast::Expr) -> @ast::Expr {
+        loop {
+            match e.node {
+                ast::ExprMac(*) => {
+                    let extsbox = @mut syntax_expander_table();
+                    let expander = expand::MacroExpander {
+                        extsbox: extsbox,
+                        cx: self,
+                    };
+                    e = expand::expand_expr(extsbox, self, e, &expander);
+                }
+                _ => return e
+            }
+        }
+    }
+
     pub fn codemap(&self) -> @CodeMap { self.parse_sess.cm }
     pub fn parse_sess(&self) -> @mut parse::ParseSess { self.parse_sess }
     pub fn cfg(&self) -> ast::CrateConfig { self.cfg.clone() }
diff --git a/src/libsyntax/ext/concat.rs b/src/libsyntax/ext/concat.rs
new file mode 100644 (file)
index 0000000..a89e8d2
--- /dev/null
@@ -0,0 +1,58 @@
+// 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.
+
+use std::char;
+
+use ast;
+use codemap;
+use ext::base;
+use ext::build::AstBuilder;
+
+pub fn expand_syntax_ext(cx: @base::ExtCtxt,
+                         sp: codemap::Span,
+                         tts: &[ast::token_tree]) -> base::MacResult {
+    let es = base::get_exprs_from_tts(cx, sp, tts);
+    let mut accumulator = ~"";
+    for e in es.move_iter() {
+        let e = cx.expand_expr(e);
+        match e.node {
+            ast::ExprLit(lit) => {
+                match lit.node {
+                    ast::lit_str(s, _) |
+                    ast::lit_float(s, _) |
+                    ast::lit_float_unsuffixed(s) => {
+                        accumulator.push_str(s);
+                    }
+                    ast::lit_char(c) => {
+                        accumulator.push_char(char::from_u32(c).unwrap());
+                    }
+                    ast::lit_int(i, _) |
+                    ast::lit_int_unsuffixed(i) => {
+                        accumulator.push_str(format!("{}", i));
+                    }
+                    ast::lit_uint(u, _) => {
+                        accumulator.push_str(format!("{}", u));
+                    }
+                    ast::lit_nil => {}
+                    ast::lit_bool(b) => {
+                        accumulator.push_str(format!("{}", b));
+                    }
+                    ast::lit_binary(*) => {
+                        cx.span_err(e.span, "cannot concatenate a binary literal");
+                    }
+                }
+            }
+            _ => {
+                cx.span_err(e.span, "expected a literal");
+            }
+        }
+    }
+    return base::MRExpr(cx.expr_str(sp, accumulator.to_managed()));
+}
index 052b177d4d8d3f4d2f49e02803fddc4334fc4af4..ba946d5fb1f1d8a48fc8eccf0ff4b02e536f1035 100644 (file)
@@ -1083,7 +1083,7 @@ struct NoOpFolder {
 
 impl ast_fold for NoOpFolder {}
 
-struct MacroExpander {
+pub struct MacroExpander {
     extsbox: @mut SyntaxEnv,
     cx: @ExtCtxt,
 }
index 31befed6c0cd4f1e195a35d80cdca81290448812..943279d2dc6831ae76661d001ba72158b8221a5c 100644 (file)
@@ -735,8 +735,10 @@ pub fn expand_args(ecx: @ExtCtxt, sp: Span,
         (_, None) => { return MRExpr(ecx.expr_uint(sp, 2)); }
     };
     cx.fmtsp = efmt.span;
-    let (fmt, _fmt_str_style) = expr_to_str(ecx, efmt,
-                                            "format argument must be a string literal.");
+    // Be sure to recursively expand macros just in case the format string uses
+    // a macro to build the format expression.
+    let (fmt, _) = expr_to_str(ecx, ecx.expand_expr(efmt),
+                               "format argument must be a string literal.");
 
     let mut err = false;
     do parse::parse_error::cond.trap(|m| {
index 00fc676f5957106ddd282785de9ab36c4cf998de..cf2c01e92b43bd145ed38250e8e279b67fa870ae 100644 (file)
@@ -77,6 +77,7 @@ pub mod tt {
     pub mod format;
     pub mod env;
     pub mod bytes;
+    pub mod concat;
     pub mod concat_idents;
     pub mod log_syntax;
     pub mod auto_encode;
diff --git a/src/test/compile-fail/concat.rs b/src/test/compile-fail/concat.rs
new file mode 100644 (file)
index 0000000..c34e402
--- /dev/null
@@ -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() {
+    concat!(foo);   //~ ERROR: expected a literal
+    concat!(foo()); //~ ERROR: expected a literal
+}
diff --git a/src/test/run-pass/concat.rs b/src/test/run-pass/concat.rs
new file mode 100644 (file)
index 0000000..bcf1c4c
--- /dev/null
@@ -0,0 +1,19 @@
+// 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.
+
+pub fn main() {
+    assert_eq!(format!(concat!("foo", "bar", "{}"), "baz"), ~"foobarbaz");
+    assert_eq!(format!(concat!()), ~"");
+
+    assert_eq!(
+        concat!(1, 2i, 3u, 4f32, 4.0, 'a', true, ()),
+        "12344.0atrue"
+    );
+}