2016-08-23 25 views
6

Ben Neredeyse şimdi yapılıyorModül oluşturan bir Rust derleyici eklentisi nasıl yazılır?

choose! { 
    test_a 
    test_b 
} 

#[cfg(feature = "a")] 
mod test_a; 
#[cfg(feature = "b")] 
mod test_b; 

için genişleyen bir Pas derleyici eklenti yazıyorum ama modül sonunda genişletilmiş kodda bir şey içermiyor. Sanırım nedeni, modül modülü dosyasını kapsamıyor.

use syntax::ast; 
use syntax::ptr::P; 
use syntax::codemap; 
use syntax::parse::token; 
use syntax::tokenstream::TokenTree; 
use syntax::ext::base::{ExtCtxt, MacResult, DummyResult, MacEager}; 
use syntax::ext::build::AstBuilder; 
use syntax_pos::Span; 
use rustc_plugin::Registry; 
use syntax::util::small_vector::SmallVector; 

// Ideally, it will expand 
// 
// ```rust 
// choose! { 
// test_a 
// test_b 
// } 
// ``` 
// to 
// ```rust 
// #[cfg(feature = "a")] 
// mod test_a; 
// #[cfg(feature = "b")] 
// mod test_b; 
// ``` 
// 
// but the modules contain nothing in the expanded code at present 

fn choose(cx: &mut ExtCtxt, sp: Span, args: &[TokenTree]) -> Box<MacResult + 'static> { 
    let mut test_mods: SmallVector<P<ast::Item>> = SmallVector::many(vec![]); 
    for arg in args { 
     let mut attrs = vec![]; 
     let text = match arg { 
      &TokenTree::Token(_, token::Ident(s)) => s.to_string(), 
      _ => { 
       return DummyResult::any(sp); 
      } 
     }; 
     let cfg_str = token::InternedString::new("cfg"); 
     let feat_str = token::InternedString::new("feature"); 
     attrs.push(cx.attribute(sp, 
           cx.meta_list(sp, 
              cfg_str, 
              vec![cx.meta_name_value(sp, 
                    feat_str, 
                    ast::LitKind::Str(token::intern_and_get_ident(text.trim_left_matches("test_")), ast::StrStyle::Cooked))]))); 
     test_mods.push(P(ast::Item { 
      ident: cx.ident_of(text.as_str()), 
      attrs: attrs, 
      id: ast::DUMMY_NODE_ID, 
      node: ast::ItemKind::Mod(
       // === How to include the specified module file here? === 
       ast::Mod { 
        inner: codemap::DUMMY_SP, 
        items: vec![], 
       } 
      ), 
      vis: ast::Visibility::Inherited, 
      span: sp, 
     })) 
    } 

    MacEager::items(test_mods) 
} 

#[plugin_registrar] 
pub fn plugin_registrar(reg: &mut Registry) { 
    reg.register_macro("choose", choose); 
} 

(Gist)

cevap

1

2016-08-25 güncelleme: elle modül yolunu ayarlayarak önlemek için kullanım libsyntax::parse::new_parser_from_source_str.new_parser_from_source_str, yalnızca beklenmedik olan CWD'deki modülleri bulur.

olarak @Francis tarafından işaret, bir modül dosyasının gerçek yolu foo/mod.rs, gibi bir şey olabilir ve ben libsyntax::parse yılında, bir kaynak dizesi yeni ayrıştırıcı yaratabilir new_parser_from_source_str adında bir fonksiyon keşfedilmiştir, bu yüzden Bu davayı elimden ele almak için derleyiciye sormaya karar verdim, bu yüzden bu davayı el ile ele almalıyım. güncellenmiş kod:


