]> git.lizzy.rs Git - rust.git/blob - src/librusti/rusti.rs
86290ea65b5646f939eed20204ae3a07b79c6bc4
[rust.git] / src / librusti / rusti.rs
1 // Copyright 2012-2013 The Rust Project Developers. See the COPYRIGHT
2 // file at the top-level directory of this distribution and at
3 // http://rust-lang.org/COPYRIGHT.
4 //
5 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8 // option. This file may not be copied, modified, or distributed
9 // except according to those terms.
10
11 /*!
12  * rusti - A REPL using the JIT backend
13  *
14  * Rusti works by serializing state between lines of input. This means that each
15  * line can be run in a separate task, and the only limiting factor is that all
16  * local bound variables are encodable.
17  *
18  * This is accomplished by feeding in generated input to rustc for execution in
19  * the JIT compiler. Currently input actually gets fed in three times to get
20  * information about the program.
21  *
22  * - Pass #1
23  *   In this pass, the input is simply thrown at the parser and the input comes
24  *   back. This validates the structure of the program, and at this stage the
25  *   global items (fns, structs, impls, traits, etc.) are filtered from the
26  *   input into the "global namespace". These declarations shadow all previous
27  *   declarations of an item by the same name.
28  *
29  * - Pass #2
30  *   After items have been stripped, the remaining input is passed to rustc
31  *   along with all local variables declared (initialized to nothing). This pass
32  *   runs up to typechecking. From this, we can learn about the types of each
33  *   bound variable, what variables are bound, and also ensure that all the
34  *   types are encodable (the input can actually be run).
35  *
36  * - Pass #3
37  *   Finally, a program is generated to deserialize the local variable state,
38  *   run the code input, and then reserialize all bindings back into a local
39  *   hash map. This code is then run in the JIT engine provided by the rust
40  *   compiler.
41  *
42  * - Pass #4
43  *   Once this code runs, the input has fully been run and the hash map of local
44  *   variables from TLS is read back into the local store of variables. This is
45  *   then used later to pass back along to the parent rusti task and then begin
46  *   waiting for input again.
47  *
48  * - Pass #5
49  *   When running rusti code, it's important to consume ownership of the LLVM
50  *   jit contextual information to prevent code from being deallocated too soon
51  *   (before drop glue runs, see #7732). For this reason, the jit context is
52  *   consumed and also passed along to the parent task. The parent task then
53  *   keeps around all contexts while rusti is running. This must be done because
54  *   tasks could in theory be spawned off and running in the background (still
55  *   using the code).
56  *
57  * Encoding/decoding is done with EBML, and there is simply a map of ~str ->
58  * ~[u8] maintaining the values of each local binding (by name).
59  */
60
61 #[link(name = "rusti",
62        vers = "0.8-pre",
63        uuid = "7fb5bf52-7d45-4fee-8325-5ad3311149fc",
64        url = "https://github.com/mozilla/rust/tree/master/src/rusti")];
65
66 #[license = "MIT/ASL2"];
67 #[crate_type = "lib"];
68
69 extern mod extra;
70 extern mod rustc;
71 extern mod syntax;
72
73 use std::{libc, io, os, task};
74 use std::cell::Cell;
75 use extra::rl;
76
77 use rustc::driver::{driver, session};
78 use rustc::back::link::jit;
79 use syntax::{ast, diagnostic};
80 use syntax::ast_util::*;
81 use syntax::parse::token;
82 use syntax::print::pprust;
83
84 use program::Program;
85 use utils::*;
86
87 mod program;
88 pub mod utils;
89
90 /**
91  * A structure shared across REPL instances for storing history
92  * such as statements and view items. I wish the AST was sendable.
93  */
94 pub struct Repl {
95     prompt: ~str,
96     binary: ~str,
97     running: bool,
98     lib_search_paths: ~[~str],
99     engines: ~[~jit::Engine],
100
101     program: ~Program,
102 }
103
104 // Action to do after reading a :command
105 enum CmdAction {
106     action_none,
107     action_run_line(~str),
108 }
109
110 /// Run an input string in a Repl, returning the new Repl.
111 fn run(mut program: ~Program, binary: ~str, lib_search_paths: ~[~str],
112        input: ~str) -> (~Program, Option<~jit::Engine>)
113 {
114     // Build some necessary rustc boilerplate for compiling things
115     let binary = binary.to_managed();
116     let options = @session::options {
117         crate_type: session::unknown_crate,
118         binary: binary,
119         addl_lib_search_paths: @mut lib_search_paths.map(|p| Path(*p)),
120         jit: true,
121         .. (*session::basic_options()).clone()
122     };
123     // Because we assume that everything is encodable (and assert so), add some
124     // extra helpful information if the error crops up. Otherwise people are
125     // bound to be very confused when they find out code is running that they
126     // never typed in...
127     let sess = driver::build_session(options, |cm, msg, lvl| {
128         diagnostic::emit(cm, msg, lvl);
129         if msg.contains("failed to find an implementation of trait") &&
130            msg.contains("extra::serialize::Encodable") {
131             diagnostic::emit(cm,
132                              "Currrently rusti serializes bound locals between \
133                               different lines of input. This means that all \
134                               values of local variables need to be encodable, \
135                               and this type isn't encodable",
136                              diagnostic::note);
137         }
138     });
139     let intr = token::get_ident_interner();
140
141     //
142     // Stage 1: parse the input and filter it into the program (as necessary)
143     //
144     debug!("parsing: %s", input);
145     let crate = parse_input(sess, binary, input);
146     let mut to_run = ~[];       // statements to run (emitted back into code)
147     let new_locals = @mut ~[];  // new locals being defined
148     let mut result = None;      // resultant expression (to print via pp)
149     do find_main(crate, sess) |blk| {
150         // Fish out all the view items, be sure to record 'extern mod' items
151         // differently beause they must appear before all 'use' statements
152         for vi in blk.view_items.iter() {
153             let s = do with_pp(intr) |pp, _| {
154                 pprust::print_view_item(pp, vi);
155             };
156             match vi.node {
157                 ast::view_item_extern_mod(*) => {
158                     program.record_extern(s);
159                 }
160                 ast::view_item_use(*) => { program.record_view_item(s); }
161             }
162         }
163
164         // Iterate through all of the block's statements, inserting them into
165         // the correct portions of the program
166         for stmt in blk.stmts.iter() {
167             let s = do with_pp(intr) |pp, _| { pprust::print_stmt(pp, *stmt); };
168             match stmt.node {
169                 ast::stmt_decl(d, _) => {
170                     match d.node {
171                         ast::decl_item(it) => {
172                             let name = sess.str_of(it.ident);
173                             match it.node {
174                                 // Structs are treated specially because to make
175                                 // them at all usable they need to be decorated
176                                 // with #[deriving(Encoable, Decodable)]
177                                 ast::item_struct(*) => {
178                                     program.record_struct(name, s);
179                                 }
180                                 // Item declarations are hoisted out of main()
181                                 _ => { program.record_item(name, s); }
182                             }
183                         }
184
185                         // Local declarations must be specially dealt with,
186                         // record all local declarations for use later on
187                         ast::decl_local(l) => {
188                             let mutbl = l.is_mutbl;
189                             do each_binding(l) |path, _| {
190                                 let s = do with_pp(intr) |pp, _| {
191                                     pprust::print_path(pp, path, false);
192                                 };
193                                 new_locals.push((s, mutbl));
194                             }
195                             to_run.push(s);
196                         }
197                     }
198                 }
199
200                 // run statements with expressions (they have effects)
201                 ast::stmt_mac(*) | ast::stmt_semi(*) | ast::stmt_expr(*) => {
202                     to_run.push(s);
203                 }
204             }
205         }
206         result = do blk.expr.map_consume |e| {
207             do with_pp(intr) |pp, _| { pprust::print_expr(pp, e); }
208         };
209     }
210     // return fast for empty inputs
211     if to_run.len() == 0 && result.is_none() {
212         return (program, None);
213     }
214
215     //
216     // Stage 2: run everything up to typeck to learn the types of the new
217     //          variables introduced into the program
218     //
219     info!("Learning about the new types in the program");
220     program.set_cache(); // before register_new_vars (which changes them)
221     let input = to_run.connect("\n");
222     let test = program.test_code(input, &result, *new_locals);
223     debug!("testing with ^^^^^^ %?", (||{ println(test) })());
224     let dinput = driver::str_input(test.to_managed());
225     let cfg = driver::build_configuration(sess, binary, &dinput);
226
227     let crate = driver::phase_1_parse_input(sess, cfg.clone(), &dinput);
228     let expanded_crate = driver::phase_2_configure_and_expand(sess, cfg, crate);
229     let analysis = driver::phase_3_run_analysis_passes(sess, expanded_crate);
230
231     // Once we're typechecked, record the types of all local variables defined
232     // in this input
233     do find_main(crate, sess) |blk| {
234         program.register_new_vars(blk, analysis.ty_cx);
235     }
236
237     //
238     // Stage 3: Actually run the code in the JIT
239     //
240     info!("actually running code");
241     let code = program.code(input, &result);
242     debug!("actually running ^^^^^^ %?", (||{ println(code) })());
243     let input = driver::str_input(code.to_managed());
244     let cfg = driver::build_configuration(sess, binary, &input);
245     let outputs = driver::build_output_filenames(&input, &None, &None, [], sess);
246     let sess = driver::build_session(options, diagnostic::emit);
247
248     let crate = driver::phase_1_parse_input(sess, cfg.clone(), &input);
249     let expanded_crate = driver::phase_2_configure_and_expand(sess, cfg, crate);
250     let analysis = driver::phase_3_run_analysis_passes(sess, expanded_crate);
251     let trans = driver::phase_4_translate_to_llvm(sess, expanded_crate, &analysis, outputs);
252     driver::phase_5_run_llvm_passes(sess, &trans, outputs);
253
254     //
255     // Stage 4: Inform the program that computation is done so it can update all
256     //          local variable bindings.
257     //
258     info!("cleaning up after code");
259     program.consume_cache();
260
261     //
262     // Stage 5: Extract the LLVM execution engine to take ownership of the
263     //          generated JIT code. This means that rusti can spawn parallel
264     //          tasks and we won't deallocate the code emitted until rusti
265     //          itself is destroyed.
266     //
267     return (program, jit::consume_engine());
268
269     fn parse_input(sess: session::Session, binary: @str,
270                    input: &str) -> @ast::Crate {
271         let code = fmt!("fn main() {\n %s \n}", input);
272         let input = driver::str_input(code.to_managed());
273         let cfg = driver::build_configuration(sess, binary, &input);
274         driver::phase_1_parse_input(sess, cfg.clone(), &input)
275     }
276
277     fn find_main(crate: @ast::Crate, sess: session::Session,
278                  f: &fn(&ast::Block)) {
279         for item in crate.module.items.iter() {
280             match item.node {
281                 ast::item_fn(_, _, _, _, ref blk) => {
282                     if item.ident == sess.ident_of("main") {
283                         return f(blk);
284                     }
285                 }
286                 _ => {}
287             }
288         }
289         fail!("main function was expected somewhere...");
290     }
291 }
292
293 // Compiles a crate given by the filename as a library if the compiled
294 // version doesn't exist or is older than the source file. Binary is
295 // the name of the compiling executable. Returns Some(true) if it
296 // successfully compiled, Some(false) if the crate wasn't compiled
297 // because it already exists and is newer than the source file, or
298 // None if there were compile errors.
299 fn compile_crate(src_filename: ~str, binary: ~str) -> Option<bool> {
300     match do task::try {
301         let src_path = Path(src_filename);
302         let binary = binary.to_managed();
303         let options = @session::options {
304             binary: binary,
305             addl_lib_search_paths: @mut ~[os::getcwd()],
306             .. (*session::basic_options()).clone()
307         };
308         let input = driver::file_input(src_path.clone());
309         let sess = driver::build_session(options, diagnostic::emit);
310         *sess.building_library = true;
311         let cfg = driver::build_configuration(sess, binary, &input);
312         let outputs = driver::build_output_filenames(
313             &input, &None, &None, [], sess);
314         // If the library already exists and is newer than the source
315         // file, skip compilation and return None.
316         let mut should_compile = true;
317         let dir = os::list_dir_path(&Path(outputs.out_filename.dirname()));
318         let maybe_lib_path = do dir.iter().find_ |file| {
319             // The actual file's name has a hash value and version
320             // number in it which is unknown at this time, so looking
321             // for a file that matches out_filename won't work,
322             // instead we guess which file is the library by matching
323             // the prefix and suffix of out_filename to files in the
324             // directory.
325             let file_str = file.filename().unwrap();
326             file_str.starts_with(outputs.out_filename.filestem().unwrap())
327                 && file_str.ends_with(outputs.out_filename.filetype().unwrap())
328         };
329         match maybe_lib_path {
330             Some(lib_path) => {
331                 let (src_mtime, _) = src_path.get_mtime().unwrap();
332                 let (lib_mtime, _) = lib_path.get_mtime().unwrap();
333                 if lib_mtime >= src_mtime {
334                     should_compile = false;
335                 }
336             },
337             None => { },
338         }
339         if (should_compile) {
340             println(fmt!("compiling %s...", src_filename));
341             let crate = driver::phase_1_parse_input(sess, cfg.clone(), &input);
342             let expanded_crate = driver::phase_2_configure_and_expand(sess, cfg, crate);
343             let analysis = driver::phase_3_run_analysis_passes(sess, expanded_crate);
344             let trans = driver::phase_4_translate_to_llvm(sess, expanded_crate, &analysis, outputs);
345             driver::phase_5_run_llvm_passes(sess, &trans, outputs);
346             true
347         } else { false }
348     } {
349         Ok(true) => Some(true),
350         Ok(false) => Some(false),
351         Err(_) => None,
352     }
353 }
354
355 /// Tries to get a line from rl after outputting a prompt. Returns
356 /// None if no input was read (e.g. EOF was reached).
357 fn get_line(use_rl: bool, prompt: &str) -> Option<~str> {
358     if use_rl {
359         let result = unsafe { rl::read(prompt) };
360
361         match result {
362             None => None,
363             Some(line) => {
364                 unsafe { rl::add_history(line) };
365                 Some(line)
366             }
367         }
368     } else {
369         if io::stdin().eof() {
370             None
371         } else {
372             Some(io::stdin().read_line())
373         }
374     }
375 }
376
377 /// Run a command, e.g. :clear, :exit, etc.
378 fn run_cmd(repl: &mut Repl, _in: @io::Reader, _out: @io::Writer,
379            cmd: ~str, args: ~[~str], use_rl: bool) -> CmdAction {
380     let mut action = action_none;
381     match cmd {
382         ~"exit" => repl.running = false,
383         ~"clear" => {
384             repl.program.clear();
385
386             // XXX: Win32 version of linenoise can't do this
387             //rl::clear();
388         }
389         ~"help" => {
390             println(
391                 ":{\\n ..lines.. \\n:}\\n - execute multiline command\n\
392                  :load <crate> ... - loads given crates as dynamic libraries\n\
393                  :clear - clear the bindings\n\
394                  :exit - exit from the repl\n\
395                  :help - show this message");
396         }
397         ~"load" => {
398             let mut loaded_crates: ~[~str] = ~[];
399             for arg in args.iter() {
400                 let (crate, filename) =
401                     if arg.ends_with(".rs") || arg.ends_with(".rc") {
402                     (arg.slice_to(arg.len() - 3).to_owned(), (*arg).clone())
403                 } else {
404                     ((*arg).clone(), *arg + ".rs")
405                 };
406                 match compile_crate(filename, repl.binary.clone()) {
407                     Some(_) => loaded_crates.push(crate),
408                     None => { }
409                 }
410             }
411             for crate in loaded_crates.iter() {
412                 let crate_path = Path(*crate);
413                 let crate_dir = crate_path.dirname();
414                 repl.program.record_extern(fmt!("extern mod %s;", *crate));
415                 if !repl.lib_search_paths.iter().any(|x| x == &crate_dir) {
416                     repl.lib_search_paths.push(crate_dir);
417                 }
418             }
419             if loaded_crates.is_empty() {
420                 println("no crates loaded");
421             } else {
422                 printfln!("crates loaded: %s", loaded_crates.connect(", "));
423             }
424         }
425         ~"{" => {
426             let mut multiline_cmd = ~"";
427             let mut end_multiline = false;
428             while (!end_multiline) {
429                 match get_line(use_rl, "rusti| ") {
430                     None => fail!("unterminated multiline command :{ .. :}"),
431                     Some(line) => {
432                         if line.trim() == ":}" {
433                             end_multiline = true;
434                         } else {
435                             multiline_cmd.push_str(line);
436                             multiline_cmd.push_char('\n');
437                         }
438                     }
439                 }
440             }
441             action = action_run_line(multiline_cmd);
442         }
443         _ => println(~"unknown cmd: " + cmd)
444     }
445     return action;
446 }
447
448 /// Executes a line of input, which may either be rust code or a
449 /// :command. Returns a new Repl if it has changed.
450 pub fn run_line(repl: &mut Repl, input: @io::Reader, out: @io::Writer, line: ~str,
451                 use_rl: bool) -> bool
452 {
453     if line.starts_with(":") {
454         // drop the : and the \n (one byte each)
455         let full = line.slice(1, line.len());
456         let split: ~[~str] = full.word_iter().transform(|s| s.to_owned()).collect();
457         let len = split.len();
458
459         if len > 0 {
460             let cmd = split[0].clone();
461
462             if !cmd.is_empty() {
463                 let args = if len > 1 {
464                     split.slice(1, len).to_owned()
465                 } else { ~[] };
466
467                 match run_cmd(repl, input, out, cmd, args, use_rl) {
468                     action_none => { }
469                     action_run_line(multiline_cmd) => {
470                         if !multiline_cmd.is_empty() {
471                             return run_line(repl, input, out, multiline_cmd, use_rl);
472                         }
473                     }
474                 }
475                 return true;
476             }
477         }
478     }
479
480     let line = Cell::new(line);
481     let program = Cell::new(repl.program.clone());
482     let lib_search_paths = Cell::new(repl.lib_search_paths.clone());
483     let binary = Cell::new(repl.binary.clone());
484     let result = do task::try {
485         run(program.take(), binary.take(), lib_search_paths.take(), line.take())
486     };
487
488     match result {
489         Ok((program, engine)) => {
490             repl.program = program;
491             match engine {
492                 Some(e) => { repl.engines.push(e); }
493                 None => {}
494             }
495             return true;
496         }
497         Err(*) => { return false; }
498     }
499 }
500
501 pub fn main() {
502     let args = os::args();
503     let input = io::stdin();
504     let out = io::stdout();
505     let mut repl = Repl {
506         prompt: ~"rusti> ",
507         binary: args[0].clone(),
508         running: true,
509         lib_search_paths: ~[],
510         engines: ~[],
511
512         program: ~Program::new(),
513     };
514
515     let istty = unsafe { libc::isatty(libc::STDIN_FILENO as i32) } != 0;
516
517     // only print this stuff if the user is actually typing into rusti
518     if istty {
519         println("WARNING: The Rust REPL is experimental and may be");
520         println("unstable. If you encounter problems, please use the");
521         println("compiler instead. Type :help for help.");
522
523         unsafe {
524             do rl::complete |line, suggest| {
525                 if line.starts_with(":") {
526                     suggest(~":clear");
527                     suggest(~":exit");
528                     suggest(~":help");
529                     suggest(~":load");
530                 }
531             }
532         }
533     }
534
535     while repl.running {
536         match get_line(istty, repl.prompt) {
537             None => break,
538             Some(line) => {
539                 if line.is_empty() {
540                     if istty {
541                         println("()");
542                     }
543                     loop;
544                 }
545                 run_line(&mut repl, input, out, line, istty);
546             }
547         }
548     }
549 }
550
551 #[cfg(test)]
552 mod tests {
553     use std::io;
554     use program::Program;
555     use super::*;
556
557     fn repl() -> Repl {
558         Repl {
559             prompt: ~"rusti> ",
560             binary: ~"rusti",
561             running: true,
562             lib_search_paths: ~[],
563             engines: ~[],
564             program: ~Program::new(),
565         }
566     }
567
568     #[cfg(not(target_word_size = "32"))]
569     fn run_program(prog: &str) {
570         let mut r = repl();
571         for cmd in prog.split_iter('\n') {
572             assert!(run_line(&mut r, io::stdin(), io::stdout(),
573                              cmd.to_owned(), false),
574                     "the command '%s' failed", cmd);
575         }
576     }
577     // FIXME: #7220 rusti on 32bit mac doesn't work
578     // FIXME: #7641 rusti on 32bit linux cross compile doesn't work
579     #[cfg(target_word_size = "32")]
580     fn run_program(_: &str) {}
581
582     #[test]
583     fn super_basic() {
584         run_program("");
585     }
586
587     #[test]
588     fn regression_5937() {
589         run_program("use std::hashmap;");
590     }
591
592     #[test]
593     fn regression_5784() {
594         run_program("let a = 3;");
595     }
596
597     #[test]
598     fn new_tasks() {
599         run_program("
600             use std::task::try;
601             try( || println(\"Please don't segfault\") );
602             do try { println(\"Please?\"); }
603         ");
604     }
605
606     #[test]
607     fn inferred_integers_usable() {
608         run_program("let a = 2;\n()\n");
609         run_program("
610             let a = 3;
611             let b = 4u;
612             assert!((a as uint) + b == 7)
613         ");
614     }
615
616     #[test]
617     fn local_variables_allow_shadowing() {
618         run_program("
619             let a = 3;
620             let a = 5;
621             assert!(a == 5)
622         ");
623     }
624
625     #[test]
626     fn string_usable() {
627         run_program("
628             let a = ~\"\";
629             let b = \"\";
630             let c = @\"\";
631             let d = a + b + c;
632             assert!(d.len() == 0);
633         ");
634     }
635
636     #[test]
637     fn vectors_usable() {
638         run_program("
639             let a = ~[1, 2, 3];
640             let b = &[1, 2, 3];
641             let c = @[1, 2, 3];
642             let d = a + b + c;
643             assert!(d.len() == 9);
644             let e: &[int] = [];
645         ");
646     }
647
648     #[test]
649     fn structs_usable() {
650         run_program("
651             struct A{ a: int }
652             let b = A{ a: 3 };
653             assert!(b.a == 3)
654         ");
655     }
656
657     #[test]
658     fn mutable_variables_work() {
659         run_program("
660             let mut a = 3;
661             a = 5;
662             let mut b = std::hashmap::HashSet::new::<int>();
663             b.insert(a);
664             assert!(b.contains(&5))
665             assert!(b.len() == 1)
666         ");
667     }
668
669     #[test]
670     fn functions_saved() {
671         run_program("
672             fn fib(x: int) -> int { if x < 2 {x} else { fib(x - 1) + fib(x - 2) } }
673             let a = fib(3);
674             let a = a + fib(4);
675             assert!(a == 5)
676         ");
677     }
678
679     #[test]
680     fn modules_saved() {
681         run_program("
682             mod b { pub fn foo() -> uint { 3 } }
683             assert!(b::foo() == 3)
684         ");
685     }
686
687     #[test]
688     fn multiple_functions() {
689         run_program("
690             fn f() {}
691             fn f() {}
692             f()
693         ");
694     }
695
696     #[test]
697     fn multiple_items_same_name() {
698         run_program("
699             fn f() {}
700             mod f {}
701             struct f;
702             enum f {}
703             fn f() {}
704             f()
705         ");
706     }
707
708     #[test]
709     fn simultaneous_definition_and_expression() {
710         run_program("
711             let a = 3; a as u8
712         ");
713     }
714
715     #[test]
716     fn exit_quits() {
717         let mut r = repl();
718         assert!(r.running);
719         let result = run_line(&mut r, io::stdin(), io::stdout(),
720                               ~":exit", false);
721         assert!(result);
722         assert!(!r.running);
723     }
724 }