]> git.lizzy.rs Git - rust.git/commitdiff
Rewrite rusti
authorAlex Crichton <alex@alexcrichton.com>
Thu, 30 May 2013 02:56:24 +0000 (21:56 -0500)
committerAlex Crichton <alex@alexcrichton.com>
Thu, 13 Jun 2013 04:03:17 +0000 (21:03 -0700)
src/librusti/program.rs [new file with mode: 0644]
src/librusti/rusti.rc
src/librusti/utils.rs [new file with mode: 0644]
src/librusti/wrapper.rs [deleted file]

diff --git a/src/librusti/program.rs b/src/librusti/program.rs
new file mode 100644 (file)
index 0000000..513baa6
--- /dev/null
@@ -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 {""}
+    }
+}
index 8df1018a0defa56b4ce559890ce28d7747423ad1..90a5a350b7fa4b2b9af68edf50731d70f8ea4c28 100644 (file)
@@ -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",
 #[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()
+        ");
     }
 }
diff --git a/src/librusti/utils.rs b/src/librusti/utils.rs
new file mode 100644 (file)
index 0000000..0ac0f5a
--- /dev/null
@@ -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);
+    }
+}
diff --git a/src/librusti/wrapper.rs b/src/librusti/wrapper.rs
deleted file mode 100644 (file)
index 664e5e3..0000000
+++ /dev/null
@@ -1,30 +0,0 @@
-// Copyright 2012 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.
-
-#[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)];
-
-extern mod std;
-
-fn print<T>(result: T) {
-    println(fmt!("%?", result));
-}