// Copyright 2012-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. // rusti - REPL using the JIT backend #[link(name = "rusti", vers = "0.7-pre", uuid = "7fb5bf52-7d45-4fee-8325-5ad3311149fc", url = "https://github.com/mozilla/rust/tree/master/src/rusti")]; #[license = "MIT/ASL2"]; #[crate_type = "lib"]; #[allow(vecs_implicitly_copyable, non_implicitly_copyable_typarams)]; extern mod std(vers = "0.7-pre"); extern mod rustc(vers = "0.7-pre"); extern mod syntax(vers = "0.7-pre"); use core::*; use rustc::driver::{driver, session}; use syntax::{ast, diagnostic}; use syntax::ast_util::*; use syntax::parse::token; use syntax::print::{pp, pprust}; use std::rl; /** * A structure shared across REPL instances for storing history * such as statements and view items. I wish the AST was sendable. */ struct Repl { prompt: ~str, binary: ~str, running: bool, view_items: ~str, lib_search_paths: ~[~str], stmts: ~str } // Action to do after reading a :command enum CmdAction { action_none, action_run_line(~str), } /// A utility function that hands off a pretty printer to a callback. fn with_pp(intr: @token::ident_interner, cb: &fn(@pprust::ps, @io::Writer)) -> ~str { do io::with_str_writer |writer| { let pp = pprust::rust_printer(writer, intr); cb(pp, writer); pp::eof(pp.s); } } /** * The AST (or the rest of rustc) are not sendable yet, * so recorded things are printed to strings. A terrible hack that * needs changes to rustc in order to be outed. This is unfortunately * going to cause the REPL to regress in parser performance, * because it has to parse the statements and view_items on each * input. */ fn record(repl: Repl, blk: @ast::blk, intr: @token::ident_interner) -> Repl { let view_items = if blk.node.view_items.len() > 0 { let new_view_items = do with_pp(intr) |pp, writer| { for blk.node.view_items.each |view_item| { pprust::print_view_item(pp, *view_item); writer.write_line(~""); } }; debug!("new view items %s", new_view_items); repl.view_items + "\n" + new_view_items } else { repl.view_items }; let stmts = if blk.node.stmts.len() > 0 { let new_stmts = do with_pp(intr) |pp, writer| { for blk.node.stmts.each |stmt| { match stmt.node { ast::stmt_decl(*) | ast::stmt_mac(*) => { pprust::print_stmt(pp, *stmt); writer.write_line(~""); } ast::stmt_expr(expr, _) | ast::stmt_semi(expr, _) => { match expr.node { ast::expr_assign(*) | ast::expr_assign_op(*) | ast::expr_swap(*) => { pprust::print_stmt(pp, *stmt); writer.write_line(~""); } _ => {} } } } } }; debug!("new stmts %s", new_stmts); repl.stmts + "\n" + new_stmts } else { repl.stmts }; Repl{ view_items: view_items, stmts: stmts, .. repl } } /// Run an input string in a Repl, returning the new Repl. fn run(repl: Repl, input: ~str) -> Repl { let options = @session::options { crate_type: session::unknown_crate, binary: @repl.binary, addl_lib_search_paths: repl.lib_search_paths.map(|p| Path(*p)), jit: true, .. *session::basic_options() }; debug!("building driver input"); let head = include_str!("wrapper.rs").to_owned(); let foot = fmt!("%s\nfn main() {\n%s\n\nprint({\n%s\n})\n}", repl.view_items, repl.stmts, input); let wrapped = driver::str_input(head + foot); debug!("inputting %s", head + foot); debug!("building a driver session"); let sess = driver::build_session(options, diagnostic::emit); debug!("building driver configuration"); let cfg = driver::build_configuration(sess, @repl.binary, &wrapped); let outputs = driver::build_output_filenames(&wrapped, &None, &None, sess); debug!("calling compile_upto"); let (crate, _) = driver::compile_upto(sess, cfg, &wrapped, driver::cu_everything, Some(outputs)); let mut opt = None; for crate.node.module.items.each |item| { match item.node { ast::item_fn(_, _, _, _, blk) => { if item.ident == sess.ident_of("main") { opt = blk.node.expr; } } _ => {} } } let blk = match opt.get().node { ast::expr_call(_, exprs, _) => { match exprs[0].node { ast::expr_block(blk) => @blk, _ => fail!() } } _ => fail!() }; debug!("recording input into repl history"); record(repl, blk, sess.parse_sess.interner) } // Compiles a crate given by the filename as a library if the compiled // version doesn't exist or is older than the source file. Binary is // the name of the compiling executable. Returns Some(true) if it // successfully compiled, Some(false) if the crate wasn't compiled // because it already exists and is newer than the source file, or // None if there were compile errors. fn compile_crate(src_filename: ~str, binary: ~str) -> Option { match do task::try { let src_path = Path(src_filename); let options = @session::options { binary: @binary, addl_lib_search_paths: ~[os::getcwd()], .. *session::basic_options() }; let input = driver::file_input(src_path); let sess = driver::build_session(options, diagnostic::emit); *sess.building_library = true; let cfg = driver::build_configuration(sess, @binary, &input); let outputs = driver::build_output_filenames( &input, &None, &None, sess); // If the library already exists and is newer than the source // file, skip compilation and return None. let mut should_compile = true; let dir = os::list_dir_path(&Path(outputs.out_filename.dirname())); let maybe_lib_path = do dir.find |file| { // The actual file's name has a hash value and version // number in it which is unknown at this time, so looking // for a file that matches out_filename won't work, // instead we guess which file is the library by matching // the prefix and suffix of out_filename to files in the // directory. let file_str = file.filename().get(); file_str.starts_with(outputs.out_filename.filestem().get()) && file_str.ends_with(outputs.out_filename.filetype().get()) }; match maybe_lib_path { Some(lib_path) => { let (src_mtime, _) = src_path.get_mtime().get(); let (lib_mtime, _) = lib_path.get_mtime().get(); if lib_mtime >= src_mtime { should_compile = false; } }, None => { }, } if (should_compile) { io::println(fmt!("compiling %s...", src_filename)); driver::compile_upto(sess, cfg, &input, driver::cu_everything, Some(outputs)); true } else { false } } { Ok(true) => Some(true), Ok(false) => Some(false), Err(_) => None, } } /// Tries to get a line from rl after outputting a prompt. Returns /// None if no input was read (e.g. EOF was reached). fn get_line(use_rl: bool, prompt: ~str) -> Option<~str> { if use_rl { let result = unsafe { rl::read(prompt) }; match result { None => None, Some(line) => { unsafe { rl::add_history(line) }; Some(line) } } } else { if io::stdin().eof() { None } else { Some(io::stdin().read_line()) } } } /// Run a command, e.g. :clear, :exit, etc. fn run_cmd(repl: &mut Repl, _in: @io::Reader, _out: @io::Writer, cmd: ~str, args: ~[~str], use_rl: bool) -> CmdAction { let mut action = action_none; match cmd { ~"exit" => repl.running = false, ~"clear" => { repl.view_items = ~""; repl.stmts = ~""; // XXX: Win32 version of linenoise can't do this //rl::clear(); } ~"help" => { io::println( ~":{\\n ..lines.. \\n:}\\n - execute multiline command\n" + ~":load ... - \ loads given crates as dynamic libraries\n" + ~":clear - clear the bindings\n" + ~":exit - exit from the repl\n" + ~":help - show this message"); } ~"load" => { let mut loaded_crates: ~[~str] = ~[]; for args.each |arg| { let (crate, filename) = if arg.ends_with(".rs") || arg.ends_with(".rc") { (arg.substr(0, arg.len() - 3).to_owned(), *arg) } else { (*arg, arg + ~".rs") }; match compile_crate(filename, repl.binary) { Some(_) => loaded_crates.push(crate), None => { } } } for loaded_crates.each |crate| { let crate_path = Path(*crate); let crate_dir = crate_path.dirname(); let crate_name = crate_path.filename().get(); if !repl.view_items.contains(*crate) { repl.view_items += fmt!("extern mod %s;\n", crate_name); if !repl.lib_search_paths.contains(&crate_dir) { repl.lib_search_paths.push(crate_dir); } } } if loaded_crates.is_empty() { io::println("no crates loaded"); } else { io::println(fmt!("crates loaded: %s", str::connect(loaded_crates, ", "))); } } ~"{" => { let mut multiline_cmd = ~""; let mut end_multiline = false; while (!end_multiline) { match get_line(use_rl, ~"rusti| ") { None => fail!(~"unterminated multiline command :{ .. :}"), Some(line) => { if str::trim(line) == ~":}" { end_multiline = true; } else { multiline_cmd += line + ~"\n"; } } } } action = action_run_line(multiline_cmd); } _ => io::println(~"unknown cmd: " + cmd) } return action; } /// Executes a line of input, which may either be rust code or a /// :command. Returns a new Repl if it has changed. fn run_line(repl: &mut Repl, in: @io::Reader, out: @io::Writer, line: ~str, use_rl: bool) -> Option { if line.starts_with(~":") { let full = line.substr(1, line.len() - 1); let mut split = ~[]; for str::each_word(full) |word| { split.push(word.to_owned()) } let len = split.len(); if len > 0 { let cmd = split[0]; if !cmd.is_empty() { let args = if len > 1 { vec::slice(split, 1, len).to_vec() } else { ~[] }; match run_cmd(repl, in, out, cmd, args, use_rl) { action_none => { } action_run_line(multiline_cmd) => { if !multiline_cmd.is_empty() { return run_line(repl, in, out, multiline_cmd, use_rl); } } } return None; } } } let r = *repl; let result = do task::try { run(r, line) }; if result.is_ok() { return Some(result.get()); } return None; } pub fn main() { let args = os::args(); let in = io::stdin(); let out = io::stdout(); let mut repl = Repl { prompt: ~"rusti> ", binary: args[0], running: true, view_items: ~"", lib_search_paths: ~[], stmts: ~"" }; let istty = unsafe { libc::isatty(libc::STDIN_FILENO as i32) } != 0; // only print this stuff if the user is actually typing into rusti if istty { io::println("WARNING: The Rust REPL is experimental and may be"); io::println("unstable. If you encounter problems, please use the"); io::println("compiler instead."); unsafe { do rl::complete |line, suggest| { if line.starts_with(":") { suggest(~":clear"); suggest(~":exit"); suggest(~":help"); suggest(~":load"); } } } } while repl.running { match get_line(istty, repl.prompt) { None => break, Some(line) => { if line.is_empty() { if istty { io::println(~"()"); } loop; } match run_line(&mut repl, in, out, line, istty) { Some(new_repl) => repl = new_repl, None => { } } } } } }