From 645314b21583c8e1b582887dec5342dc942f1927 Mon Sep 17 00:00:00 2001 From: Noah <33094578+coolreader18@users.noreply.github.com> Date: Sat, 29 Feb 2020 09:19:43 -0600 Subject: [PATCH 1/4] Move readline.rs into the vm crate --- Cargo.lock | 2 + Cargo.toml | 1 + src/shell.rs | 9 ++-- src/shell/{rustyline_helper.rs => helper.rs} | 48 +++++++++--------- vm/Cargo.toml | 1 + vm/src/lib.rs | 1 + {src/shell => vm/src}/readline.rs | 51 +++++++++++--------- 7 files changed, 62 insertions(+), 51 deletions(-) rename src/shell/{rustyline_helper.rs => helper.rs} (82%) rename {src/shell => vm/src}/readline.rs (79%) diff --git a/Cargo.lock b/Cargo.lock index cc9f4166f..50b3be9a1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1420,6 +1420,7 @@ dependencies = [ name = "rustpython" version = "0.1.1" dependencies = [ + "cfg-if", "clap", "cpython", "dirs 2.0.2", @@ -1539,6 +1540,7 @@ dependencies = [ "rustpython-compiler", "rustpython-derive", "rustpython-parser", + "rustyline", "serde", "serde_json", "sha-1", diff --git a/Cargo.toml b/Cargo.toml index d26f57e99..ee3ec2814 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,6 +27,7 @@ rustpython-parser = {path = "parser", version = "0.1.1"} rustpython-vm = {path = "vm", version = "0.1.1"} dirs = "2.0" num-traits = "0.2.8" +cfg-if = "0.1" flame = { version = "0.2", optional = true } flamescope = { version = "0.1", optional = true } diff --git a/src/shell.rs b/src/shell.rs index aa31b3949..7371c34ef 100644 --- a/src/shell.rs +++ b/src/shell.rs @@ -1,9 +1,8 @@ -mod readline; -#[cfg(not(target_os = "wasi"))] -mod rustyline_helper; +mod helper; use rustpython_compiler::{compile, error::CompileError, error::CompileErrorType}; use rustpython_parser::error::ParseErrorType; +use rustpython_vm::readline::{Readline, ReadlineResult}; use rustpython_vm::{ exceptions::{print_exception, PyBaseExceptionRef}, obj::objtype, @@ -12,8 +11,6 @@ use rustpython_vm::{ VirtualMachine, }; -use readline::{Readline, ReadlineResult}; - enum ShellExecResult { Ok, PyErr(PyBaseExceptionRef), @@ -49,7 +46,7 @@ pub fn run_shell(vm: &VirtualMachine, scope: Scope) -> PyResult<()> { crate_version!() ); - let mut repl = Readline::new(vm, scope.clone()); + let mut repl = Readline::new(helper::ShellHelper::new(vm, scope.clone())); let mut full_input = String::new(); // Retrieve a `history_path_str` dependent on the OS diff --git a/src/shell/rustyline_helper.rs b/src/shell/helper.rs similarity index 82% rename from src/shell/rustyline_helper.rs rename to src/shell/helper.rs index 45b3ca324..30900d1da 100644 --- a/src/shell/rustyline_helper.rs +++ b/src/shell/helper.rs @@ -2,10 +2,6 @@ use rustpython_vm::obj::objstr::PyStringRef; use rustpython_vm::pyobject::{PyIterable, PyResult, TryFromObject}; use rustpython_vm::scope::{NameProtocol, Scope}; use rustpython_vm::VirtualMachine; -use rustyline::{ - completion::Completer, highlight::Highlighter, hint::Hinter, validate::Validator, Context, - Helper, -}; pub struct ShellHelper<'vm> { vm: &'vm VirtualMachine, @@ -146,24 +142,32 @@ impl<'vm> ShellHelper<'vm> { } } -impl Completer for ShellHelper<'_> { - type Candidate = String; +cfg_if::cfg_if! { + if #[cfg(not(target_os = "wasi"))] { + use rustyline::{ + completion::Completer, highlight::Highlighter, hint::Hinter, validate::Validator, Context, + Helper, + }; + impl Completer for ShellHelper<'_> { + type Candidate = String; - fn complete( - &self, - line: &str, - pos: usize, - _ctx: &Context, - ) -> rustyline::Result<(usize, Vec)> { - Ok(self - .complete_opt(&line[0..pos]) - // as far as I can tell, there's no better way to do both completion - // and indentation (or even just indentation) - .unwrap_or_else(|| (line.len(), vec!["\t".to_owned()]))) + fn complete( + &self, + line: &str, + pos: usize, + _ctx: &Context, + ) -> rustyline::Result<(usize, Vec)> { + Ok(self + .complete_opt(&line[0..pos]) + // as far as I can tell, there's no better way to do both completion + // and indentation (or even just indentation) + .unwrap_or_else(|| (line.len(), vec!["\t".to_owned()]))) + } + } + + impl Hinter for ShellHelper<'_> {} + impl Highlighter for ShellHelper<'_> {} + impl Validator for ShellHelper<'_> {} + impl Helper for ShellHelper<'_> {} } } - -impl Hinter for ShellHelper<'_> {} -impl Highlighter for ShellHelper<'_> {} -impl Validator for ShellHelper<'_> {} -impl Helper for ShellHelper<'_> {} diff --git a/vm/Cargo.toml b/vm/Cargo.toml index a026f9d4a..f0e9a89e5 100644 --- a/vm/Cargo.toml +++ b/vm/Cargo.toml @@ -89,6 +89,7 @@ gethostname = "0.2.0" subprocess = "0.2.2" num_cpus = "1" socket2 = { version = "0.3", features = ["unix"] } +rustyline = "6.0" [target.'cfg(not(any(target_arch = "wasm32", target_os = "redox")))'.dependencies] dns-lookup = "1.0" diff --git a/vm/src/lib.rs b/vm/src/lib.rs index 68d7403e2..6e791e264 100644 --- a/vm/src/lib.rs +++ b/vm/src/lib.rs @@ -70,6 +70,7 @@ pub mod obj; pub mod py_serde; mod pyhash; pub mod pyobject; +pub mod readline; pub mod scope; mod sequence; pub mod slots; diff --git a/src/shell/readline.rs b/vm/src/readline.rs similarity index 79% rename from src/shell/readline.rs rename to vm/src/readline.rs index 3eb9d6e2c..35ce10db7 100644 --- a/src/shell/readline.rs +++ b/vm/src/readline.rs @@ -1,8 +1,6 @@ use std::io; use std::path::Path; -use rustpython_vm::{scope::Scope, VirtualMachine}; - type OtherError = Box; type OtherResult = Result; @@ -19,13 +17,16 @@ pub enum ReadlineResult { mod basic_readline { use super::*; - pub struct BasicReadline<'vm> { - vm: &'vm VirtualMachine, + pub trait Helper {} + impl Helper for T {} + + pub struct Readline { + helper: H, } - impl<'vm> BasicReadline<'vm> { - pub fn new(vm: &'vm VirtualMachine, _scope: Scope) -> Self { - BasicReadline { vm } + impl Readline { + pub fn new(helper: H) -> Self { + Readline { helper } } pub fn load_history(&mut self, _path: &Path) -> OtherResult<()> { @@ -60,16 +61,19 @@ mod basic_readline { } } -#[cfg(not(target_os = "wasi"))] +#[cfg(not(target_arch = "wasm32"))] mod rustyline_readline { - use super::{super::rustyline_helper::ShellHelper, *}; + use super::*; - pub struct RustylineReadline<'vm> { - repl: rustyline::Editor>, + pub trait Helper: rustyline::Helper {} + impl Helper for T {} + + pub struct Readline { + repl: rustyline::Editor, } - impl<'vm> RustylineReadline<'vm> { - pub fn new(vm: &'vm VirtualMachine, scope: Scope) -> Self { + impl Readline { + pub fn new(helper: H) -> Self { use rustyline::{At, Cmd, CompletionType, Config, Editor, KeyPress, Movement, Word}; let mut repl = Editor::with_config( Config::builder() @@ -85,8 +89,8 @@ mod rustyline_readline { KeyPress::ControlRight, Cmd::Move(Movement::ForwardWord(1, At::AfterEnd, Word::Vi)), ); - repl.set_helper(Some(ShellHelper::new(vm, scope))); - RustylineReadline { repl } + repl.set_helper(Some(helper)); + Readline { repl } } pub fn load_history(&mut self, path: &Path) -> OtherResult<()> { @@ -126,17 +130,18 @@ mod rustyline_readline { } } -#[cfg(target_os = "wasi")] -type ReadlineInner<'vm> = basic_readline::BasicReadline<'vm>; +#[cfg(target_arch = "wasm32")] +use basic_readline as readline_inner; +#[cfg(not(target_arch = "wasm32"))] +use rustyline_readline as readline_inner; -#[cfg(not(target_os = "wasi"))] -type ReadlineInner<'vm> = rustyline_readline::RustylineReadline<'vm>; +pub use readline_inner::Helper; -pub struct Readline<'vm>(ReadlineInner<'vm>); +pub struct Readline(readline_inner::Readline); -impl<'vm> Readline<'vm> { - pub fn new(vm: &'vm VirtualMachine, scope: Scope) -> Self { - Readline(ReadlineInner::new(vm, scope)) +impl Readline { + pub fn new(helper: H) -> Self { + Readline(readline_inner::Readline::new(helper)) } pub fn load_history(&mut self, path: &Path) -> OtherResult<()> { self.0.load_history(path) From d3343b6de438078ddf572026543cbd01e4959c62 Mon Sep 17 00:00:00 2001 From: Noah <33094578+coolreader18@users.noreply.github.com> Date: Sat, 29 Feb 2020 09:42:53 -0600 Subject: [PATCH 2/4] Add vm.new_runtime_error --- vm/src/frame.rs | 5 +---- vm/src/obj/objcoroutine.rs | 7 +++---- vm/src/obj/objdict.rs | 7 +++---- vm/src/obj/objgenerator.rs | 7 +++---- vm/src/pyobject.rs | 8 ++++---- vm/src/stdlib/os.rs | 5 +---- vm/src/vm.rs | 5 +++++ 7 files changed, 20 insertions(+), 24 deletions(-) diff --git a/vm/src/frame.rs b/vm/src/frame.rs index 97b4f9d83..0d4ee3462 100644 --- a/vm/src/frame.rs +++ b/vm/src/frame.rs @@ -987,10 +987,7 @@ impl Frame { 0 => match vm.current_exception() { Some(exc) => exc, None => { - return Err(vm.new_exception_msg( - vm.ctx.exceptions.runtime_error.clone(), - "No active exception to reraise".to_owned(), - )) + return Err(vm.new_runtime_error("No active exception to reraise".to_owned())) } }, 1 | 2 => ExceptionCtor::try_from_object(vm, self.pop_value())?.instantiate(vm)?, diff --git a/vm/src/obj/objcoroutine.rs b/vm/src/obj/objcoroutine.rs index 86cf17a83..6f30d8eb6 100644 --- a/vm/src/obj/objcoroutine.rs +++ b/vm/src/obj/objcoroutine.rs @@ -89,10 +89,9 @@ impl PyCoroutine { vm.frames.borrow_mut().pop(); self.closed.set(true); match result { - Ok(ExecutionResult::Yield(_)) => Err(vm.new_exception_msg( - vm.ctx.exceptions.runtime_error.clone(), - "generator ignored GeneratorExit".to_owned(), - )), + Ok(ExecutionResult::Yield(_)) => { + Err(vm.new_runtime_error("generator ignored GeneratorExit".to_owned())) + } Err(e) => { if isinstance(&e, &vm.ctx.exceptions.generator_exit) { Ok(()) diff --git a/vm/src/obj/objdict.rs b/vm/src/obj/objdict.rs index bcd66d87d..9bc69c132 100644 --- a/vm/src/obj/objdict.rs +++ b/vm/src/obj/objdict.rs @@ -564,10 +564,9 @@ macro_rules! dict_iterator { let mut position = self.position.get(); let dict = self.dict.entries.borrow(); if dict.has_changed_size(&self.size) { - return Err(vm.new_exception_msg( - vm.ctx.exceptions.runtime_error.clone(), - "dictionary changed size during iteration".to_owned(), - )); + return Err( + vm.new_runtime_error("dictionary changed size during iteration".to_owned()) + ); } match dict.next_entry(&mut position) { Some((key, value)) => { diff --git a/vm/src/obj/objgenerator.rs b/vm/src/obj/objgenerator.rs index 8d72218c4..4e4256a7f 100644 --- a/vm/src/obj/objgenerator.rs +++ b/vm/src/obj/objgenerator.rs @@ -102,10 +102,9 @@ impl PyGenerator { vm.frames.borrow_mut().pop(); self.closed.set(true); match result { - Ok(ExecutionResult::Yield(_)) => Err(vm.new_exception_msg( - vm.ctx.exceptions.runtime_error.clone(), - "generator ignored GeneratorExit".to_owned(), - )), + Ok(ExecutionResult::Yield(_)) => { + Err(vm.new_runtime_error("generator ignored GeneratorExit".to_owned())) + } Err(e) => { if isinstance(&e, &vm.ctx.exceptions.generator_exit) { Ok(()) diff --git a/vm/src/pyobject.rs b/vm/src/pyobject.rs index 14d4460a6..8ea94e842 100644 --- a/vm/src/pyobject.rs +++ b/vm/src/pyobject.rs @@ -682,10 +682,10 @@ impl PyRef { if obj.payload_is::() { Ok(Self::new_ref_unchecked(obj)) } else { - Err(vm.new_exception_msg( - vm.ctx.exceptions.runtime_error.clone(), - format!("Unexpected payload for type {:?}", obj.class().name), - )) + Err(vm.new_runtime_error(format!( + "Unexpected payload for type {:?}", + obj.class().name + ))) } } diff --git a/vm/src/stdlib/os.rs b/vm/src/stdlib/os.rs index 99a120ece..d81ae56c9 100644 --- a/vm/src/stdlib/os.rs +++ b/vm/src/stdlib/os.rs @@ -205,10 +205,7 @@ pub fn convert_nix_error(vm: &VirtualMachine, err: nix::Error) -> PyBaseExceptio let exc_type = vm.ctx.exceptions.unicode_error.clone(); vm.new_exception_msg(exc_type, err.to_string()) } - nix::Error::UnsupportedOperation => { - let exc_type = vm.ctx.exceptions.runtime_error.clone(); - vm.new_exception_msg(exc_type, err.to_string()) - } + nix::Error::UnsupportedOperation => vm.new_runtime_error(err.to_string()), nix::Error::Sys(errno) => { let exc_type = convert_nix_errno(vm, errno); vm.new_exception_msg(exc_type, err.to_string()) diff --git a/vm/src/vm.rs b/vm/src/vm.rs index a480fb4cf..8d82ce471 100644 --- a/vm/src/vm.rs +++ b/vm/src/vm.rs @@ -520,6 +520,11 @@ impl VirtualMachine { self.new_exception_msg(import_error, msg) } + pub fn new_runtime_error(&self, msg: String) -> PyBaseExceptionRef { + let runtime_error = self.ctx.exceptions.runtime_error.clone(); + self.new_exception_msg(runtime_error, msg) + } + // TODO: #[track_caller] when stabilized fn _py_panic_failed(&self, exc: &PyBaseExceptionRef, msg: &str) -> ! { let show_backtrace = env::var_os("RUST_BACKTRACE").map_or(false, |v| &v != "0"); From 6e776da75dae462ebe928a75cdc874a2d4556ed6 Mon Sep 17 00:00:00 2001 From: Noah <33094578+coolreader18@users.noreply.github.com> Date: Sat, 29 Feb 2020 09:43:54 -0600 Subject: [PATCH 3/4] Implement builtin_input --- vm/src/builtins.rs | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/vm/src/builtins.rs b/vm/src/builtins.rs index 0ad3b37e9..e15c46baf 100644 --- a/vm/src/builtins.rs +++ b/vm/src/builtins.rs @@ -30,6 +30,7 @@ use crate::pyobject::{ Either, IdProtocol, ItemProtocol, PyIterable, PyObjectRef, PyResult, PyValue, TryFromObject, TypeProtocol, }; +use crate::readline::{Readline, ReadlineResult}; use crate::scope::Scope; use crate::stdlib::ast; use crate::vm::VirtualMachine; @@ -345,7 +346,22 @@ fn builtin_id(obj: PyObjectRef) -> usize { obj.get_id() } -// builtin_input +fn builtin_input(prompt: OptionalArg, vm: &VirtualMachine) -> PyResult { + let prompt = prompt.as_ref().map_or("", |s| s.as_str()); + let mut readline = Readline::new(()); + match readline.readline(prompt) { + ReadlineResult::Line(s) => Ok(s), + ReadlineResult::EOF => Err(vm.new_exception_empty(vm.ctx.exceptions.eof_error.clone())), + ReadlineResult::Interrupt => { + Err(vm.new_exception_empty(vm.ctx.exceptions.keyboard_interrupt.clone())) + } + ReadlineResult::IO(e) => Err(vm.new_os_error(e.to_string())), + ReadlineResult::EncodingError => { + Err(vm.new_unicode_decode_error("Error decoding readline input".to_owned())) + } + ReadlineResult::Other(e) => Err(vm.new_runtime_error(e.to_string())), + } +} fn builtin_isinstance(obj: PyObjectRef, typ: PyObjectRef, vm: &VirtualMachine) -> PyResult { single_or_tuple_any( @@ -797,6 +813,7 @@ pub fn make_module(vm: &VirtualMachine, module: PyObjectRef) { "hash" => ctx.new_function(builtin_hash), "hex" => ctx.new_function(builtin_hex), "id" => ctx.new_function(builtin_id), + "input" => ctx.new_function(builtin_input), "int" => ctx.int_type(), "isinstance" => ctx.new_function(builtin_isinstance), "issubclass" => ctx.new_function(builtin_issubclass), From 1941c8932f8047e7e59cdb97cc6992f7e0903d78 Mon Sep 17 00:00:00 2001 From: Noah <33094578+coolreader18@users.noreply.github.com> Date: Sat, 29 Feb 2020 19:35:16 -0600 Subject: [PATCH 4/4] Add IOError alias --- vm/src/builtins.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/vm/src/builtins.rs b/vm/src/builtins.rs index e15c46baf..69d1e9449 100644 --- a/vm/src/builtins.rs +++ b/vm/src/builtins.rs @@ -883,6 +883,8 @@ pub fn make_module(vm: &VirtualMachine, module: PyObjectRef) { "NameError" => ctx.exceptions.name_error.clone(), "UnboundLocalError" => ctx.exceptions.unbound_local_error.clone(), "OSError" => ctx.exceptions.os_error.clone(), + // OSError alias + "IOError" => ctx.exceptions.os_error.clone(), "BlockingIOError" => ctx.exceptions.blocking_io_error.clone(), "ChildProcessError" => ctx.exceptions.child_process_error.clone(), "ConnectionError" => ctx.exceptions.connection_error.clone(),