Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rewrite rusti #7070

Closed
wants to merge 1 commit into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
434 changes: 434 additions & 0 deletions src/librusti/program.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,434 @@
// 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.

use std::cast;
use std::hashmap::HashMap;
use std::local_data;
use std::sys;

use syntax::ast;
use syntax::parse::token;
use syntax::print::pprust;
use rustc::middle::ty;
use rustc::util::ppaux;

use utils::*;

/// This structure keeps track of the state of the world for the code being
/// executed in rusti.
struct Program {
/// All known local variables
local_vars: HashMap<~str, LocalVariable>,
/// New variables which will be present (learned from typechecking)
newvars: HashMap<~str, LocalVariable>,
/// All known view items (use statements), distinct because these must
/// follow extern mods
view_items: ~str,
/// All known 'extern mod' statements (must always come first)
externs: ~str,
/// All known structs defined. These need to have
/// #[deriving(Encodable,Decodable)] to be at all useful in rusti
structs: HashMap<~str, ~str>,
/// All other items, can all be intermingled. Duplicate definitions of the
/// same name have the previous one overwritten.
items: HashMap<~str, ~str>,
}

/// Represents a local variable that the program is currently using.
struct LocalVariable {
/// Should this variable be locally declared as mutable?
mutable: bool,
/// This is the type of the serialized data below
ty: ~str,
/// This is the serialized version of the variable
data: ~[u8],
/// When taking borrowed pointers or slices, care must be taken to ensure
/// that the deserialization produces what we'd expect. If some magic is in
/// order, the first element of this pair is the actual type of the local
/// variable (which can be different from the deserialized type), and the
/// second element are the '&'s which need to be prepended.
alterations: Option<(~str, ~str)>,
}

type LocalCache = @mut HashMap<~str, @~[u8]>;
fn tls_key(_k: @LocalCache) {}