fn choose(cx: &mut ExtCtxt, sp: Span, args: &[TokenTree]) -> Box<MacResult + 'static> { 
    let mut test_mods = SmallVector::zero(); 
    let cfg_str = intern("cfg"); 
    let ftre_str = intern("feature"); 
    for arg in args { 
     let mut attrs = vec![]; 
     let mod_name = match arg { 
      &TokenTree::Token(_, token::Ident(s)) => s.to_string(), 
      _ => { 
       return DummyResult::any(sp); 
      } 
     }; 
     attrs.push(cx.attribute(sp, 
           cx.meta_list(sp, 
              // simply increase the reference counter 
              cfg_str.clone(), 
              vec![cx.meta_name_value(sp, 
                    ftre_str.clone(), 
                    ast::LitKind::Str(intern(mod_name.trim_left_matches("test_")), ast::StrStyle::Cooked))]))); 

     let mut mod_path = PathBuf::from(&cx.codemap().span_to_filename(sp)); 
     let dir = mod_path.parent().expect("no parent directory").to_owned(); 
     let default_path = dir.join(format!("{}.rs", mod_name.as_str())); 
     let secondary_path = dir.join(format!("{}/mod.rs", mod_name.as_str())); 
     match (default_path.exists(), secondary_path.exists()) { 
      (false, false) => { 
       cx.span_err(sp, &format!("file not found for module `{}`", mod_name.as_str())); 
       return DummyResult::any(sp); 
      } 
      (true, true) => { 
       cx.span_err(sp, &format!("file for module `{}` found at both {} and {}", mod_name.as_str(), default_path.display(), secondary_path.display())); 
       return DummyResult::any(sp); 
      } 
      (true, false) => mod_path = default_path, 
      (false, true) => mod_path = secondary_path, 
     } 

     test_mods.push(P(ast::Item { 
      ident: cx.ident_of(mod_name.as_str()), 
      attrs: attrs, 
      id: ast::DUMMY_NODE_ID, 
      node: ast::ItemKind::Mod(
       ast::Mod { 
        inner: sp, 
        items: expand_include(cx, sp, &mod_path), 
       } 
      ), 
      vis: ast::Visibility::Inherited, 
      span: sp, 
     })) 
    } 

    MacEager::items(test_mods) 
} 
Sonunda çözüm bulundu! \ o/

Rust işleme modülü işlem modülü include! gibidir. Sonuç olarak, ben here bulunabilir makro include! uygulanması, baktı ve benim ihtiyaçlarına uygun şekilde yeniden yazdım:

use ::std::path::Path; 
use ::std::path::PathBuf; 
use syntax::parse::{self, token}; 
use syntax::errors::FatalError; 
macro_rules! panictry { 
    ($e:expr) => ({ 
     match $e { 
      Ok(e) => e, 
      Err(mut e) => { 
       e.emit(); 
       panic!(FatalError); 
      } 
     } 
    }) 
} 

pub fn expand_include<'cx>(cx: &'cx mut ExtCtxt, sp: Span, file: &Path) -> Vec<P<ast::Item>> { 
    let mut p = parse::new_sub_parser_from_file(cx.parse_sess(), cx.cfg(), file, true, None, sp); 
    let mut ret = vec![]; 
    while p.token != token::Eof { 
     match panictry!(p.parse_item()) { 
      Some(item) => ret.push(item), 
      None => { 
       panic!(p.diagnostic().span_fatal(p.span, 
               &format!("expected item, found `{}`", p.this_token_to_string()))) 
      } 
     } 
    } 
    ret 
} 

modül dosyasından öğeleri almak için, gerçek modülünü öğrenmek zorundadır yol:

let mut mod_path = PathBuf::from(&cx.codemap().span_to_filename(sp)); 
mod_path.set_file_name(mod_name.as_str()); 
mod_path.set_extension("rs"); 

Sonra böyle bizim modül düğümü hazırlayabilirsiniz:

P(ast::Item { 
    ident: cx.ident_of(mod_name.as_str()), 
    attrs: attrs, 
    id: ast::DUMMY_NODE_ID, 
    node: ast::ItemKind::Mod(ast::Mod { 
     inner: sp, 
     items: expand_include(cx, sp, &mod_path), 
    }), 
    vis: ast::Visibility::Inherited, 
    span: sp, 
}) 

