Skip to content

Fold traverses macros #6981

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jun 6, 2013
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
113 changes: 110 additions & 3 deletions src/libsyntax/fold.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use core::prelude::*;
use ast::*;
use ast;
use codemap::{span, spanned};
use parse::token;
use opt_vec::OptVec;

use core::vec;
Expand Down Expand Up @@ -115,11 +116,43 @@ fn fold_arg_(a: arg, fld: @ast_fold) -> arg {
id: fld.new_id(a.id),
}
}

//used in noop_fold_expr, and possibly elsewhere in the future
fn fold_mac_(m: &mac, fld: @ast_fold) -> mac {
spanned {
node: match m.node { mac_invoc_tt(*) => copy m.node },
span: fld.new_span(m.span),
node: match m.node {
mac_invoc_tt(p,ref tts) =>
mac_invoc_tt(fld.fold_path(p),
fold_tts(*tts,fld))
},
span: fld.new_span(m.span)
}
}

fn fold_tts(tts : &[token_tree], fld: @ast_fold) -> ~[token_tree] {
do tts.map |tt| {
match *tt {
tt_tok(span, ref tok) =>
tt_tok(span,maybe_fold_ident(tok,fld)),
tt_delim(ref tts) =>
tt_delim(fold_tts(*tts,fld)),
tt_seq(span, ref pattern, ref sep, is_optional) =>
tt_seq(span,
fold_tts(*pattern,fld),
sep.map(|tok|maybe_fold_ident(tok,fld)),
is_optional),
tt_nonterminal(sp,ref ident) =>
tt_nonterminal(sp,fld.fold_ident(*ident))
}
}
}

// apply ident folder if it's an ident, otherwise leave it alone
fn maybe_fold_ident(t : &token::Token, fld: @ast_fold) -> token::Token {
match *t {
token::IDENT(id,followed_by_colons) =>
token::IDENT(fld.fold_ident(id),followed_by_colons),
_ => copy *t
}
}

