&str can be turned into @~str on demand, using to_owned(), so for strings, we can create a specialized interner that accepts &str for intern() and find() but stores and returns @~str.
430 lines
14 KiB
Plaintext
430 lines
14 KiB
Plaintext
// 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 <LICENSE-APACHE or
|
|
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
|
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, 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<bool> {
|
|
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 <crate> ... - \
|
|
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<Repl> {
|
|
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 => { }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|