Özetle, eklenti aşağıdaki olarak yeniden edilmelidir:

#![feature(plugin_registrar, rustc_private)] 

extern crate syntax; 
extern crate rustc_plugin; 

use syntax::ast; 
use syntax::ptr::P; 
use syntax::codemap::Span; 
use syntax::parse::{self, token}; 
use syntax::tokenstream::TokenTree; 
use syntax::ext::base::{ExtCtxt, MacResult, DummyResult, MacEager}; 
use syntax::errors::FatalError; 
use syntax::ext::build::AstBuilder; 
use rustc_plugin::Registry; 
use syntax::util::small_vector::SmallVector; 

use ::std::path::Path; 
use ::std::path::PathBuf; 

macro_rules! panictry { 
    ($e:expr) => ({ 
     match $e { 
      Ok(e) => e, 
      Err(mut e) => { 
       e.emit(); 
       panic!(FatalError); 
      } 
     } 
    }) 
} 

pub fn expand_include<'cx>(cx: &'cx mut ExtCtxt, sp: Span, file: &Path) -> Vec<P<ast::Item>> { 
    let mut p = parse::new_sub_parser_from_file(cx.parse_sess(), cx.cfg(), file, true, None, sp); 
    let mut ret = vec![]; 
    while p.token != token::Eof { 
     match panictry!(p.parse_item()) { 
      Some(item) => ret.push(item), 
      None => { 
       panic!(p.diagnostic().span_fatal(p.span, 
               &format!("expected item, found `{}`", p.this_token_to_string()))) 
      } 
     } 
    } 
    ret 
} 

fn intern(s: &str) -> token::InternedString { 
    token::intern_and_get_ident(s) 
} 

fn choose(cx: &mut ExtCtxt, sp: Span, args: &[TokenTree]) -> Box<MacResult + 'static> { 
    let mut test_mods = SmallVector::zero(); 
    let cfg_str = intern("cfg"); 
    let feat_str = intern("feature"); 
    for arg in args { 
     let mut attrs = vec![]; 
     let mod_name = match arg { 
      &TokenTree::Token(_, token::Ident(s)) => s.to_string(), 
      _ => { 
       return DummyResult::any(sp); 
      } 
     }; 
     attrs.push(cx.attribute(sp, 
           cx.meta_list(sp, 
              // simply increase the reference counter 
              cfg_str.clone(), 
              vec![cx.meta_name_value(sp, 
                    feat_str.clone(), 
                    ast::LitKind::Str(intern(mod_name.trim_left_matches("test_")), ast::StrStyle::Cooked))]))); 

     let mut mod_path = PathBuf::from(&cx.codemap().span_to_filename(sp)); 
     mod_path.set_file_name(mod_name.as_str()); 
     mod_path.set_extension("rs"); 

     test_mods.push(P(ast::Item { 
      ident: cx.ident_of(mod_name.as_str()), 
      attrs: attrs, 
      id: ast::DUMMY_NODE_ID, 
      node: ast::ItemKind::Mod(
       ast::Mod { 
        inner: sp, 
        items: expand_include(cx, sp, &mod_path), 
       } 
      ), 
      vis: ast::Visibility::Inherited, 
      span: sp, 
     })) 
    } 

    MacEager::items(test_mods) 
} 

#[plugin_registrar] 
pub fn plugin_registrar(reg: &mut Registry) { 
    reg.register_macro("choose", choose); 
} 
+0

derleyici 'foo.rs' veya foo/mod.rs' ya modül' foo' içeriği arayacaktır (ve her ikisi de mevcut olmadığını hata olacaktır). Aynı şeyi yapmak isteyebilirsiniz. –

+0

@ FrancisGagné güncellendi. – knight42

İlgili konular