impl Program {
pub fn new() -> Program {
Program {
local_vars: HashMap::new(),
newvars: HashMap::new(),
view_items: ~"",
externs: ~"",
structs: HashMap::new(),
items: HashMap::new(),
}
}

/// Clears all local bindings about variables, items, externs, etc.
pub fn clear(&mut self) {
*self = Program::new();
}

/// Creates a block of code to be fed to rustc. This code is not meant to
/// run, but rather it is meant to learn about the input given. This will
/// assert that the types of all bound local variables are encodable,
/// along with checking syntax and other rust-related things. The reason
/// that we only check for encodability is that some super-common types
/// (like &'static str) are not decodable, but are encodable. By doing some
/// mild approximation when decoding, we can emulate at least &str and &[T].
///
/// Once this code has been fed to rustc, it is intended that the code()
/// function is used to actually generate code to fully compile and run.
pub fn test_code(&self, user_input: &str, to_print: &Option<~str>,
new_locals: &[(~str, bool)]) -> ~str {
let mut code = self.program_header();
code.push_str("
fn assert_encodable<T: Encodable<::extra::ebml::writer::Encoder>>(t: &T) {}
");

code.push_str("fn main() {\n");
// It's easy to initialize things if we don't run things...
for self.local_vars.each |name, var| {
let mt = var.mt();
code.push_str(fmt!("let%s %s: %s = fail!();\n", mt, *name, var.ty));
var.alter(*name, &mut code);
}
code.push_str("{\n");
code.push_str(user_input);
code.push_char('\n');
match *to_print {
Some(ref s) => {
code.push_str(*s);
code.push_char('\n');
}
None => {}
}

for new_locals.each |p| {
code.push_str(fmt!("assert_encodable(&%s);\n", *p.first_ref()));
}
code.push_str("};}");
return code;
}

/// Creates a program to be fed into rustc. This program is structured to
/// deserialize all bindings into local variables, run the code input, and
/// then reserialize all the variables back out.
///
/// This program (unlike test_code) is meant to run to actually execute the
/// user's input
pub fn code(&mut self, user_input: &str, to_print: &Option<~str>) -> ~str {
let mut code = self.program_header();
code.push_str("
fn main() {
");

let key: sys::Closure = unsafe {
let tls_key: &'static fn(@LocalCache) = tls_key;
cast::transmute(tls_key)
};
// First, get a handle to the tls map which stores all the local
// variables. This works by totally legitimately using the 'code'
// pointer of the 'tls_key' function as a uint, and then casting it back
// up to a function
code.push_str(fmt!("
let __tls_map: @mut ::std::hashmap::HashMap<~str, @~[u8]> = unsafe {
let key = ::std::sys::Closure{ code: %? as *(),
env: ::std::ptr::null() };
let key = ::std::cast::transmute(key);
*::std::local_data::local_data_get(key).unwrap()
};\n", key.code as uint));

// Using this __tls_map handle, deserialize each variable binding that
// we know about
for self.local_vars.each |name, var| {
let mt = var.mt();
code.push_str(fmt!("let%s %s: %s = {
let data = __tls_map.get_copy(&~\"%s\");
let doc = ::extra::ebml::reader::Doc(data);
let mut decoder = ::extra::ebml::reader::Decoder(doc);
::extra::serialize::Decodable::decode(&mut decoder)
};\n", mt, *name, var.ty, *name));
var.alter(*name, &mut code);
}

// After all that, actually run the user's code.
code.push_str(user_input);
code.push_char('\n');

match *to_print {
Some(ref s) => { code.push_str(fmt!("pp({\n%s\n});", *s)); }
None => {}
}

do self.newvars.consume |name, var| {
self.local_vars.insert(name, var);
}

// After the input code is run, we can re-serialize everything back out
// into tls map (to be read later on by this task)
for self.local_vars.each |name, var| {
code.push_str(fmt!("{
let local: %s = %s;
let bytes = do ::std::io::with_bytes_writer |io| {
let mut enc = ::extra::ebml::writer::Encoder(io);
local.encode(&mut enc);
};
__tls_map.insert(~\"%s\", @bytes);
}\n", var.real_ty(), *name, *name));
}

// Close things up, and we're done.
code.push_str("}");
return code;
}

/// Creates the header of the programs which are generated to send to rustc
fn program_header(&self) -> ~str {
// up front, disable lots of annoying lints, then include all global
// state such as items, view items, and extern mods.
let mut code = fmt!("
#[allow(ctypes)];
#[allow(heap_memory)];
#[allow(implicit_copies)];
#[allow(managed_heap_memory)];
#[allow(non_camel_case_types)];
#[allow(owned_heap_memory)];
#[allow(path_statement)];
#[allow(unrecognized_lint)];
#[allow(unused_imports)];
#[allow(while_true)];
#[allow(unused_variable)];
#[allow(dead_assignment)];
#[allow(unused_unsafe)];
#[allow(unused_mut)];
#[allow(unreachable_code)];
extern mod extra;
%s // extern mods
use extra::serialize::*;
%s // view items
", self.externs, self.view_items);
for self.structs.each_value |s| {
// The structs aren't really useful unless they're encodable
code.push_str("#[deriving(Encodable, Decodable)]");
code.push_str(*s);
code.push_str("\n");
}
for self.items.each_value |s| {
code.push_str(*s);
code.push_str("\n");
}
code.push_str("fn pp<T>(t: T) { println(fmt!(\"%?\", t)); }\n");
return code;
}

/// Initializes the task-local cache of all local variables known to the
/// program. This will be used to read local variables out of once the
/// program starts
pub fn set_cache(&self) {
let map = @mut HashMap::new();
for self.local_vars.each |name, value| {
map.insert(copy *name, @copy value.data);
}
unsafe {
local_data::local_data_set(tls_key, @map);
}
}

/// Once the program has finished running, this function will consume the
/// task-local cache of local variables. After the program finishes running,
/// it updates this cache with the new values of each local variable.
pub fn consume_cache(&mut self) {
let map = unsafe {
local_data::local_data_pop(tls_key).expect("tls is empty")
};
do map.consume |name, value| {
match self.local_vars.find_mut(&name) {
Some(v) => { v.data = copy *value; }
None => { fail!("unknown variable %s", name) }
}
}
}

// Simple functions to record various global things (as strings)

pub fn record_view_item(&mut self, vi: &str) {
self.view_items.push_str(vi);
self.view_items.push_char('\n');
}

pub fn record_struct(&mut self, name: &str, s: ~str) {
let name = name.to_owned();
self.items.remove(&name);
self.structs.insert(name, s);
}

pub fn record_item(&mut self, name: &str, it: ~str) {
let name = name.to_owned();
self.structs.remove(&name);
self.items.insert(name, it);
}

pub fn record_extern(&mut self, name: &str) {
self.externs.push_str(name);
self.externs.push_char('\n');
}

/// This monster function is responsible for reading the main function
/// generated by test_code() to determine the type of each local binding
/// created by the user's input.
///
/// Once the types are known, they are inserted into the local_vars map in
/// this Program (to be deserialized later on
pub fn register_new_vars(&mut self, blk: &ast::blk, tcx: ty::ctxt) {
debug!("looking for new variables");
let newvars = @mut HashMap::new();
do each_user_local(blk) |local| {
let mutable = local.node.is_mutbl;
do each_binding(local) |path, id| {
let name = do with_pp(token::get_ident_interner()) |pp, _| {
pprust::print_path(pp, path, false);
};
let mut t = ty::node_id_to_type(tcx, id);
let mut tystr = ~"";
let mut lvar = LocalVariable {
ty: ~"",
data: ~[],
mutable: mutable,
alterations: None,
};
// This loop is responsible for figuring out what "alterations"
// are necessary for this local variable.
loop {
match ty::get(t).sty {
// &T encoded will decode to T, so we need to be sure to
// re-take a loan after decoding
ty::ty_rptr(_, mt) => {
if mt.mutbl == ast::m_mutbl {
tystr.push_str("&mut ");
} else {
tystr.push_str("&");
}
t = mt.ty;
}
// Literals like [1, 2, 3] and (~[0]).slice() will both
// be serialized to ~[T], whereas it's requested to be a
// &[T] instead.
ty::ty_evec(mt, ty::vstore_slice(*)) |
ty::ty_evec(mt, ty::vstore_fixed(*)) => {
let vty = ppaux::ty_to_str(tcx, mt.ty);
let derefs = copy tystr;
lvar.ty = tystr + "~[" + vty + "]";
lvar.alterations = Some((tystr + "&[" + vty + "]",
derefs));
break;
}
// Similar to vectors, &str serializes to ~str, so a
// borrow must be taken
ty::ty_estr(ty::vstore_slice(*)) => {
let derefs = copy tystr;
lvar.ty = tystr + "~str";
lvar.alterations = Some((tystr + "&str", derefs));
break;
}
// Don't generate extra stuff if there's no borrowing
// going on here
_ if "" == tystr => {
lvar.ty = ppaux::ty_to_str(tcx, t);
break;
}
// If we're just borrowing (no vectors or strings), then
// we just need to record how many borrows there were.
_ => {
let derefs = copy tystr;
let tmptystr = ppaux::ty_to_str(tcx, t);
lvar.alterations = Some((tystr + tmptystr, derefs));
lvar.ty = tmptystr;
break;
}
}
}
newvars.insert(name, lvar);
}
}

// I'm not an @ pointer, so this has to be done outside.
do newvars.consume |k, v| {
self.newvars.insert(k, v);
}

// helper functions to perform ast iteration
fn each_user_local(blk: &ast::blk, f: &fn(@ast::local)) {
do find_user_block(blk) |blk| {
for blk.node.stmts.each |stmt| {
match stmt.node {
ast::stmt_decl(d, _) => {
match d.node {
ast::decl_local(l) => { f(l); }
_ => {}
}
}
_ => {}
}
}
}
}

fn find_user_block(blk: &ast::blk, f: &fn(&ast::blk)) {
for blk.node.stmts.each |stmt| {
match stmt.node {
ast::stmt_semi(e, _) => {
match e.node {
ast::expr_block(ref blk) => { return f(blk); }
_ => {}
}
}
_ => {}
}
}
fail!("couldn't find user block");
}
}
}

impl LocalVariable {
/// Performs alterations to the code provided, given the name of this
/// variable.
fn alter(&self, name: &str, code: &mut ~str) {
match self.alterations {
Some((ref real_ty, ref prefix)) => {
code.push_str(fmt!("let%s %s: %s = %s%s;\n",
self.mt(), name,
*real_ty, *prefix, name));
}
None => {}
}
}

fn real_ty<'a>(&'a self) -> &'a str {
match self.alterations {
Some((ref real_ty, _)) => {
let ret: &'a str = *real_ty;
return ret;
}
None => {
let ret: &'a str = self.ty;
return ret;
}
}
}

fn mt(&self) -> &'static str {
if self.mutable {" mut"} else {""}
}
}
430 changes: 298 additions & 132 deletions src/librusti/rusti.rc
Original file line number Diff line number Diff line change
@@ -8,7 +8,40 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.

// rusti - REPL using the JIT backend
/*!
* rusti - A REPL using the JIT backend
*
* Rusti works by serializing state between lines of input. This means that each
* line can be run in a separate task, and the only limiting factor is that all
* local bound variables are encodable.
*
* This is accomplished by feeding in generated input to rustc for execution in
* the JIT compiler. Currently input actually gets fed in three times to get
* information about the program.
*
* - Pass #1
* In this pass, the input is simply thrown at the parser and the input comes
* back. This validates the structure of the program, and at this stage the
* global items (fns, structs, impls, traits, etc.) are filtered from the
* input into the "global namespace". These declarations shadow all previous
* declarations of an item by the same name.
*
* - Pass #2
* After items have been stripped, the remaining input is passed to rustc
* along with all local variables declared (initialized to nothing). This pass
* runs up to typechecking. From this, we can learn about the types of each
* bound variable, what variables are bound, and also ensure that all the
* types are encodable (the input can actually be run).
*
* - Pass #3
* Finally, a program is generated to deserialize the local variable state,
* run the code input, and then reserialize all bindings back into a local
* hash map. Once this code runs, the input has fully been run and the REPL
* waits for new input.
*
* Encoding/decoding is done with EBML, and there is simply a map of ~str ->
* ~[u8] maintaining the values of each local binding (by name).
*/

#[link(name = "rusti",
vers = "0.7-pre",
@@ -18,24 +51,25 @@
#[license = "MIT/ASL2"];
#[crate_type = "lib"];

#[no_std];

extern mod core(name = "std");
extern mod std(name = "extra");

extern mod extra;
extern mod rustc;
extern mod syntax;

use core::prelude::*;
use core::*;
use std::{libc, io, os, task, vec};
use std::cell::Cell;
use extra::rl;

use core::cell::Cell;
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;
use syntax::print::pprust;

use program::Program;
use utils::*;

mod program;
pub mod utils;

/**
* A structure shared across REPL instances for storing history
@@ -45,9 +79,9 @@ pub struct Repl {
prompt: ~str,
binary: ~str,
running: bool,
view_items: ~str,
lib_search_paths: ~[~str],
stmts: ~str

program: Program,
}

// Action to do after reading a :command
@@ -56,67 +90,9 @@ enum CmdAction {
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(mut repl: Repl, blk: &ast::blk, intr: @token::ident_interner) -> Repl {
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 = repl.view_items + "\n" + new_view_items
}
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(*) |
_ => {}
}
}
}
}
};

debug!("new stmts %s", new_stmts);

repl.stmts = repl.stmts + "\n" + new_stmts
}

return repl;
}

/// Run an input string in a Repl, returning the new Repl.
fn run(repl: Repl, input: ~str) -> Repl {
fn run(mut repl: Repl, input: ~str) -> Repl {
// Build some necessary rustc boilerplate for compiling things
let binary = repl.binary.to_managed();
let options = @session::options {
crate_type: session::unknown_crate,
@@ -125,56 +101,165 @@ fn run(repl: Repl, input: ~str) -> Repl {
jit: true,
.. copy *session::basic_options()
};
// Because we assume that everything is encodable (and assert so), add some
// extra helpful information if the error crops up. Otherwise people are
// bound to be very confused when they find out code is running that they
// never typed in...
let sess = driver::build_session(options, |cm, msg, lvl| {
diagnostic::emit(cm, msg, lvl);
if msg.contains("failed to find an implementation of trait") &&
msg.contains("extra::serialize::Encodable") {
diagnostic::emit(cm,
"Currrently rusti serializes bound locals between \
different lines of input. This means that all \
values of local variables need to be encodable, \
and this type isn't encodable",
diagnostic::note);
}
});
let intr = token::get_ident_interner();

//
// Stage 1: parse the input and filter it into the program (as necessary)
//
debug!("parsing: %s", input);
let crate = parse_input(sess, binary, input);
let mut to_run = ~[]; // statements to run (emitted back into code)
let new_locals = @mut ~[]; // new locals being defined
let mut result = None; // resultant expression (to print via pp)
do find_main(crate, sess) |blk| {
// Fish out all the view items, be sure to record 'extern mod' items
// differently beause they must appear before all 'use' statements
for blk.node.view_items.each |vi| {
let s = do with_pp(intr) |pp, _| {
pprust::print_view_item(pp, *vi);
};
match vi.node {
ast::view_item_extern_mod(*) => {
repl.program.record_extern(s);
}
ast::view_item_use(*) => { repl.program.record_view_item(s); }
}
}

debug!("building driver input");
let head = include_str!("wrapper.rs").to_owned();
let foot = fmt!("fn main() {\n%s\n%s\n\nprint({\n%s\n})\n}",
repl.view_items, repl.stmts, input);
let wrapped = driver::str_input((head + foot).to_managed());
// Iterate through all of the block's statements, inserting them into
// the correct portions of the program
for blk.node.stmts.each |stmt| {
let s = do with_pp(intr) |pp, _| { pprust::print_stmt(pp, *stmt); };
match stmt.node {
ast::stmt_decl(d, _) => {
match d.node {
ast::decl_item(it) => {
let name = sess.str_of(it.ident);
match it.node {
// Structs are treated specially because to make
// them at all usable they need to be decorated
// with #[deriving(Encoable, Decodable)]
ast::item_struct(*) => {
repl.program.record_struct(name, s);
}
// Item declarations are hoisted out of main()
_ => { repl.program.record_item(name, s); }
}
}

debug!("inputting %s", head + foot);
// Local declarations must be specially dealt with,
// record all local declarations for use later on
ast::decl_local(l) => {
let mutbl = l.node.is_mutbl;
do each_binding(l) |path, _| {
let s = do with_pp(intr) |pp, _| {
pprust::print_path(pp, path, false);
};
new_locals.push((s, mutbl));
}
to_run.push(s);
}
}
}

debug!("building a driver session");
let sess = driver::build_session(options, diagnostic::emit);
// run statements with expressions (they have effects)
ast::stmt_mac(*) | ast::stmt_semi(*) | ast::stmt_expr(*) => {
to_run.push(s);
}
}
}
result = do blk.node.expr.map_consume |e| {
do with_pp(intr) |pp, _| { pprust::print_expr(pp, e); }
};
}
// return fast for empty inputs
if to_run.len() == 0 && result.is_none() {
return repl;
}

debug!("building driver configuration");
let cfg = driver::build_configuration(sess,
binary,
&wrapped);
//
// Stage 2: run everything up to typeck to learn the types of the new
// variables introduced into the program
//
info!("Learning about the new types in the program");
repl.program.set_cache(); // before register_new_vars (which changes them)
let input = to_run.connect("\n");
let test = repl.program.test_code(input, &result, *new_locals);
debug!("testing with ^^^^^^ %?", (||{ println(test) })());
let dinput = driver::str_input(test.to_managed());
let cfg = driver::build_configuration(sess, binary, &dinput);
let outputs = driver::build_output_filenames(&dinput, &None, &None, [], sess);
let (crate, tcx) = driver::compile_upto(sess, copy cfg, &dinput,
driver::cu_typeck, Some(outputs));
// Once we're typechecked, record the types of all local variables defined
// in this input
do find_main(crate.expect("crate after cu_typeck"), sess) |blk| {
repl.program.register_new_vars(blk, tcx.expect("tcx after cu_typeck"));
}

let outputs = driver::build_output_filenames(&wrapped, &None, &None, [], sess);
debug!("calling compile_upto");
//
// Stage 3: Actually run the code in the JIT
//
info!("actually running code");
let code = repl.program.code(input, &result);
debug!("actually running ^^^^^^ %?", (||{ println(code) })());
let input = driver::str_input(code.to_managed());
let cfg = driver::build_configuration(sess, binary, &input);
let outputs = driver::build_output_filenames(&input, &None, &None, [], sess);
let sess = driver::build_session(options, diagnostic::emit);
driver::compile_upto(sess, cfg, &input, driver::cu_everything,
Some(outputs));

let crate = driver::parse_input(sess, copy cfg, &wrapped);
driver::compile_rest(sess, cfg, driver::compile_upto { from: driver::cu_parse,
to: driver::cu_everything },
Some(outputs), Some(crate));
//
// Stage 4: Inform the program that computation is done so it can update all
// local variable bindings.
//
info!("cleaning up after code");
repl.program.consume_cache();

let mut opt = None;
return repl;

for crate.node.module.items.each |item| {
match item.node {
ast::item_fn(_, _, _, _, ref blk) => {
if item.ident == sess.ident_of("main") {
opt = blk.node.expr;
}
}
_ => {}
}
fn parse_input(sess: session::Session, binary: @str,
input: &str) -> @ast::crate {
let code = fmt!("fn main() {\n %s \n}", input);
let input = driver::str_input(code.to_managed());
let cfg = driver::build_configuration(sess, binary, &input);
let outputs = driver::build_output_filenames(&input, &None, &None, [], sess);
let (crate, _) = driver::compile_upto(sess, cfg, &input,
driver::cu_parse, Some(outputs));
crate.expect("parsing should return a crate")
}

let e = opt.unwrap();
let blk = match e.node {
ast::expr_call(_, ref exprs, _) => {
match exprs[0].node {
ast::expr_block(ref blk) => blk,
_ => fail!()
fn find_main(crate: @ast::crate, sess: session::Session,
f: &fn(&ast::blk)) {
for crate.node.module.items.each |item| {
match item.node {
ast::item_fn(_, _, _, _, ref blk) => {
if item.ident == sess.ident_of("main") {
return f(blk);
}
}
_ => {}
}
}
_ => fail!()
};
debug!("recording input into repl history");
record(repl, blk, token::get_ident_interner())
fail!("main function was expected somewhere...");
}
}

// Compiles a crate given by the filename as a library if the compiled
@@ -265,8 +350,7 @@ fn run_cmd(repl: &mut Repl, _in: @io::Reader, _out: @io::Writer,
match cmd {
~"exit" => repl.running = false,
~"clear" => {
repl.view_items = ~"";
repl.stmts = ~"";
repl.program.clear();

// XXX: Win32 version of linenoise can't do this
//rl::clear();
@@ -296,12 +380,9 @@ fn run_cmd(repl: &mut Repl, _in: @io::Reader, _out: @io::Writer,
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);
}
repl.program.record_extern(fmt!("extern mod %s;", *crate));
if !repl.lib_search_paths.contains(&crate_dir) {
repl.lib_search_paths.push(crate_dir);
}
}
if loaded_crates.is_empty() {
@@ -340,7 +421,7 @@ pub fn run_line(repl: &mut Repl, in: @io::Reader, out: @io::Writer, line: ~str,
-> Option<Repl> {
if line.starts_with(":") {
// FIXME #5898: conflicts with Cell.take(), so can't be at the top level
use core::iterator::IteratorUtil;
use std::iterator::IteratorUtil;

// drop the : and the \n (one byte each)
let full = line.slice(1, line.len() - 1);
@@ -388,9 +469,9 @@ pub fn main() {
prompt: ~"rusti> ",
binary: copy args[0],
running: true,
view_items: ~"",
lib_search_paths: ~[],
stmts: ~""

program: Program::new(),
};

let istty = unsafe { libc::isatty(libc::STDIN_FILENO as i32) } != 0;
@@ -434,23 +515,24 @@ pub fn main() {

#[cfg(test)]
mod tests {
use std::io;
use std::iterator::IteratorUtil;
use program::Program;
use super::*;
use core::io;

fn repl() -> Repl {
Repl {
prompt: ~"rusti> ",
binary: ~"rusti",
running: true,
view_items: ~"",
lib_search_paths: ~[],
stmts: ~""
program: Program::new(),
}
}

fn run_cmds(cmds: &[&str]) {
fn run_program(prog: &str) {
let mut r = repl();
for cmds.each |&cmd| {
for prog.split_iter('\n').advance |cmd| {
let result = run_line(&mut r, io::stdin(), io::stdout(),
cmd.to_owned(), false);
r = result.expect(fmt!("the command '%s' failed", cmd));
@@ -469,18 +551,102 @@ mod tests {
// To get some interesting output, run with RUST_LOG=rusti::tests

debug!("hopefully this runs");
run_cmds([""]);
run_program("");

debug!("regression test for #5937");
run_cmds(["use std;", ""]);
run_program("use std::hashmap;");

debug!("regression test for #5784");
run_cmds(["let a = 1;"]);
run_program("let a = 3;");

// XXX: can't spawn new tasks because the JIT code is cleaned up
// after the main function is done.
// debug!("regression test for #5803");
// run_cmds(["spawn( || println(\"Please don't segfault\") );",
// "do spawn { println(\"Please?\"); }"]);
// run_program("
// spawn( || println(\"Please don't segfault\") );
// do spawn { println(\"Please?\"); }
// ");

debug!("inferred integers are usable");
run_program("let a = 2;\n()\n");
run_program("
let a = 3;
let b = 4u;
assert!((a as uint) + b == 7)
");

debug!("local variables can be shadowed");
run_program("
let a = 3;
let a = 5;
assert!(a == 5)
");

debug!("strings are usable");
run_program("
let a = ~\"\";
let b = \"\";
let c = @\"\";
let d = a + b + c;
assert!(d.len() == 0);
");

debug!("vectors are usable");
run_program("
let a = ~[1, 2, 3];
let b = &[1, 2, 3];
let c = @[1, 2, 3];
let d = a + b + c;
assert!(d.len() == 9);
let e: &[int] = [];
");

debug!("structs are usable");
run_program("
struct A{ a: int }
let b = A{ a: 3 };
assert!(b.a == 3)
");

debug!("mutable variables");
run_program("
let mut a = 3;
a = 5;
let mut b = std::hashmap::HashSet::new::<int>();
b.insert(a);
assert!(b.contains(&5))
assert!(b.len() == 1)
");

debug!("functions are cached");
run_program("
fn fib(x: int) -> int { if x < 2 {x} else { fib(x - 1) + fib(x - 2) } }
let a = fib(3);
let a = a + fib(4);
assert!(a == 5)
");

debug!("modules are cached");
run_program("
mod b { pub fn foo() -> uint { 3 } }
assert!(b::foo() == 3)
");

debug!("multiple function definitions are allowed");
run_program("
fn f() {}
fn f() {}
f()
");

debug!("multiple item definitions are allowed");
run_program("
fn f() {}
mod f {}
struct f;
enum f {}
fn f() {}
f()
");
}
}
45 changes: 45 additions & 0 deletions src/librusti/utils.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// 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.

use std::io;
use syntax::ast;
use syntax::print::pp;
use syntax::print::pprust;
use syntax::parse::token;

pub fn each_binding(l: @ast::local, f: @fn(@ast::Path, ast::node_id)) {
use syntax::visit;

let vt = visit::mk_simple_visitor(
@visit::SimpleVisitor {
visit_pat: |pat| {
match pat.node {
ast::pat_ident(_, path, _) => {
f(path, pat.id);
}
_ => {}
}
},
.. *visit::default_simple_visitor()
}
);
(vt.visit_pat)(l.node.pat, ((), vt));
}

/// A utility function that hands off a pretty printer to a callback.
pub 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);
}
}
30 changes: 0 additions & 30 deletions src/librusti/wrapper.rs

This file was deleted.