| /*! |
| Provides all access to AST-related, non-sendable info |
| |
| Rustdoc is intended to be parallel, and the rustc AST is filled with |
| shared boxes. The AST service attempts to provide a single place to |
| query AST-related information, shielding the rest of Rustdoc from its |
| non-sendableness. |
| */ |
| |
| use std::map::HashMap; |
| use rustc::driver::session; |
| use session::{basic_options, options}; |
| use session::session; |
| use rustc::driver::driver; |
| use syntax::diagnostic; |
| use syntax::diagnostic::handler; |
| use syntax::ast; |
| use syntax::codemap; |
| use syntax::ast_map; |
| use rustc::back::link; |
| use rustc::metadata::filesearch; |
| use rustc::front; |
| |
| export Ctxt; |
| export CtxtHandler; |
| export Srv; |
| export from_str; |
| export from_file; |
| export exec; |
| |
| type Ctxt = { |
| ast: @ast::crate, |
| ast_map: ast_map::map |
| }; |
| |
| type SrvOwner<T> = fn(srv: Srv) -> T; |
| type CtxtHandler<T> = fn~(ctxt: Ctxt) -> T; |
| type Parser = fn~(session, ~str) -> @ast::crate; |
| |
| enum Msg { |
| HandleRequest(fn~(Ctxt)), |
| Exit |
| } |
| |
| enum Srv = { |
| ch: comm::Chan<Msg> |
| }; |
| |
| fn from_str<T>(source: ~str, owner: SrvOwner<T>) -> T { |
| run(owner, source, parse::from_str_sess) |
| } |
| |
| fn from_file<T>(file: ~str, owner: SrvOwner<T>) -> T { |
| run(owner, file, |sess, f| parse::from_file_sess(sess, &Path(f))) |
| } |
| |
| fn run<T>(owner: SrvOwner<T>, source: ~str, +parse: Parser) -> T { |
| |
| let srv_ = Srv({ |
| ch: do task::spawn_listener |po| { |
| act(po, source, parse); |
| } |
| }); |
| |
| let res = owner(srv_); |
| comm::send(srv_.ch, Exit); |
| return res; |
| } |
| |
| fn act(po: comm::Port<Msg>, source: ~str, parse: Parser) { |
| let sess = build_session(); |
| |
| let ctxt = build_ctxt( |
| sess, |
| parse(sess, source) |
| ); |
| |
| let mut keep_going = true; |
| while keep_going { |
| match comm::recv(po) { |
| HandleRequest(f) => { |
| f(ctxt); |
| } |
| Exit => { |
| keep_going = false; |
| } |
| } |
| } |
| } |
| |
| fn exec<T:Send>( |
| srv: Srv, |
| +f: fn~(ctxt: Ctxt) -> T |
| ) -> T { |
| let po = comm::Port(); |
| let ch = comm::Chan(&po); |
| let msg = HandleRequest(fn~(move f, ctxt: Ctxt) { |
| comm::send(ch, f(ctxt)) |
| }); |
| comm::send(srv.ch, msg); |
| comm::recv(po) |
| } |
| |
| fn build_ctxt(sess: session, |
| ast: @ast::crate) -> Ctxt { |
| |
| use rustc::front::config; |
| |
| let ast = config::strip_unconfigured_items(ast); |
| let ast = syntax::ext::expand::expand_crate(sess.parse_sess, |
| sess.opts.cfg, ast); |
| let ast = front::test::modify_for_testing(sess, ast); |
| let ast_map = ast_map::map_crate(sess.diagnostic(), *ast); |
| |
| { |
| ast: ast, |
| ast_map: ast_map, |
| } |
| } |
| |
| fn build_session() -> session { |
| let sopts: @options = basic_options(); |
| let codemap = codemap::new_codemap(); |
| let error_handlers = build_error_handlers(codemap); |
| let {emitter, span_handler} = error_handlers; |
| |
| let session = driver::build_session_(sopts, codemap, emitter, |
| span_handler); |
| session |
| } |
| |
| type ErrorHandlers = { |
| emitter: diagnostic::emitter, |
| span_handler: diagnostic::span_handler |
| }; |
| |
| // Build a custom error handler that will allow us to ignore non-fatal |
| // errors |
| fn build_error_handlers( |
| codemap: codemap::codemap |
| ) -> ErrorHandlers { |
| |
| type DiagnosticHandler = { |
| inner: diagnostic::handler, |
| }; |
| |
| impl DiagnosticHandler: diagnostic::handler { |
| fn fatal(msg: &str) -> ! { self.inner.fatal(msg) } |
| fn err(msg: &str) { self.inner.err(msg) } |
| fn bump_err_count() { |
| self.inner.bump_err_count(); |
| } |
| fn has_errors() -> bool { self.inner.has_errors() } |
| fn abort_if_errors() { self.inner.abort_if_errors() } |
| fn warn(msg: &str) { self.inner.warn(msg) } |
| fn note(msg: &str) { self.inner.note(msg) } |
| fn bug(msg: &str) -> ! { self.inner.bug(msg) } |
| fn unimpl(msg: &str) -> ! { self.inner.unimpl(msg) } |
| fn emit(cmsp: Option<(codemap::codemap, codemap::span)>, |
| msg: &str, lvl: diagnostic::level) { |
| self.inner.emit(cmsp, msg, lvl) |
| } |
| } |
| |
| let emitter = fn@(cmsp: Option<(codemap::codemap, codemap::span)>, |
| msg: &str, lvl: diagnostic::level) { |
| diagnostic::emit(cmsp, msg, lvl); |
| }; |
| let inner_handler = diagnostic::mk_handler(Some(emitter)); |
| let handler = { |
| inner: inner_handler, |
| }; |
| let span_handler = diagnostic::mk_span_handler( |
| handler as diagnostic::handler, codemap); |
| |
| { |
| emitter: emitter, |
| span_handler: span_handler |
| } |
| } |
| |
| #[test] |
| fn should_prune_unconfigured_items() { |
| let source = ~"#[cfg(shut_up_and_leave_me_alone)]fn a() { }"; |
| do from_str(source) |srv| { |
| do exec(srv) |ctxt| { |
| assert vec::is_empty(ctxt.ast.node.module.items); |
| } |
| } |
| } |
| |
| #[test] |
| fn srv_should_build_ast_map() { |
| let source = ~"fn a() { }"; |
| do from_str(source) |srv| { |
| do exec(srv) |ctxt| { |
| assert ctxt.ast_map.size() != 0u |
| }; |
| } |
| } |
| |
| #[test] |
| fn should_ignore_external_import_paths_that_dont_exist() { |
| let source = ~"use forble; use forble::bippy;"; |
| from_str(source, |_srv| { } ) |
| } |
| |
| #[test] |
| fn srv_should_return_request_result() { |
| let source = ~"fn a() { }"; |
| do from_str(source) |srv| { |
| let result = exec(srv, |_ctxt| 1000 ); |
| assert result == 1000; |
| } |
| } |