1mod helper;
2
3use rustpython_compiler::{
4 CompileError, ParseError, parser::InterpolatedStringErrorType, parser::LexicalErrorType,
5 parser::ParseErrorType,
6};
7use rustpython_vm::{
8 AsObject, PyResult, VirtualMachine,
9 builtins::PyBaseExceptionRef,
10 compiler::{self},
11 readline::{Readline, ReadlineResult},
12 scope::Scope,
13};
14
15enum ShellExecResult {
16 Ok,
17 PyErr(PyBaseExceptionRef),
18 ContinueBlock,
19 ContinueLine,
20}
21
22fn shell_exec(
23 vm: &VirtualMachine,
24 source: &str,
25 scope: Scope,
26 empty_line_given: bool,
27 continuing_block: bool,
28) -> ShellExecResult {
29 #[cfg(windows)]
34 let source = &source.replace("\r\n", "\n");
35 match vm.compile(source, compiler::Mode::Single, "<stdin>".to_owned()) {
36 Ok(code) => {
37 if empty_line_given || !continuing_block {
38 match vm.run_code_obj(code, scope) {
40 Ok(_val) => ShellExecResult::Ok,
41 Err(err) => ShellExecResult::PyErr(err),
42 }
43 } else {
44 ShellExecResult::Ok
46 }
47 }
48 Err(CompileError::Parse(ParseError {
49 error: ParseErrorType::Lexical(LexicalErrorType::Eof),
50 ..
51 })) => ShellExecResult::ContinueLine,
52 Err(CompileError::Parse(ParseError {
53 error:
54 ParseErrorType::Lexical(LexicalErrorType::FStringError(
55 InterpolatedStringErrorType::UnterminatedTripleQuotedString,
56 )),
57 ..
58 })) => ShellExecResult::ContinueLine,
59 Err(err) => {
60 if let CompileError::Parse(ParseError {
63 error: ParseErrorType::Lexical(LexicalErrorType::UnclosedStringError),
64 raw_location,
65 ..
66 }) = err
67 {
68 let loc = raw_location.start().to_usize();
69 let mut iter = source.chars();
70 if let Some(quote) = iter.nth(loc)
71 && iter.next() == Some(quote)
72 && iter.next() == Some(quote)
73 {
74 return ShellExecResult::ContinueLine;
75 }
76 };
77
78 let bad_error = match err {
84 CompileError::Parse(ref p) => {
85 match &p.error {
86 ParseErrorType::Lexical(LexicalErrorType::IndentationError) => {
87 continuing_block
88 } ParseErrorType::OtherError(msg) => {
90 !msg.starts_with("Expected an indented block")
91 }
92 _ => true, }
94 }
95 _ => true, };
97
98 if empty_line_given || bad_error {
100 ShellExecResult::PyErr(vm.new_syntax_error(&err, Some(source)))
101 } else {
102 ShellExecResult::ContinueBlock
103 }
104 }
105 }
106}
107
108pub fn run_shell(vm: &VirtualMachine, scope: Scope) -> PyResult<()> {
110 let mut repl = Readline::new(helper::ShellHelper::new(vm, scope.globals.clone()));
111 let mut full_input = String::new();
112
113 let repl_history_path = match dirs::config_dir() {
115 Some(mut path) => {
116 path.push("rustpython");
117 path.push("repl_history.txt");
118 path
119 }
120 None => ".repl_history.txt".into(),
121 };
122
123 if repl.load_history(&repl_history_path).is_err() {
124 println!("No previous history.");
125 }
126
127 let mut continuing_block = false;
132 let mut continuing_line = false;
133
134 loop {
135 let prompt_name = if continuing_block || continuing_line {
136 "ps2"
137 } else {
138 "ps1"
139 };
140 let prompt = vm
141 .sys_module
142 .get_attr(prompt_name, vm)
143 .and_then(|prompt| prompt.str(vm));
144 let prompt = match prompt {
145 Ok(ref s) => s.expect_str(),
146 Err(_) => "",
147 };
148
149 continuing_line = false;
150 let result = match repl.readline(prompt) {
151 ReadlineResult::Line(line) => {
152 #[cfg(debug_assertions)]
153 debug!("You entered {line:?}");
154
155 repl.add_history_entry(line.trim_end()).unwrap();
156
157 let empty_line_given = line.is_empty();
158
159 if full_input.is_empty() {
160 full_input = line;
161 } else {
162 full_input.push_str(&line);
163 }
164 full_input.push('\n');
165
166 match shell_exec(
167 vm,
168 &full_input,
169 scope.clone(),
170 empty_line_given,
171 continuing_block,
172 ) {
173 ShellExecResult::Ok => {
174 if continuing_block {
175 if empty_line_given {
176 continuing_block = false;
178 full_input.clear();
179 }
180 } else {
181 full_input.clear();
183 }
184 Ok(())
185 }
186 ShellExecResult::ContinueLine => {
188 continuing_line = true;
189 Ok(())
190 }
191 ShellExecResult::ContinueBlock => {
192 continuing_block = true;
193 Ok(())
194 }
195 ShellExecResult::PyErr(err) => {
196 continuing_block = false;
197 full_input.clear();
198 Err(err)
199 }
200 }
201 }
202 ReadlineResult::Interrupt => {
203 continuing_block = false;
204 full_input.clear();
205 let keyboard_interrupt =
206 vm.new_exception_empty(vm.ctx.exceptions.keyboard_interrupt.to_owned());
207 Err(keyboard_interrupt)
208 }
209 ReadlineResult::Eof => {
210 break;
211 }
212 #[cfg(unix)]
213 ReadlineResult::OsError(num) => {
214 let os_error = vm.new_exception_msg(
215 vm.ctx.exceptions.os_error.to_owned(),
216 format!("{num:?}").into(),
217 );
218 vm.print_exception(os_error);
219 break;
220 }
221 ReadlineResult::Other(err) => {
222 eprintln!("Readline error: {err:?}");
223 break;
224 }
225 ReadlineResult::Io(err) => {
226 eprintln!("IO error: {err:?}");
227 break;
228 }
229 };
230
231 if let Err(exc) = result {
232 if exc.fast_isinstance(vm.ctx.exceptions.system_exit) {
233 repl.save_history(&repl_history_path).unwrap();
234 return Err(exc);
235 }
236 vm.print_exception(exc);
237 }
238 }
239 repl.save_history(&repl_history_path).unwrap();
240
241 Ok(())
242}