1 // Copyright 2012 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.
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.
11 use ast::{blk_, attribute_, attr_outer, meta_word};
12 use ast::{crate, expr_, expr_mac, mac_invoc_tt};
13 use ast::{item_mac, stmt_, stmt_mac, stmt_expr, stmt_semi};
17 use codemap::{span, CallInfo, ExpandedFrom, NameAndSpan, spanned};
21 use parse::{parse_item_from_source_str};
23 pub fn expand_expr(extsbox: @mut SyntaxEnv,
28 orig: @fn(&expr_, span, @ast_fold) -> (expr_, span))
31 // expr_mac should really be expr_ext or something; it's the
32 // entry-point for all syntax extensions.
33 expr_mac(ref mac) => {
36 mac_invoc_tt(pth, ref tts) => {
37 if (pth.idents.len() > 1u) {
40 fmt!("expected macro name without module \
43 /* using idents and token::special_idents would make the
44 the macro names be hygienic */
45 let extname = cx.parse_sess().interner.get(pth.idents[0]);
46 // leaving explicit deref here to highlight unbox op:
47 match (*extsbox).find(&extname) {
51 fmt!("macro undefined: '%s'", *extname))
53 Some(@SE(NormalTT(SyntaxExpanderTT{
57 cx.bt_push(ExpandedFrom(CallInfo {
65 let expanded = match exp(cx, mac.span, *tts) {
67 MRAny(expr_maker,_,_) => expr_maker(),
72 "non-expr macro in expr pos: %s",
79 //keep going, outside-in
81 copy fld.fold_expr(expanded).node;
89 fmt!("'%s' is not a tt-style macro", *extname)
100 // This is a secondary mechanism for invoking syntax extensions on items:
101 // "decorator" attributes, such as #[auto_encode]. These are invoked by an
102 // attribute prefixing an item, and are interpreted by feeding the item
103 // through the named attribute _as a syntax extension_ and splicing in the
104 // resulting item vec into place in favour of the decorator. Note that
105 // these do _not_ work for macro extensions, just ItemDecorator ones.
107 // NB: there is some redundancy between this and expand_item, below, and
108 // they might benefit from some amount of semantic and language-UI merger.
109 pub fn expand_mod_items(extsbox: @mut SyntaxEnv,
113 orig: @fn(&ast::_mod, @ast_fold) -> ast::_mod)
115 // Fold the contents first:
116 let module_ = orig(module_, fld);
118 // For each item, look through the attributes. If any of them are
119 // decorated with "item decorators", then use that function to transform
120 // the item into a new set of items.
121 let new_items = do vec::flat_map(module_.items) |item| {
122 do vec::foldr(item.attrs, ~[*item]) |attr, items| {
123 let mname = attr::get_attr_name(attr);
125 match (*extsbox).find(&mname) {
126 Some(@SE(ItemDecorator(dec_fn))) => {
127 cx.bt_push(ExpandedFrom(CallInfo {
128 call_site: attr.span,
129 callee: NameAndSpan {
130 name: /*bad*/ copy *mname,
134 let r = dec_fn(cx, attr.span, attr.node.value, items);
143 ast::_mod { items: new_items, ..module_ }
147 // eval $e with a new exts frame:
148 macro_rules! with_exts_frame (
149 ($extsboxexpr:expr,$e:expr) =>
150 ({let extsbox = $extsboxexpr;
151 let oldexts = *extsbox;
152 *extsbox = oldexts.push_frame();
159 // When we enter a module, record it, for the sake of `module!`
160 pub fn expand_item(extsbox: @mut SyntaxEnv,
164 orig: @fn(@ast::item, @ast_fold) -> Option<@ast::item>)
165 -> Option<@ast::item> {
166 // need to do expansion first... it might turn out to be a module.
167 let maybe_it = match it.node {
168 ast::item_mac(*) => expand_item_mac(extsbox, cx, it, fld),
174 ast::item_mod(_) | ast::item_foreign_mod(_) => {
175 cx.mod_push(it.ident);
177 // don't push a macro scope for macro_escape:
178 if contains_macro_escape(it.attrs) {
181 // otherwise, push a scope:
182 with_exts_frame!(extsbox,orig(it,fld))
194 // does this attribute list contain "macro_escape" ?
195 pub fn contains_macro_escape (attrs: &[ast::attribute]) -> bool{
196 let mut accum = false;
197 do attrs.each |attr| {
198 let mname = attr::get_attr_name(attr);
199 if (mname == @~"macro_escape") {
209 // this macro disables (one layer of) macro
210 // scoping, to allow a block to add macro bindings
212 macro_rules! without_macro_scoping(
213 ($extsexpr:expr,$exp:expr) =>
215 // only evaluate this once:
216 let exts = $extsexpr;
217 // capture the existing binding:
218 let existingBlockBinding =
219 match exts.find(&@~" block"){
220 Some(binding) => binding,
221 None => cx.bug("expected to find \" block\" binding")
223 // this prevents the block from limiting the macros' scope:
224 exts.insert(@~" block",@ScopeMacros(false));
226 // reset the block binding. Note that since the original
227 // one may have been inherited, this procedure may wind
228 // up introducing a block binding where one didn't exist
230 exts.insert(@~" block",existingBlockBinding);
234 // Support for item-position macro invocations, exactly the same
235 // logic as for expression-position macro invocations.
236 pub fn expand_item_mac(extsbox: @mut SyntaxEnv,
237 cx: @ext_ctxt, it: @ast::item,
239 -> Option<@ast::item> {
240 let (pth, tts) = match it.node {
241 item_mac(codemap::spanned { node: mac_invoc_tt(pth, ref tts), _}) => {
244 _ => cx.span_bug(it.span, ~"invalid item macro invocation")
247 let extname = cx.parse_sess().interner.get(pth.idents[0]);
248 let expanded = match (*extsbox).find(&extname) {
249 None => cx.span_fatal(pth.span,
250 fmt!("macro undefined: '%s!'", *extname)),
252 Some(@SE(NormalTT(ref expand))) => {
253 if it.ident != parse::token::special_idents::invalid {
254 cx.span_fatal(pth.span,
255 fmt!("macro %s! expects no ident argument, \
256 given '%s'", *extname,
257 *cx.parse_sess().interner.get(it.ident)));
259 cx.bt_push(ExpandedFrom(CallInfo {
261 callee: NameAndSpan {
266 ((*expand).expander)(cx, it.span, tts)
268 Some(@SE(IdentTT(ref expand))) => {
269 if it.ident == parse::token::special_idents::invalid {
270 cx.span_fatal(pth.span,
271 fmt!("macro %s! expects an ident argument",
274 cx.bt_push(ExpandedFrom(CallInfo {
276 callee: NameAndSpan {
281 ((*expand).expander)(cx, it.span, it.ident, tts)
284 it.span, fmt!("%s! is not legal in item position", *extname))
287 let maybe_it = match expanded {
288 MRItem(it) => fld.fold_item(it),
289 MRExpr(_) => cx.span_fatal(pth.span,
290 ~"expr macro in item position: "
292 MRAny(_, item_maker, _) => item_maker().chain(|i| {fld.fold_item(i)}),
294 extsbox.insert(@/*bad*/ copy mdef.name, @SE((*mdef).ext));
303 pub fn expand_stmt(extsbox: @mut SyntaxEnv,
308 orig: @fn(&stmt_, span, @ast_fold) -> (stmt_, span))
310 let (mac, pth, tts, semi) = match *s {
311 stmt_mac(ref mac, semi) => {
313 mac_invoc_tt(pth, ref tts) => {
314 (copy *mac, pth, copy *tts, semi)
318 _ => return orig(s, sp, fld)
320 if (pth.idents.len() > 1u) {
323 fmt!("expected macro name without module \
326 let extname = cx.parse_sess().interner.get(pth.idents[0]);
327 let (fully_expanded, sp) = match (*extsbox).find(&extname) {
329 cx.span_fatal(pth.span, fmt!("macro undefined: '%s'", *extname)),
332 SyntaxExpanderTT{expander: exp, span: exp_sp}))) => {
333 cx.bt_push(ExpandedFrom(CallInfo {
335 callee: NameAndSpan { name: copy *extname, span: exp_sp }
337 let expanded = match exp(cx, mac.span, tts) {
339 @codemap::spanned { node: stmt_expr(e, cx.next_id()),
341 MRAny(_,_,stmt_mkr) => stmt_mkr(),
344 fmt!("non-stmt macro in stmt pos: %s", *extname))
347 //keep going, outside-in
348 let fully_expanded = copy fld.fold_stmt(expanded).node;
355 cx.span_fatal(pth.span,
356 fmt!("'%s' is not a tt-style macro", *extname))
360 (match fully_expanded {
361 stmt_expr(e, stmt_id) if semi => stmt_semi(e, stmt_id),
362 _ => { fully_expanded } /* might already have a semi */
369 pub fn expand_block(extsbox: @mut SyntaxEnv,
374 orig: @fn(&blk_, span, @ast_fold) -> (blk_, span))
376 match (*extsbox).find(&@~" block") {
377 // no scope limit on macros in this block, no need
378 // to push an exts frame:
379 Some(@ScopeMacros(false)) => {
382 // this block should limit the scope of its macros:
383 Some(@ScopeMacros(true)) => {
384 // see note below about treatment of exts table
385 with_exts_frame!(extsbox,orig(blk,sp,fld))
388 ~"expected ScopeMacros binding for \" block\"")
392 pub fn new_span(cx: @ext_ctxt, sp: span) -> span {
393 /* this discards information in the case of macro-defining macros */
394 return span {lo: sp.lo, hi: sp.hi, expn_info: cx.backtrace()};
397 // FIXME (#2247): this is a moderately bad kludge to inject some macros into
398 // the default compilation environment. It would be much nicer to use
399 // a mechanism like syntax_quote to ensure hygiene.
401 pub fn core_macros() -> ~str {
404 macro_rules! ignore (($($x:tt)*) => (()))
408 __log(1u32, fmt!( \"%?\", $arg ))
410 ($( $arg:expr ),+) => (
411 __log(1u32, fmt!( $($arg),+ ))
417 __log(2u32, fmt!( \"%?\", $arg ))
419 ($( $arg:expr ),+) => (
420 __log(2u32, fmt!( $($arg),+ ))
426 __log(3u32, fmt!( \"%?\", $arg ))
428 ($( $arg:expr ),+) => (
429 __log(3u32, fmt!( $($arg),+ ))
435 __log(4u32, fmt!( \"%?\", $arg ))
437 ($( $arg:expr ),+) => (
438 __log(4u32, fmt!( $($arg),+ ))
444 fail!(\"explicit failure\")
447 ::core::sys::FailWithCause::fail_with($msg, file!(), line!())
449 ($( $arg:expr ),+) => (
450 ::core::sys::FailWithCause::fail_with(fmt!( $($arg),+ ), file!(), line!())
457 ::core::sys::FailWithCause::fail_with(
458 ~\"assertion failed: \" + stringify!($cond), file!(), line!())
461 ($cond:expr, $msg:expr) => {
463 ::core::sys::FailWithCause::fail_with($msg, file!(), line!())
466 ($cond:expr, $( $arg:expr ),+) => {
468 ::core::sys::FailWithCause::fail_with(fmt!( $($arg),+ ), file!(), line!())
473 macro_rules! assert_eq (
474 ($given:expr , $expected:expr) => (
476 let given_val = $given;
477 let expected_val = $expected;
478 // check both directions of equality....
479 if !((given_val == expected_val) && (expected_val == given_val)) {
480 fail!(fmt!(\"left: %? != right: %?\", given_val, expected_val));
486 macro_rules! condition (
488 { $c:ident: $in:ty -> $out:ty; } => {
491 fn key(_x: @::core::condition::Handler<$in,$out>) { }
494 ::core::condition::Condition<'static,$in,$out> =
495 ::core::condition::Condition {
496 name: stringify!($c),
507 pub fn expand_crate(parse_sess: @mut parse::ParseSess,
508 cfg: ast::crate_cfg, c: @crate) -> @crate {
509 // adding *another* layer of indirection here so that the block
510 // visitor can swap out one exts table for another for the duration
511 // of the block. The cleaner alternative would be to thread the
512 // exts table through the fold, but that would require updating
513 // every method/element of AstFoldFns in fold.rs.
514 let extsbox = @mut syntax_expander_table();
515 let afp = default_ast_fold();
516 let cx: @ext_ctxt = mk_ctxt(parse_sess, copy cfg);
517 let f_pre = @AstFoldFns {
518 fold_expr: |expr,span,recur|
519 expand_expr(extsbox, cx, expr, span, recur, afp.fold_expr),
520 fold_mod: |modd,recur|
521 expand_mod_items(extsbox, cx, modd, recur, afp.fold_mod),
522 fold_item: |item,recur|
523 expand_item(extsbox, cx, item, recur, afp.fold_item),
524 fold_stmt: |stmt,span,recur|
525 expand_stmt(extsbox, cx, stmt, span, recur, afp.fold_stmt),
526 fold_block: |blk,span,recur|
527 expand_block(extsbox, cx, blk, span, recur, afp.fold_block),
528 new_span: |a| new_span(cx, a),
530 let f = make_fold(f_pre);
531 // add a bunch of macros as though they were placed at the
532 // head of the program (ick).
535 span: codemap::dummy_sp(),
539 node: meta_word(@~"macro_escape"),
540 span: codemap::dummy_sp(),
542 is_sugared_doc: false,
547 let cm = match parse_item_from_source_str(~"<core-macros>",
553 None => cx.bug(~"expected core macros to parse correctly")
555 // This is run for its side-effects on the expander env,
556 // as it registers all the core macros as expanders.
562 // given a function from paths to paths, produce
563 // an ast_fold that applies that function:
564 fn fun_to_path_folder(f: @fn(&ast::Path)->ast::Path) -> @ast_fold{
565 let afp = default_ast_fold();
566 let f_pre = @AstFoldFns{
567 fold_path : |p, _| f(p),
572 /* going to have to figure out whether the table is passed in or
573 extracted from TLS...
574 // update the ctxts in a path to get a rename node
575 fn ctxt_update_rename(from: ast::Name,
576 fromctx: ast::SyntaxContext, to: ast::Name) ->
577 @fn(&ast::Path,@ast_fold)->ast::Path {
578 return |p:&ast::Path,_|
579 ast::Path {span: p.span,
581 idents: p.idents.map(|id|
584 // this needs to be cached....
585 ctxt: Some(@ast::Rename(from,fromctx,
592 // update the ctxts in a path to get a mark node
593 fn ctxt_update_mark(mark: uint) ->
594 @fn(&ast::Path,@ast_fold)->ast::Path {
595 return |p:&ast::Path,_|
596 ast::Path {span: p.span,
598 idents: p.idents.map(|id|
601 // this needs to be cached....
602 ctxt: Some(@ast::Mark(mark,id.ctxt))
613 use ast::{attribute_, attr_outer, meta_word};
615 use codemap::spanned;
617 use core::option::{None, Some};
619 // make sure that fail! is present
620 #[test] fn fail_exists_test () {
621 let src = ~"fn main() { fail!(~\"something appropriately gloomy\");}";
622 let sess = parse::new_parse_sess(None);
624 let crate_ast = parse::parse_crate_from_source_str(
628 expand_crate(sess,cfg,crate_ast);
631 // these following tests are quite fragile, in that they don't test what
632 // *kind* of failure occurs.
634 // make sure that macros can leave scope
636 #[test] fn macros_cant_escape_fns_test () {
637 let src = ~"fn bogus() {macro_rules! z (() => (3+4))}\
638 fn inty() -> int { z!() }";
639 let sess = parse::new_parse_sess(None);
641 let crate_ast = parse::parse_crate_from_source_str(
646 expand_crate(sess,cfg,crate_ast);
649 // make sure that macros can leave scope for modules
651 #[test] fn macros_cant_escape_mods_test () {
652 let src = ~"mod foo {macro_rules! z (() => (3+4))}\
653 fn inty() -> int { z!() }";
654 let sess = parse::new_parse_sess(None);
656 let crate_ast = parse::parse_crate_from_source_str(
661 expand_crate(sess,cfg,crate_ast);
664 // macro_escape modules shouldn't cause macros to leave scope
665 #[test] fn macros_can_escape_flattened_mods_test () {
666 let src = ~"#[macro_escape] mod foo {macro_rules! z (() => (3+4))}\
667 fn inty() -> int { z!() }";
668 let sess = parse::new_parse_sess(None);
670 let crate_ast = parse::parse_crate_from_source_str(
675 expand_crate(sess,cfg,crate_ast);
678 #[test] fn core_macros_must_parse () {
681 macro_rules! ignore (($($x:tt)*) => (()))
683 macro_rules! error ( ($( $arg:expr ),+) => (
684 log(::core::error, fmt!( $($arg),+ )) ))
686 let sess = parse::new_parse_sess(None);
688 let item_ast = parse::parse_item_from_source_str(
691 cfg,~[make_dummy_attr (@~"macro_escape")],sess);
693 Some(_) => (), // success
694 None => fail!(~"expected this to parse")
698 #[test] fn test_contains_flatten (){
699 let attr1 = make_dummy_attr (@~"foo");
700 let attr2 = make_dummy_attr (@~"bar");
701 let escape_attr = make_dummy_attr (@~"macro_escape");
702 let attrs1 = ~[attr1, escape_attr, attr2];
703 assert_eq!(contains_macro_escape (attrs1),true);
704 let attrs2 = ~[attr1,attr2];
705 assert_eq!(contains_macro_escape (attrs2),false);
708 // make a "meta_word" outer attribute with the given name
709 fn make_dummy_attr(s: @~str) -> ast::attribute {
711 span:codemap::dummy_sp(),
716 span: codemap::dummy_sp(),
718 is_sugared_doc: false,