molt_shell/
shell.rs

1use molt::Interp;
2use molt::MoltList;
3use molt::Value;
4use rustyline::error::ReadlineError;
5use rustyline::Editor;
6use std::fs;
7
8/// Invokes an interactive REPL for the given interpreter, using `rustyline` line editing.
9///
10/// The REPL will display a default prompt to the user.  Press `^C` to terminate
11/// the REPL, returning control to the caller.  Entering `exit` will also normally cause the
12/// application to terminate (but the `exit` command can be removed or redefined by the
13/// application).
14///
15/// To change the prompt, set the `tcl_prompt1` TCL variable to a script that returns
16/// the desired prompt.
17///
18/// See [`molt::interp`](../molt/interp/index.html) for details on how to configure and
19/// add commands to a Molt interpreter.
20///
21/// # Example
22///
23/// ```
24/// use molt::Interp;
25///
26/// // FIRST, create and initialize the interpreter.
27/// let mut interp = Interp::new();
28///
29/// // NOTE: commands can be added to the interpreter here.
30///
31/// // NEXT, invoke the REPL.
32/// molt_shell::repl(&mut interp);
33/// ```
34pub fn repl(interp: &mut Interp) {
35    let mut rl = Editor::<()>::new();
36
37    loop {
38        let readline = if let Ok(pscript) = interp.scalar("tcl_prompt1") {
39            match interp.eval(pscript.as_str()) {
40                Ok(prompt) => rl.readline(prompt.as_str()),
41                Err(exception) => {
42                    println!("{}", exception.value());
43                    rl.readline("% ")
44                }
45            }
46        } else {
47            rl.readline("% ")
48        };
49
50        match readline {
51            Ok(line) => {
52                let line = line.trim();
53                if !line.is_empty() {
54                    match interp.eval(line) {
55                        Ok(value) => {
56                            rl.add_history_entry(line);
57
58                            // Don't output empty values.
59                            if !value.as_str().is_empty() {
60                                println!("{}", value);
61                            }
62                        }
63                        Err(exception) => {
64                            println!("{}", exception.value());
65                        }
66                    }
67                }
68            }
69            Err(ReadlineError::Interrupted) => {
70                println!("^C");
71                break;
72            }
73            Err(ReadlineError::Eof) => break,
74            Err(err) => {
75                println!("I/O Error: {:?}", err);
76                break;
77            }
78        }
79    }
80}
81
82/// Executes a script from a set of command line arguments.
83///
84/// `args[0]` is presumed to be the name of a Molt script file, with any subsequent
85/// arguments being arguments to pass to the script.  The script will be be executed in
86/// the context of the given interpreter.
87///
88/// # Molt Variables
89///
90/// The calling information will be passed to the interpreter in the form of Molt
91/// variables:
92///
93/// * The Molt variable `arg0` will be set to the `arg0` value.
94/// * The Molt variable `argv` will be set to a Molt list containing the remainder of the
95///   `argv` array.
96///
97/// See [`molt::interp`](../molt/interp/index.html) for details on how to configure and
98/// add commands to a Molt interpreter.
99///
100/// # Example
101///
102/// ```
103/// use molt::Interp;
104/// use std::env;
105///
106/// // FIRST, get the command line arguments.
107/// let args: Vec<String> = env::args().collect();
108///
109/// // NEXT, create and initialize the interpreter.
110/// let mut interp = Interp::new();
111///
112/// // NOTE: commands can be added to the interpreter here.
113///
114/// // NEXT, evaluate the file, if any.
115/// if args.len() > 1 {
116///     molt_shell::script(&mut interp, &args[1..]);
117/// } else {
118///     eprintln!("Usage: myshell *filename.tcl");
119/// }
120/// ```
121pub fn script(interp: &mut Interp, args: &[String]) {
122    let arg0 = &args[0];
123    let argv = &args[1..];
124    match fs::read_to_string(&args[0]) {
125        Ok(script) => execute_script(interp, script, arg0, argv),
126        Err(e) => println!("{}", e),
127    }
128}
129
130/// Executes a script read from a file, with any command-line arguments, in
131/// the context of the given interpreter.  The `script` is the text of the
132/// script, `arg0` is the name of the script file, and `argv` contains the script
133/// arguments.
134///
135/// # Molt Variables
136///
137/// The calling information will be passed to the interpreter in the form of Molt
138/// variables:
139///
140/// * The Molt variable `arg0` will be set to the `arg0` value.
141/// * The Molt variable `argv` will be set to the `argv` array as a Molt list.
142fn execute_script(interp: &mut Interp, script: String, arg0: &str, argv: &[String]) {
143    let argv: MoltList = argv.iter().map(Value::from).collect();
144    interp
145        .set_scalar("arg0", Value::from(arg0))
146        .expect("arg0 predefined as array!");
147    interp
148        .set_scalar("argv", Value::from(argv))
149        .expect("argv predefined as array!");
150
151    match interp.eval(&script) {
152        Ok(_) => (),
153        Err(exception) => {
154            eprintln!("{}", exception.value());
155            std::process::exit(1);
156        }
157    }
158}