Skip to content

Commit a49e65c

Browse files
committed
Implement a concat!() format extension
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.
1 parent e976de3 commit a49e65c

File tree

8 files changed

+118
-3
lines changed

8 files changed

+118
-3
lines changed

doc/rust.md

+1
Original file line numberDiff line numberDiff line change
@@ -568,6 +568,7 @@ This requirement most often affects name-designator pairs when they occur at the
568568
* `log_syntax!` : print out the arguments at compile time
569569
* `trace_macros!` : supply `true` or `false` to enable or disable macro expansion logging
570570
* `stringify!` : turn the identifier argument into a string literal
571+
* `concat!` : concatenates a comma-separated list of literals
571572
* `concat_idents!` : create a new identifier by concatenating the arguments
572573

573574
# Crates and source files

src/libsyntax/ext/base.rs

+20
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ use codemap;
1414
use codemap::{CodeMap, Span, ExpnInfo};
1515
use diagnostic::span_handler;
1616
use ext;
17+
use ext::expand;
1718
use parse;
1819
use parse::token;
1920
use parse::token::{ident_to_str, intern, str_to_ident};
@@ -246,6 +247,9 @@ pub fn syntax_expander_table() -> SyntaxEnv {
246247
syntax_expanders.insert(intern("concat_idents"),
247248
builtin_normal_tt_no_ctxt(
248249
ext::concat_idents::expand_syntax_ext));
250+
syntax_expanders.insert(intern("concat"),
251+
builtin_normal_tt_no_ctxt(
252+
ext::concat::expand_syntax_ext));
249253
syntax_expanders.insert(intern(&"log_syntax"),
250254
builtin_normal_tt_no_ctxt(
251255
ext::log_syntax::expand_syntax_ext));
@@ -338,6 +342,22 @@ impl ExtCtxt {
338342
}
339343
}
340344

345+
pub fn expand_expr(@self, mut e: @ast::Expr) -> @ast::Expr {
346+
loop {
347+
match e.node {
348+
ast::ExprMac(*) => {
349+
let extsbox = @mut syntax_expander_table();
350+
let expander = expand::MacroExpander {
351+
extsbox: extsbox,
352+
cx: self,
353+
};
354+
e = expand::expand_expr(extsbox, self, e, &expander);
355+
}
356+
_ => return e
357+
}
358+
}
359+
}
360+
341361
pub fn codemap(&self) -> @CodeMap { self.parse_sess.cm }
342362
pub fn parse_sess(&self) -> @mut parse::ParseSess { self.parse_sess }
343363
pub fn cfg(&self) -> ast::CrateConfig { self.cfg.clone() }

src/libsyntax/ext/concat.rs

+58
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
// Copyright 2013 The Rust Project Developers. See the COPYRIGHT
2+
// file at the top-level directory of this distribution and at
3+
// http://rust-lang.org/COPYRIGHT.
4+
//
5+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8+
// option. This file may not be copied, modified, or distributed
9+
// except according to those terms.
10+
11+
use std::char;
12+
13+
use ast;
14+
use codemap;
15+
use ext::base;
16+
use ext::build::AstBuilder;
17+
18+
pub fn expand_syntax_ext(cx: @base::ExtCtxt,
19+
sp: codemap::Span,
20+
tts: &[ast::token_tree]) -> base::MacResult {
21+
let es = base::get_exprs_from_tts(cx, sp, tts);
22+
let mut accumulator = ~"";
23+
for e in es.move_iter() {
24+
let e = cx.expand_expr(e);
25+
match e.node {
26+
ast::ExprLit(lit) => {
27+
match lit.node {
28+
ast::lit_str(s, _) |
29+
ast::lit_float(s, _) |
30+
ast::lit_float_unsuffixed(s) => {
31+
accumulator.push_str(s);
32+
}
33+
ast::lit_char(c) => {
34+
accumulator.push_char(char::from_u32(c).unwrap());
35+
}
36+
ast::lit_int(i, _) |
37+
ast::lit_int_unsuffixed(i) => {
38+
accumulator.push_str(format!("{}", i));
39+
}
40+
ast::lit_uint(u, _) => {
41+
accumulator.push_str(format!("{}", u));
42+
}
43+
ast::lit_nil => {}
44+
ast::lit_bool(b) => {
45+
accumulator.push_str(format!("{}", b));
46+
}
47+
ast::lit_binary(*) => {
48+
cx.span_err(e.span, "cannot concatenate a binary literal");
49+
}
50+
}
51+
}
52+
_ => {
53+
cx.span_err(e.span, "expected a literal");
54+
}
55+
}
56+
}
57+
return base::MRExpr(cx.expr_str(sp, accumulator.to_managed()));
58+
}

src/libsyntax/ext/expand.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1083,7 +1083,7 @@ struct NoOpFolder {
10831083

10841084
impl ast_fold for NoOpFolder {}
10851085

1086-
struct MacroExpander {
1086+
pub struct MacroExpander {
10871087
extsbox: @mut SyntaxEnv,
10881088
cx: @ExtCtxt,
10891089
}

src/libsyntax/ext/format.rs

+4-2
Original file line numberDiff line numberDiff line change
@@ -735,8 +735,10 @@ pub fn expand_args(ecx: @ExtCtxt, sp: Span,
735735
(_, None) => { return MRExpr(ecx.expr_uint(sp, 2)); }
736736
};
737737
cx.fmtsp = efmt.span;
738-
let (fmt, _fmt_str_style) = expr_to_str(ecx, efmt,
739-
"format argument must be a string literal.");
738+
// Be sure to recursively expand macros just in case the format string uses
739+
// a macro to build the format expression.
740+
let (fmt, _) = expr_to_str(ecx, ecx.expand_expr(efmt),
741+
"format argument must be a string literal.");
740742

741743
let mut err = false;
742744
do parse::parse_error::cond.trap(|m| {

src/libsyntax/syntax.rs

+1
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ pub mod ext {
7777
pub mod format;
7878
pub mod env;
7979
pub mod bytes;
80+
pub mod concat;
8081
pub mod concat_idents;
8182
pub mod log_syntax;
8283
pub mod auto_encode;

src/test/compile-fail/concat.rs

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// Copyright 2013 The Rust Project Developers. See the COPYRIGHT
2+
// file at the top-level directory of this distribution and at
3+
// http://rust-lang.org/COPYRIGHT.
4+
//
5+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8+
// option. This file may not be copied, modified, or distributed
9+
// except according to those terms.
10+
11+
fn main() {
12+
concat!(foo); //~ ERROR: expected a literal
13+
concat!(foo()); //~ ERROR: expected a literal
14+
}

src/test/run-pass/concat.rs

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// Copyright 2013 The Rust Project Developers. See the COPYRIGHT
2+
// file at the top-level directory of this distribution and at
3+
// http://rust-lang.org/COPYRIGHT.
4+
//
5+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8+
// option. This file may not be copied, modified, or distributed
9+
// except according to those terms.
10+
11+
pub fn main() {
12+
assert_eq!(format!(concat!("foo", "bar", "{}"), "baz"), ~"foobarbaz");
13+
assert_eq!(format!(concat!()), ~"");
14+
15+
assert_eq!(
16+
concat!(1, 2i, 3u, 4f32, 4.0, 'a', true, ()),
17+
"12344.0atrue"
18+
);
19+
}

0 commit comments

Comments
 (0)