rusti: Add linenoise, wrap into core::rl and add rusti REPL tool

Add Brian Leibig to AUTHORS.txt for REPL contributions
This commit is contained in:
Zack Corr 2012-10-03 11:15:02 +10:00
parent 39c0d3591e
commit 4912428cb5
21 changed files with 591 additions and 13 deletions

View file

@ -30,6 +30,8 @@ compiletest/ The test runner
cargo/ The package manager
rusti/ The JIT REPL
rustdoc/ The Rust API documentation tool
llvm/ The LLVM submodule
@ -38,6 +40,8 @@ libuv/ The libuv submodule
rustllvm/ LLVM support code
linenoise\ Minimalistic libreadline alternative
fuzzer/ A collection of fuzz testers
etc/ Scripts, editor support, misc

View file

@ -152,6 +152,7 @@ pub mod option_iter {
pub mod inst;
}
pub mod result;
pub mod rl;
pub mod to_str;
pub mod to_bytes;
pub mod from_str;

78
src/libcore/rl.rs Normal file
View file

@ -0,0 +1,78 @@
use libc::{c_char, c_int};
#[link_args = "-Llinenoise"]
#[link_name = "linenoise"]
#[abi = "cdecl"]
extern mod linenoise {
#[legacy_exports];
fn linenoise(prompt: *c_char) -> *c_char;
fn linenoiseHistoryAdd(line: *c_char) -> c_int;
fn linenoiseHistorySetMaxLen(len: c_int) -> c_int;
fn linenoiseHistorySave(file: *c_char) -> c_int;
fn linenoiseHistoryLoad(file: *c_char) -> c_int;
fn linenoiseSetCompletionCallback(callback: *u8);
fn linenoiseAddCompletion(completions: *(), line: *c_char);
fn linenoiseClearScreen();
}
/// Add a line to history
pub fn add_history(line: ~str) -> bool {
do str::as_c_str(line) |buf| {
linenoise::linenoiseHistoryAdd(buf) == 1 as c_int
}
}
/// Set the maximum amount of lines stored
pub fn set_history_max_len(len: int) -> bool {
linenoise::linenoiseHistorySetMaxLen(len as c_int) == 1 as c_int
}
/// Save line history to a file
pub fn save_history(file: ~str) -> bool {
do str::as_c_str(file) |buf| {
linenoise::linenoiseHistorySave(buf) == 1 as c_int
}
}
/// Load line history from a file
pub fn load_history(file: ~str) -> bool {
do str::as_c_str(file) |buf| {
linenoise::linenoiseHistoryLoad(buf) == 1 as c_int
}
}
/// Print out a prompt and then wait for input and return it
pub fn read(prompt: ~str) -> Option<~str> {
do str::as_c_str(prompt) |buf| unsafe {
let line = linenoise::linenoise(buf);
if line.is_null() { None }
else { Some(str::raw::from_c_str(line)) }
}
}
/// Clear the screen
pub fn clear() {
linenoise::linenoiseClearScreen();
}
pub type CompletionCb = fn~(~str, fn(~str));
fn complete_key(_v: @CompletionCb) {}
/// Bind to the main completion callback
pub fn complete(cb: CompletionCb) unsafe {
task::local_data::local_data_set(complete_key, @(move cb));
extern fn callback(line: *c_char, completions: *()) unsafe {
let cb: CompletionCb = copy *task::local_data::local_data_get(complete_key).get();
do cb(str::raw::from_c_str(line)) |suggestion| {
do str::as_c_str(suggestion) |buf| {
linenoise::linenoiseAddCompletion(completions, buf);
}
}
}
linenoise::linenoiseSetCompletionCallback(callback);
}

1
src/linenoise Submodule

@ -0,0 +1 @@
Subproject commit 8c9b481281ba401f6baf45bc9ca9fc940b59405f

36
src/rusti/rusti.rc Normal file
View file

@ -0,0 +1,36 @@
// rusti - REPL using the JIT backend
#[link(name = "rusti",
vers = "0.4",
uuid = "7fb5bf52-7d45-4fee-8325-5ad3311149fc",
url = "https://github.com/mozilla/rust/tree/master/src/rusti")];
#[crate_type = "bin"];
#[no_core];
#[allow(vecs_implicitly_copyable,
non_implicitly_copyable_typarams)];
#[allow(non_camel_case_types)];
#[allow(deprecated_mode)];
#[allow(deprecated_pattern)];
extern mod core(vers = "0.4");
extern mod std(vers = "0.4");
extern mod rustc(vers = "0.4");
extern mod syntax(vers = "0.4");
use core::*;
use io::{ReaderUtil, WriterUtil};
use std::c_vec;
use rustc::back;
use rustc::driver::{driver, session};
use rustc::front;
use rustc::lib::llvm::llvm;
use rustc::metadata::{creader, filesearch};
use rustc::middle::{freevars, kind, lint, trans, ty, typeck};
use rustc::middle;
use syntax::{ast, ast_util, codemap, diagnostic, fold, parse, print, visit};
use syntax::ast_util::*;
use parse::token;
use print::{pp, pprust};

323
src/rusti/rusti.rs Normal file
View file