Expand Down Expand Up @@ -290,7 +323,10 @@ pub fn noop_fold_item_underscore(i: &item_, fld: @ast_fold) -> item_ {
}
item_mac(ref m) => {
// FIXME #2888: we might actually want to do something here.
item_mac(copy *m)
// ... okay, we're doing something. It would probably be nicer
// to add something to the ast_fold trait, but I'll defer
// that work.
item_mac(fold_mac_(m,fld))
}
}
}
Expand Down Expand Up @@ -904,3 +940,74 @@ impl AstFoldExtensions for @ast_fold {
pub fn make_fold(afp: ast_fold_fns) -> @ast_fold {
afp as @ast_fold
}

#[cfg(test)]
mod test {
use ast;
use util::parser_testing::{string_to_crate, matches_codepattern};
use parse::token;
use print::pprust;
use super::*;

// taken from expand
// given a function from idents to idents, produce
// an ast_fold that applies that function:
pub fn fun_to_ident_folder(f: @fn(ast::ident)->ast::ident) -> @ast_fold{
let afp = default_ast_fold();
let f_pre = @AstFoldFns{
fold_ident : |id, _| f(id),
.. *afp
};
make_fold(f_pre)
}

// this version doesn't care about getting comments or docstrings in.
fn fake_print_crate(s: @pprust::ps, crate: ast::crate) {
pprust::print_mod(s, &crate.node.module, crate.node.attrs);
}

// change every identifier to "zz"
pub fn to_zz() -> @fn(ast::ident)->ast::ident {
let zz_id = token::str_to_ident("zz");
|id| {zz_id}
}

// maybe add to expand.rs...
macro_rules! assert_pred (
($pred:expr, $predname:expr, $a:expr , $b:expr) => (
{
let pred_val = $pred;
let a_val = $a;
let b_val = $b;
if !(pred_val(a_val,b_val)) {
fail!("expected args satisfying %s, got %? and %?",
$predname, a_val, b_val);
}
}
)
)

// make sure idents get transformed everywhere
#[test] fn ident_transformation () {
let zz_fold = fun_to_ident_folder(to_zz());
let ast = string_to_crate(@~"#[a] mod b {fn c (d : e, f : g) {h!(i,j,k);l;m}}");
assert_pred!(matches_codepattern,
"matches_codepattern",
pprust::to_str(zz_fold.fold_crate(ast),fake_print_crate,
token::get_ident_interner()),
~"#[a]mod zz{fn zz(zz:zz,zz:zz){zz!(zz,zz,zz);zz;zz}}");
}

// even inside macro defs....
#[test] fn ident_transformation_in_defs () {
let zz_fold = fun_to_ident_folder(to_zz());
let ast = string_to_crate(@~"macro_rules! a {(b $c:expr $(d $e:token)f+
=> (g $(d $d $e)+))} ");
assert_pred!(matches_codepattern,
"matches_codepattern",
pprust::to_str(zz_fold.fold_crate(ast),fake_print_crate,
token::get_ident_interner()),
~"zz!zz((zz$zz:zz$(zz $zz:zz)zz+=>(zz$(zz$zz$zz)+)))");
}

}
78 changes: 78 additions & 0 deletions src/libsyntax/util/parser_testing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,3 +69,81 @@ pub fn string_to_pat(source_str : @~str) -> @ast::pat {
pub fn strs_to_idents(ids: ~[&str]) -> ~[ast::ident] {
ids.map(|u| token::str_to_ident(*u))
}

// does the given string match the pattern? whitespace in the first string
// may be deleted or replaced with other whitespace to match the pattern.
// this function is unicode-ignorant; fortunately, the careful design of
// UTF-8 mitigates this ignorance. In particular, this function only collapses
// sequences of \n, \r, ' ', and \t, but it should otherwise tolerate unicode
// chars. Unsurprisingly, it doesn't do NKF-normalization(?).
pub fn matches_codepattern(a : &str, b : &str) -> bool {
let mut idx_a = 0;
let mut idx_b = 0;
loop {
if (idx_a == a.len() && idx_b == b.len()) {
return true;
}
else if (idx_a == a.len()) {return false;}
else if (idx_b == b.len()) {
// maybe the stuff left in a is all ws?
if (is_whitespace(a.char_at(idx_a))) {
return (scan_for_non_ws_or_end(a,idx_a) == a.len());
} else {
return false;
}
}
// ws in both given and pattern:
else if (is_whitespace(a.char_at(idx_a))
&& is_whitespace(b.char_at(idx_b))) {
idx_a = scan_for_non_ws_or_end(a,idx_a);
idx_b = scan_for_non_ws_or_end(b,idx_b);
}
// ws in given only:
else if (is_whitespace(a.char_at(idx_a))) {
idx_a = scan_for_non_ws_or_end(a,idx_a);
}
// *don't* silently eat ws in expected only.
else if (a.char_at(idx_a) == b.char_at(idx_b)) {
idx_a += 1;
idx_b += 1;
}
else {
return false;
}
}
}

// given a string and an index, return the first uint >= idx
// that is a non-ws-char or is outside of the legal range of
// the string.
fn scan_for_non_ws_or_end(a : &str, idx: uint) -> uint {
let mut i = idx;
let len = a.len();
while ((i < len) && (is_whitespace(a.char_at(i)))) {
i += 1;
}
i
}

// copied from lexer.
pub fn is_whitespace(c: char) -> bool {
return c == ' ' || c == '\t' || c == '\r' || c == '\n';
}

#[cfg(test)]
mod test {
use super::*;

#[test] fn eqmodws() {
assert_eq!(matches_codepattern("",""),true);
assert_eq!(matches_codepattern("","a"),false);
assert_eq!(matches_codepattern("a",""),false);
assert_eq!(matches_codepattern("a","a"),true);
assert_eq!(matches_codepattern("a b","a \n\t\r b"),true);
assert_eq!(matches_codepattern("a b ","a \n\t\r b"),true);
assert_eq!(matches_codepattern("a b","a \n\t\r b "),false);
assert_eq!(matches_codepattern("a b","a b"),true);
assert_eq!(matches_codepattern("ab","a b"),false);
assert_eq!(matches_codepattern("a b","ab"),true);
}
}