diff --git a/src/librustc/driver/driver.rs b/src/librustc/driver/driver.rs index a7cc90f473d4b..7f7674f2887bc 100644 --- a/src/librustc/driver/driver.rs +++ b/src/librustc/driver/driver.rs @@ -187,6 +187,7 @@ pub fn compile_rest(sess: Session, crate = time(time_passes, ~"expansion", || syntax::ext::expand::expand_crate(sess.parse_sess, copy cfg, + sess.opts.dynamic_syntax_extensions, crate)); crate = time(time_passes, ~"configuration", || @@ -652,6 +653,7 @@ pub fn build_session_options(binary: @~str, let debuginfo = debugging_opts & session::debug_info != 0 || extra_debuginfo; let statik = debugging_opts & session::statik != 0; + let dynamic_syntax_extensions = debugging_opts & session::dynamic_syntax_extensions != 0; let target = match target_opt { None => host_triple(), @@ -700,7 +702,8 @@ pub fn build_session_options(binary: @~str, parse_only: parse_only, no_trans: no_trans, debugging_opts: debugging_opts, - android_cross_path: android_cross_path + android_cross_path: android_cross_path, + dynamic_syntax_extensions: dynamic_syntax_extensions }; return sopts; } diff --git a/src/librustc/driver/session.rs b/src/librustc/driver/session.rs index 788be3a3f2799..24f439e903072 100644 --- a/src/librustc/driver/session.rs +++ b/src/librustc/driver/session.rs @@ -69,6 +69,7 @@ pub static extra_debug_info: uint = 1 << 21; pub static statik: uint = 1 << 22; pub static print_link_args: uint = 1 << 23; pub static no_debug_borrows: uint = 1 << 24; +pub static dynamic_syntax_extensions: uint = 1 << 25; pub fn debugging_opts_map() -> ~[(~str, ~str, uint)] { ~[(~"verbose", ~"in general, enable more debug printouts", verbose), @@ -107,6 +108,9 @@ pub fn debugging_opts_map() -> ~[(~str, ~str, uint)] { (~"no-debug-borrows", ~"do not show where borrow checks fail", no_debug_borrows), + (~"dynamic-syntax-extensions", + ~"allow loading of syntax extensions via #[syntax_extension] (experimental)", + dynamic_syntax_extensions) ] } @@ -148,6 +152,8 @@ pub struct options { no_trans: bool, debugging_opts: uint, android_cross_path: Option<~str>, + // whether syntax extensions can be loaded + dynamic_syntax_extensions: bool } pub struct crate_metadata { @@ -318,6 +324,7 @@ pub fn basic_options() -> @options { no_trans: false, debugging_opts: 0u, android_cross_path: None, + dynamic_syntax_extensions: false } } diff --git a/src/librustdoc/astsrv.rs b/src/librustdoc/astsrv.rs index 21ce70e7f5f6e..ad51dd304933f 100644 --- a/src/librustdoc/astsrv.rs +++ b/src/librustdoc/astsrv.rs @@ -115,7 +115,9 @@ fn build_ctxt(sess: Session, let ast = config::strip_unconfigured_items(ast); let ast = syntax::ext::expand::expand_crate(sess.parse_sess, - copy sess.opts.cfg, ast); + copy sess.opts.cfg, + false, // no dynamic syntax extensions + ast); let ast = front::test::modify_for_testing(sess, ast); let ast_map = ast_map::map_crate(sess.diagnostic(), ast); diff --git a/src/libstd/unstable/dynamic_lib.rs b/src/libstd/unstable/dynamic_lib.rs new file mode 100644 index 0000000000000..416ac03133e4b --- /dev/null +++ b/src/libstd/unstable/dynamic_lib.rs @@ -0,0 +1,193 @@ +// 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 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +/*! + +Dynamic libraries. + +Highly experimental, no guarantees if it works with non-Rust +libraries, or anything other than libraries compiled with the exact +Rust compiler that the program using this was compiled with. + +*/ + +use str; +use cast; +use option::{None, Option}; +use result::{Ok, Err, Result}; +use libc::{c_int, c_void}; +use ops::Drop; +use ptr::Ptr; + +mod raw { + use libc::{c_char, c_int, c_void}; + + #[cfg(target_os = "linux")] + #[cfg(target_os = "android")] + #[cfg(target_os = "macos")] + #[cfg(target_os = "freebsd")] + pub enum RTLD { + LAZY = 1, + NOW = 2, + GLOBAL = 256, + LOCAL = 0 + } + + #[cfg(target_os = "linux")] + #[cfg(target_os = "android")] + #[cfg(target_os = "macos")] + #[cfg(target_os = "freebsd")] + pub extern { + fn dlopen(name: *c_char, mode: c_int) -> *c_void; + fn dlsym(handle: *c_void, name: *c_char) -> *c_void; + fn dlerror() -> *c_char; + fn dlclose(handle: *c_void); + } + + #[cfg(target_os = "win32")] + pub extern { + fn LoadLibrary(name: *c_char); + fn GetProcAddress(handle: *c_void, name: *c_char) -> *c_void; + fn FreeLibrary(handle: *c_void); + } +} + +/// An object representing a dynamic library +pub struct DynamicLib { + priv handle: *c_void +} + +/// An object representing a symbol from a dynamic library. A +/// work-around for either extern fns becoming &extern fn (so that +/// they can have a lifetime), or #5922. +pub struct TaggedSymbol<'self, T> { + priv sym: T, + priv lifetime: Option<&'self ()> +} + +impl DynamicLib { + /// Open a dynamic library. + #[cfg(target_os = "linux")] + #[cfg(target_os = "android")] + #[cfg(target_os = "macos")] + #[cfg(target_os = "freebsd")] + pub fn open(name: &str) -> Result { + let handle = do str::as_c_str(name) |n| { + unsafe { raw::dlopen(n, raw::NOW as c_int | raw::GLOBAL as c_int) } + }; + if handle.is_not_null() { + Ok(DynamicLib { handle: handle }) + } else { + Err( + unsafe { + let error = raw::dlerror(); + if error.is_not_null() { + str::raw::from_c_str(error) + } else { + ~"unknown error" + } + }) + } + } + /// Open a dynamic library. + #[cfg(target_os = "win32")] + pub fn open(name: &str) -> Result { + let handle = do str::as_c_str(name) |n| { + unsafe { raw::LoadLibrary(n) } + }; + if handle.is_not_null() { + Ok(DynamicLib { handle: handle }) + } else { + let err = unsafe { intrinsics::GetLastError() } as uint; + // XXX: make this message nice + Err(fmt!("`LoadLibrary` failed with code %u", err)) + } + } + + /// Retrieve a pointer to a symbol from the library. Note: this + /// operation has no way of knowing if the symbol actually has + /// type `T`, and so using the returned value can crash the + /// program, not just cause the task to fail. + #[cfg(target_os = "linux")] + #[cfg(target_os = "android")] + #[cfg(target_os = "macos")] + #[cfg(target_os = "freebsd")] + pub fn get_symbol<'r, T>(&'r self, name: &str) -> Result, ~str> { + let sym = do str::as_c_str(name) |n| { + unsafe { raw::dlsym(self.handle, n) } + }; + if sym.is_not_null() { + Ok(TaggedSymbol { + sym: unsafe { cast::transmute(sym) }, + lifetime: None::<&'r ()> + }) + } else { + let error = unsafe { raw::dlerror() }; + if error.is_not_null() { + Err(unsafe { str::raw::from_c_str(error) }) + } else { + Err(~"unknown error") + } + } + } + + /// Retrieve a pointer to a symbol from the library. Note: this + /// operation has no way of knowing if the symbol actually has + /// type `T`, and so using the returned value can crash the + /// program, not just cause the task to fail. + #[cfg(target_os = "win32")] + pub fn get_symbol<'r, T>(&'r self, name: &str) -> Result, ~str> { + let sym = do str::as_c_str(name) |n| { + unsafe { raw::GetProcAddress(self.handle, n) } + }; + if sym.is_not_null() { + Ok(TaggedSymbol { sym: unsafe { cast::transmute(sym) }, + lifetime: None::<&'r ()> }) + } else { + let err = unsafe { intrinsics::GetLastError() } as uint; + Err(fmt!("`GetProcAddress` failed with code %u", err)) + } + } +} + +#[unsafe_destructor] +impl Drop for DynamicLib { + #[cfg(target_os = "linux")] + #[cfg(target_os = "android")] + #[cfg(target_os = "macos")] + #[cfg(target_os = "freebsd")] + fn finalize(&self) { + if self.handle.is_not_null() { + unsafe { raw::dlclose(self.handle) } + } + } + + #[cfg(target_os = "win32")] + fn finalize(&self) { + if self.handle.is_not_null() { + unsafe { raw::FreeLibrary(self.handle) } + } + } +} + +impl<'self, T> TaggedSymbol<'self, T> { + /// Get a reference to the (reference to the) symbol. + /// + /// WARNING: Using the returned value can lead to crashes, since + /// the symbol may not really have type `T`. + /// + /// WARNING: Storing the derefence of this pointer can lead to + /// crashes, if the library the symbol comes from has been dropped + /// before using the stored symbol (the compiler doesn't give any + /// indication that this is a problem). + pub unsafe fn get<'r>(&'r self) -> &'r T { + &'r self.sym + } +} diff --git a/src/libstd/unstable/mod.rs b/src/libstd/unstable/mod.rs index 9681a3f36fcee..a10a926b3bf23 100644 --- a/src/libstd/unstable/mod.rs +++ b/src/libstd/unstable/mod.rs @@ -15,6 +15,7 @@ use comm::{GenericChan, GenericPort}; use prelude::*; use task; +pub mod dynamic_lib; pub mod at_exit; pub mod global; pub mod finally; diff --git a/src/libsyntax/ext/base.rs b/src/libsyntax/ext/base.rs index 311636ee7c148..ae2479a7051ad 100644 --- a/src/libsyntax/ext/base.rs +++ b/src/libsyntax/ext/base.rs @@ -113,17 +113,17 @@ pub enum Transformer { ScopeMacros(bool) } +// utility function to simplify creating NormalTT syntax extensions +pub fn builtin_normal_tt(f: SyntaxExpanderTTFun) -> @Transformer { + @SE(NormalTT(SyntaxExpanderTT{expander: f, span: None})) +} +// utility function to simplify creating IdentTT syntax extensions +pub fn builtin_item_tt(f: SyntaxExpanderTTItemFun) -> @Transformer { + @SE(IdentTT(SyntaxExpanderTTItem{expander: f, span: None})) +} // The base map of methods for expanding syntax extension // AST nodes into full ASTs pub fn syntax_expander_table() -> SyntaxEnv { - // utility function to simplify creating NormalTT syntax extensions - fn builtin_normal_tt(f: SyntaxExpanderTTFun) -> @Transformer { - @SE(NormalTT(SyntaxExpanderTT{expander: f, span: None})) - } - // utility function to simplify creating IdentTT syntax extensions - fn builtin_item_tt(f: SyntaxExpanderTTItemFun) -> @Transformer { - @SE(IdentTT(SyntaxExpanderTTItem{expander: f, span: None})) - } let mut syntax_expanders = HashMap::new(); // NB identifier starts with space, and can't conflict with legal idents syntax_expanders.insert(@~" block", diff --git a/src/libsyntax/ext/dynamic.rs b/src/libsyntax/ext/dynamic.rs new file mode 100644 index 0000000000000..1bde47ec05867 --- /dev/null +++ b/src/libsyntax/ext/dynamic.rs @@ -0,0 +1,127 @@ +// 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 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +/*! +Dynamically loading syntax extensions. + +Implementation of the #[syntax_extension="filename"] attribute, that +allows an external Rust library to provide custom syntax +extensions. This library should define public function called +`register_syntax_extensions` (with #[no_mangle], to preserve the +name), with type `fn (@ExtCtxt) -> ~[(@~str, @Transformer)]` (both +`ExtCtxt` and `Transformer` are defined in `ext::base`). This function +returns a vector of the new syntax extensions and their names. + +The `#[syntax_extension]` attribute current only works at the crate +level, is highly experimental and requires the `-Z +dynamic-syntax-extensions` flag. + +# Example + +## `my_ext.rs` +~~~ +extern mod syntax; +use syntax::ast; +use syntax::parse; +use syntax::codemap::span; +use syntax::ext::base::{ExtCtxt, builtin_normal_tt, MacResult, MRExpr, Transformer}; +use syntax::ext::build::AstBuilder; + +#[no_mangle] +pub fn register_syntax_extensions(_cx: @ExtCtxt) -> ~[(@~str, @Transformer)] { + ~[(@~"my_macro", builtin_normal_tt(my_macro))] +} + +fn my_macro(cx: @ExtCtxt, sp: span, _tts: &[ast::token_tree]) -> MacResult { + MRExpr(cx.expr_str(sp, ~"hello world")) +} +~~~ + +## `main.rs` +Compiled with `-Z dynamic-syntax-extensions` +~~~ +#[syntax_extension="libmy_ext--.so"] + +fn main() { + println(my_macro!()) +} +~~~ + +The exact file name of the library is required, and it needs to either +be in a directory that the library loader will search in (e.g. in +`LD_LIBRARY_PATH`), or be specified as a file path. + +*/ + +use core::prelude::*; +use core::unstable::dynamic_lib::DynamicLib; +use ast; +use attr; +use codemap::span; +use ext::base::{ExtCtxt, SyntaxEnv, Transformer}; + +static REGISTER_FN: &'static str = "register_syntax_extensions"; +type RegisterFnType = extern fn(@ExtCtxt) -> ~[(@~str, @Transformer)]; + +// the return value needs to be stored until all expansion has +// happened, otherwise syntax extensions will be calling into a +// library that has been closed, causing segfaults +pub fn load_dynamic_crate(cx: @ExtCtxt, table: @mut SyntaxEnv, c: @ast::crate, enabled: bool) + -> ~[DynamicLib] { + do vec::build |push| { + for attr::find_attrs_by_name(c.node.attrs, "syntax_extension").each |attr| { + if !enabled { + cx.span_fatal(attr.span, + "#[syntax_extension] is experimental and disabled by \ + default; use `-Z dynamic-syntax-extensions` to enable"); + } + + match attr::get_meta_item_value_str(attr::attr_meta(*attr)) { + Some(value) => { + match load_dynamic(cx, *value, table) { + Ok(lib) => push(lib), + Err(err) => { + cx.span_err(attr.span, + fmt!("could not load syntax extensions from `%s`: %s", + *value, err)); + } + } + } + None => { cx.span_err(attr.span, "Expecting `syntax_extension=\"filename\"`") } + } + } + } +} + +fn load_dynamic(cx: @ExtCtxt, name: &str, table: @mut SyntaxEnv) -> Result { + match DynamicLib::open(name) { + Ok(lib) => { + let mut error = None; + { // borrow checker complains about lib & sym without this block + let sym = lib.get_symbol::(REGISTER_FN); + match sym { + Ok(func) => { + // should need unsafe { } (#3080) + let syn_exts = (*func.get())(cx); + for syn_exts.each |&(name, transformer)| { + table.insert(name, transformer); + } + } + Err(err) => { error = Some(err); } + } + } + match error { + None => Ok(lib), + Some(err) => Err(err) + } + } + Err(err) => Err(err) + } +} \ No newline at end of file diff --git a/src/libsyntax/ext/expand.rs b/src/libsyntax/ext/expand.rs index 7fa235fc334b8..06aa583b159cf 100644 --- a/src/libsyntax/ext/expand.rs +++ b/src/libsyntax/ext/expand.rs @@ -20,6 +20,7 @@ use attr; use codemap; use codemap::{span, CallInfo, ExpandedFrom, NameAndSpan, spanned}; use ext::base::*; +use ext::dynamic; use fold::*; use parse; use parse::{parse_item_from_source_str}; @@ -584,15 +585,20 @@ pub fn core_macros() -> ~str { } pub fn expand_crate(parse_sess: @mut parse::ParseSess, - cfg: ast::crate_cfg, c: @crate) -> @crate { + cfg: ast::crate_cfg, enable_dynamic_syntax: bool, + c: @crate) -> @crate { // adding *another* layer of indirection here so that the block // visitor can swap out one exts table for another for the duration // of the block. The cleaner alternative would be to thread the // exts table through the fold, but that would require updating // every method/element of AstFoldFns in fold.rs. + let cx = ExtCtxt::new(parse_sess, copy cfg); let extsbox = @mut syntax_expander_table(); + + // keep this in scope to keep function pointers valid + let libraries = dynamic::load_dynamic_crate(cx, extsbox, c, enable_dynamic_syntax); + let afp = default_ast_fold(); - let cx = ExtCtxt::new(parse_sess, copy cfg); let f_pre = @AstFoldFns { fold_expr: |expr,span,recur| expand_expr(extsbox, cx, expr, span, recur, afp.fold_expr), @@ -635,7 +641,9 @@ pub fn expand_crate(parse_sess: @mut parse::ParseSess, // as it registers all the core macros as expanders. f.fold_item(cm); - @f.fold_crate(&*c) + let expanded = @f.fold_crate(&*c); + let _ = libraries; + expanded } // given a function from idents to idents, produce @@ -706,7 +714,7 @@ mod test { ~"", @src, ~[],sess); - expand_crate(sess,~[],crate_ast); + expand_crate(sess,~[],false,crate_ast); } // these following tests are quite fragile, in that they don't test what @@ -723,7 +731,7 @@ mod test { @src, ~[],sess); // should fail: - expand_crate(sess,~[],crate_ast); + expand_crate(sess,~[],false,crate_ast); } // make sure that macros can leave scope for modules @@ -737,7 +745,7 @@ mod test { @src, ~[],sess); // should fail: - expand_crate(sess,~[],crate_ast); + expand_crate(sess,~[],false,crate_ast); } // macro_escape modules shouldn't cause macros to leave scope @@ -750,7 +758,7 @@ mod test { @src, ~[], sess); // should fail: - expand_crate(sess,~[],crate_ast); + expand_crate(sess,~[],false,crate_ast); } #[test] fn core_macros_must_parse () { diff --git a/src/libsyntax/syntax.rc b/src/libsyntax/syntax.rc index cb1c2269c1e0a..7f4c5018df7ab 100644 --- a/src/libsyntax/syntax.rc +++ b/src/libsyntax/syntax.rc @@ -72,6 +72,7 @@ pub mod ext { pub mod asm; pub mod base; pub mod expand; + pub mod dynamic; pub mod quote;