11mod helper;
22
33use rustpython_compiler:: {
4- CompileError , ParseError , parser:: LexicalErrorType , parser:: ParseErrorType ,
4+ CompileError , ParseError , parser:: FStringErrorType , parser:: LexicalErrorType ,
5+ parser:: ParseErrorType ,
56} ;
67use rustpython_vm:: {
78 AsObject , PyResult , VirtualMachine ,
@@ -14,19 +15,26 @@ use rustpython_vm::{
1415enum ShellExecResult {
1516 Ok ,
1617 PyErr ( PyBaseExceptionRef ) ,
17- Continue ,
18+ ContinueBlock ,
19+ ContinueLine ,
1820}
1921
2022fn shell_exec (
2123 vm : & VirtualMachine ,
2224 source : & str ,
2325 scope : Scope ,
2426 empty_line_given : bool ,
25- continuing : bool ,
27+ continuing_block : bool ,
2628) -> ShellExecResult {
29+ // compiling expects only UNIX style line endings, and will replace windows line endings
30+ // internally. Since we might need to analyze the source to determine if an error could be
31+ // resolved by future input, we need the location from the error to match the source code that
32+ // was actually compiled.
33+ #[ cfg( windows) ]
34+ let source = & source. replace ( "\r \n " , "\n " ) ;
2735 match vm. compile ( source, compiler:: Mode :: Single , "<stdin>" . to_owned ( ) ) {
2836 Ok ( code) => {
29- if empty_line_given || !continuing {
37+ if empty_line_given || !continuing_block {
3038 // We want to execute the full code
3139 match vm. run_code_obj ( code, scope) {
3240 Ok ( _val) => ShellExecResult :: Ok ,
@@ -40,8 +48,32 @@ fn shell_exec(
4048 Err ( CompileError :: Parse ( ParseError {
4149 error : ParseErrorType :: Lexical ( LexicalErrorType :: Eof ) ,
4250 ..
43- } ) ) => ShellExecResult :: Continue ,
51+ } ) ) => ShellExecResult :: ContinueLine ,
52+ Err ( CompileError :: Parse ( ParseError {
53+ error :
54+ ParseErrorType :: Lexical ( LexicalErrorType :: FStringError (
55+ FStringErrorType :: UnterminatedTripleQuotedString ,
56+ ) ) ,
57+ ..
58+ } ) ) => ShellExecResult :: ContinueLine ,
4459 Err ( err) => {
60+ // Check if the error is from an unclosed triple quoted string (which should always
61+ // continue)
62+ 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+ if iter. next ( ) == Some ( quote) && iter. next ( ) == Some ( quote) {
72+ return ShellExecResult :: ContinueLine ;
73+ }
74+ }
75+ } ;
76+
4577 // bad_error == true if we are handling an error that should be thrown even if we are continuing
4678 // if its an indentation error, set to true if we are continuing and the error is on column 0,
4779 // since indentations errors on columns other than 0 should be ignored.
@@ -50,10 +82,12 @@ fn shell_exec(
5082 let bad_error = match err {
5183 CompileError :: Parse ( ref p) => {
5284 match & p. error {
53- ParseErrorType :: Lexical ( LexicalErrorType :: IndentationError ) => continuing, // && p.location.is_some()
85+ ParseErrorType :: Lexical ( LexicalErrorType :: IndentationError ) => {
86+ continuing_block
87+ } // && p.location.is_some()
5488 ParseErrorType :: OtherError ( msg) => {
5589 if msg. starts_with ( "Expected an indented block" ) {
56- continuing
90+ continuing_block
5791 } else {
5892 true
5993 }
@@ -68,7 +102,7 @@ fn shell_exec(
68102 if empty_line_given || bad_error {
69103 ShellExecResult :: PyErr ( vm. new_syntax_error ( & err, Some ( source) ) )
70104 } else {
71- ShellExecResult :: Continue
105+ ShellExecResult :: ContinueBlock
72106 }
73107 }
74108 }
@@ -93,10 +127,19 @@ pub fn run_shell(vm: &VirtualMachine, scope: Scope) -> PyResult<()> {
93127 println ! ( "No previous history." ) ;
94128 }
95129
96- let mut continuing = false ;
130+ // We might either be waiting to know if a block is complete, or waiting to know if a multiline
131+ // statement is complete. In the former case, we need to ensure that we read one extra new line
132+ // to know that the block is complete. In the latter, we can execute as soon as the statement is
133+ // valid.
134+ let mut continuing_block = false ;
135+ let mut continuing_line = false ;
97136
98137 loop {
99- let prompt_name = if continuing { "ps2" } else { "ps1" } ;
138+ let prompt_name = if continuing_block || continuing_line {
139+ "ps2"
140+ } else {
141+ "ps1"
142+ } ;
100143 let prompt = vm
101144 . sys_module
102145 . get_attr ( prompt_name, vm)
@@ -105,6 +148,8 @@ pub fn run_shell(vm: &VirtualMachine, scope: Scope) -> PyResult<()> {
105148 Ok ( ref s) => s. as_str ( ) ,
106149 Err ( _) => "" ,
107150 } ;
151+
152+ continuing_line = false ;
108153 let result = match repl. readline ( prompt) {
109154 ReadlineResult :: Line ( line) => {
110155 debug ! ( "You entered {:?}" , line) ;
@@ -120,39 +165,44 @@ pub fn run_shell(vm: &VirtualMachine, scope: Scope) -> PyResult<()> {
120165 }
121166 full_input. push ( '\n' ) ;
122167
123- match shell_exec ( vm, & full_input, scope. clone ( ) , empty_line_given, continuing) {
168+ match shell_exec (
169+ vm,
170+ & full_input,
171+ scope. clone ( ) ,
172+ empty_line_given,
173+ continuing_block,
174+ ) {
124175 ShellExecResult :: Ok => {
125- if continuing {
176+ if continuing_block {
126177 if empty_line_given {
127- // We should be exiting continue mode
128- continuing = false ;
178+ // We should exit continue mode since the block successfully executed
179+ continuing_block = false ;
129180 full_input. clear ( ) ;
130- Ok ( ( ) )
131- } else {
132- // We should stay in continue mode
133- continuing = true ;
134- Ok ( ( ) )
135181 }
136182 } else {
137183 // We aren't in continue mode so proceed normally
138- continuing = false ;
139184 full_input. clear ( ) ;
140- Ok ( ( ) )
141185 }
186+ Ok ( ( ) )
187+ }
188+ // Continue, but don't change the mode
189+ ShellExecResult :: ContinueLine => {
190+ continuing_line = true ;
191+ Ok ( ( ) )
142192 }
143- ShellExecResult :: Continue => {
144- continuing = true ;
193+ ShellExecResult :: ContinueBlock => {
194+ continuing_block = true ;
145195 Ok ( ( ) )
146196 }
147197 ShellExecResult :: PyErr ( err) => {
148- continuing = false ;
198+ continuing_block = false ;
149199 full_input. clear ( ) ;
150200 Err ( err)
151201 }
152202 }
153203 }
154204 ReadlineResult :: Interrupt => {
155- continuing = false ;
205+ continuing_block = false ;
156206 full_input. clear ( ) ;
157207 let keyboard_interrupt =
158208 vm. new_exception_empty ( vm. ctx . exceptions . keyboard_interrupt . to_owned ( ) ) ;
0 commit comments