From 0c47632898ff480804c53bcccd21a5715d2804d7 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Wed, 29 May 2013 21:56:24 -0500 Subject: [PATCH] Rewrite rusti --- src/librusti/program.rs | 434 ++++++++++++++++++++++++++++++++++++++++ src/librusti/rusti.rc | 432 +++++++++++++++++++++++++++------------ src/librusti/utils.rs | 45 +++++ src/librusti/wrapper.rs | 30 --- 4 files changed, 778 insertions(+), 163 deletions(-) create mode 100644 src/librusti/program.rs create mode 100644 src/librusti/utils.rs delete mode 100644 src/librusti/wrapper.rs diff --git a/src/librusti/program.rs b/src/librusti/program.rs new file mode 100644 index 00000000000..513baa67ac1 --- /dev/null +++ b/src/librusti/program.rs @@ -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 or the MIT license +// , 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: &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) { 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 {""} + } +} diff --git a/src/librusti/rusti.rc b/src/librusti/rusti.rc index 8df1018a0de..90a5a350b7f 100644 --- a/src/librusti/rusti.rc +++ b/src/librusti/rusti.rc @@ -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(); - 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()); + // + // 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!("inputting %s", head + foot); + // 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!("building a driver session"); - let sess = driver::build_session(options, diagnostic::emit); + // 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 driver configuration"); - let cfg = driver::build_configuration(sess, - binary, - &wrapped); - - let outputs = driver::build_output_filenames(&wrapped, &None, &None, [], sess); - debug!("calling compile_upto"); - - 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)); - - let mut opt = None; - - 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; + // 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; } - 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!() + // + // 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")); + } + + // + // 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)); + + // + // 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(); + + return repl; + + 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") + } + + 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 { 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::(); + 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 index 00000000000..0ac0f5a3c4c --- /dev/null +++ b/src/librusti/utils.rs @@ -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 or the MIT license +// , 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 index 664e5e3b246..00000000000 --- a/src/librusti/wrapper.rs +++ /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 or the MIT license -// , 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(result: T) { - println(fmt!("%?", result)); -}