@ -0,0 +1,323 @@
/**
* 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,
stmts: ~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(*) => {
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,
.. *session::basic_options()
};
debug!("building driver input");
let head = include_str!("wrapper.rs");
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);
debug!("parsing");
let mut crate = driver::parse_input(sess, cfg, wrapped);
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!("configuration");
crate = front::config::strip_unconfigured_items(crate);
debug!("maybe building test harness");
crate = front::test::modify_for_testing(sess, crate);
debug!("expansion");
crate = syntax::ext::expand::expand_crate(sess.parse_sess,
sess.opts.cfg,
crate);
debug!("intrinsic injection");
crate = front::intrinsic_inject::inject_intrinsic(sess, crate);
debug!("core injection");
crate = front::core_inject::maybe_inject_libcore_ref(sess, crate);
debug!("building lint settings table");
lint::build_settings_crate(sess, crate);
debug!("ast indexing");
let ast_map = syntax::ast_map::map_crate(sess.diagnostic(), *crate);
debug!("external crate/lib resolution");
creader::read_crates(sess.diagnostic(), *crate, sess.cstore,
sess.filesearch,
session::sess_os_to_meta_os(sess.targ_cfg.os),
sess.opts.static, sess.parse_sess.interner);
debug!("language item collection");
let lang_items = middle::lang_items::collect_language_items(crate, sess);
debug!("resolution");
let {def_map: def_map,
exp_map2: exp_map2,
trait_map: trait_map} = middle::resolve::resolve_crate(sess,
lang_items,
crate);
debug!("freevar finding");
let freevars = freevars::annotate_freevars(def_map, crate);
debug!("region_resolution");
let region_map = middle::region::resolve_crate(sess, def_map, crate);
debug!("region paramaterization inference");
let rp_set = middle::region::determine_rp_in_crate(sess, ast_map,
def_map, crate);
debug!("typechecking");
let ty_cx = ty::mk_ctxt(sess, def_map, ast_map, freevars,
region_map, rp_set, move lang_items, crate);
let (method_map, vtable_map) = typeck::check_crate(ty_cx, trait_map,
crate);
debug!("const marking");
middle::const_eval::process_crate(crate, def_map, ty_cx);
debug!("const checking");
middle::check_const::check_crate(sess, crate, ast_map, def_map,
method_map, ty_cx);
debug!("privacy checking");
middle::privacy::check_crate(ty_cx, &method_map, crate);
debug!("loop checking");
middle::check_loop::check_crate(ty_cx, crate);
debug!("alt checking");
middle::check_alt::check_crate(ty_cx, crate);
debug!("liveness checking");
let last_use_map = middle::liveness::check_crate(ty_cx,
method_map, crate);
debug!("borrow checking");
let (root_map, mutbl_map) = middle::borrowck::check_crate(ty_cx,
method_map,
last_use_map,
crate);
debug!("kind checking");
kind::check_crate(ty_cx, method_map, last_use_map, crate);
debug!("lint checking");
lint::check_crate(ty_cx, crate);
let maps = {mutbl_map: mutbl_map,
root_map: root_map,
last_use_map: last_use_map,
method_map: method_map,
vtable_map: vtable_map};
debug!("translation");
let (llmod, _) = trans::base::trans_crate(sess, crate, ty_cx,
~path::from_str("<repl>"),
exp_map2, maps);
let pm = llvm::LLVMCreatePassManager();
debug!("executing jit");
back::link::jit::exec(sess, pm, llmod, 0, false);
llvm::LLVMDisposePassManager(pm);
debug!("recording input into repl history");
record(repl, blk, sess.parse_sess.interner)
}
/// Run a command, e.g. :clear, :exit, etc.
fn run_cmd(repl: &mut Repl, _in: io::Reader, _out: io::Writer,
cmd: ~str, _args: ~[~str]) {
match cmd {
~"exit" => repl.running = false,
~"clear" => {
repl.view_items = ~"";
repl.stmts = ~"";
rl::clear();
}
~"help" => {
io::println(~":clear - clear the screen\n" +
~":exit - exit from the repl\n" +
~":help - show this message");
}
_ => io::println(~"unknown cmd: " + cmd)
}
}
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: ~"",
stmts: ~""
};
do rl::complete |line, suggest| {
if line.starts_with(":") {
suggest(~":clear");
suggest(~":exit");
suggest(~":help");
}
}
while repl.running {
let result = rl::read(repl.prompt);
if result.is_none() {
break;
}
let line = result.get();
if line.is_empty() {
io::println(~"()");
loop;
}
rl::add_history(line);
if line.starts_with(~":") {
let full = line.substr(1, line.len() - 1);
let split = full.split_char(' ');
let len = split.len();
if len > 0 {
let cmd = split[0];
if !cmd.is_empty() {
let args = if len > 1 {
do vec::view(split, 1, len - 1).map |arg| {
*arg
}
} else { ~[] };
run_cmd(&mut repl, in, out, cmd, args);
loop;
}
}
}
let result = do task::try |copy repl| {
run(copy repl, line)
};
if result.is_ok() {
repl = result.get();
}
}
}

24
src/rusti/wrapper.rs Normal file
View file

@ -0,0 +1,24 @@
#[legacy_modes];
#[legacy_exports];
#[allow(ctypes)];
#[allow(deprecated_mode)];
#[allow(deprecated_pattern)];
#[allow(heap_memory)];
#[allow(implicit_copies)];
#[allow(managed_heap_memory)];
#[allow(non_camel_case_types)];
#[allow(non_implicitly_copyable_typarams)];
#[allow(owned_heap_memory)];
#[allow(path_statement)];
#[allow(structural_records)];
#[allow(unrecognized_lint)];
#[allow(unused_imports)];
#[allow(vecs_implicitly_copyable)];
#[allow(while_true)];
extern mod std;
fn print<T>(result: T) {
io::println(fmt!("%?", result));
}