diff --git a/.gitignore b/.gitignore index e965f0242..c0bc411bd 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,6 @@ __pycache__ **/*.pytest_cache .*sw* .repl_history.txt +.vscode wasm-pack.log +.idea/ diff --git a/Cargo.lock b/Cargo.lock index ddc013a58..aa31678ad 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,3 +1,5 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. [[package]] name = "aho-corasick" version = "0.6.4" @@ -684,6 +686,7 @@ dependencies = [ "rustpython_parser 0.0.1", "rustpython_vm 0.1.0", "rustyline 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "xdg 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -708,6 +711,7 @@ dependencies = [ "log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", "num-bigint 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "num-complex 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "num-integer 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)", "num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)", "regex 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", @@ -716,6 +720,7 @@ dependencies = [ "serde_derive 1.0.66 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.26 (registry+https://github.com/rust-lang/crates.io-index)", "statrs 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-segmentation 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1128,6 +1133,11 @@ dependencies = [ "winapi-util 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "xdg" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [metadata] "checksum aho-corasick 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)" = "d6531d44de723825aa81398a6415283229725a00fa30713812ab9323faa82fc4" "checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" @@ -1263,3 +1273,4 @@ dependencies = [ "checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" "checksum wincolor 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "eeb06499a3a4d44302791052df005d5232b927ed1a9658146d842165c4de7767" "checksum wincolor 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "561ed901ae465d6185fa7864d63fbd5720d0ef718366c9a4dc83cf6170d7e9ba" +"checksum xdg 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d089681aa106a86fade1b0128fb5daf07d5867a509ab036d99988dec80429a57" diff --git a/Cargo.toml b/Cargo.toml index 23b9e02a5..92213e8cb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,7 @@ clap = "2.31.2" rustpython_parser = {path = "parser"} rustpython_vm = {path = "vm"} rustyline = "2.1.0" +xdg = "2.2.0" [profile.release] opt-level = "s" diff --git a/README.md b/README.md index 92623e8db..3d3f84820 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,7 @@ If you wish to update the online documentation. Push directly to the `release` b - `obj`: python builtin types - `src`: using the other subcrates to bring rustpython to life. - `docs`: documentation (work in progress) -- `py_code_object`: CPython bytecode to rustpython bytecode convertor (work in progress) +- `py_code_object`: CPython bytecode to rustpython bytecode converter (work in progress) - `wasm`: Binary crate and resources for WebAssembly build - `tests`: integration test snippets @@ -81,8 +81,8 @@ To test rustpython, there is a collection of python snippets located in the ```shell $ cd tests -$ pipenv shell -$ pytest -v +$ pipenv install +$ pipenv run pytest -v ``` There also are some unittests, you can run those will cargo: diff --git a/docs/builtins.md b/docs/builtins.md index d918f0861..2daa2b275 100644 --- a/docs/builtins.md +++ b/docs/builtins.md @@ -1,6 +1,6 @@ Byterun -* Builtins are exposted to frame.f_builtins +* Builtins are exposed to frame.f_builtins * f_builtins is assigned during frame creation, self.f_builtins = f_locals['__builtins__'] if hasattr(self.f_builtins, '__dict__'): @@ -21,10 +21,10 @@ TODO: * Implement a new type NativeFunction * Wrap a function pointer in NativeFunction * Refactor the CALL_FUNCTION case so it can call both python function and native function -* During frame creation, force push a nativefunction `print` into the namespace +* During frame creation, force push a native function `print` into the namespace * Modify LOAD_* so they can search for names in builtins * Create a module type * In VM initialization, load the builtins module into locals -* During frame creation, create a field that conatins the builtins dict +* During frame creation, create a field that contains the builtins dict diff --git a/parser/src/ast.rs b/parser/src/ast.rs index d0ed2b3fe..d28f69e2f 100644 --- a/parser/src/ast.rs +++ b/parser/src/ast.rs @@ -65,9 +65,9 @@ pub enum Statement { value: Expression, }, AugAssign { - target: Expression, + target: Box, op: Operator, - value: Expression, + value: Box, }, Expression { expression: Expression, @@ -221,7 +221,7 @@ pub enum Expression { /* * In cpython this is called arguments, but we choose parameters to - * distuingish between function parameters and actual call arguments. + * distinguish between function parameters and actual call arguments. */ #[derive(Debug, PartialEq, Default)] pub struct Parameters { diff --git a/parser/src/lexer.rs b/parser/src/lexer.rs index cf611e8a1..f48c48d49 100644 --- a/parser/src/lexer.rs +++ b/parser/src/lexer.rs @@ -1,5 +1,5 @@ //! This module takes care of lexing python source text. This means source -//! code is translated into seperate tokens. +//! code is translated into separate tokens. pub use super::token::Tok; use num_bigint::BigInt; @@ -54,6 +54,7 @@ pub struct Lexer> { #[derive(Debug)] pub enum LexicalError { StringError, + NestingError, } #[derive(Clone, Debug, Default, PartialEq)] @@ -64,10 +65,7 @@ pub struct Location { impl Location { pub fn new(row: usize, column: usize) -> Self { - Location { - row: row, - column: column, - } + Location { row, column } } pub fn get_row(&self) -> usize { @@ -126,8 +124,7 @@ pub type Spanned = Result<(Location, Tok, Location), LexicalError>; pub fn make_tokenizer<'a>(source: &'a str) -> impl Iterator> + 'a { let nlh = NewlineHandler::new(source.chars()); let lch = LineContinationHandler::new(nlh); - let lexer = Lexer::new(lch); - lexer + Lexer::new(lch) } // The newline handler is an iterator which collapses different newline @@ -144,7 +141,7 @@ where { pub fn new(source: T) -> Self { let mut nlh = NewlineHandler { - source: source, + source, chr0: None, chr1: None, }; @@ -200,7 +197,7 @@ where { pub fn new(source: T) -> Self { let mut nlh = LineContinationHandler { - source: source, + source, chr0: None, chr1: None, }; @@ -313,7 +310,7 @@ where if keywords.contains_key(&name) { Ok((start_pos, keywords.remove(&name).unwrap(), end_pos)) } else { - Ok((start_pos, Tok::Name { name: name }, end_pos)) + Ok((start_pos, Tok::Name { name }, end_pos)) } } @@ -358,7 +355,7 @@ where let end_pos = self.get_pos(); let value = BigInt::from_str_radix(&value_text, radix).unwrap(); - Ok((start_pos, Tok::Int { value: value }, end_pos)) + Ok((start_pos, Tok::Int { value }, end_pos)) } fn lex_normal_number(&mut self) -> Spanned { @@ -410,7 +407,7 @@ where )) } else { let end_pos = self.get_pos(); - Ok((start_pos, Tok::Float { value: value }, end_pos)) + Ok((start_pos, Tok::Float { value }, end_pos)) } } else { // Parse trailing 'j': @@ -418,18 +415,11 @@ where self.next_char(); let end_pos = self.get_pos(); let imag = f64::from_str(&value_text).unwrap(); - Ok(( - start_pos, - Tok::Complex { - real: 0.0, - imag: imag, - }, - end_pos, - )) + Ok((start_pos, Tok::Complex { real: 0.0, imag }, end_pos)) } else { let end_pos = self.get_pos(); let value = value_text.parse::().unwrap(); - Ok((start_pos, Tok::Int { value: value }, end_pos)) + Ok((start_pos, Tok::Int { value }, end_pos)) } } } @@ -439,9 +429,7 @@ where self.next_char(); loop { match self.chr0 { - Some('\n') => { - return; - } + Some('\n') => return, Some(_) => {} None => return, } @@ -548,33 +536,33 @@ where } }; - return Ok((start_pos, tok, end_pos)); + Ok((start_pos, tok, end_pos)) } fn is_char(&self) -> bool { match self.chr0 { - Some('a'...'z') | Some('A'...'Z') | Some('_') | Some('0'...'9') => return true, - _ => return false, + Some('a'...'z') | Some('A'...'Z') | Some('_') | Some('0'...'9') => true, + _ => false, } } fn is_number(&self, radix: u32) -> bool { match radix { 2 => match self.chr0 { - Some('0'...'1') => return true, - _ => return false, + Some('0'...'1') => true, + _ => false, }, 8 => match self.chr0 { - Some('0'...'7') => return true, - _ => return false, + Some('0'...'7') => true, + _ => false, }, 10 => match self.chr0 { - Some('0'...'9') => return true, - _ => return false, + Some('0'...'9') => true, + _ => false, }, 16 => match self.chr0 { - Some('0'...'9') | Some('a'...'f') | Some('A'...'F') => return true, - _ => return false, + Some('0'...'9') | Some('a'...'f') | Some('A'...'F') => true, + _ => false, }, x => unimplemented!("Radix not implemented: {}", x), } @@ -915,6 +903,9 @@ where } Some(')') => { let result = self.eat_single_char(Tok::Rpar); + if self.nesting == 0 { + return Some(Err(LexicalError::NestingError)); + } self.nesting -= 1; return Some(result); } @@ -925,6 +916,9 @@ where } Some(']') => { let result = self.eat_single_char(Tok::Rsqb); + if self.nesting == 0 { + return Some(Err(LexicalError::NestingError)); + } self.nesting -= 1; return Some(result); } @@ -935,6 +929,9 @@ where } Some('}') => { let result = self.eat_single_char(Tok::Rbrace); + if self.nesting == 0 { + return Some(Err(LexicalError::NestingError)); + } self.nesting -= 1; return Some(result); } diff --git a/parser/src/lib.rs b/parser/src/lib.rs index cefb3938b..e6372ce7c 100644 --- a/parser/src/lib.rs +++ b/parser/src/lib.rs @@ -7,6 +7,7 @@ extern crate num_traits; pub mod ast; pub mod lexer; pub mod parser; +#[cfg_attr(rustfmt, rustfmt_skip)] mod python; pub mod token; diff --git a/parser/src/python.lalrpop b/parser/src/python.lalrpop index 4d126ee17..07b6ec3b6 100644 --- a/parser/src/python.lalrpop +++ b/parser/src/python.lalrpop @@ -105,7 +105,11 @@ ExpressionStatement: ast::LocatedStatement = { let rhs = e2.into_iter().next().unwrap(); ast::LocatedStatement { location: loc, - node: ast::Statement::AugAssign { target: expr, op: op, value: rhs }, + node: ast::Statement::AugAssign { + target: Box::new(expr), + op, + value: Box::new(rhs) + }, } }, }; diff --git a/py_code_object/src/vm_old.rs b/py_code_object/src/vm_old.rs index aecefde46..a3747a946 100644 --- a/py_code_object/src/vm_old.rs +++ b/py_code_object/src/vm_old.rs @@ -56,7 +56,7 @@ impl VirtualMachine { } } - // Can we get rid of the code paramter? + // Can we get rid of the code parameter? fn make_frame(&self, code: PyCodeObject, callargs: HashMap>, globals: Option>>) -> Frame { //populate the globals and locals @@ -345,7 +345,7 @@ impl VirtualMachine { let exception = match argc { 1 => curr_frame.stack.pop().unwrap(), 0 | 2 | 3 => panic!("Not implemented!"), - _ => panic!("Invalid paramter for RAISE_VARARGS, must be between 0 to 3") + _ => panic!("Invalid parameter for RAISE_VARARGS, must be between 0 to 3") }; panic!("{:?}", exception); } diff --git a/src/main.rs b/src/main.rs index 12b79873c..e6d5e5684 100644 --- a/src/main.rs +++ b/src/main.rs @@ -68,8 +68,8 @@ fn main() { handle_exception(&mut vm, result); } -fn _run_string(vm: &mut VirtualMachine, source: &str, source_path: Option) -> PyResult { - let code_obj = compile::compile(vm, source, compile::Mode::Exec, source_path)?; +fn _run_string(vm: &mut VirtualMachine, source: &str, source_path: String) -> PyResult { + let code_obj = compile::compile(vm, source, &compile::Mode::Exec, source_path)?; // trace!("Code object: {:?}", code_obj.borrow()); let builtins = vm.get_builtin_scope(); let vars = vm.context().new_scope(Some(builtins)); // Keep track of local variables @@ -91,7 +91,7 @@ fn run_command(vm: &mut VirtualMachine, mut source: String) -> PyResult { // This works around https://github.com/RustPython/RustPython/issues/17 source.push_str("\n"); - _run_string(vm, &source, None) + _run_string(vm, &source, "".to_string()) } fn run_module(vm: &mut VirtualMachine, module: &str) -> PyResult { @@ -105,7 +105,7 @@ fn run_script(vm: &mut VirtualMachine, script_file: &str) -> PyResult { // Parse an ast from it: let filepath = Path::new(script_file); match parser::read_file(filepath) { - Ok(source) => _run_string(vm, &source, Some(filepath.to_str().unwrap().to_string())), + Ok(source) => _run_string(vm, &source, filepath.to_str().unwrap().to_string()), Err(msg) => { error!("Parsing went horribly wrong: {}", msg); std::process::exit(1); @@ -114,7 +114,7 @@ fn run_script(vm: &mut VirtualMachine, script_file: &str) -> PyResult { } fn shell_exec(vm: &mut VirtualMachine, source: &str, scope: PyObjectRef) -> bool { - match compile::compile(vm, source, compile::Mode::Single, None) { + match compile::compile(vm, source, &compile::Mode::Single, "".to_string()) { Ok(code) => { match vm.run_code_obj(code, scope) { Ok(_value) => { @@ -142,6 +142,22 @@ fn shell_exec(vm: &mut VirtualMachine, source: &str, scope: PyObjectRef) -> bool true } +#[cfg(not(target_family = "unix"))] +fn get_history_path() -> PathBuf { + //Path buffer + PathBuf::from(".repl_history.txt") +} + +#[cfg(target_family = "unix")] +fn get_history_path() -> PathBuf { + //work around for windows dependent builds. The xdg crate is unix specific + //so access to the BaseDirectories struct breaks builds on python. + extern crate xdg; + + let xdg_dirs = xdg::BaseDirectories::with_prefix("rustpython").unwrap(); + xdg_dirs.place_cache_file("repl_history.txt").unwrap() +} + fn run_shell(vm: &mut VirtualMachine) -> PyResult { println!( "Welcome to the magnificent Rust Python {} interpreter", @@ -154,14 +170,14 @@ fn run_shell(vm: &mut VirtualMachine) -> PyResult { let mut input = String::new(); let mut rl = Editor::<()>::new(); - // TODO: Store the history in a proper XDG directory - let repl_history_path = ".repl_history.txt"; - if rl.load_history(repl_history_path).is_err() { + //retrieve a history_path_str dependent to the os + let repl_history_path_str = &get_history_path(); + if rl.load_history(repl_history_path_str).is_err() { println!("No previous history."); } loop { - // TODO: modules dont support getattr / setattr yet + // TODO: modules don't support getattr / setattr yet //let prompt = match vm.get_attribute(vm.sys_module.clone(), "ps1") { // Ok(value) => objstr::get_value(&value), // Err(_) => ">>>>> ".to_string(), @@ -220,7 +236,7 @@ fn run_shell(vm: &mut VirtualMachine) -> PyResult { } }; } - rl.save_history(repl_history_path).unwrap(); + rl.save_history(repl_history_path_str).unwrap(); Ok(vm.get_none()) } diff --git a/tests/snippets/basic_types.py b/tests/snippets/basic_types.py index 4ed164f30..006669b3f 100644 --- a/tests/snippets/basic_types.py +++ b/tests/snippets/basic_types.py @@ -44,7 +44,12 @@ assert int() == 0 a = complex(2, 4) assert type(a) is complex assert type(a + a) is complex +assert repr(a) == '(2+4j)' +a = 10j +assert repr(a) == '10j' +a = 1 +assert a.conjugate() == a a = 12345 diff --git a/tests/snippets/bools.py b/tests/snippets/bools.py index aeec25639..2aa817ca4 100644 --- a/tests/snippets/bools.py +++ b/tests/snippets/bools.py @@ -46,3 +46,5 @@ assert True + True == 2 assert False * 7 == 0 assert True > 0 assert int(True) == 1 +assert True.conjugate() == 1 +assert isinstance(True.conjugate(), int) diff --git a/tests/snippets/builtin_divmod.py b/tests/snippets/builtin_divmod.py index 7bab71c99..43b77a452 100644 --- a/tests/snippets/builtin_divmod.py +++ b/tests/snippets/builtin_divmod.py @@ -1,3 +1,17 @@ assert divmod(11, 3) == (3, 2) assert divmod(8,11) == (0, 8) assert divmod(0.873, 0.252) == (3.0, 0.11699999999999999) + +try: + divmod(5, 0) +except ZeroDivisionError: + pass +else: + assert False, "Expected divmod by zero to throw ZeroDivisionError" + +try: + divmod(5.0, 0.0) +except ZeroDivisionError: + pass +else: + assert False, "Expected divmod by zero to throw ZeroDivisionError" diff --git a/tests/snippets/builtin_enumerate.py b/tests/snippets/builtin_enumerate.py new file mode 100644 index 000000000..75f6f7412 --- /dev/null +++ b/tests/snippets/builtin_enumerate.py @@ -0,0 +1,23 @@ +assert list(enumerate(['a', 'b', 'c'])) == [(0, 'a'), (1, 'b'), (2, 'c')] + +assert type(enumerate([])) == enumerate + +assert list(enumerate(['a', 'b', 'c'], -100)) == [(-100, 'a'), (-99, 'b'), (-98, 'c')] +assert list(enumerate(['a', 'b', 'c'], 2**200)) == [(2**200, 'a'), (2**200 + 1, 'b'), (2**200 + 2, 'c')] + + +# test infinite iterator +class Counter(object): + counter = 0 + + def __next__(self): + self.counter += 1 + return self.counter + + def __iter__(self): + return self + + +it = enumerate(Counter()) +assert next(it) == (0, 1) +assert next(it) == (1, 2) diff --git a/tests/snippets/builtin_filter.py b/tests/snippets/builtin_filter.py new file mode 100644 index 000000000..d0b5ccd5c --- /dev/null +++ b/tests/snippets/builtin_filter.py @@ -0,0 +1,32 @@ +assert list(filter(lambda x: ((x % 2) == 0), [0, 1, 2])) == [0, 2] + +# None implies identity +assert list(filter(None, [0, 1, 2])) == [1, 2] + +assert type(filter(None, [])) == filter + + +# test infinite iterator +class Counter(object): + counter = 0 + + def __next__(self): + self.counter += 1 + return self.counter + + def __iter__(self): + return self + + +it = filter(lambda x: ((x % 2) == 0), Counter()) +assert next(it) == 2 +assert next(it) == 4 + + +def predicate(x): + if x == 0: + raise StopIteration() + return True + + +assert list(filter(predicate, [1, 2, 0, 4, 5])) == [1, 2] diff --git a/tests/snippets/builtin_map.py b/tests/snippets/builtin_map.py new file mode 100644 index 000000000..0de8d2c59 --- /dev/null +++ b/tests/snippets/builtin_map.py @@ -0,0 +1,34 @@ +a = list(map(str, [1, 2, 3])) +assert a == ['1', '2', '3'] + + +b = list(map(lambda x, y: x + y, [1, 2, 4], [3, 5])) +assert b == [4, 7] + +assert type(map(lambda x: x, [])) == map + + +# test infinite iterator +class Counter(object): + counter = 0 + + def __next__(self): + self.counter += 1 + return self.counter + + def __iter__(self): + return self + + +it = map(lambda x: x+1, Counter()) +assert next(it) == 2 +assert next(it) == 3 + + +def mapping(x): + if x == 0: + raise StopIteration() + return x + + +assert list(map(mapping, [1, 2, 0, 4, 5])) == [1, 2] diff --git a/tests/snippets/builtin_open.py b/tests/snippets/builtin_open.py new file mode 100644 index 000000000..805c975c8 --- /dev/null +++ b/tests/snippets/builtin_open.py @@ -0,0 +1,8 @@ +fd = open('README.md') +assert 'RustPython' in fd.read() + +try: + open('DoesNotExist') + assert False +except FileNotFoundError: + pass diff --git a/tests/snippets/builtin_range.py b/tests/snippets/builtin_range.py new file mode 100644 index 000000000..667e776a2 --- /dev/null +++ b/tests/snippets/builtin_range.py @@ -0,0 +1,63 @@ +def assert_raises(expr, exc_type): + """ + Helper function to assert `expr` raises an exception of type `exc_type` + Args: + expr: Callable + exec_type: Exception + Returns: + None + Raises: + Assertion error on failure + """ + try: + expr(None) + except exc_type: + assert True + else: + assert False + +assert range(2**63+1)[2**63] == 9223372036854775808 + +# len tests +assert len(range(10, 5)) == 0, 'Range with no elements should have length = 0' +assert len(range(10, 5, -2)) == 3, 'Expected length 3, for elements: 10, 8, 6' +assert len(range(5, 10, 2)) == 3, 'Expected length 3, for elements: 5, 7, 9' + +# index tests +assert range(10).index(6) == 6 +assert range(4, 10).index(6) == 2 +assert range(4, 10, 2).index(6) == 1 +assert range(10, 4, -2).index(8) == 1 + +# index raises value error on out of bounds +assert_raises(lambda _: range(10).index(-1), ValueError) +assert_raises(lambda _: range(10).index(10), ValueError) + +# index raises value error if out of step +assert_raises(lambda _: range(4, 10, 2).index(5), ValueError) + +# index raises value error if needle is not an int +assert_raises(lambda _: range(10).index('foo'), ValueError) + +# __bool__ +assert bool(range(1)) +assert bool(range(1, 2)) + +assert not bool(range(0)) +assert not bool(range(1, 1)) + +# __contains__ +assert 6 in range(10) +assert 6 in range(4, 10) +assert 6 in range(4, 10, 2) +assert 10 in range(10, 4, -2) +assert 8 in range(10, 4, -2) + +assert -1 not in range(10) +assert 9 not in range(10, 4, -2) +assert 4 not in range(10, 4, -2) +assert 'foo' not in range(10) + +# __reversed__ +assert list(reversed(range(5))) == [4, 3, 2, 1, 0] +assert list(reversed(range(5, 0, -1))) == [1, 2, 3, 4, 5] diff --git a/tests/snippets/builtin_reversed.py b/tests/snippets/builtin_reversed.py new file mode 100644 index 000000000..2bbfcb98a --- /dev/null +++ b/tests/snippets/builtin_reversed.py @@ -0,0 +1 @@ +assert list(reversed(range(5))) == [4, 3, 2, 1, 0] diff --git a/tests/snippets/builtin_slice.py b/tests/snippets/builtin_slice.py new file mode 100644 index 000000000..b7c3922c0 --- /dev/null +++ b/tests/snippets/builtin_slice.py @@ -0,0 +1,77 @@ + +a = [] +assert a[:] == [] +assert a[:2**100] == [] +assert a[-2**100:] == [] +assert a[::2**100] == [] +assert a[10:20] == [] +assert a[-20:-10] == [] + +b = [1, 2] + +assert b[:] == [1, 2] +assert b[:2**100] == [1, 2] +assert b[-2**100:] == [1, 2] +assert b[2**100:] == [] +assert b[::2**100] == [1] +assert b[-10:1] == [1] +assert b[0:0] == [] +assert b[1:0] == [] + +try: + _ = b[::0] +except ValueError: + pass +else: + assert False, "Zero step slice should raise ValueError" + +assert b[::-1] == [2, 1] +assert b[1::-1] == [2, 1] +assert b[0::-1] == [1] +assert b[0:-5:-1] == [1] +assert b[:0:-1] == [2] +assert b[5:0:-1] == [2] + +c = list(range(10)) + +assert c[9:6:-3] == [9] +assert c[9::-3] == [9, 6, 3, 0] +assert c[9::-4] == [9, 5, 1] +assert c[8::-2**100] == [8] + +assert c[7:7:-2] == [] +assert c[7:8:-2] == [] + +d = "123456" + +assert d[3::-1] == "4321" +assert d[4::-3] == "52" + + +slice_a = slice(5) +assert slice_a.start is None +assert slice_a.stop == 5 +assert slice_a.step is None + +slice_b = slice(1, 5) +assert slice_b.start == 1 +assert slice_b.stop == 5 +assert slice_b.step is None + +slice_c = slice(1, 5, 2) +assert slice_c.start == 1 +assert slice_c.stop == 5 +assert slice_c.step == 2 + + +class SubScript(object): + def __getitem__(self, item): + assert type(item) == slice + + def __setitem__(self, key, value): + assert type(key) == slice + + +ss = SubScript() +_ = ss[:] +ss[:1] = 1 diff --git a/tests/snippets/builtin_zip.py b/tests/snippets/builtin_zip.py new file mode 100644 index 000000000..3665c7702 --- /dev/null +++ b/tests/snippets/builtin_zip.py @@ -0,0 +1,24 @@ +assert list(zip(['a', 'b', 'c'], range(3), [9, 8, 7, 99])) == [('a', 0, 9), ('b', 1, 8), ('c', 2, 7)] + +assert list(zip(['a', 'b', 'c'])) == [('a',), ('b',), ('c',)] +assert list(zip()) == [] + +assert list(zip(*zip(['a', 'b', 'c'], range(1, 4)))) == [('a', 'b', 'c'), (1, 2, 3)] + + +# test infinite iterator +class Counter(object): + def __init__(self, counter=0): + self.counter = counter + + def __next__(self): + self.counter += 1 + return self.counter + + def __iter__(self): + return self + + +it = zip(Counter(), Counter(3)) +assert next(it) == (1, 4) +assert next(it) == (2, 5) diff --git a/tests/snippets/builtins.py b/tests/snippets/builtins.py index bbf116abc..76b28a7b9 100644 --- a/tests/snippets/builtins.py +++ b/tests/snippets/builtins.py @@ -1,22 +1,12 @@ - -a = list(map(str, [1, 2, 3])) -assert a == ['1', '2', '3'] - -x = sum(map(int, a)) +x = sum(map(int, ['1', '2', '3'])) assert x == 6 assert callable(type) # TODO: # assert callable(callable) -assert list(enumerate(['a', 'b', 'c'])) == [(0, 'a'), (1, 'b'), (2, 'c')] - assert type(frozenset) is type -assert list(zip(['a', 'b', 'c'], range(3), [9, 8, 7, 99])) == [('a', 0, 9), ('b', 1, 8), ('c', 2, 7)] - -assert list(filter(lambda x: ((x % 2) == 0), [0, 1, 2])) == [0, 2] - assert 3 == eval('1+2') code = compile('5+3', 'x.py', 'eval') diff --git a/tests/snippets/bytearray.py b/tests/snippets/bytearray.py new file mode 100644 index 000000000..2fffe3111 --- /dev/null +++ b/tests/snippets/bytearray.py @@ -0,0 +1,31 @@ +#__getitem__ not implemented yet +#a = bytearray(b'abc') +#assert a[0] == b'a' +#assert a[1] == b'b' + +assert len(bytearray([1,2,3])) == 3 + +assert bytearray(b'1a23').isalnum() +assert not bytearray(b'1%a23').isalnum() + +assert bytearray(b'abc').isalpha() +assert not bytearray(b'abc1').isalpha() + +# travis doesn't like this +#assert bytearray(b'xyz').isascii() +#assert not bytearray([128, 157, 32]).isascii() + +assert bytearray(b'1234567890').isdigit() +assert not bytearray(b'12ab').isdigit() + +assert bytearray(b'lower').islower() +assert not bytearray(b'Super Friends').islower() + +assert bytearray(b' \n\t').isspace() +assert not bytearray(b'\td\n').isspace() + +assert bytearray(b'UPPER').isupper() +assert not bytearray(b'tuPpEr').isupper() + +assert bytearray(b'Is Title Case').istitle() +assert not bytearray(b'is Not title casE').istitle() diff --git a/tests/snippets/code.py b/tests/snippets/code.py new file mode 100644 index 000000000..ab3353e3b --- /dev/null +++ b/tests/snippets/code.py @@ -0,0 +1,28 @@ +c1 = compile("1 + 1", "", 'eval') + +code_class = type(c1) + +def f(x, y, *args, power=1, **kwargs): + assert code_class == type(c1) + z = x * y + return z ** power + +c2 = f.__code__ +# print(c2) +assert type(c2) == code_class +print(dir(c2)) +assert c2.co_argcount == 2 +# assert c2.co_cellvars == () +# assert isinstance(c2.co_code, bytes) +# assert c2.co_consts == (None,) +assert "code.py" in c2.co_filename +assert c2.co_firstlineno == 5, str(c2.co_firstlineno) +# assert isinstance(c2.co_flags, int) # 'OPTIMIZED, NEWLOCALS, NOFREE' +# assert c2.co_freevars == (), str(c2.co_freevars) +assert c2.co_kwonlyargcount == 1, (c2.co_kwonlyargcount) +# assert c2.co_lnotab == 0, c2.co_lnotab # b'\x00\x01' # Line number table +assert c2.co_name == 'f', c2.co_name +# assert c2.co_names == ('code_class', 'type', 'c1', 'AssertionError'), c2.co_names # , c2.co_names +# assert c2.co_nlocals == 4, c2.co_nlocals # +# assert c2.co_stacksize == 2, 'co_stacksize', +# assert c2.co_varnames == ('x', 'y', 'power', 'z'), c2.co_varnames diff --git a/tests/snippets/division_by_zero.py b/tests/snippets/division_by_zero.py new file mode 100644 index 000000000..d92419d79 --- /dev/null +++ b/tests/snippets/division_by_zero.py @@ -0,0 +1,55 @@ +try: + 5 / 0 +except ZeroDivisionError: + pass +else: + assert False, 'Expected ZeroDivisionError' + +try: + 5 / -0.0 +except ZeroDivisionError: + pass +else: + assert False, 'Expected ZeroDivisionError' + +try: + 5 / (2-2) +except ZeroDivisionError: + pass +else: + assert False, 'Expected ZeroDivisionError' + +try: + 5 % 0 +except ZeroDivisionError: + pass +else: + assert False, 'Expected ZeroDivisionError' + +try: + 5 // 0 +except ZeroDivisionError: + pass +else: + assert False, 'Expected ZeroDivisionError' + +try: + 5.3 // (-0.0) +except ZeroDivisionError: + pass +else: + assert False, 'Expected ZeroDivisionError' + +try: + divmod(5, 0) +except ZeroDivisionError: + pass +else: + assert False, 'Expected ZeroDivisionError' + +try: + raise ZeroDivisionError('Is an ArithmeticError subclass?') +except ArithmeticError: + pass +else: + assert False, 'Expected ZeroDivisionError' diff --git a/tests/snippets/floats.py b/tests/snippets/floats.py index cf6a9a0c0..b2b2cad8c 100644 --- a/tests/snippets/floats.py +++ b/tests/snippets/floats.py @@ -15,3 +15,6 @@ assert b >= a assert c >= a assert not a >= b +assert a + b == 2.5 +assert a - c == 0 +assert a / c == 1 diff --git a/tests/snippets/import.py b/tests/snippets/import.py index 1a5130269..0a36e6342 100644 --- a/tests/snippets/import.py +++ b/tests/snippets/import.py @@ -16,6 +16,12 @@ assert import_target.Y == aliased_other_func() assert STAR_IMPORT == '123' +try: + from import_target import func, unknown_name + raise AssertionError('`unknown_name` does not cause an exception') +except ImportError: + pass + # TODO: Once we can determine current directory, use that to construct this # path: #import sys diff --git a/tests/snippets/index_overflow.py b/tests/snippets/index_overflow.py new file mode 100644 index 000000000..fa0d80b9a --- /dev/null +++ b/tests/snippets/index_overflow.py @@ -0,0 +1,26 @@ +import sys + + +def expect_cannot_fit_index_error(s, index): + try: + s[index] + except IndexError: + pass + # TODO: Replace current except block with commented + # after solving https://github.com/RustPython/RustPython/issues/322 + # except IndexError as error: + # assert str(error) == "cannot fit 'int' into an index-sized integer" + else: + assert False + + +MAX_INDEX = sys.maxsize + 1 +MIN_INDEX = -(MAX_INDEX + 1) + +test_str = "test" +expect_cannot_fit_index_error(test_str, MIN_INDEX) +expect_cannot_fit_index_error(test_str, MAX_INDEX) + +test_list = [0, 1, 2, 3] +expect_cannot_fit_index_error(test_list, MIN_INDEX) +expect_cannot_fit_index_error(test_list, MAX_INDEX) diff --git a/tests/snippets/list.py b/tests/snippets/list.py index 85010a755..dba809868 100644 --- a/tests/snippets/list.py +++ b/tests/snippets/list.py @@ -10,6 +10,7 @@ y.extend(x) assert y == [2, 1, 2, 3, 1, 2, 3] assert x * 0 == [], "list __mul__ by 0 failed" +assert x * -1 == [], "list __mul__ by -1 failed" assert x * 2 == [1, 2, 3, 1, 2, 3], "list __mul__ by 2 failed" assert ['a', 'b', 'c'].index('b') == 1 @@ -20,3 +21,20 @@ except ValueError: pass else: assert False, "ValueError was not raised" + +x = [[1,0,-3], 'a', 1] +y = [[3,2,1], 'z', 2] +assert x < y, "list __lt__ failed" + +x = [5, 13, 31] +y = [1, 10, 29] +assert x > y, "list __gt__ failed" + + +assert [1,2,'a'].pop() == 'a', "list pop failed" +try: + [].pop() +except IndexError: + pass +else: + assert False, "IndexError was not raised" diff --git a/tests/snippets/math.py b/tests/snippets/math.py index 9b31c1860..09f3ed3b3 100644 --- a/tests/snippets/math.py +++ b/tests/snippets/math.py @@ -18,4 +18,12 @@ assert -a == -4 assert +a == 4 # import math -# print(math.cos(1.2)) +# assert(math.exp(2) == math.exp(2.0)) +# assert(math.exp(True) == math.exp(1.0)) +# +# class Conversible(): +# def __float__(self): +# print("Converting to float now!") +# return 1.1111 +# +# assert math.log(1.1111) == math.log(Conversible()) diff --git a/tests/snippets/numbers.py b/tests/snippets/numbers.py index eae26b102..c36602ee1 100644 --- a/tests/snippets/numbers.py +++ b/tests/snippets/numbers.py @@ -2,9 +2,43 @@ x = 5 x.__init__(6) assert x == 5 + class A(int): pass + x = A(7) assert x == 7 assert type(x) is A + +assert int(2).__index__() == 2 +assert int(2).__trunc__() == 2 +assert int(2).__ceil__() == 2 +assert int(2).__floor__() == 2 +assert int(2).__round__() == 2 +assert int(2).__round__(3) == 2 +assert int(-2).__index__() == -2 +assert int(-2).__trunc__() == -2 +assert int(-2).__ceil__() == -2 +assert int(-2).__floor__() == -2 +assert int(-2).__round__() == -2 +assert int(-2).__round__(3) == -2 + +assert round(10) == 10 +assert round(10, 2) == 10 +assert round(10, -1) == 10 + +assert int(2).__bool__() == True +assert int(0.5).__bool__() == False +assert int(-1).__bool__() == True + +assert int(0).__invert__() == -1 +assert int(-3).__invert__() == 2 +assert int(4).__invert__() == -5 + +assert int(0).__rxor__(0) == 0 +assert int(1).__rxor__(0) == 1 +assert int(0).__rxor__(1) == 1 +assert int(1).__rxor__(1) == 0 +assert int(3).__rxor__(-3) == -2 +assert int(3).__rxor__(4) == 7 diff --git a/tests/snippets/os_open.py b/tests/snippets/os_open.py index ca5a61d5a..8138f820c 100644 --- a/tests/snippets/os_open.py +++ b/tests/snippets/os_open.py @@ -2,3 +2,9 @@ import os assert os.open('README.md', 0) > 0 + +try: + os.open('DOES_NOT_EXIST', 0) + assert False +except FileNotFoundError: + pass diff --git a/tests/snippets/set.py b/tests/snippets/set.py new file mode 100644 index 000000000..8b31c7c23 --- /dev/null +++ b/tests/snippets/set.py @@ -0,0 +1,26 @@ +assert set([1,2]) == set([1,2]) +assert not set([1,2,3]) == set([1,2]) + +assert set([1,2,3]) >= set([1,2]) +assert set([1,2]) >= set([1,2]) +assert not set([1,3]) >= set([1,2]) + +assert set([1,2,3]).issuperset(set([1,2])) +assert set([1,2]).issuperset(set([1,2])) +assert not set([1,3]).issuperset(set([1,2])) + +assert set([1,2,3]) > set([1,2]) +assert not set([1,2]) > set([1,2]) +assert not set([1,3]) > set([1,2]) + +assert set([1,2]) <= set([1,2,3]) +assert set([1,2]) <= set([1,2]) +assert not set([1,3]) <= set([1,2]) + +assert set([1,2]).issubset(set([1,2,3])) +assert set([1,2]).issubset(set([1,2])) +assert not set([1,3]).issubset(set([1,2])) + +assert set([1,2]) < set([1,2,3]) +assert not set([1,2]) < set([1,2]) +assert not set([1,3]) < set([1,2]) diff --git a/tests/snippets/strings.py b/tests/snippets/strings.py index b6c735cc4..66157f533 100644 --- a/tests/snippets/strings.py +++ b/tests/snippets/strings.py @@ -8,6 +8,9 @@ assert "\n" == """ """ assert len(""" " \" """) == 5 +assert len("é") == 1 +assert len("é") == 2 +assert len("あ") == 1 assert type("") is str assert type(b"") is bytes @@ -35,6 +38,7 @@ assert not a.endswith('on') assert a.zfill(8) == '000Hallo' assert a.isalnum() assert not a.isdigit() +assert not a.isdecimal() assert not a.isnumeric() assert a.istitle() assert a.isalpha() @@ -65,6 +69,9 @@ assert '-'.join(['1', '2', '3']) == '1-2-3' assert 'HALLO'.isupper() assert "hello, my name is".partition("my ") == ('hello, ', 'my ', 'name is') assert "hello, my name is".rpartition("is") == ('hello, my name ', 'is', '') +assert not ''.isdecimal() +assert '123'.isdecimal() +assert not '\u00B2'.isdecimal() # String Formatting assert "{} {}".format(1,2) == "1 2" diff --git a/tests/snippets/sysmod_argv.py b/tests/snippets/sysmod.py similarity index 100% rename from tests/snippets/sysmod_argv.py rename to tests/snippets/sysmod.py diff --git a/tests/snippets/test_bitwise.py b/tests/snippets/test_bitwise.py new file mode 100644 index 000000000..18b90a197 --- /dev/null +++ b/tests/snippets/test_bitwise.py @@ -0,0 +1,37 @@ +def assert_raises(expr, exc_type): + """ + Helper function to assert `expr` raises an exception of type `exc_type` + Args: + expr: Callable + exec_type: Exception + Returns: + None + Raises: + Assertion error on failure + """ + try: + expr(None) + except exc_type: + assert True + else: + assert False + +# +# Tests +# +assert 8 >> 3 == 1 +assert 8 << 3 == 64 + +# Left shift raises type error +assert_raises(lambda _: 1 << 0.1, TypeError) +assert_raises(lambda _: 1 << "abc", TypeError) + +# Right shift raises type error +assert_raises(lambda _: 1 >> 0.1, TypeError) +assert_raises(lambda _: 1 >> "abc", TypeError) + +# Left shift raises value error on negative +assert_raises(lambda _: 1 << -1, ValueError) + +# Right shift raises value error on negative +assert_raises(lambda _: 1 >> -1, ValueError) diff --git a/tests/snippets/tuple.py b/tests/snippets/tuple.py index 9227f9591..eb5102fa3 100644 --- a/tests/snippets/tuple.py +++ b/tests/snippets/tuple.py @@ -5,3 +5,17 @@ assert x[0] == 1 y = (1,) assert y[0] == 1 + +assert x + y == (1, 2, 1) + +assert x * 3 == (1, 2, 1, 2, 1, 2) +# assert 3 * x == (1, 2, 1, 2, 1, 2) +assert x * 0 == () +assert x * -1 == () # integers less than zero treated as 0 + +assert y < x, "tuple __lt__ failed" +assert x > y, "tuple __gt__ failed" + + +b = (1,2,3) +assert b.index(2) == 1 diff --git a/tests/snippets/unicode_slicing.py b/tests/snippets/unicode_slicing.py new file mode 100644 index 000000000..de4184513 --- /dev/null +++ b/tests/snippets/unicode_slicing.py @@ -0,0 +1,33 @@ +def test_slice_bounds(s): + # End out of range + assert s[0:100] == s + assert s[0:-100] == '' + # Start out of range + assert s[100:1] == '' + # Out of range both sides + # This is the behaviour in cpython + # assert s[-100:100] == s + +def expect_index_error(s, index): + try: + s[index] + except IndexError: + pass + else: + assert False + +unicode_str = "∀∂" +assert unicode_str[0] == "∀" +assert unicode_str[1] == "∂" +assert unicode_str[-1] == "∂" + +test_slice_bounds(unicode_str) +expect_index_error(unicode_str, 100) +expect_index_error(unicode_str, -100) + +ascii_str = "hello world" +test_slice_bounds(ascii_str) +assert ascii_str[0] == "h" +assert ascii_str[1] == "e" +assert ascii_str[-1] == "d" + diff --git a/tests/snippets/whats_left_to_implement.py b/tests/snippets/whats_left_to_implement.py index c67cef580..b6c8cbe42 100644 --- a/tests/snippets/whats_left_to_implement.py +++ b/tests/snippets/whats_left_to_implement.py @@ -1,4 +1,7 @@ -bool_expected_methods = [ +expected_methods = [] + +# TODO: using tuple could have been better +expected_methods.append({'name': 'bool', 'methods': [ '__abs__', '__add__', '__and__', @@ -69,9 +72,8 @@ bool_expected_methods = [ 'numerator', 'real', 'to_bytes', -] - -bytearray_expected_methods = [ +], 'type': bool}) +expected_methods.append({'name': 'bytearray', 'type': bytearray, 'methods': [ '__add__', '__alloc__', '__class__', @@ -157,9 +159,8 @@ bytearray_expected_methods = [ 'translate', 'upper', 'zfill', -] - -bytes_expected_methods = [ +]}) +expected_methods.append({'name': 'bytes', 'type': bytes, 'methods': [ '__add__', '__class__', '__contains__', @@ -233,9 +234,8 @@ bytes_expected_methods = [ 'translate', 'upper', 'zfill', -] - -complex_expected_methods = [ +]}) +expected_methods.append({'name': 'complex', 'type': complex, 'methods': [ '__abs__', '__add__', '__bool__', @@ -285,9 +285,8 @@ complex_expected_methods = [ 'conjugate', 'imag', 'real', -] - -dict_expected_methods = [ +]}) +expected_methods.append({'name': 'dict', 'type': dict, 'methods': [ '__class__', '__contains__', '__delattr__', @@ -328,9 +327,8 @@ dict_expected_methods = [ 'setdefault', 'update', 'values', -] - -float_expected_methods = [ +]}) +expected_methods.append({'name': 'float','type':float,'methods':[ '__abs__', '__add__', '__bool__', @@ -388,9 +386,8 @@ float_expected_methods = [ 'imag', 'is_integer', 'real', -] - -frozenset_expected_methods = [ +]}) +expected_methods.append({'name': 'frozenset','type':frozenset, 'methods': [ '__and__', '__class__', '__contains__', @@ -433,9 +430,8 @@ frozenset_expected_methods = [ 'issuperset', 'symmetric_difference', 'union', -] - -int_expected_methods = [ +]}) +expected_methods.append({'name': 'int', 'type':int, 'methods': [ '__abs__', '__add__', '__and__', @@ -506,9 +502,8 @@ int_expected_methods = [ 'numerator', 'real', 'to_bytes', -] - -iter_expected_methods = [ +]}) +expected_methods.append({'name': 'iter','type':iter,'methods':[ '__class__', '__delattr__', '__dir__', @@ -536,9 +531,8 @@ iter_expected_methods = [ '__sizeof__', '__str__', '__subclasshook__', -] - -list_expected_methods = [ +]}) +expected_methods.append({'name': 'list','type':list,'methods':[ '__add__', '__class__', '__contains__', @@ -585,9 +579,8 @@ list_expected_methods = [ 'remove', 'reverse', 'sort', -] - -memoryview_expected_methods = [ +]}) +expected_methods.append({'name': 'memoryview','type':memoryview,'methods':[ '__class__', '__delattr__', '__delitem__', @@ -634,9 +627,8 @@ memoryview_expected_methods = [ 'suboffsets', 'tobytes', 'tolist', -] - -range_expected_methods = [ +]}) +expected_methods.append({'name': 'range','type':range,'methods':[ '__bool__', '__class__', '__contains__', @@ -671,9 +663,8 @@ range_expected_methods = [ 'start', 'step', 'stop', -] - -set_expected_methods = [ +]}) +expected_methods.append({'name': 'set','type':set,'methods':[ '__and__', '__class__', '__contains__', @@ -729,9 +720,8 @@ set_expected_methods = [ 'symmetric_difference_update', 'union', 'update', -] - -string_expected_methods = [ +]}) +expected_methods.append({'name': 'string','type':str,'methods':[ '__add__', '__class__', '__contains__', @@ -810,9 +800,8 @@ string_expected_methods = [ 'translate', 'upper', 'zfill' -] - -tuple_expected_methods = [ +]}) +expected_methods.append({'name': 'tuple','type':tuple, 'methods': [ '__add__', '__class__', '__contains__', @@ -846,116 +835,42 @@ tuple_expected_methods = [ '__subclasshook__', 'count', 'index', -] +]}) +expected_methods.append({'name': 'object', 'type':object, 'methods':[ + '__repr__', + '__hash__', + '__str__', + '__getattribute__', + '__setattr__', + '__delattr__', + '__lt__', + '__le__', + '__eq__', + '__ne__', + '__gt__', + '__ge__', + '__init__', + '__new__', + '__reduce_ex__', + '__reduce__', + '__subclasshook__', + '__init_subclass__', + '__format__', + '__sizeof__', + '__dir__', + '__class__', + '__doc__' +]}) not_implemented = [] -for method in bool_expected_methods: - try: - if not hasattr(bool, method): - not_implemented.append(("bool", method)) - except NameError: - not_implemented.append(("bool", method)) - -for method in bytearray_expected_methods: - try: - if not hasattr(bytearray(), method): - not_implemented.append(("bytearray", method)) - except NameError: - not_implemented.append(("bytearray", method)) - -for method in bytes_expected_methods: - try: - if not hasattr(bytes, method): - not_implemented.append(("bytes", method)) - except NameError: - not_implemented.append(("bytes", method)) - -for method in complex_expected_methods: - try: - if not hasattr(complex, method): - not_implemented.append(("complex", method)) - except NameError: - not_implemented.append(("complex", method)) - -for method in dict_expected_methods: - try: - if not hasattr(dict, method): - not_implemented.append(("dict", method)) - except NameError: - not_implemented.append(("dict", method)) - -for method in float_expected_methods: - try: - if not hasattr(float, method): - not_implemented.append(("float", method)) - except NameError: - not_implemented.append(("float", method)) - -for method in frozenset_expected_methods: -# TODO: uncomment this when frozenset is implemented -# try: -# if not hasattr(frozenset, method): -# not_implemented.append(("frozenset", method)) -# except NameError: - not_implemented.append(("frozenset", method)) - -for method in int_expected_methods: - try: - if not hasattr(int, method): - not_implemented.append(("int", method)) - except NameError: - not_implemented.append(("int", method)) - -for method in iter_expected_methods: - try: - if not hasattr(iter([]), method): - not_implemented.append(("iter", method)) - except NameError: - not_implemented.append(("iter", method)) - -for method in list_expected_methods: - try: - if not hasattr(list, method): - not_implemented.append(("list", method)) - except NameError: - not_implemented.append(("list", method)) - -for method in memoryview_expected_methods: -# TODO: uncomment this when memoryview is implemented -# try: -# if not hasattr(memoryview, method): -# not_implemented.append(("memoryview", method)) -# except NameError: - not_implemented.append(("memoryview", method)) - -for method in range_expected_methods: - try: - if not hasattr(range, method): - not_implemented.append(("range", method)) - except NameError: - not_implemented.append(("range", method)) - -for method in set_expected_methods: - try: - if not hasattr(set, method): - not_implemented.append(("set", method)) - except NameError: - not_implemented.append(("set", method)) - -for method in string_expected_methods: - try: - if not hasattr(str, method): - not_implemented.append(("string", method)) - except NameError: - not_implemented.append(("string", method)) - -for method in tuple_expected_methods: - try: - if not hasattr(tuple, method): - not_implemented.append(("tuple", method)) - except NameError: - not_implemented.append(("tuple", method)) +for item in expected_methods: + for method in item['methods']: + try: + if not hasattr(item['type'], method): + not_implemented.append((item['name'], method)) + except NameError: + not_implemented.append((item['name'], method)) for r in not_implemented: print(r[0], ".", r[1]) diff --git a/vm/Cargo.toml b/vm/Cargo.toml index 7f1aed7ab..fe278edb7 100644 --- a/vm/Cargo.toml +++ b/vm/Cargo.toml @@ -8,6 +8,7 @@ bitflags = "1.0.4" num-complex = "0.2" num-bigint = "0.2.1" num-traits = "0.2" +num-integer = "0.1.39" rand = "0.5" log = "0.3" rustpython_parser = {path = "../parser"} @@ -17,4 +18,5 @@ serde_json = "1.0.26" byteorder = "1.2.6" regex = "1" statrs = "0.10.0" -caseless = "0.2.1" \ No newline at end of file +caseless = "0.2.1" +unicode-segmentation = "1.2.1" diff --git a/vm/src/builtins.rs b/vm/src/builtins.rs index f1067d81e..1321a96e4 100644 --- a/vm/src/builtins.rs +++ b/vm/src/builtins.rs @@ -21,8 +21,7 @@ use super::pyobject::{ use super::stdlib::io::io_open; use super::vm::VirtualMachine; -use num_bigint::ToBigInt; -use num_traits::{Signed, ToPrimitive, Zero}; +use num_traits::{Signed, ToPrimitive}; fn get_locals(vm: &mut VirtualMachine) -> PyObjectRef { let d = vm.new_dict(); @@ -136,11 +135,11 @@ fn builtin_compile(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { let mode = { let mode = objstr::get_value(mode); - if mode == String::from("exec") { + if mode == "exec" { compile::Mode::Exec - } else if mode == "eval".to_string() { + } else if mode == "eval" { compile::Mode::Eval - } else if mode == "single".to_string() { + } else if mode == "single" { compile::Mode::Single } else { return Err( @@ -151,7 +150,7 @@ fn builtin_compile(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { let filename = objstr::get_value(filename); - compile::compile(vm, &source, mode, Some(filename)) + compile::compile(vm, &source, &mode, filename) } fn builtin_delattr(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { @@ -180,29 +179,8 @@ fn builtin_divmod(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { } } -fn builtin_enumerate(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(iterable, None)], - optional = [(start, None)] - ); - let items = vm.extract_elements(iterable)?; - let start = if let Some(start) = start { - objint::get_value(start) - } else { - Zero::zero() - }; - let mut new_items = vec![]; - for (i, item) in items.into_iter().enumerate() { - let element = vm - .ctx - .new_tuple(vec![vm.ctx.new_int(i.to_bigint().unwrap() + &start), item]); - new_items.push(element); - } - Ok(vm.ctx.new_list(new_items)) -} - +/// Implements `eval`. +/// See also: https://docs.python.org/3/library/functions.html#eval fn builtin_eval(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!( vm, @@ -222,7 +200,7 @@ fn builtin_eval(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { let source = objstr::get_value(source); // TODO: fix this newline bug: let source = format!("{}\n", source); - compile::compile(vm, &source, mode, None)? + compile::compile(vm, &source, &mode, "".to_string())? } else { return Err(vm.new_type_error("code argument must be str or code object".to_string())); }; @@ -236,7 +214,7 @@ fn builtin_eval(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { // TODO: handle optional globals // Construct new scope: let scope_inner = Scope { - locals: locals, + locals, parent: None, }; let scope = PyObject { @@ -249,6 +227,8 @@ fn builtin_eval(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { vm.run_code_obj(code_obj.clone(), scope) } +/// Implements `exec` +/// https://docs.python.org/3/library/functions.html#exec fn builtin_exec(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!( vm, @@ -266,7 +246,7 @@ fn builtin_exec(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { let source = objstr::get_value(source); // TODO: fix this newline bug: let source = format!("{}\n", source); - compile::compile(vm, &source, mode, None)? + compile::compile(vm, &source, &mode, "".to_string())? } else if objtype::isinstance(source, &vm.ctx.code_type()) { source.clone() } else { @@ -284,7 +264,7 @@ fn builtin_exec(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { // Construct new scope: let scope_inner = Scope { - locals: locals, + locals, parent: None, }; let scope = PyObject { @@ -297,29 +277,6 @@ fn builtin_exec(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { vm.run_code_obj(code_obj, scope) } -fn builtin_filter(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(function, None), (iterable, None)]); - - // TODO: process one element at a time from iterators. - let iterable = vm.extract_elements(iterable)?; - - let mut new_items = vec![]; - for element in iterable { - // apply function: - let args = PyFuncArgs { - args: vec![element.clone()], - kwargs: vec![], - }; - let result = vm.invoke(function.clone(), args)?; - let result = objbool::boolval(vm, result)?; - if result { - new_items.push(element); - } - } - - Ok(vm.ctx.new_list(new_items)) -} - fn builtin_format(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!( vm, @@ -378,7 +335,7 @@ fn builtin_hex(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { fn builtin_id(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(obj, None)]); - Ok(vm.context().new_int(obj.get_id().to_bigint().unwrap())) + Ok(vm.context().new_int(obj.get_id())) } // builtin_input @@ -424,33 +381,6 @@ fn builtin_locals(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { Ok(vm.get_locals()) } -fn builtin_map(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(function, None), (iter_target, None)]); - let iterator = objiter::get_iter(vm, iter_target)?; - let mut elements = vec![]; - loop { - match vm.call_method(&iterator, "__next__", vec![]) { - Ok(v) => { - // Now apply function: - let mapped_value = vm.invoke( - function.clone(), - PyFuncArgs { - args: vec![v], - kwargs: vec![], - }, - )?; - elements.push(mapped_value); - } - Err(_) => break, - } - } - - trace!("Mapped elements: {:?}", elements); - - // TODO: when iterators are implemented, we can improve this function. - Ok(vm.ctx.new_list(elements)) -} - fn builtin_max(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { let candidates = if args.args.len() > 1 { args.args.clone() @@ -461,7 +391,7 @@ fn builtin_max(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { return Err(vm.new_type_error("Expected 1 or more arguments".to_string())); }; - if candidates.len() == 0 { + if candidates.is_empty() { let default = args.get_optional_kwarg("default"); if default.is_none() { return Err(vm.new_value_error("max() arg is an empty sequence".to_string())); @@ -512,7 +442,7 @@ fn builtin_min(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { return Err(vm.new_type_error("Expected 1 or more arguments".to_string())); }; - if candidates.len() == 0 { + if candidates.is_empty() { let default = args.get_optional_kwarg("default"); if default.is_none() { return Err(vm.new_value_error("min() arg is an empty sequence".to_string())); @@ -599,9 +529,7 @@ fn builtin_ord(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { ))); } match string.chars().next() { - Some(character) => Ok(vm - .context() - .new_int((character as i32).to_bigint().unwrap())), + Some(character) => Ok(vm.context().new_int(character as i32)), None => Err(vm.new_type_error( "ord() could not guess the integer representing this character".to_string(), )), @@ -655,21 +583,42 @@ pub fn builtin_print(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { Ok(vm.get_none()) } -fn builtin_range(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(range, Some(vm.ctx.int_type()))]); - let value = objint::get_value(range); - let range_elements: Vec = (0..value.to_i32().unwrap()) - .map(|num| vm.context().new_int(num.to_bigint().unwrap())) - .collect(); - Ok(vm.context().new_list(range_elements)) -} - fn builtin_repr(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(obj, None)]); vm.to_repr(obj) } + +fn builtin_reversed(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!(vm, args, required = [(obj, None)]); + + match vm.get_method(obj.clone(), "__reversed__") { + Ok(value) => vm.invoke(value, PyFuncArgs::default()), + // TODO: fallback to using __len__ and __getitem__, if object supports sequence protocol + Err(..) => Err(vm.new_type_error(format!( + "'{}' object is not reversible", + objtype::get_type_name(&obj.typ()), + ))), + } +} // builtin_reversed -// builtin_round + +fn builtin_round(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!( + vm, + args, + required = [(number, Some(vm.ctx.object()))], + optional = [(ndigits, None)] + ); + if let Some(ndigits) = ndigits { + let ndigits = vm.call_method(ndigits, "__int__", vec![])?; + let rounded = vm.call_method(number, "__round__", vec![ndigits])?; + Ok(rounded) + } else { + // without a parameter, the result type is coerced to int + let rounded = &vm.call_method(number, "__round__", vec![])?; + Ok(vm.ctx.new_int(objint::get_value(rounded))) + } +} fn builtin_setattr(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!( @@ -690,7 +639,7 @@ fn builtin_sum(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { let items = vm.extract_elements(iterable)?; // Start with zero and add at will: - let mut sum = vm.ctx.new_int(Zero::zero()); + let mut sum = vm.ctx.new_int(0); for item in items { sum = vm._add(sum, item)?; } @@ -698,32 +647,6 @@ fn builtin_sum(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { } // builtin_vars - -fn builtin_zip(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - no_kwargs!(vm, args); - - // TODO: process one element at a time from iterators. - let mut iterables = vec![]; - for iterable in args.args.iter() { - let iterable = vm.extract_elements(iterable)?; - iterables.push(iterable); - } - - let minsize: usize = iterables.iter().map(|i| i.len()).min().unwrap_or(0); - - let mut new_items = vec![]; - for i in 0..minsize { - let items = iterables - .iter() - .map(|iterable| iterable[i].clone()) - .collect(); - let element = vm.ctx.new_tuple(items); - new_items.push(element); - } - - Ok(vm.ctx.new_list(new_items)) -} - // builtin___import__ pub fn make_module(ctx: &PyContext) -> PyObjectRef { @@ -749,12 +672,12 @@ pub fn make_module(ctx: &PyContext) -> PyObjectRef { ctx.set_attr(&py_mod, "dict", ctx.dict_type()); ctx.set_attr(&py_mod, "divmod", ctx.new_rustfunc(builtin_divmod)); ctx.set_attr(&py_mod, "dir", ctx.new_rustfunc(builtin_dir)); - ctx.set_attr(&py_mod, "enumerate", ctx.new_rustfunc(builtin_enumerate)); + ctx.set_attr(&py_mod, "enumerate", ctx.enumerate_type()); ctx.set_attr(&py_mod, "eval", ctx.new_rustfunc(builtin_eval)); ctx.set_attr(&py_mod, "exec", ctx.new_rustfunc(builtin_exec)); ctx.set_attr(&py_mod, "float", ctx.float_type()); ctx.set_attr(&py_mod, "frozenset", ctx.frozenset_type()); - ctx.set_attr(&py_mod, "filter", ctx.new_rustfunc(builtin_filter)); + ctx.set_attr(&py_mod, "filter", ctx.filter_type()); ctx.set_attr(&py_mod, "format", ctx.new_rustfunc(builtin_format)); ctx.set_attr(&py_mod, "getattr", ctx.new_rustfunc(builtin_getattr)); ctx.set_attr(&py_mod, "hasattr", ctx.new_rustfunc(builtin_hasattr)); @@ -768,7 +691,7 @@ pub fn make_module(ctx: &PyContext) -> PyObjectRef { ctx.set_attr(&py_mod, "len", ctx.new_rustfunc(builtin_len)); ctx.set_attr(&py_mod, "list", ctx.list_type()); ctx.set_attr(&py_mod, "locals", ctx.new_rustfunc(builtin_locals)); - ctx.set_attr(&py_mod, "map", ctx.new_rustfunc(builtin_map)); + ctx.set_attr(&py_mod, "map", ctx.map_type()); ctx.set_attr(&py_mod, "max", ctx.new_rustfunc(builtin_max)); ctx.set_attr(&py_mod, "memoryview", ctx.memoryview_type()); ctx.set_attr(&py_mod, "min", ctx.new_rustfunc(builtin_min)); @@ -780,17 +703,20 @@ pub fn make_module(ctx: &PyContext) -> PyObjectRef { ctx.set_attr(&py_mod, "pow", ctx.new_rustfunc(builtin_pow)); ctx.set_attr(&py_mod, "print", ctx.new_rustfunc(builtin_print)); ctx.set_attr(&py_mod, "property", ctx.property_type()); - ctx.set_attr(&py_mod, "range", ctx.new_rustfunc(builtin_range)); + ctx.set_attr(&py_mod, "range", ctx.range_type()); ctx.set_attr(&py_mod, "repr", ctx.new_rustfunc(builtin_repr)); + ctx.set_attr(&py_mod, "reversed", ctx.new_rustfunc(builtin_reversed)); + ctx.set_attr(&py_mod, "round", ctx.new_rustfunc(builtin_round)); ctx.set_attr(&py_mod, "set", ctx.set_type()); ctx.set_attr(&py_mod, "setattr", ctx.new_rustfunc(builtin_setattr)); + ctx.set_attr(&py_mod, "slice", ctx.slice_type()); ctx.set_attr(&py_mod, "staticmethod", ctx.staticmethod_type()); ctx.set_attr(&py_mod, "str", ctx.str_type()); ctx.set_attr(&py_mod, "sum", ctx.new_rustfunc(builtin_sum)); ctx.set_attr(&py_mod, "super", ctx.super_type()); ctx.set_attr(&py_mod, "tuple", ctx.tuple_type()); ctx.set_attr(&py_mod, "type", ctx.type_type()); - ctx.set_attr(&py_mod, "zip", ctx.new_rustfunc(builtin_zip)); + ctx.set_attr(&py_mod, "zip", ctx.zip_type()); // Exceptions: ctx.set_attr( @@ -799,6 +725,11 @@ pub fn make_module(ctx: &PyContext) -> PyObjectRef { ctx.exceptions.base_exception_type.clone(), ); ctx.set_attr(&py_mod, "Exception", ctx.exceptions.exception_type.clone()); + ctx.set_attr( + &py_mod, + "ArithmeticError", + ctx.exceptions.arithmetic_error.clone(), + ); ctx.set_attr( &py_mod, "AssertionError", @@ -810,6 +741,11 @@ pub fn make_module(ctx: &PyContext) -> PyObjectRef { ctx.exceptions.attribute_error.clone(), ); ctx.set_attr(&py_mod, "NameError", ctx.exceptions.name_error.clone()); + ctx.set_attr( + &py_mod, + "OverflowError", + ctx.exceptions.overflow_error.clone(), + ); ctx.set_attr( &py_mod, "RuntimeError", @@ -822,6 +758,23 @@ pub fn make_module(ctx: &PyContext) -> PyObjectRef { ); ctx.set_attr(&py_mod, "TypeError", ctx.exceptions.type_error.clone()); ctx.set_attr(&py_mod, "ValueError", ctx.exceptions.value_error.clone()); + ctx.set_attr(&py_mod, "IndexError", ctx.exceptions.index_error.clone()); + ctx.set_attr(&py_mod, "ImportError", ctx.exceptions.import_error.clone()); + ctx.set_attr( + &py_mod, + "FileNotFoundError", + ctx.exceptions.file_not_found_error.clone(), + ); + ctx.set_attr( + &py_mod, + "StopIteration", + ctx.exceptions.stop_iteration.clone(), + ); + ctx.set_attr( + &py_mod, + "ZeroDivisionError", + ctx.exceptions.zero_division_error.clone(), + ); py_mod } diff --git a/vm/src/bytecode.rs b/vm/src/bytecode.rs index 0c08d0d1b..c0309f841 100644 --- a/vm/src/bytecode.rs +++ b/vm/src/bytecode.rs @@ -22,7 +22,8 @@ pub struct CodeObject { pub varargs: Option>, // *args or * pub kwonlyarg_names: Vec, pub varkeywords: Option>, // **kwargs or ** - pub source_path: Option, + pub source_path: String, + pub first_line_number: usize, pub obj_name: String, // Name of the object that created this code object pub is_generator: bool, } @@ -33,19 +34,21 @@ impl CodeObject { varargs: Option>, kwonlyarg_names: Vec, varkeywords: Option>, - source_path: Option, + source_path: String, + first_line_number: usize, obj_name: String, ) -> CodeObject { CodeObject { instructions: Vec::new(), label_map: HashMap::new(), locations: Vec::new(), - arg_names: arg_names, - varargs: varargs, - kwonlyarg_names: kwonlyarg_names, - varkeywords: varkeywords, - source_path: source_path, - obj_name: obj_name, + arg_names, + varargs, + kwonlyarg_names, + varkeywords, + source_path, + first_line_number, + obj_name, is_generator: false, } } diff --git a/vm/src/compile.rs b/vm/src/compile.rs index 149bb85a9..1aa8cce27 100644 --- a/vm/src/compile.rs +++ b/vm/src/compile.rs @@ -7,7 +7,7 @@ //! https://github.com/micropython/micropython/blob/master/py/compile.c use super::bytecode::{self, CallType, CodeObject, Instruction}; -use super::pyobject::{PyObject, PyObjectPayload, PyResult}; +use super::pyobject::PyResult; use super::vm::VirtualMachine; use num_complex::Complex64; use rustpython_parser::{ast, parser}; @@ -23,11 +23,11 @@ struct Compiler { pub fn compile( vm: &mut VirtualMachine, source: &str, - mode: Mode, - source_path: Option, + mode: &Mode, + source_path: String, ) -> PyResult { let mut compiler = Compiler::new(); - compiler.source_path = source_path.clone(); + compiler.source_path = Some(source_path.clone()); compiler.push_new_code_object(source_path, "".to_string()); let syntax_error = vm.context().exceptions.syntax_error.clone(); let result = match mode { @@ -45,17 +45,13 @@ pub fn compile( }, }; - match result { - Err(msg) => return Err(vm.new_exception(syntax_error.clone(), msg)), - _ => {} + if let Err(msg) = result { + return Err(vm.new_exception(syntax_error.clone(), msg)); } let code = compiler.pop_code_object(); trace!("Compilation completed: {:?}", code); - Ok(PyObject::new( - PyObjectPayload::Code { code: code }, - vm.ctx.code_type(), - )) + Ok(vm.ctx.new_code_object(code)) } pub enum Mode { @@ -82,13 +78,15 @@ impl Compiler { } } - fn push_new_code_object(&mut self, source_path: Option, obj_name: String) { + fn push_new_code_object(&mut self, source_path: String, obj_name: String) { + let line_number = self.get_source_line_number(); self.code_object_stack.push(CodeObject::new( Vec::new(), None, Vec::new(), None, source_path.clone(), + line_number, obj_name, )); } @@ -112,7 +110,7 @@ impl Compiler { fn compile_program_single(&mut self, program: &ast::Program) -> Result<(), String> { for statement in &program.statements { - if let &ast::Statement::Expression { ref expression } = &statement.node { + if let ast::Statement::Expression { ref expression } = statement.node { self.compile_expression(expression)?; self.emit(Instruction::PrintExpr); } else { @@ -128,7 +126,7 @@ impl Compiler { // Compile statement in eval mode: fn compile_statement_eval(&mut self, statement: &ast::LocatedStatement) -> Result<(), String> { - if let &ast::Statement::Expression { ref expression } = &statement.node { + if let ast::Statement::Expression { ref expression } = statement.node { self.compile_expression(expression)?; self.emit(Instruction::ReturnValue); Ok(()) @@ -432,7 +430,7 @@ impl Compiler { self.prepare_decorators(decorator_list)?; self.emit(Instruction::LoadConst { - value: bytecode::Constant::Code { code: code }, + value: bytecode::Constant::Code { code }, }); self.emit(Instruction::LoadConst { value: bytecode::Constant::String { @@ -441,7 +439,7 @@ impl Compiler { }); // Turn code object into function object: - self.emit(Instruction::MakeFunction { flags: flags }); + self.emit(Instruction::MakeFunction { flags }); self.apply_decorators(decorator_list); self.emit(Instruction::StoreName { @@ -457,12 +455,14 @@ impl Compiler { } => { self.prepare_decorators(decorator_list)?; self.emit(Instruction::LoadBuildClass); + let line_number = self.get_source_line_number(); self.code_object_stack.push(CodeObject::new( vec![String::from("__locals__")], None, vec![], None, - self.source_path.clone(), + self.source_path.clone().unwrap(), + line_number, name.clone(), )); self.emit(Instruction::LoadName { @@ -477,7 +477,7 @@ impl Compiler { let code = self.pop_code_object(); self.emit(Instruction::LoadConst { - value: bytecode::Constant::Code { code: code }, + value: bytecode::Constant::Code { code }, }); self.emit(Instruction::LoadConst { value: bytecode::Constant::String { @@ -500,7 +500,7 @@ impl Compiler { self.compile_expression(base)?; } - if keywords.len() > 0 { + if !keywords.is_empty() { let mut kwarg_names = vec![]; for keyword in keywords { if let Some(name) = &keyword.name { @@ -592,7 +592,7 @@ impl Compiler { ast::Statement::Assign { targets, value } => { self.compile_expression(value)?; - for (i, target) in targets.into_iter().enumerate() { + for (i, target) in targets.iter().enumerate() { if i + 1 != targets.len() { self.emit(Instruction::Duplicate); } @@ -627,7 +627,7 @@ impl Compiler { self.emit(Instruction::DeleteSubscript); } _ => { - return Err(format!("Invalid delete statement")); + return Err("Invalid delete statement".to_string()); } } } @@ -641,10 +641,10 @@ impl Compiler { fn enter_function( &mut self, - name: &String, + name: &str, args: &ast::Parameters, ) -> Result { - let have_kwargs = args.defaults.len() > 0; + let have_kwargs = !args.defaults.is_empty(); if have_kwargs { // Construct a tuple: let size = args.defaults.len(); @@ -657,31 +657,33 @@ impl Compiler { }); } + let line_number = self.get_source_line_number(); self.code_object_stack.push(CodeObject::new( args.args.clone(), args.vararg.clone(), args.kwonlyargs.clone(), args.kwarg.clone(), - self.source_path.clone(), - name.clone(), + self.source_path.clone().unwrap(), + line_number, + name.to_string(), )); let mut flags = bytecode::FunctionOpArg::empty(); if have_kwargs { - flags = flags | bytecode::FunctionOpArg::HAS_DEFAULTS; + flags |= bytecode::FunctionOpArg::HAS_DEFAULTS; } Ok(flags) } - fn prepare_decorators(&mut self, decorator_list: &Vec) -> Result<(), String> { + fn prepare_decorators(&mut self, decorator_list: &[ast::Expression]) -> Result<(), String> { for decorator in decorator_list { self.compile_expression(decorator)?; } Ok(()) } - fn apply_decorators(&mut self, decorator_list: &Vec) { + fn apply_decorators(&mut self, decorator_list: &[ast::Expression]) { // Apply decorators: for _ in decorator_list { self.emit(Instruction::CallFunction { @@ -780,7 +782,7 @@ impl Compiler { let f = false_label.unwrap_or_else(|| self.new_label()); self.compile_test(a, None, Some(f), context)?; self.compile_test(b, true_label, false_label, context)?; - if let None = false_label { + if false_label.is_none() { self.set_label(f); } } @@ -788,7 +790,7 @@ impl Compiler { let t = true_label.unwrap_or_else(|| self.new_label()); self.compile_test(a, Some(t), None, context)?; self.compile_test(b, true_label, false_label, context)?; - if let None = true_label { + if true_label.is_none() { self.set_label(t); } } @@ -838,21 +840,21 @@ impl Compiler { self.compile_test(expression, None, None, EvalContext::Expression)? } ast::Expression::Binop { a, op, b } => { - self.compile_expression(&*a)?; - self.compile_expression(&*b)?; + self.compile_expression(a)?; + self.compile_expression(b)?; // Perform operation: self.compile_op(op); } ast::Expression::Subscript { a, b } => { - self.compile_expression(&*a)?; - self.compile_expression(&*b)?; + self.compile_expression(a)?; + self.compile_expression(b)?; self.emit(Instruction::BinaryOperation { op: bytecode::BinaryOperator::Subscript, }); } ast::Expression::Unop { op, a } => { - self.compile_expression(&*a)?; + self.compile_expression(a)?; // Perform operation: let i = match op { @@ -865,14 +867,14 @@ impl Compiler { self.emit(i); } ast::Expression::Attribute { value, name } => { - self.compile_expression(&*value)?; + self.compile_expression(value)?; self.emit(Instruction::LoadAttr { name: name.to_string(), }); } ast::Expression::Compare { a, op, b } => { - self.compile_expression(&*a)?; - self.compile_expression(&*b)?; + self.compile_expression(a)?; + self.compile_expression(b)?; let i = match op { ast::Comparison::Equal => bytecode::ComparisonOperator::Equal, @@ -905,7 +907,7 @@ impl Compiler { let size = elements.len(); let must_unpack = self.gather_elements(elements)?; self.emit(Instruction::BuildList { - size: size, + size, unpack: must_unpack, }); } @@ -913,7 +915,7 @@ impl Compiler { let size = elements.len(); let must_unpack = self.gather_elements(elements)?; self.emit(Instruction::BuildTuple { - size: size, + size, unpack: must_unpack, }); } @@ -921,7 +923,7 @@ impl Compiler { let size = elements.len(); let must_unpack = self.gather_elements(elements)?; self.emit(Instruction::BuildSet { - size: size, + size, unpack: must_unpack, }); } @@ -932,7 +934,7 @@ impl Compiler { self.compile_expression(value)?; } self.emit(Instruction::BuildMap { - size: size, + size, unpack: false, }); } @@ -941,7 +943,7 @@ impl Compiler { for element in elements { self.compile_expression(element)?; } - self.emit(Instruction::BuildSlice { size: size }); + self.emit(Instruction::BuildSlice { size }); } ast::Expression::Yield { value } => { self.mark_generator(); @@ -1003,13 +1005,13 @@ impl Compiler { self.emit(Instruction::ReturnValue); let code = self.pop_code_object(); self.emit(Instruction::LoadConst { - value: bytecode::Constant::Code { code: code }, + value: bytecode::Constant::Code { code }, }); self.emit(Instruction::LoadConst { value: bytecode::Constant::String { value: name }, }); // Turn code object into function object: - self.emit(Instruction::MakeFunction { flags: flags }); + self.emit(Instruction::MakeFunction { flags }); } ast::Expression::Comprehension { kind, generators } => { self.compile_comprehension(kind, generators)?; @@ -1036,8 +1038,8 @@ impl Compiler { fn compile_call( &mut self, function: &ast::Expression, - args: &Vec, - keywords: &Vec, + args: &[ast::Expression], + keywords: &[ast::Keyword], ) -> Result<(), String> { self.compile_expression(function)?; let count = args.len() + keywords.len(); @@ -1054,7 +1056,7 @@ impl Compiler { }); // Create an optional map with kw-args: - if keywords.len() > 0 { + if !keywords.is_empty() { for keyword in keywords { if let Some(name) = &keyword.name { self.emit(Instruction::LoadConst { @@ -1090,7 +1092,7 @@ impl Compiler { } } else { // Keyword arguments: - if keywords.len() > 0 { + if !keywords.is_empty() { let mut kwarg_names = vec![]; for keyword in keywords { if let Some(name) = &keyword.name { @@ -1123,7 +1125,7 @@ impl Compiler { // Given a vector of expr / star expr generate code which gives either // a list of expressions on the stack, or a list of tuples. - fn gather_elements(&mut self, elements: &Vec) -> Result { + fn gather_elements(&mut self, elements: &[ast::Expression]) -> Result { // First determine if we have starred elements: let has_stars = elements.iter().any(|e| { if let ast::Expression::Starred { .. } = e { @@ -1153,10 +1155,10 @@ impl Compiler { fn compile_comprehension( &mut self, kind: &ast::ComprehensionKind, - generators: &Vec, + generators: &[ast::Comprehension], ) -> Result<(), String> { // We must have at least one generator: - assert!(generators.len() > 0); + assert!(!generators.is_empty()); let name = match kind { ast::ComprehensionKind::GeneratorExpression { .. } => "", @@ -1166,13 +1168,15 @@ impl Compiler { } .to_string(); + let line_number = self.get_source_line_number(); // Create magnificent function : self.code_object_stack.push(CodeObject::new( vec![".0".to_string()], None, vec![], None, - self.source_path.clone(), + self.source_path.clone().unwrap(), + line_number, name.clone(), )); @@ -1201,8 +1205,7 @@ impl Compiler { let mut loop_labels = vec![]; for generator in generators { - let first = loop_labels.len() == 0; - if first { + if loop_labels.is_empty() { // Load iterator onto stack (passed as first argument): self.emit(Instruction::LoadName { name: String::from(".0"), @@ -1287,7 +1290,7 @@ impl Compiler { // List comprehension code: self.emit(Instruction::LoadConst { - value: bytecode::Constant::Code { code: code }, + value: bytecode::Constant::Code { code }, }); // List comprehension function name: @@ -1343,6 +1346,10 @@ impl Compiler { self.current_source_location = location.clone(); } + fn get_source_line_number(&mut self) -> usize { + self.current_source_location.get_row() + } + fn mark_generator(&mut self) { self.current_code_object().is_generator = true; } @@ -1357,7 +1364,7 @@ mod tests { use rustpython_parser::parser; fn compile_exec(source: &str) -> CodeObject { let mut compiler = Compiler::new(); - compiler.push_new_code_object(Option::None, "".to_string()); + compiler.push_new_code_object("source_path".to_string(), "".to_string()); let ast = parser::parse_program(&source.to_string()).unwrap(); compiler.compile_program(&ast).unwrap(); compiler.pop_code_object() diff --git a/vm/src/dictdatatype.rs b/vm/src/dictdatatype.rs new file mode 100644 index 000000000..3c4dee22a --- /dev/null +++ b/vm/src/dictdatatype.rs @@ -0,0 +1,230 @@ +use super::obj::objbool; +use super::obj::objint; +use super::pyobject::{IdProtocol, PyObjectRef, PyResult}; +use super::vm::VirtualMachine; +use num_traits::ToPrimitive; +/// Ordered dictionary implementation. +/// Inspired by: https://morepypy.blogspot.com/2015/01/faster-more-memory-efficient-and-more.html +/// And: https://www.youtube.com/watch?v=p33CVV29OG8 +/// And: http://code.activestate.com/recipes/578375/ +use std::collections::HashMap; + +pub struct Dict { + size: usize, + indices: HashMap, + entries: Vec>, +} + +struct DictEntry { + hash: usize, + key: PyObjectRef, + value: PyObjectRef, +} + +impl Dict { + pub fn new() -> Self { + Dict { + size: 0, + indices: HashMap::new(), + entries: Vec::new(), + } + } + + /// Store a key + pub fn insert( + &mut self, + vm: &mut VirtualMachine, + key: &PyObjectRef, + value: PyObjectRef, + ) -> Result<(), PyObjectRef> { + match self.lookup(vm, key)? { + LookupResult::Existing(index) => { + // Update existing key + if let Some(ref mut entry) = self.entries[index] { + entry.value = value; + Ok(()) + } else { + panic!("Lookup returned invalid index into entries!"); + } + } + LookupResult::NewIndex { + hash_index, + hash_value, + } => { + // New key: + let entry = DictEntry { + hash: hash_value, + key: key.clone(), + value, + }; + let index = self.entries.len(); + self.entries.push(Some(entry)); + self.indices.insert(hash_index, index); + self.size += 1; + Ok(()) + } + } + } + + pub fn contains( + &self, + vm: &mut VirtualMachine, + key: &PyObjectRef, + ) -> Result { + if let LookupResult::Existing(_index) = self.lookup(vm, key)? { + Ok(true) + } else { + Ok(false) + } + } + + /// Retrieve a key + pub fn get(&self, vm: &mut VirtualMachine, key: &PyObjectRef) -> PyResult { + if let LookupResult::Existing(index) = self.lookup(vm, key)? { + if let Some(entry) = &self.entries[index] { + Ok(entry.value.clone()) + } else { + panic!("Lookup returned invalid index into entries!"); + } + } else { + let key_repr = vm.to_pystr(key)?; + Err(vm.new_value_error(format!("Key not found: {}", key_repr))) + } + } + + /// Delete a key + pub fn delete( + &mut self, + vm: &mut VirtualMachine, + key: &PyObjectRef, + ) -> Result<(), PyObjectRef> { + if let LookupResult::Existing(index) = self.lookup(vm, key)? { + self.entries[index] = None; + self.size -= 1; + Ok(()) + } else { + let key_repr = vm.to_pystr(key)?; + Err(vm.new_value_error(format!("Key not found: {}", key_repr))) + } + } + + pub fn len(&self) -> usize { + self.size + } + + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + pub fn get_items(&self) -> Vec<(PyObjectRef, PyObjectRef)> { + self.entries + .iter() + .filter(|e| e.is_some()) + .map(|e| e.as_ref().unwrap()) + .map(|e| (e.key.clone(), e.value.clone())) + .collect() + } + + /// Lookup the index for the given key. + fn lookup( + &self, + vm: &mut VirtualMachine, + key: &PyObjectRef, + ) -> Result { + let hash_value = calc_hash(vm, key)?; + let perturb = hash_value; + let mut hash_index: usize = hash_value; + loop { + if self.indices.contains_key(&hash_index) { + // Now we have an index, lets check the key. + let index = self.indices[&hash_index]; + if let Some(entry) = &self.entries[index] { + // Okay, we have an entry at this place + if entry.key.is(key) { + // Literally the same object + break Ok(LookupResult::Existing(index)); + } else if entry.hash == hash_value { + if do_eq(vm, &entry.key, key)? { + break Ok(LookupResult::Existing(index)); + } else { + // entry mismatch. + } + } else { + // entry mismatch. + } + } else { + // Removed entry, continue search... + } + } else { + // Hash not in table, we are at free slot now. + break Ok(LookupResult::NewIndex { + hash_value, + hash_index, + }); + } + + // Update i to next probe location: + hash_index = hash_index + .wrapping_mul(5) + .wrapping_add(perturb) + .wrapping_add(1); + // warn!("Perturb value: {}", i); + } + } +} + +enum LookupResult { + NewIndex { + hash_value: usize, + hash_index: usize, + }, // return not found, index into indices + Existing(usize), // Existing record, index into entries +} + +fn calc_hash(vm: &mut VirtualMachine, key: &PyObjectRef) -> Result { + let hash = vm.call_method(key, "__hash__", vec![])?; + Ok(objint::get_value(&hash).to_usize().unwrap()) +} + +/// Invoke __eq__ on two keys +fn do_eq( + vm: &mut VirtualMachine, + key1: &PyObjectRef, + key2: &PyObjectRef, +) -> Result { + let result = vm._eq(key1, key2.clone())?; + Ok(objbool::get_value(&result)) +} + +#[cfg(test)] +mod tests { + use super::{Dict, VirtualMachine}; + + #[test] + fn test_insert() { + let mut vm = VirtualMachine::new(); + let mut dict = Dict::new(); + assert_eq!(0, dict.len()); + + let key1 = vm.new_bool(true); + let value1 = vm.new_str("abc".to_string()); + dict.insert(&mut vm, &key1, value1.clone()).unwrap(); + assert_eq!(1, dict.len()); + + let key2 = vm.new_str("x".to_string()); + let value2 = vm.new_str("def".to_string()); + dict.insert(&mut vm, &key2, value2.clone()).unwrap(); + assert_eq!(2, dict.len()); + + dict.insert(&mut vm, &key1, value2.clone()).unwrap(); + assert_eq!(2, dict.len()); + + dict.delete(&mut vm, &key1).unwrap(); + assert_eq!(1, dict.len()); + + dict.insert(&mut vm, &key1, value2).unwrap(); + assert_eq!(2, dict.len()); + + assert_eq!(true, dict.contains(&mut vm, &key1).unwrap()); + } +} diff --git a/vm/src/eval.rs b/vm/src/eval.rs index d1facd26e..ef27e63af 100644 --- a/vm/src/eval.rs +++ b/vm/src/eval.rs @@ -4,8 +4,13 @@ use super::compile; use super::pyobject::{PyObjectRef, PyResult}; use super::vm::VirtualMachine; -pub fn eval(vm: &mut VirtualMachine, source: &str, scope: PyObjectRef) -> PyResult { - match compile::compile(vm, source, compile::Mode::Eval, None) { +pub fn eval( + vm: &mut VirtualMachine, + source: &str, + scope: PyObjectRef, + source_path: &str, +) -> PyResult { + match compile::compile(vm, source, &compile::Mode::Eval, source_path.to_string()) { Ok(bytecode) => { debug!("Code object: {:?}", bytecode); vm.run_code_obj(bytecode, scope) @@ -24,7 +29,7 @@ mod tests { let source = String::from("print('Hello world')\n"); let mut vm = VirtualMachine::new(); let vars = vm.context().new_scope(None); - let _result = eval(&mut vm, &source, vars); + let _result = eval(&mut vm, &source, vars, ""); // TODO: check result? //assert_eq!( diff --git a/vm/src/exceptions.rs b/vm/src/exceptions.rs index 72d640521..be3652a73 100644 --- a/vm/src/exceptions.rs +++ b/vm/src/exceptions.rs @@ -81,21 +81,27 @@ fn exception_str(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { #[derive(Debug)] pub struct ExceptionZoo { - pub base_exception_type: PyObjectRef, - pub exception_type: PyObjectRef, - pub syntax_error: PyObjectRef, + pub arithmetic_error: PyObjectRef, pub assertion_error: PyObjectRef, pub attribute_error: PyObjectRef, + pub base_exception_type: PyObjectRef, + pub exception_type: PyObjectRef, + pub file_not_found_error: PyObjectRef, + pub import_error: PyObjectRef, pub index_error: PyObjectRef, pub key_error: PyObjectRef, + pub module_not_found_error: PyObjectRef, pub name_error: PyObjectRef, - pub runtime_error: PyObjectRef, pub not_implemented_error: PyObjectRef, + pub os_error: PyObjectRef, + pub overflow_error: PyObjectRef, + pub permission_error: PyObjectRef, + pub runtime_error: PyObjectRef, pub stop_iteration: PyObjectRef, + pub syntax_error: PyObjectRef, pub type_error: PyObjectRef, pub value_error: PyObjectRef, - pub import_error: PyObjectRef, - pub module_not_found_error: PyObjectRef, + pub zero_division_error: PyObjectRef, } impl ExceptionZoo { @@ -104,69 +110,86 @@ impl ExceptionZoo { object_type: &PyObjectRef, dict_type: &PyObjectRef, ) -> Self { + // Sorted By Hierarchy then alphabetized. let base_exception_type = create_type("BaseException", &type_type, &object_type, &dict_type); let exception_type = create_type("Exception", &type_type, &base_exception_type, &dict_type); - let syntax_error = create_type("SyntaxError", &type_type, &exception_type, &dict_type); + + let arithmetic_error = + create_type("ArithmeticError", &type_type, &exception_type, &dict_type); let assertion_error = create_type("AssertionError", &type_type, &exception_type, &dict_type); - let attribute_error = create_type( - "AttributeError", - &type_type, - &exception_type.clone(), - &dict_type, - ); - let index_error = create_type( - "IndexError", - &type_type, - &exception_type.clone(), - &dict_type, - ); - let key_error = create_type("KeyError", &type_type, &exception_type.clone(), &dict_type); - let name_error = create_type("NameError", &type_type, &exception_type.clone(), &dict_type); + let attribute_error = + create_type("AttributeError", &type_type, &exception_type, &dict_type); + let import_error = create_type("ImportError", &type_type, &exception_type, &dict_type); + let index_error = create_type("IndexError", &type_type, &exception_type, &dict_type); + let key_error = create_type("KeyError", &type_type, &exception_type, &dict_type); + let name_error = create_type("NameError", &type_type, &exception_type, &dict_type); + let os_error = create_type("OSError", &type_type, &exception_type, &dict_type); let runtime_error = create_type("RuntimeError", &type_type, &exception_type, &dict_type); + let stop_iteration = create_type("StopIteration", &type_type, &exception_type, &dict_type); + let syntax_error = create_type("SyntaxError", &type_type, &exception_type, &dict_type); + let type_error = create_type("TypeError", &type_type, &exception_type, &dict_type); + let value_error = create_type("ValueError", &type_type, &exception_type, &dict_type); + + let overflow_error = + create_type("OverflowError", &type_type, &arithmetic_error, &dict_type); + let zero_division_error = create_type( + "ZeroDivisionError", + &type_type, + &arithmetic_error, + &dict_type, + ); + + let module_not_found_error = + create_type("ModuleNotFoundError", &type_type, &import_error, &dict_type); + let not_implemented_error = create_type( "NotImplementedError", &type_type, &runtime_error, &dict_type, ); - let stop_iteration = create_type("StopIteration", &type_type, &exception_type, &dict_type); - let type_error = create_type("TypeError", &type_type, &exception_type, &dict_type); - let value_error = create_type("ValueError", &type_type, &exception_type, &dict_type); - let import_error = create_type("ImportError", &type_type, &exception_type, &dict_type); - let module_not_found_error = - create_type("ModuleNotFoundError", &type_type, &import_error, &dict_type); + + let file_not_found_error = + create_type("FileNotFoundError", &type_type, &os_error, &dict_type); + let permission_error = create_type("PermissionError", &type_type, &os_error, &dict_type); ExceptionZoo { - base_exception_type: base_exception_type, - exception_type: exception_type, - syntax_error: syntax_error, - assertion_error: assertion_error, - attribute_error: attribute_error, - index_error: index_error, - key_error: key_error, - name_error: name_error, - runtime_error: runtime_error, - not_implemented_error: not_implemented_error, - stop_iteration: stop_iteration, - type_error: type_error, - value_error: value_error, - import_error: import_error, - module_not_found_error: module_not_found_error, + arithmetic_error, + assertion_error, + attribute_error, + base_exception_type, + exception_type, + file_not_found_error, + import_error, + index_error, + key_error, + module_not_found_error, + name_error, + not_implemented_error, + os_error, + overflow_error, + permission_error, + runtime_error, + stop_iteration, + syntax_error, + type_error, + value_error, + zero_division_error, } } } pub fn init(context: &PyContext) { - let ref base_exception_type = context.exceptions.base_exception_type; + let base_exception_type = &context.exceptions.base_exception_type; context.set_attr( &base_exception_type, "__init__", context.new_rustfunc(exception_init), ); - let ref exception_type = context.exceptions.exception_type; + let exception_type = &context.exceptions.exception_type; context.set_attr( &exception_type, "__str__", diff --git a/vm/src/format.rs b/vm/src/format.rs index 37f8bfa57..6dfa77c7e 100644 --- a/vm/src/format.rs +++ b/vm/src/format.rs @@ -87,7 +87,7 @@ fn parse_align(text: &str) -> (Option, &str) { fn parse_fill_and_align(text: &str) -> (Option, Option, &str) { let char_indices: Vec<(usize, char)> = text.char_indices().take(3).collect(); - if char_indices.len() == 0 { + if char_indices.is_empty() { (None, None, text) } else if char_indices.len() == 1 { let (maybe_align, remaining) = parse_align(text); @@ -314,7 +314,7 @@ impl FormatSpec { } None => Ok(magnitude.to_str_radix(10)), }; - if !raw_magnitude_string_result.is_ok() { + if raw_magnitude_string_result.is_err() { return raw_magnitude_string_result; } let magnitude_string = format!( @@ -438,14 +438,14 @@ impl FormatString { fn parse_literal(text: &str) -> Result<(FormatPart, &str), FormatParseError> { let mut cur_text = text; let mut result_string = String::new(); - while cur_text.len() > 0 { + while !cur_text.is_empty() { match FormatString::parse_literal_single(cur_text) { Ok((next_char, remaining)) => { result_string.push(next_char); cur_text = remaining; } Err(err) => { - if result_string.len() > 0 { + if !result_string.is_empty() { return Ok((FormatPart::Literal(result_string.to_string()), cur_text)); } else { return Err(err); @@ -467,7 +467,7 @@ impl FormatString { String::new() }; - if arg_part.len() == 0 { + if arg_part.is_empty() { return Ok(FormatPart::AutoSpec(format_spec)); } @@ -500,7 +500,7 @@ impl FormatString { pub fn from_str(text: &str) -> Result { let mut cur_text: &str = text; let mut parts: Vec = Vec::new(); - while cur_text.len() > 0 { + while !cur_text.is_empty() { // Try to parse both literals and bracketed format parts util we // run out of text cur_text = FormatString::parse_literal(cur_text) diff --git a/vm/src/frame.rs b/vm/src/frame.rs index eed72c796..5353846e0 100644 --- a/vm/src/frame.rs +++ b/vm/src/frame.rs @@ -20,8 +20,7 @@ use super::pyobject::{ PyResult, TypeProtocol, }; use super::vm::VirtualMachine; -use num_bigint::ToBigInt; -use num_traits::ToPrimitive; +use num_bigint::BigInt; #[derive(Clone, Debug)] enum Block { @@ -71,12 +70,12 @@ impl Frame { // locals.extend(callargs); Frame { - code: objcode::copy_code(&code), + code: objcode::get_value(&code), stack: vec![], blocks: vec![], // save the callargs as locals // globals: locals.clone(), - locals: locals, + locals, lasti: 0, } } @@ -89,11 +88,7 @@ impl Frame { } pub fn run_frame(&mut self, vm: &mut VirtualMachine) -> Result { - let filename = if let Some(source_path) = &self.code.source_path { - source_path.to_string() - } else { - "".to_string() - }; + let filename = &self.code.source_path.to_string(); let prev_frame = mem::replace(&mut vm.current_frame, Some(vm.ctx.new_frame(self.clone()))); @@ -121,7 +116,7 @@ impl Frame { trace!("Adding to traceback: {:?} {:?}", traceback, lineno); let pos = vm.ctx.new_tuple(vec![ vm.ctx.new_str(filename.clone()), - vm.ctx.new_int(lineno.get_row().to_bigint().unwrap()), + vm.ctx.new_int(lineno.get_row()), vm.ctx.new_str(run_obj_name.clone()), ]); objlist::list_append( @@ -263,22 +258,22 @@ impl Frame { assert!(*size == 2 || *size == 3); let elements = self.pop_multiple(*size); - let mut out: Vec> = elements + let mut out: Vec> = elements .into_iter() .map(|x| match x.borrow().payload { - PyObjectPayload::Integer { ref value } => Some(value.to_i32().unwrap()), + PyObjectPayload::Integer { ref value } => Some(value.clone()), PyObjectPayload::None => None, _ => panic!("Expect Int or None as BUILD_SLICE arguments, got {:?}", x), }) .collect(); - let start = out[0]; - let stop = out[1]; - let step = if out.len() == 3 { out[2] } else { None }; + let start = out[0].take(); + let stop = out[1].take(); + let step = if out.len() == 3 { out[2].take() } else { None }; let obj = PyObject::new( PyObjectPayload::Slice { start, stop, step }, - vm.ctx.type_type(), + vm.ctx.slice_type(), ); self.push_value(obj); Ok(None) @@ -440,7 +435,7 @@ impl Frame { bytecode::CallType::Positional(count) => { let args: Vec = self.pop_multiple(*count); PyFuncArgs { - args: args, + args, kwargs: vec![], } } @@ -504,7 +499,7 @@ impl Frame { let exception = match argc { 1 => self.pop_value(), 0 | 2 | 3 => panic!("Not implemented!"), - _ => panic!("Invalid paramter for RAISE_VARARGS, must be between 0 to 3"), + _ => panic!("Invalid parameter for RAISE_VARARGS, must be between 0 to 3"), }; if objtype::isinstance(&exception, &vm.ctx.exceptions.base_exception_type) { info!("Exception raised: {:?}", exception); @@ -522,7 +517,7 @@ impl Frame { bytecode::Instruction::Break => { let block = self.unwind_loop(vm); - if let Block::Loop { start: _, end } = block { + if let Block::Loop { end, .. } = block { self.jump(end); } Ok(None) @@ -533,7 +528,7 @@ impl Frame { } bytecode::Instruction::Continue => { let block = self.unwind_loop(vm); - if let Block::Loop { start, end: _ } = block { + if let Block::Loop { start, .. } = block { self.jump(start); } else { assert!(false); @@ -611,7 +606,7 @@ impl Frame { .iter() .skip(*before) .take(middle) - .map(|x| x.clone()) + .cloned() .collect(); let t = vm.ctx.new_list(middle_elements); self.push_value(t); @@ -662,13 +657,10 @@ impl Frame { module: &str, symbol: &Option, ) -> FrameResult { - let current_path = match &self.code.source_path { - Some(source_path) => { - let mut source_pathbuf = PathBuf::from(source_path); - source_pathbuf.pop(); - source_pathbuf - } - None => PathBuf::from("."), + let current_path = { + let mut source_pathbuf = PathBuf::from(&self.code.source_path); + source_pathbuf.pop(); + source_pathbuf }; let obj = import(vm, current_path, module, symbol)?; @@ -679,13 +671,10 @@ impl Frame { } fn import_star(&mut self, vm: &mut VirtualMachine, module: &str) -> FrameResult { - let current_path = match &self.code.source_path { - Some(source_path) => { - let mut source_pathbuf = PathBuf::from(source_path); - source_pathbuf.pop(); - source_pathbuf - } - None => PathBuf::from("."), + let current_path = { + let mut source_pathbuf = PathBuf::from(&self.code.source_path); + source_pathbuf.pop(); + source_pathbuf }; // Grab all the names from the module and put them in the context @@ -708,8 +697,7 @@ impl Frame { // TODO: execute finally handler } Some(Block::With { - end: _, - context_manager, + context_manager, .. }) => { match self.with_exit(vm, &context_manager, None) { Ok(..) => {} @@ -728,13 +716,12 @@ impl Frame { loop { let block = self.pop_block(); match block { - Some(Block::Loop { start: _, end: __ }) => break block.unwrap(), + Some(Block::Loop { .. }) => break block.unwrap(), Some(Block::TryExcept { .. }) => { // TODO: execute finally handler } Some(Block::With { - end: _, - context_manager, + context_manager, .. }) => match self.with_exit(vm, &context_manager, None) { Ok(..) => {} Err(exc) => { @@ -889,25 +876,25 @@ impl Frame { ) -> FrameResult { let b_ref = self.pop_value(); let a_ref = self.pop_value(); - let value = match op { - &bytecode::BinaryOperator::Subtract => vm._sub(a_ref, b_ref), - &bytecode::BinaryOperator::Add => vm._add(a_ref, b_ref), - &bytecode::BinaryOperator::Multiply => vm._mul(a_ref, b_ref), - &bytecode::BinaryOperator::MatrixMultiply => { + let value = match *op { + bytecode::BinaryOperator::Subtract => vm._sub(a_ref, b_ref), + bytecode::BinaryOperator::Add => vm._add(a_ref, b_ref), + bytecode::BinaryOperator::Multiply => vm._mul(a_ref, b_ref), + bytecode::BinaryOperator::MatrixMultiply => { vm.call_method(&a_ref, "__matmul__", vec![b_ref]) } - &bytecode::BinaryOperator::Power => vm._pow(a_ref, b_ref), - &bytecode::BinaryOperator::Divide => vm._div(a_ref, b_ref), - &bytecode::BinaryOperator::FloorDivide => { + bytecode::BinaryOperator::Power => vm._pow(a_ref, b_ref), + bytecode::BinaryOperator::Divide => vm._div(a_ref, b_ref), + bytecode::BinaryOperator::FloorDivide => { vm.call_method(&a_ref, "__floordiv__", vec![b_ref]) } - &bytecode::BinaryOperator::Subscript => self.subscript(vm, a_ref, b_ref), - &bytecode::BinaryOperator::Modulo => vm._modulo(a_ref, b_ref), - &bytecode::BinaryOperator::Lshift => vm.call_method(&a_ref, "__lshift__", vec![b_ref]), - &bytecode::BinaryOperator::Rshift => vm.call_method(&a_ref, "__rshift__", vec![b_ref]), - &bytecode::BinaryOperator::Xor => vm._xor(a_ref, b_ref), - &bytecode::BinaryOperator::Or => vm._or(a_ref, b_ref), - &bytecode::BinaryOperator::And => vm._and(a_ref, b_ref), + bytecode::BinaryOperator::Subscript => self.subscript(vm, a_ref, b_ref), + bytecode::BinaryOperator::Modulo => vm._modulo(a_ref, b_ref), + bytecode::BinaryOperator::Lshift => vm.call_method(&a_ref, "__lshift__", vec![b_ref]), + bytecode::BinaryOperator::Rshift => vm.call_method(&a_ref, "__rshift__", vec![b_ref]), + bytecode::BinaryOperator::Xor => vm._xor(a_ref, b_ref), + bytecode::BinaryOperator::Or => vm._or(a_ref, b_ref), + bytecode::BinaryOperator::And => vm._and(a_ref, b_ref), }?; self.push_value(value); @@ -920,11 +907,11 @@ impl Frame { op: &bytecode::UnaryOperator, ) -> FrameResult { let a = self.pop_value(); - let value = match op { - &bytecode::UnaryOperator::Minus => vm.call_method(&a, "__neg__", vec![])?, - &bytecode::UnaryOperator::Plus => vm.call_method(&a, "__pos__", vec![])?, - &bytecode::UnaryOperator::Invert => vm.call_method(&a, "__invert__", vec![])?, - &bytecode::UnaryOperator::Not => { + let value = match *op { + bytecode::UnaryOperator::Minus => vm.call_method(&a, "__neg__", vec![])?, + bytecode::UnaryOperator::Plus => vm.call_method(&a, "__pos__", vec![])?, + bytecode::UnaryOperator::Invert => vm.call_method(&a, "__invert__", vec![])?, + bytecode::UnaryOperator::Not => { let value = objbool::boolval(vm, a)?; vm.ctx.new_bool(!value) } @@ -997,17 +984,17 @@ impl Frame { ) -> FrameResult { let b = self.pop_value(); let a = self.pop_value(); - let value = match op { - &bytecode::ComparisonOperator::Equal => vm._eq(&a, b)?, - &bytecode::ComparisonOperator::NotEqual => vm._ne(&a, b)?, - &bytecode::ComparisonOperator::Less => vm._lt(&a, b)?, - &bytecode::ComparisonOperator::LessOrEqual => vm._le(&a, b)?, - &bytecode::ComparisonOperator::Greater => vm._gt(&a, b)?, - &bytecode::ComparisonOperator::GreaterOrEqual => vm._ge(&a, b)?, - &bytecode::ComparisonOperator::Is => vm.ctx.new_bool(self._is(a, b)), - &bytecode::ComparisonOperator::IsNot => self._is_not(vm, a, b)?, - &bytecode::ComparisonOperator::In => self._in(vm, a, b)?, - &bytecode::ComparisonOperator::NotIn => self._not_in(vm, a, b)?, + let value = match *op { + bytecode::ComparisonOperator::Equal => vm._eq(&a, b)?, + bytecode::ComparisonOperator::NotEqual => vm._ne(&a, b)?, + bytecode::ComparisonOperator::Less => vm._lt(&a, b)?, + bytecode::ComparisonOperator::LessOrEqual => vm._le(&a, b)?, + bytecode::ComparisonOperator::Greater => vm._gt(&a, b)?, + bytecode::ComparisonOperator::GreaterOrEqual => vm._ge(&a, b)?, + bytecode::ComparisonOperator::Is => vm.ctx.new_bool(self._is(a, b)), + bytecode::ComparisonOperator::IsNot => self._is_not(vm, a, b)?, + bytecode::ComparisonOperator::In => self._in(vm, a, b)?, + bytecode::ComparisonOperator::NotIn => self._not_in(vm, a, b)?, }; self.push_value(value); @@ -1038,15 +1025,13 @@ impl Frame { fn unwrap_constant(&self, vm: &VirtualMachine, value: &bytecode::Constant) -> PyObjectRef { match *value { - bytecode::Constant::Integer { ref value } => vm.ctx.new_int(value.to_bigint().unwrap()), + bytecode::Constant::Integer { ref value } => vm.ctx.new_int(value.clone()), bytecode::Constant::Float { ref value } => vm.ctx.new_float(*value), bytecode::Constant::Complex { ref value } => vm.ctx.new_complex(*value), bytecode::Constant::String { ref value } => vm.new_str(value.clone()), bytecode::Constant::Bytes { ref value } => vm.ctx.new_bytes(value.clone()), bytecode::Constant::Boolean { ref value } => vm.new_bool(value.clone()), - bytecode::Constant::Code { ref code } => { - PyObject::new(PyObjectPayload::Code { code: code.clone() }, vm.get_type()) - } + bytecode::Constant::Code { ref code } => vm.ctx.new_code_object(code.clone()), bytecode::Constant::Tuple { ref elements } => vm.ctx.new_tuple( elements .iter() diff --git a/vm/src/import.rs b/vm/src/import.rs index 0edd4626b..d7393bd67 100644 --- a/vm/src/import.rs +++ b/vm/src/import.rs @@ -34,8 +34,8 @@ fn import_uncached_module( let code_obj = compile::compile( vm, &source, - compile::Mode::Exec, - Some(filepath.to_str().unwrap().to_string()), + &compile::Mode::Exec, + filepath.to_str().unwrap().to_string(), )?; // trace!("Code object: {:?}", code_obj); @@ -71,11 +71,17 @@ pub fn import( let module = import_module(vm, current_path, module_name)?; // If we're importing a symbol, look it up and use it, otherwise construct a module and return // that - let obj = match symbol { - Some(symbol) => module.get_item(symbol).unwrap(), - None => module, - }; - Ok(obj) + if let Some(symbol) = symbol { + module.get_item(symbol).map_or_else( + || { + let import_error = vm.context().exceptions.import_error.clone(); + Err(vm.new_exception(import_error, format!("cannot import name '{}'", symbol))) + }, + |obj| Ok(obj), + ) + } else { + Ok(module) + } } fn find_source(vm: &VirtualMachine, current_path: PathBuf, name: &str) -> Result { @@ -97,7 +103,7 @@ fn find_source(vm: &VirtualMachine, current_path: PathBuf, name: &str) -> Result } } - match filepaths.iter().filter(|p| p.exists()).next() { + match filepaths.iter().find(|p| p.exists()) { Some(path) => Ok(path.to_path_buf()), None => Err(format!("No module named '{}'", name)), } diff --git a/vm/src/lib.rs b/vm/src/lib.rs index 7c1df250e..107ac3896 100644 --- a/vm/src/lib.rs +++ b/vm/src/lib.rs @@ -12,6 +12,7 @@ extern crate log; // extern crate env_logger; extern crate num_bigint; extern crate num_complex; +extern crate num_integer; extern crate num_traits; extern crate serde; extern crate serde_json; diff --git a/vm/src/obj/mod.rs b/vm/src/obj/mod.rs index 16670fa5d..f08998fcb 100644 --- a/vm/src/obj/mod.rs +++ b/vm/src/obj/mod.rs @@ -6,6 +6,8 @@ pub mod objbytes; pub mod objcode; pub mod objcomplex; pub mod objdict; +pub mod objenumerate; +pub mod objfilter; pub mod objfloat; pub mod objframe; pub mod objfunction; @@ -13,12 +15,16 @@ pub mod objgenerator; pub mod objint; pub mod objiter; pub mod objlist; +pub mod objmap; pub mod objmemory; pub mod objobject; pub mod objproperty; +pub mod objrange; pub mod objsequence; pub mod objset; +pub mod objslice; pub mod objstr; pub mod objsuper; pub mod objtuple; pub mod objtype; +pub mod objzip; diff --git a/vm/src/obj/objbool.rs b/vm/src/obj/objbool.rs index 85814be1d..dbec65a7c 100644 --- a/vm/src/obj/objbool.rs +++ b/vm/src/obj/objbool.rs @@ -20,6 +20,7 @@ pub fn boolval(vm: &mut VirtualMachine, obj: PyObjectRef) -> Result !value.is_zero(), _ => return Err(vm.new_type_error(String::from("TypeError"))), }; + v } else { true @@ -30,9 +31,16 @@ pub fn boolval(vm: &mut VirtualMachine, obj: PyObjectRef) -> Result PyResult { @@ -74,7 +82,7 @@ fn bool_new(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { Ok(match val { Some(val) => { let bv = boolval(vm, val.clone())?; - vm.new_bool(bv.clone()) + vm.new_bool(bv) } None => vm.context().new_bool(false), }) diff --git a/vm/src/obj/objbytearray.rs b/vm/src/obj/objbytearray.rs index 0f7e8d8da..b4afaa639 100644 --- a/vm/src/obj/objbytearray.rs +++ b/vm/src/obj/objbytearray.rs @@ -9,14 +9,27 @@ use super::objint; use super::super::vm::VirtualMachine; use super::objbytes::get_value; use super::objtype; -use num_bigint::ToBigInt; use num_traits::ToPrimitive; // Binary data support /// Fill bytearray class methods dictionary. pub fn init(context: &PyContext) { - let ref bytearray_type = context.bytearray_type; + let bytearray_type = &context.bytearray_type; + + let bytearray_doc = + "bytearray(iterable_of_ints) -> bytearray\n\ + bytearray(string, encoding[, errors]) -> bytearray\n\ + bytearray(bytes_or_buffer) -> mutable copy of bytes_or_buffer\n\ + bytearray(int) -> bytes array of size given by the parameter initialized with null bytes\n\ + bytearray() -> empty bytes array\n\n\ + Construct a mutable bytearray object from:\n \ + - an iterable yielding integers in range(256)\n \ + - a text string encoded using the specified encoding\n \ + - a bytes or a buffer object\n \ + - any object implementing the buffer API.\n \ + - an integer"; + context.set_attr( &bytearray_type, "__eq__", @@ -37,6 +50,51 @@ pub fn init(context: &PyContext) { "__len__", context.new_rustfunc(bytesarray_len), ); + context.set_attr( + &bytearray_type, + "__doc__", + context.new_str(bytearray_doc.to_string()), + ); + context.set_attr( + &bytearray_type, + "isalnum", + context.new_rustfunc(bytearray_isalnum), + ); + context.set_attr( + &bytearray_type, + "isalpha", + context.new_rustfunc(bytearray_isalpha), + ); + context.set_attr( + &bytearray_type, + "isascii", + context.new_rustfunc(bytearray_isascii), + ); + context.set_attr( + &bytearray_type, + "isdigit", + context.new_rustfunc(bytearray_isdigit), + ); + context.set_attr( + &bytearray_type, + "islower", + context.new_rustfunc(bytearray_islower), + ); + context.set_attr( + &bytearray_type, + "isspace", + context.new_rustfunc(bytearray_isspace), + ); + context.set_attr( + &bytearray_type, + "isupper", + context.new_rustfunc(bytearray_isupper), + ); + context.set_attr( + &bytearray_type, + "istitle", + context.new_rustfunc(bytearray_istitle), + ); } fn bytearray_new(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { @@ -63,18 +121,14 @@ fn bytearray_new(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { } else { vec![] }; - Ok(PyObject::new( - PyObjectPayload::Bytes { value: value }, - cls.clone(), - )) + Ok(PyObject::new(PyObjectPayload::Bytes { value }, cls.clone())) } fn bytesarray_len(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(a, Some(vm.ctx.bytearray_type()))]); let byte_vec = get_value(a).to_vec(); - let value = byte_vec.len().to_bigint(); - Ok(vm.ctx.new_int(value.unwrap())) + Ok(vm.ctx.new_int(byte_vec.len())) } fn bytearray_eq(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { @@ -92,6 +146,100 @@ fn bytearray_eq(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { Ok(vm.ctx.new_bool(result)) } +fn bytearray_isalnum(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!(vm, args, required = [(zelf, Some(vm.ctx.bytearray_type()))]); + let bytes = get_value(zelf); + Ok(vm.new_bool(!bytes.is_empty() && bytes.iter().all(|x| char::from(*x).is_alphanumeric()))) +} + +fn bytearray_isalpha(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!(vm, args, required = [(zelf, Some(vm.ctx.bytearray_type()))]); + let bytes = get_value(zelf); + Ok(vm.new_bool(!bytes.is_empty() && bytes.iter().all(|x| char::from(*x).is_alphabetic()))) +} + +fn bytearray_isascii(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!(vm, args, required = [(zelf, Some(vm.ctx.bytearray_type()))]); + let bytes = get_value(zelf); + Ok(vm.new_bool(!bytes.is_empty() && bytes.iter().all(|x| char::from(*x).is_ascii()))) +} + +fn bytearray_isdigit(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!(vm, args, required = [(zelf, Some(vm.ctx.bytearray_type()))]); + let bytes = get_value(zelf); + Ok(vm.new_bool(!bytes.is_empty() && bytes.iter().all(|x| char::from(*x).is_digit(10)))) +} + +fn bytearray_islower(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!(vm, args, required = [(zelf, Some(vm.ctx.bytearray_type()))]); + let bytes = get_value(zelf); + Ok(vm.new_bool( + !bytes.is_empty() + && bytes + .iter() + .filter(|x| char::from(**x).is_whitespace()) + .all(|x| char::from(*x).is_lowercase()), + )) +} + +fn bytearray_isspace(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!(vm, args, required = [(zelf, Some(vm.ctx.bytearray_type()))]); + let bytes = get_value(zelf); + Ok(vm.new_bool(!bytes.is_empty() && bytes.iter().all(|x| char::from(*x).is_whitespace()))) +} + +fn bytearray_isupper(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!(vm, args, required = [(zelf, Some(vm.ctx.bytearray_type()))]); + let bytes = get_value(zelf); + Ok(vm.new_bool( + !bytes.is_empty() + && bytes + .iter() + .filter(|x| !char::from(**x).is_whitespace()) + .all(|x| char::from(*x).is_uppercase()), + )) +} + +fn bytearray_istitle(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!(vm, args, required = [(zelf, Some(vm.ctx.bytearray_type()))]); + let bytes = get_value(zelf); + + if bytes.is_empty() { + Ok(vm.new_bool(false)) + } else { + let mut iter = bytes.iter().peekable(); + let mut prev_cased = false; + + while let Some(c) = iter.next() { + let current = char::from(*c); + let next = if let Some(k) = iter.peek() { + char::from(**k) + } else { + if current.is_uppercase() { + return Ok(vm.new_bool(!prev_cased)); + } else { + return Ok(vm.new_bool(prev_cased)); + } + }; + + if (is_cased(current) && next.is_uppercase() && !prev_cased) + || (!is_cased(current) && next.is_lowercase()) + { + return Ok(vm.new_bool(false)); + } + + prev_cased = is_cased(current); + } + + Ok(vm.new_bool(true)) + } +} + +// helper function for istitle +fn is_cased(c: char) -> bool { + c.to_uppercase().next().unwrap() != c || c.to_lowercase().next().unwrap() != c +} + /* fn bytearray_getitem(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!( diff --git a/vm/src/obj/objbytes.rs b/vm/src/obj/objbytes.rs index 55ca04c9b..1909247cb 100644 --- a/vm/src/obj/objbytes.rs +++ b/vm/src/obj/objbytes.rs @@ -4,7 +4,6 @@ use super::super::pyobject::{ use super::super::vm::VirtualMachine; use super::objint; use super::objtype; -use num_bigint::ToBigInt; use num_traits::ToPrimitive; use std::cell::Ref; use std::hash::{Hash, Hasher}; @@ -14,12 +13,30 @@ use std::ops::Deref; // Fill bytes class methods: pub fn init(context: &PyContext) { - let ref bytes_type = context.bytes_type; + let bytes_type = &context.bytes_type; + + let bytes_doc = + "bytes(iterable_of_ints) -> bytes\n\ + bytes(string, encoding[, errors]) -> bytes\n\ + bytes(bytes_or_buffer) -> immutable copy of bytes_or_buffer\n\ + bytes(int) -> bytes object of size given by the parameter initialized with null bytes\n\ + bytes() -> empty bytes object\n\nConstruct an immutable array of bytes from:\n \ + - an iterable yielding integers in range(256)\n \ + - a text string encoded using the specified encoding\n \ + - any object implementing the buffer API.\n \ + - an integer"; + context.set_attr(bytes_type, "__eq__", context.new_rustfunc(bytes_eq)); context.set_attr(bytes_type, "__hash__", context.new_rustfunc(bytes_hash)); context.set_attr(bytes_type, "__new__", context.new_rustfunc(bytes_new)); context.set_attr(bytes_type, "__repr__", context.new_rustfunc(bytes_repr)); context.set_attr(bytes_type, "__len__", context.new_rustfunc(bytes_len)); + context.set_attr(bytes_type, "__iter__", context.new_rustfunc(bytes_iter)); + context.set_attr( + bytes_type, + "__doc__", + context.new_str(bytes_doc.to_string()), + ); } fn bytes_new(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { @@ -47,10 +64,7 @@ fn bytes_new(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { vec![] }; - Ok(PyObject::new( - PyObjectPayload::Bytes { value: value }, - cls.clone(), - )) + Ok(PyObject::new(PyObjectPayload::Bytes { value }, cls.clone())) } fn bytes_eq(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { @@ -72,8 +86,7 @@ fn bytes_len(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(a, Some(vm.ctx.bytes_type()))]); let byte_vec = get_value(a).to_vec(); - let value = byte_vec.len().to_bigint(); - Ok(vm.ctx.new_int(value.unwrap())) + Ok(vm.ctx.new_int(byte_vec.len())) } fn bytes_hash(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { @@ -82,7 +95,7 @@ fn bytes_hash(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { let mut hasher = std::collections::hash_map::DefaultHasher::new(); data.hash(&mut hasher); let hash = hasher.finish(); - Ok(vm.ctx.new_int(hash.to_bigint().unwrap())) + Ok(vm.ctx.new_int(hash)) } pub fn get_value<'a>(obj: &'a PyObjectRef) -> impl Deref> + 'a { @@ -101,3 +114,17 @@ fn bytes_repr(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { let data = String::from_utf8(value.to_vec()).unwrap(); Ok(vm.new_str(format!("b'{}'", data))) } + +fn bytes_iter(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!(vm, args, required = [(obj, Some(vm.ctx.bytes_type()))]); + + let iter_obj = PyObject::new( + PyObjectPayload::Iterator { + position: 0, + iterated_obj: obj.clone(), + }, + vm.ctx.iter_type(), + ); + + Ok(iter_obj) +} diff --git a/vm/src/obj/objcode.rs b/vm/src/obj/objcode.rs index 757e1aca3..2649c1190 100644 --- a/vm/src/obj/objcode.rs +++ b/vm/src/obj/objcode.rs @@ -4,47 +4,94 @@ use super::super::bytecode; use super::super::pyobject::{ - PyContext, PyFuncArgs, PyObjectPayload, PyObjectRef, PyResult, TypeProtocol, + IdProtocol, PyContext, PyFuncArgs, PyObjectPayload, PyObjectRef, PyResult, TypeProtocol, }; use super::super::vm::VirtualMachine; use super::objtype; pub fn init(context: &PyContext) { - let ref code_type = context.code_type; + let code_type = &context.code_type; context.set_attr(code_type, "__new__", context.new_rustfunc(code_new)); context.set_attr(code_type, "__repr__", context.new_rustfunc(code_repr)); + + for (name, f) in vec![ + ( + "co_argcount", + code_co_argcount as fn(&mut VirtualMachine, PyFuncArgs) -> PyResult, + ), + ("co_filename", code_co_filename), + ("co_firstlineno", code_co_firstlineno), + ("co_kwonlyargcount", code_co_kwonlyargcount), + ("co_name", code_co_name), + ] { + context.set_attr(code_type, name, context.new_member_descriptor(f)) + } } -/// Extract rust bytecode object from a python code object. -pub fn copy_code(code_obj: &PyObjectRef) -> bytecode::CodeObject { - let code_obj = code_obj.borrow(); - if let PyObjectPayload::Code { ref code } = code_obj.payload { +pub fn get_value(obj: &PyObjectRef) -> bytecode::CodeObject { + if let PyObjectPayload::Code { code } = &obj.borrow().payload { code.clone() } else { - panic!("Must be code obj"); + panic!("Inner error getting code {:?}", obj) } } fn code_new(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(_cls, None)]); - Err(vm.new_type_error(format!("Cannot directly create code object"))) + Err(vm.new_type_error("Cannot directly create code object".to_string())) } fn code_repr(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(o, Some(vm.ctx.code_type()))]); - // Fetch actual code: - let code = copy_code(o); - - let file = if let Some(source_path) = code.source_path { - format!(", file {}", source_path) - } else { - String::new() - }; - - // TODO: fetch proper line info from code object - let line = format!(", line 1"); - - let repr = format!("", file, line); + let code = get_value(o); + let repr = format!( + "", + code.obj_name, + o.get_id(), + code.source_path, + code.first_line_number + ); Ok(vm.new_str(repr)) } + +fn member_code_obj( + vm: &mut VirtualMachine, + args: PyFuncArgs, +) -> Result { + arg_check!( + vm, + args, + required = [ + (zelf, Some(vm.ctx.code_type())), + (_cls, Some(vm.ctx.type_type())) + ] + ); + Ok(get_value(zelf)) +} + +fn code_co_argcount(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { + let code_obj = member_code_obj(vm, args)?; + Ok(vm.ctx.new_int(code_obj.arg_names.len())) +} + +fn code_co_filename(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { + let code_obj = member_code_obj(vm, args)?; + let source_path = code_obj.source_path; + Ok(vm.new_str(source_path)) +} + +fn code_co_firstlineno(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { + let code_obj = member_code_obj(vm, args)?; + Ok(vm.ctx.new_int(code_obj.first_line_number)) +} + +fn code_co_kwonlyargcount(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { + let code_obj = member_code_obj(vm, args)?; + Ok(vm.ctx.new_int(code_obj.kwonlyarg_names.len())) +} + +fn code_co_name(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { + let code_obj = member_code_obj(vm, args)?; + Ok(vm.new_str(code_obj.obj_name)) +} diff --git a/vm/src/obj/objcomplex.rs b/vm/src/obj/objcomplex.rs index aa6d53e4a..353b89b7f 100644 --- a/vm/src/obj/objcomplex.rs +++ b/vm/src/obj/objcomplex.rs @@ -7,9 +7,19 @@ use super::objtype; use num_complex::Complex64; pub fn init(context: &PyContext) { - let ref complex_type = context.complex_type; + let complex_type = &context.complex_type; + + let complex_doc = + "Create a complex number from a real part and an optional imaginary part.\n\n\ + This is equivalent to (real + imag*1j) where imag defaults to 0."; + context.set_attr(&complex_type, "__add__", context.new_rustfunc(complex_add)); context.set_attr(&complex_type, "__new__", context.new_rustfunc(complex_new)); + context.set_attr( + &complex_type, + "__doc__", + context.new_str(complex_doc.to_string()), + ); context.set_attr( &complex_type, "__repr__", @@ -85,5 +95,10 @@ fn complex_conjugate(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { fn complex_repr(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(obj, Some(vm.ctx.complex_type()))]); let v = get_value(obj); - Ok(vm.new_str(v.to_string())) + let repr = if v.re == 0. { + format!("{}j", v.im) + } else { + format!("({}+{}j)", v.re, v.im) + }; + Ok(vm.new_str(repr)) } diff --git a/vm/src/obj/objdict.rs b/vm/src/obj/objdict.rs index 02f98fa44..8a3e77ff8 100644 --- a/vm/src/obj/objdict.rs +++ b/vm/src/obj/objdict.rs @@ -5,8 +5,7 @@ use super::super::vm::VirtualMachine; use super::objiter; use super::objstr; use super::objtype; -use num_bigint::ToBigInt; -use std::cell::{Ref, RefMut}; +use std::cell::{Ref, RefCell, RefMut}; use std::collections::HashMap; use std::ops::{Deref, DerefMut}; @@ -106,10 +105,7 @@ pub fn contains_key_str(dict: &PyObjectRef, key: &str) -> bool { pub fn content_contains_key_str(elements: &DictContentType, key: &str) -> bool { // TODO: let hash: usize = key; - match elements.get(key) { - Some(_) => true, - None => false, - } + elements.get(key).is_some() } // Python dict methods: @@ -140,7 +136,7 @@ fn dict_new(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { let elem_iter = objiter::get_iter(vm, &element)?; let needle = objiter::get_next_object(vm, &elem_iter)?.ok_or_else(|| err(vm))?; let value = objiter::get_next_object(vm, &elem_iter)?.ok_or_else(|| err(vm))?; - if let Some(_) = objiter::get_next_object(vm, &elem_iter)? { + if objiter::get_next_object(vm, &elem_iter)?.is_some() { return Err(err(vm)); } set_item(&dict, &needle, &value); @@ -156,7 +152,7 @@ fn dict_new(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { fn dict_len(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(dict_obj, Some(vm.ctx.dict_type()))]); let elements = get_elements(dict_obj); - Ok(vm.ctx.new_int(elements.len().to_bigint().unwrap())) + Ok(vm.ctx.new_int(elements.len())) } fn dict_repr(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { @@ -277,14 +273,14 @@ fn dict_getitem(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { pub fn create_type(type_type: PyObjectRef, object_type: PyObjectRef, dict_type: PyObjectRef) { (*dict_type.borrow_mut()).payload = PyObjectPayload::Class { name: String::from("dict"), - dict: new(dict_type.clone()), + dict: RefCell::new(HashMap::new()), mro: vec![object_type], }; (*dict_type.borrow_mut()).typ = Some(type_type.clone()); } pub fn init(context: &PyContext) { - let ref dict_type = context.dict_type; + let dict_type = &context.dict_type; context.set_attr(&dict_type, "__len__", context.new_rustfunc(dict_len)); context.set_attr( &dict_type, diff --git a/vm/src/obj/objenumerate.rs b/vm/src/obj/objenumerate.rs new file mode 100644 index 000000000..582f89852 --- /dev/null +++ b/vm/src/obj/objenumerate.rs @@ -0,0 +1,69 @@ +use super::super::pyobject::{ + PyContext, PyFuncArgs, PyObject, PyObjectPayload, PyObjectRef, PyResult, TypeProtocol, +}; +use super::super::vm::VirtualMachine; +use super::objint; +use super::objiter; +use super::objtype; // Required for arg_check! to use isinstance +use num_bigint::BigInt; +use num_traits::Zero; +use std::ops::AddAssign; + +fn enumerate_new(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!( + vm, + args, + required = [(cls, Some(vm.ctx.type_type())), (iterable, None)], + optional = [(start, Some(vm.ctx.int_type()))] + ); + let counter = if let Some(x) = start { + objint::get_value(x) + } else { + BigInt::zero() + }; + let iterator = objiter::get_iter(vm, iterable)?; + Ok(PyObject::new( + PyObjectPayload::EnumerateIterator { counter, iterator }, + cls.clone(), + )) +} + +fn enumerate_next(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!( + vm, + args, + required = [(enumerate, Some(vm.ctx.enumerate_type()))] + ); + + if let PyObjectPayload::EnumerateIterator { + ref mut counter, + ref mut iterator, + } = enumerate.borrow_mut().payload + { + let next_obj = objiter::call_next(vm, iterator)?; + let result = vm + .ctx + .new_tuple(vec![vm.ctx.new_int(counter.clone()), next_obj]); + + AddAssign::add_assign(counter, 1); + + Ok(result) + } else { + panic!("enumerate doesn't have correct payload"); + } +} + +pub fn init(context: &PyContext) { + let enumerate_type = &context.enumerate_type; + objiter::iter_type_init(context, enumerate_type); + context.set_attr( + enumerate_type, + "__new__", + context.new_rustfunc(enumerate_new), + ); + context.set_attr( + enumerate_type, + "__next__", + context.new_rustfunc(enumerate_next), + ); +} diff --git a/vm/src/obj/objfilter.rs b/vm/src/obj/objfilter.rs new file mode 100644 index 000000000..b4bc4ff5e --- /dev/null +++ b/vm/src/obj/objfilter.rs @@ -0,0 +1,63 @@ +use super::super::pyobject::{ + IdProtocol, PyContext, PyFuncArgs, PyObject, PyObjectPayload, PyObjectRef, PyResult, + TypeProtocol, +}; +use super::super::vm::VirtualMachine; +use super::objbool; +use super::objiter; +use super::objtype; // Required for arg_check! to use isinstance + +fn filter_new(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!( + vm, + args, + required = [(cls, None), (function, None), (iterable, None)] + ); + let iterator = objiter::get_iter(vm, iterable)?; + Ok(PyObject::new( + PyObjectPayload::FilterIterator { + predicate: function.clone(), + iterator, + }, + cls.clone(), + )) +} + +fn filter_next(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!(vm, args, required = [(filter, Some(vm.ctx.filter_type()))]); + + if let PyObjectPayload::FilterIterator { + ref mut predicate, + ref mut iterator, + } = filter.borrow_mut().payload + { + loop { + let next_obj = objiter::call_next(vm, iterator)?; + let predicate_value = if predicate.is(&vm.get_none()) { + next_obj.clone() + } else { + // the predicate itself can raise StopIteration which does stop the filter + // iteration + vm.invoke( + predicate.clone(), + PyFuncArgs { + args: vec![next_obj.clone()], + kwargs: vec![], + }, + )? + }; + if objbool::boolval(vm, predicate_value)? { + return Ok(next_obj); + } + } + } else { + panic!("filter doesn't have correct payload"); + } +} + +pub fn init(context: &PyContext) { + let filter_type = &context.filter_type; + objiter::iter_type_init(context, filter_type); + context.set_attr(&filter_type, "__new__", context.new_rustfunc(filter_new)); + context.set_attr(&filter_type, "__next__", context.new_rustfunc(filter_next)); +} diff --git a/vm/src/obj/objfloat.rs b/vm/src/obj/objfloat.rs index aa98b0d5e..94f9f19b7 100644 --- a/vm/src/obj/objfloat.rs +++ b/vm/src/obj/objfloat.rs @@ -172,9 +172,9 @@ fn float_divmod(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { let args = PyFuncArgs::new(vec![i.clone(), i2.clone()], vec![]); if objtype::isinstance(i2, &vm.ctx.float_type()) || objtype::isinstance(i2, &vm.ctx.int_type()) { - let r1 = float_floordiv(vm, args.clone()); - let r2 = float_mod(vm, args.clone()); - Ok(vm.ctx.new_tuple(vec![r1.unwrap(), r2.unwrap()])) + let r1 = float_floordiv(vm, args.clone())?; + let r2 = float_mod(vm, args.clone())?; + Ok(vm.ctx.new_tuple(vec![r1, r2])) } else { Err(vm.new_type_error(format!( "Cannot divmod power {} and {}", @@ -190,18 +190,26 @@ fn float_floordiv(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { args, required = [(i, Some(vm.ctx.float_type())), (i2, None)] ); - if objtype::isinstance(i2, &vm.ctx.float_type()) { - Ok(vm.ctx.new_float((get_value(i) / get_value(i2)).floor())) - } else if objtype::isinstance(i2, &vm.ctx.int_type()) { - Ok(vm - .ctx - .new_float((get_value(i) / objint::get_value(i2).to_f64().unwrap()).floor())) + + let v1 = get_value(i); + let v2 = if objtype::isinstance(i2, &vm.ctx.float_type) { + get_value(i2) + } else if objtype::isinstance(i2, &vm.ctx.int_type) { + objint::get_value(i2) + .to_f64() + .ok_or_else(|| vm.new_overflow_error("int too large to convert to float".to_string()))? } else { - Err(vm.new_type_error(format!( + return Err(vm.new_type_error(format!( "Cannot floordiv {} and {}", i.borrow(), i2.borrow() - ))) + ))); + }; + + if v2 != 0.0 { + Ok(vm.ctx.new_float((v1 / v2).floor())) + } else { + Err(vm.new_zero_division_error("float floordiv by zero".to_string())) } } @@ -229,14 +237,22 @@ fn float_mod(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { args, required = [(i, Some(vm.ctx.float_type())), (i2, None)] ); - if objtype::isinstance(i2, &vm.ctx.float_type()) { - Ok(vm.ctx.new_float(get_value(i) % get_value(i2))) - } else if objtype::isinstance(i2, &vm.ctx.int_type()) { - Ok(vm - .ctx - .new_float(get_value(i) % objint::get_value(i2).to_f64().unwrap())) + + let v1 = get_value(i); + let v2 = if objtype::isinstance(i2, &vm.ctx.float_type) { + get_value(i2) + } else if objtype::isinstance(i2, &vm.ctx.int_type) { + objint::get_value(i2) + .to_f64() + .ok_or_else(|| vm.new_overflow_error("int too large to convert to float".to_string()))? } else { - Err(vm.new_type_error(format!("Cannot mod {} and {}", i.borrow(), i2.borrow()))) + return Err(vm.new_type_error(format!("Cannot mod {} and {}", i.borrow(), i2.borrow()))); + }; + + if v2 != 0.0 { + Ok(vm.ctx.new_float(v1 % v2)) + } else { + Err(vm.new_zero_division_error("float mod by zero".to_string())) } } @@ -266,8 +282,58 @@ fn float_pow(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { } } +fn float_truediv(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!( + vm, + args, + required = [(i, Some(vm.ctx.float_type())), (i2, None)] + ); + + let v1 = get_value(i); + let v2 = if objtype::isinstance(i2, &vm.ctx.float_type) { + get_value(i2) + } else if objtype::isinstance(i2, &vm.ctx.int_type) { + objint::get_value(i2) + .to_f64() + .ok_or_else(|| vm.new_overflow_error("int too large to convert to float".to_string()))? + } else { + return Err(vm.new_type_error(format!("Cannot divide {} and {}", i.borrow(), i2.borrow()))); + }; + + if v2 != 0.0 { + Ok(vm.ctx.new_float(v1 / v2)) + } else { + Err(vm.new_zero_division_error("float division by zero".to_string())) + } +} + +fn float_mul(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!( + vm, + args, + required = [(i, Some(vm.ctx.float_type())), (i2, None)] + ); + let v1 = get_value(i); + if objtype::isinstance(i2, &vm.ctx.float_type) { + Ok(vm.ctx.new_float(v1 * get_value(i2))) + } else if objtype::isinstance(i2, &vm.ctx.int_type) { + Ok(vm + .ctx + .new_float(v1 * objint::get_value(i2).to_f64().unwrap())) + } else { + Err(vm.new_type_error(format!( + "Cannot multiply {} and {}", + i.borrow(), + i2.borrow() + ))) + } +} + pub fn init(context: &PyContext) { - let ref float_type = context.float_type; + let float_type = &context.float_type; + + let float_doc = "Convert a string or number to a floating point number, if possible."; + context.set_attr(&float_type, "__eq__", context.new_rustfunc(float_eq)); context.set_attr(&float_type, "__lt__", context.new_rustfunc(float_lt)); context.set_attr(&float_type, "__le__", context.new_rustfunc(float_le)); @@ -291,4 +357,15 @@ pub fn init(context: &PyContext) { context.set_attr(&float_type, "__pow__", context.new_rustfunc(float_pow)); context.set_attr(&float_type, "__sub__", context.new_rustfunc(float_sub)); context.set_attr(&float_type, "__repr__", context.new_rustfunc(float_repr)); + context.set_attr( + &float_type, + "__doc__", + context.new_str(float_doc.to_string()), + ); + context.set_attr( + &float_type, + "__truediv__", + context.new_rustfunc(float_truediv), + ); + context.set_attr(&float_type, "__mul__", context.new_rustfunc(float_mul)); } diff --git a/vm/src/obj/objframe.rs b/vm/src/obj/objframe.rs index c9aa366b1..43a88e2ec 100644 --- a/vm/src/obj/objframe.rs +++ b/vm/src/obj/objframe.rs @@ -10,7 +10,7 @@ use super::super::vm::VirtualMachine; use super::objtype; pub fn init(context: &PyContext) { - let ref frame_type = context.frame_type; + let frame_type = &context.frame_type; context.set_attr(&frame_type, "__new__", context.new_rustfunc(frame_new)); context.set_attr(&frame_type, "__repr__", context.new_rustfunc(frame_repr)); context.set_attr(&frame_type, "f_locals", context.new_property(frame_flocals)); @@ -19,12 +19,12 @@ pub fn init(context: &PyContext) { fn frame_new(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(_cls, None)]); - Err(vm.new_type_error(format!("Cannot directly create frame object"))) + Err(vm.new_type_error("Cannot directly create frame object".to_string())) } fn frame_repr(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(_frame, Some(vm.ctx.frame_type()))]); - let repr = format!(""); + let repr = "".to_string(); Ok(vm.new_str(repr)) } diff --git a/vm/src/obj/objfunction.rs b/vm/src/obj/objfunction.rs index 925bb7123..01c05aab1 100644 --- a/vm/src/obj/objfunction.rs +++ b/vm/src/obj/objfunction.rs @@ -1,22 +1,28 @@ use super::super::pyobject::{ - AttributeProtocol, IdProtocol, PyContext, PyFuncArgs, PyObject, PyObjectPayload, PyObjectRef, - PyResult, TypeProtocol, + AttributeProtocol, IdProtocol, PyContext, PyFuncArgs, PyObjectPayload, PyObjectRef, PyResult, + TypeProtocol, }; use super::super::vm::VirtualMachine; use super::objtype; pub fn init(context: &PyContext) { - let ref function_type = context.function_type; + let function_type = &context.function_type; context.set_attr(&function_type, "__get__", context.new_rustfunc(bind_method)); - let ref member_descriptor_type = context.member_descriptor_type; + context.set_attr( + &function_type, + "__code__", + context.new_member_descriptor(function_code), + ); + + let member_descriptor_type = &context.member_descriptor_type; context.set_attr( &member_descriptor_type, "__get__", context.new_rustfunc(member_get), ); - let ref classmethod_type = context.classmethod_type; + let classmethod_type = &context.classmethod_type; context.set_attr( &classmethod_type, "__get__", @@ -28,7 +34,7 @@ pub fn init(context: &PyContext) { context.new_rustfunc(classmethod_new), ); - let ref staticmethod_type = context.staticmethod_type; + let staticmethod_type = &context.staticmethod_type; context.set_attr( staticmethod_type, "__get__", @@ -55,6 +61,13 @@ fn bind_method(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { } } +fn function_code(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { + match args.args[0].borrow().payload { + PyObjectPayload::Function { ref code, .. } => Ok(code.clone()), + _ => Err(vm.new_type_error("no code".to_string())), + } +} + fn member_get(vm: &mut VirtualMachine, mut args: PyFuncArgs) -> PyResult { match args.shift().get_attr("function") { Some(function) => vm.invoke(function, args), @@ -97,12 +110,7 @@ fn classmethod_new(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { trace!("classmethod.__new__ {:?}", args.args); arg_check!(vm, args, required = [(cls, None), (callable, None)]); - let py_obj = PyObject::new( - PyObjectPayload::Instance { - dict: vm.ctx.new_dict(), - }, - cls.clone(), - ); + let py_obj = vm.ctx.new_instance(cls.clone(), None); vm.ctx.set_attr(&py_obj, "function", callable.clone()); Ok(py_obj) } @@ -135,12 +143,7 @@ fn staticmethod_new(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { trace!("staticmethod.__new__ {:?}", args.args); arg_check!(vm, args, required = [(cls, None), (callable, None)]); - let py_obj = PyObject::new( - PyObjectPayload::Instance { - dict: vm.ctx.new_dict(), - }, - cls.clone(), - ); + let py_obj = vm.ctx.new_instance(cls.clone(), None); vm.ctx.set_attr(&py_obj, "function", callable.clone()); Ok(py_obj) } diff --git a/vm/src/obj/objgenerator.rs b/vm/src/obj/objgenerator.rs index c98dcadf2..b1a726844 100644 --- a/vm/src/obj/objgenerator.rs +++ b/vm/src/obj/objgenerator.rs @@ -10,7 +10,7 @@ use super::super::vm::VirtualMachine; use super::objtype; pub fn init(context: &PyContext) { - let ref generator_type = context.generator_type; + let generator_type = &context.generator_type; context.set_attr( &generator_type, "__iter__", @@ -30,7 +30,7 @@ pub fn init(context: &PyContext) { pub fn new_generator(vm: &mut VirtualMachine, frame: Frame) -> PyResult { let g = PyObject::new( - PyObjectPayload::Generator { frame: frame }, + PyObjectPayload::Generator { frame }, vm.ctx.generator_type.clone(), ); Ok(g) diff --git a/vm/src/obj/objint.rs b/vm/src/obj/objint.rs index c42ddd999..443fa552e 100644 --- a/vm/src/obj/objint.rs +++ b/vm/src/obj/objint.rs @@ -8,6 +8,7 @@ use super::objfloat; use super::objstr; use super::objtype; use num_bigint::{BigInt, ToBigInt}; +use num_integer::Integer; use num_traits::{Pow, Signed, ToPrimitive, Zero}; use std::hash::{Hash, Hasher}; @@ -58,7 +59,7 @@ pub fn to_int( match i32::from_str_radix(&s, base) { Ok(v) => v.to_bigint().unwrap(), Err(err) => { - trace!("Error occured during int conversion {:?}", err); + trace!("Error occurred during int conversion {:?}", err); return Err(vm.new_value_error(format!( "invalid literal for int() with base {}: '{}'", base, s @@ -90,6 +91,20 @@ impl FromPyObjectRef for BigInt { } } +fn int_bool(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!(vm, args, required = [(zelf, Some(vm.ctx.int_type()))]); + let result = !BigInt::from_pyobj(zelf).is_zero(); + Ok(vm.ctx.new_bool(result)) +} + +fn int_invert(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!(vm, args, required = [(zelf, Some(vm.ctx.int_type()))]); + + let result = !BigInt::from_pyobj(zelf); + + Ok(vm.ctx.new_int(result)) +} + fn int_eq(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!( vm, @@ -170,13 +185,73 @@ fn int_ge(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { Ok(vm.ctx.new_bool(result)) } +fn int_lshift(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!( + vm, + args, + required = [(i, Some(vm.ctx.int_type())), (i2, None)] + ); + + if !objtype::isinstance(i2, &vm.ctx.int_type()) { + return Err(vm.new_type_error(format!( + "unsupported operand type(s) for << '{}' and '{}'", + objtype::get_type_name(&i.typ()), + objtype::get_type_name(&i2.typ()) + ))); + } + + if let Some(n_bits) = get_value(i2).to_usize() { + return Ok(vm.ctx.new_int(get_value(i) << n_bits)); + } + + // i2 failed `to_usize()` conversion + match get_value(i2) { + ref v if *v < BigInt::zero() => Err(vm.new_value_error("negative shift count".to_string())), + ref v if *v > BigInt::from(usize::max_value()) => { + // TODO: raise OverflowError + panic!("Failed converting {} to rust usize", get_value(i2)); + } + _ => panic!("Failed converting {} to rust usize", get_value(i2)), + } +} + +fn int_rshift(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!( + vm, + args, + required = [(i, Some(vm.ctx.int_type())), (i2, None)] + ); + + if !objtype::isinstance(i2, &vm.ctx.int_type()) { + return Err(vm.new_type_error(format!( + "unsupported operand type(s) for >> '{}' and '{}'", + objtype::get_type_name(&i.typ()), + objtype::get_type_name(&i2.typ()) + ))); + } + + if let Some(n_bits) = get_value(i2).to_usize() { + return Ok(vm.ctx.new_int(get_value(i) >> n_bits)); + } + + // i2 failed `to_usize()` conversion + match get_value(i2) { + ref v if *v < BigInt::zero() => Err(vm.new_value_error("negative shift count".to_string())), + ref v if *v > BigInt::from(usize::max_value()) => { + // TODO: raise OverflowError + panic!("Failed converting {} to rust usize", get_value(i2)); + } + _ => panic!("Failed converting {} to rust usize", get_value(i2)), + } +} + fn int_hash(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(zelf, Some(vm.ctx.int_type()))]); let value = BigInt::from_pyobj(zelf); let mut hasher = std::collections::hash_map::DefaultHasher::new(); value.hash(&mut hasher); let hash = hasher.finish(); - Ok(vm.ctx.new_int(hash.to_bigint().unwrap())) + Ok(vm.ctx.new_int(hash)) } fn int_abs(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { @@ -215,7 +290,13 @@ fn int_floordiv(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { required = [(i, Some(vm.ctx.int_type())), (i2, None)] ); if objtype::isinstance(i2, &vm.ctx.int_type()) { - Ok(vm.ctx.new_int(get_value(i) / get_value(i2))) + let (v1, v2) = (get_value(i), get_value(i2)); + + if v2 != BigInt::zero() { + Ok(vm.ctx.new_int(v1 / v2)) + } else { + Err(vm.new_zero_division_error("integer floordiv by zero".to_string())) + } } else { Err(vm.new_type_error(format!( "Cannot floordiv {} and {}", @@ -225,6 +306,21 @@ fn int_floordiv(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { } } +fn int_round(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!( + vm, + args, + required = [(i, Some(vm.ctx.int_type()))], + optional = [(_precision, None)] + ); + Ok(vm.ctx.new_int(get_value(i))) +} + +fn int_pass_value(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!(vm, args, required = [(i, Some(vm.ctx.int_type()))]); + Ok(vm.ctx.new_int(get_value(i))) +} + fn int_format(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!( vm, @@ -292,17 +388,25 @@ fn int_truediv(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { args, required = [(i, Some(vm.ctx.int_type())), (i2, None)] ); - let v1 = get_value(i); - if objtype::isinstance(i2, &vm.ctx.int_type()) { - Ok(vm - .ctx - .new_float(v1.to_f64().unwrap() / get_value(i2).to_f64().unwrap())) + + let v1 = get_value(i) + .to_f64() + .ok_or_else(|| vm.new_overflow_error("int too large to convert to float".to_string()))?; + + let v2 = if objtype::isinstance(i2, &vm.ctx.int_type()) { + get_value(i2) + .to_f64() + .ok_or_else(|| vm.new_overflow_error("int too large to convert to float".to_string()))? } else if objtype::isinstance(i2, &vm.ctx.float_type()) { - Ok(vm - .ctx - .new_float(v1.to_f64().unwrap() / objfloat::get_value(i2))) + objfloat::get_value(i2) } else { - Err(vm.new_type_error(format!("Cannot divide {} and {}", i.borrow(), i2.borrow()))) + return Err(vm.new_type_error(format!("Cannot divide {} and {}", i.borrow(), i2.borrow()))); + }; + + if v2 == 0.0 { + Err(vm.new_zero_division_error("integer division by zero".to_string())) + } else { + Ok(vm.ctx.new_float(v1 / v2)) } } @@ -314,7 +418,13 @@ fn int_mod(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { ); let v1 = get_value(i); if objtype::isinstance(i2, &vm.ctx.int_type()) { - Ok(vm.ctx.new_int(v1 % get_value(i2))) + let v2 = get_value(i2); + + if v2 != BigInt::zero() { + Ok(vm.ctx.new_int(v1 % get_value(i2))) + } else { + Err(vm.new_zero_division_error("integer modulo by zero".to_string())) + } } else { Err(vm.new_type_error(format!("Cannot modulo {} and {}", i.borrow(), i2.borrow()))) } @@ -340,7 +450,7 @@ fn int_pow(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { let v1 = get_value(i); if objtype::isinstance(i2, &vm.ctx.int_type()) { let v2 = get_value(i2).to_u32().unwrap(); - Ok(vm.ctx.new_int(v1.pow(v2).to_bigint().unwrap())) + Ok(vm.ctx.new_int(v1.pow(v2))) } else if objtype::isinstance(i2, &vm.ctx.float_type()) { let v2 = objfloat::get_value(i2); Ok(vm.ctx.new_float((v1.to_f64().unwrap()).powf(v2))) @@ -359,11 +469,20 @@ fn int_divmod(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { args, required = [(i, Some(vm.ctx.int_type())), (i2, None)] ); - let args = PyFuncArgs::new(vec![i.clone(), i2.clone()], vec![]); + if objtype::isinstance(i2, &vm.ctx.int_type()) { - let r1 = int_floordiv(vm, args.clone()); - let r2 = int_mod(vm, args.clone()); - Ok(vm.ctx.new_tuple(vec![r1.unwrap(), r2.unwrap()])) + let v1 = get_value(i); + let v2 = get_value(i2); + + if v2 != BigInt::zero() { + let (r1, r2) = v1.div_rem(&v2); + + Ok(vm + .ctx + .new_tuple(vec![vm.ctx.new_int(r1), vm.ctx.new_int(r2)])) + } else { + Err(vm.new_zero_division_error("integer divmod by zero".to_string())) + } } else { Err(vm.new_type_error(format!( "Cannot divmod power {} and {}", @@ -388,6 +507,23 @@ fn int_xor(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { } } +fn int_rxor(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!( + vm, + args, + required = [(i, Some(vm.ctx.int_type())), (i2, None)] + ); + + if objtype::isinstance(i2, &vm.ctx.int_type()) { + let right_val = get_value(i); + let left_val = get_value(i2); + + Ok(vm.ctx.new_int(left_val ^ right_val)) + } else { + Err(vm.new_type_error(format!("Cannot rxor {} and {}", i.borrow(), i2.borrow()))) + } +} + fn int_or(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!( vm, @@ -422,11 +558,32 @@ fn int_bit_length(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(i, Some(vm.ctx.int_type()))]); let v = get_value(i); let bits = v.bits(); - Ok(vm.ctx.new_int(bits.to_bigint().unwrap())) + Ok(vm.ctx.new_int(bits)) +} + +fn int_conjugate(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!(vm, args, required = [(i, Some(vm.ctx.int_type()))]); + let v = get_value(i); + Ok(vm.ctx.new_int(v)) } pub fn init(context: &PyContext) { - let ref int_type = context.int_type; + let int_doc = "int(x=0) -> integer +int(x, base=10) -> integer + +Convert a number or string to an integer, or return 0 if no arguments +are given. If x is a number, return x.__int__(). For floating point +numbers, this truncates towards zero. + +If x is not a number or if base is given, then x must be a string, +bytes, or bytearray instance representing an integer literal in the +given base. The literal can be preceded by '+' or '-' and be surrounded +by whitespace. The base defaults to 10. Valid bases are 0 and 2-36. +Base 0 means to interpret the base from the string as an integer literal. +>>> int('0b100', base=0) +4"; + let int_type = &context.int_type; + context.set_attr(&int_type, "__eq__", context.new_rustfunc(int_eq)); context.set_attr(&int_type, "__lt__", context.new_rustfunc(int_lt)); context.set_attr(&int_type, "__le__", context.new_rustfunc(int_le)); @@ -437,12 +594,20 @@ pub fn init(context: &PyContext) { context.set_attr(&int_type, "__and__", context.new_rustfunc(int_and)); context.set_attr(&int_type, "__divmod__", context.new_rustfunc(int_divmod)); context.set_attr(&int_type, "__float__", context.new_rustfunc(int_float)); + context.set_attr(&int_type, "__round__", context.new_rustfunc(int_round)); + context.set_attr(&int_type, "__ceil__", context.new_rustfunc(int_pass_value)); + context.set_attr(&int_type, "__floor__", context.new_rustfunc(int_pass_value)); + context.set_attr(&int_type, "__index__", context.new_rustfunc(int_pass_value)); + context.set_attr(&int_type, "__trunc__", context.new_rustfunc(int_pass_value)); + context.set_attr(&int_type, "__int__", context.new_rustfunc(int_pass_value)); context.set_attr( &int_type, "__floordiv__", context.new_rustfunc(int_floordiv), ); context.set_attr(&int_type, "__hash__", context.new_rustfunc(int_hash)); + context.set_attr(&int_type, "__lshift__", context.new_rustfunc(int_lshift)); + context.set_attr(&int_type, "__rshift__", context.new_rustfunc(int_rshift)); context.set_attr(&int_type, "__new__", context.new_rustfunc(int_new)); context.set_attr(&int_type, "__mod__", context.new_rustfunc(int_mod)); context.set_attr(&int_type, "__mul__", context.new_rustfunc(int_mul)); @@ -455,9 +620,14 @@ pub fn init(context: &PyContext) { context.set_attr(&int_type, "__format__", context.new_rustfunc(int_format)); context.set_attr(&int_type, "__truediv__", context.new_rustfunc(int_truediv)); context.set_attr(&int_type, "__xor__", context.new_rustfunc(int_xor)); + context.set_attr(&int_type, "__rxor__", context.new_rustfunc(int_rxor)); + context.set_attr(&int_type, "__bool__", context.new_rustfunc(int_bool)); + context.set_attr(&int_type, "__invert__", context.new_rustfunc(int_invert)); context.set_attr( &int_type, "bit_length", context.new_rustfunc(int_bit_length), ); + context.set_attr(&int_type, "__doc__", context.new_str(int_doc.to_string())); + context.set_attr(&int_type, "conjugate", context.new_rustfunc(int_conjugate)); } diff --git a/vm/src/obj/objiter.rs b/vm/src/obj/objiter.rs index 9844c2dfb..40633e984 100644 --- a/vm/src/obj/objiter.rs +++ b/vm/src/obj/objiter.rs @@ -22,6 +22,10 @@ pub fn get_iter(vm: &mut VirtualMachine, iter_target: &PyObjectRef) -> PyResult // return Err(type_error); } +pub fn call_next(vm: &mut VirtualMachine, iter_obj: &PyObjectRef) -> PyResult { + vm.call_method(iter_obj, "__next__", vec![]) +} + /* * Helper function to retrieve the next object (or none) from an iterator. */ @@ -29,7 +33,7 @@ pub fn get_next_object( vm: &mut VirtualMachine, iter_obj: &PyObjectRef, ) -> Result, PyObjectRef> { - let next_obj: PyResult = vm.call_method(iter_obj, "__next__", vec![]); + let next_obj: PyResult = call_next(vm, iter_obj); match next_obj { Ok(value) => Ok(Some(value)), @@ -60,6 +64,59 @@ pub fn get_all( Ok(elements) } +pub fn new_stop_iteration(vm: &mut VirtualMachine) -> PyObjectRef { + let stop_iteration_type = vm.ctx.exceptions.stop_iteration.clone(); + vm.new_exception(stop_iteration_type, "End of iterator".to_string()) +} + +fn contains(vm: &mut VirtualMachine, args: PyFuncArgs, iter_type: PyObjectRef) -> PyResult { + arg_check!( + vm, + args, + required = [(iter, Some(iter_type)), (needle, None)] + ); + loop { + if let Some(element) = get_next_object(vm, iter)? { + let equal = vm.call_method(needle, "__eq__", vec![element.clone()])?; + if objbool::get_value(&equal) { + return Ok(vm.new_bool(true)); + } else { + continue; + } + } else { + return Ok(vm.new_bool(false)); + } + } +} + +/// Common setup for iter types, adds __iter__ and __contains__ methods +pub fn iter_type_init(context: &PyContext, iter_type: &PyObjectRef) { + let contains_func = { + let cloned_iter_type = iter_type.clone(); + move |vm: &mut VirtualMachine, args: PyFuncArgs| { + contains(vm, args, cloned_iter_type.clone()) + } + }; + context.set_attr( + &iter_type, + "__contains__", + context.new_rustfunc(contains_func), + ); + let iter_func = { + let cloned_iter_type = iter_type.clone(); + move |vm: &mut VirtualMachine, args: PyFuncArgs| { + arg_check!( + vm, + args, + required = [(iter, Some(cloned_iter_type.clone()))] + ); + // Return self: + Ok(iter.clone()) + } + }; + context.set_attr(&iter_type, "__iter__", context.new_rustfunc(iter_func)); +} + // Sequence iterator: fn iter_new(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(iter_target, None)]); @@ -67,44 +124,15 @@ fn iter_new(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { get_iter(vm, iter_target) } -fn iter_iter(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(iter, Some(vm.ctx.iter_type()))]); - // Return self: - Ok(iter.clone()) -} - -fn iter_contains(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(iter, Some(vm.ctx.iter_type())), (needle, None)] - ); - loop { - match vm.call_method(&iter, "__next__", vec![]) { - Ok(element) => match vm.call_method(needle, "__eq__", vec![element.clone()]) { - Ok(value) => { - if objbool::get_value(&value) { - return Ok(vm.new_bool(true)); - } else { - continue; - } - } - Err(_) => return Err(vm.new_type_error("".to_string())), - }, - Err(_) => return Ok(vm.new_bool(false)), - } - } -} - fn iter_next(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(iter, Some(vm.ctx.iter_type()))]); if let PyObjectPayload::Iterator { ref mut position, - iterated_obj: ref iterated_obj_ref, + iterated_obj: ref mut iterated_obj_ref, } = iter.borrow_mut().payload { - let iterated_obj = &*iterated_obj_ref.borrow_mut(); + let iterated_obj = iterated_obj_ref.borrow_mut(); match iterated_obj.payload { PyObjectPayload::Sequence { ref elements } => { if *position < elements.len() { @@ -112,12 +140,29 @@ fn iter_next(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { *position += 1; Ok(obj_ref) } else { - let stop_iteration_type = vm.ctx.exceptions.stop_iteration.clone(); - let stop_iteration = - vm.new_exception(stop_iteration_type, "End of iterator".to_string()); - Err(stop_iteration) + Err(new_stop_iteration(vm)) } } + + PyObjectPayload::Range { ref range } => { + if let Some(int) = range.get(*position) { + *position += 1; + Ok(vm.ctx.new_int(int)) + } else { + Err(new_stop_iteration(vm)) + } + } + + PyObjectPayload::Bytes { ref value } => { + if *position < value.len() { + let obj_ref = vm.ctx.new_int(value[*position]); + *position += 1; + Ok(obj_ref) + } else { + Err(new_stop_iteration(vm)) + } + } + _ => { panic!("NOT IMPL"); } @@ -128,13 +173,8 @@ fn iter_next(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { } pub fn init(context: &PyContext) { - let ref iter_type = context.iter_type; - context.set_attr( - &iter_type, - "__contains__", - context.new_rustfunc(iter_contains), - ); - context.set_attr(&iter_type, "__iter__", context.new_rustfunc(iter_iter)); + let iter_type = &context.iter_type; + iter_type_init(context, iter_type); context.set_attr(&iter_type, "__new__", context.new_rustfunc(iter_new)); context.set_attr(&iter_type, "__next__", context.new_rustfunc(iter_next)); } diff --git a/vm/src/obj/objlist.rs b/vm/src/obj/objlist.rs index 641bb9aa6..369ed9fc5 100644 --- a/vm/src/obj/objlist.rs +++ b/vm/src/obj/objlist.rs @@ -5,11 +5,11 @@ use super::super::vm::VirtualMachine; use super::objbool; use super::objint; use super::objsequence::{ - get_elements, get_item, get_mut_elements, seq_equal, PySliceableSequence, + get_elements, get_item, get_mut_elements, seq_equal, seq_ge, seq_gt, seq_le, seq_lt, seq_mul, + PySliceableSequence, }; use super::objstr; use super::objtype; -use num_bigint::ToBigInt; use num_traits::ToPrimitive; // set_item: @@ -21,9 +21,12 @@ fn set_item( ) -> PyResult { if objtype::isinstance(&idx, &vm.ctx.int_type()) { let value = objint::get_value(&idx).to_i32().unwrap(); - let pos_index = l.get_pos(value); - l[pos_index] = obj; - Ok(vm.get_none()) + if let Some(pos_index) = l.get_pos(value) { + l[pos_index] = obj; + Ok(vm.get_none()) + } else { + Err(vm.new_index_error("list index out of range".to_string())) + } } else { panic!( "TypeError: indexing type {:?} with index {:?} is not supported (yet?)", @@ -51,7 +54,7 @@ fn list_new(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { }; Ok(PyObject::new( - PyObjectPayload::Sequence { elements: elements }, + PyObjectPayload::Sequence { elements }, cls.clone(), )) } @@ -73,6 +76,94 @@ fn list_eq(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { Ok(vm.ctx.new_bool(result)) } +fn list_lt(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!( + vm, + args, + required = [(zelf, Some(vm.ctx.list_type())), (other, None)] + ); + + let result = if objtype::isinstance(other, &vm.ctx.list_type()) { + let zelf = get_elements(zelf); + let other = get_elements(other); + seq_lt(vm, &zelf, &other)? + } else { + return Err(vm.new_type_error(format!( + "Cannot compare {} and {} using '<'", + zelf.borrow(), + other.borrow() + ))); + }; + + Ok(vm.ctx.new_bool(result)) +} + +fn list_gt(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!( + vm, + args, + required = [(zelf, Some(vm.ctx.list_type())), (other, None)] + ); + + let result = if objtype::isinstance(other, &vm.ctx.list_type()) { + let zelf = get_elements(zelf); + let other = get_elements(other); + seq_gt(vm, &zelf, &other)? + } else { + return Err(vm.new_type_error(format!( + "Cannot compare {} and {} using '>'", + zelf.borrow(), + other.borrow() + ))); + }; + + Ok(vm.ctx.new_bool(result)) +} + +fn list_ge(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!( + vm, + args, + required = [(zelf, Some(vm.ctx.list_type())), (other, None)] + ); + + let result = if objtype::isinstance(other, &vm.ctx.list_type()) { + let zelf = get_elements(zelf); + let other = get_elements(other); + seq_ge(vm, &zelf, &other)? + } else { + return Err(vm.new_type_error(format!( + "Cannot compare {} and {} using '>='", + zelf.borrow(), + other.borrow() + ))); + }; + + Ok(vm.ctx.new_bool(result)) +} + +fn list_le(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!( + vm, + args, + required = [(zelf, Some(vm.ctx.list_type())), (other, None)] + ); + + let result = if objtype::isinstance(other, &vm.ctx.list_type()) { + let zelf = get_elements(zelf); + let other = get_elements(other); + seq_le(vm, &zelf, &other)? + } else { + return Err(vm.new_type_error(format!( + "Cannot compare {} and {} using '<='", + zelf.borrow(), + other.borrow() + ))); + }; + + Ok(vm.ctx.new_bool(result)) +} + fn list_add(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!( vm, @@ -83,7 +174,7 @@ fn list_add(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { if objtype::isinstance(o2, &vm.ctx.list_type()) { let e1 = get_elements(o); let e2 = get_elements(o2); - let elements = e1.iter().chain(e2.iter()).map(|e| e.clone()).collect(); + let elements = e1.iter().chain(e2.iter()).cloned().collect(); Ok(vm.ctx.new_list(elements)) } else { Err(vm.new_type_error(format!("Cannot add {} and {}", o.borrow(), o2.borrow()))) @@ -135,10 +226,10 @@ fn list_count(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { for element in elements.iter() { let is_eq = vm._eq(element, value.clone())?; if objbool::boolval(vm, is_eq)? { - count = count + 1; + count += 1; } } - Ok(vm.context().new_int(count.to_bigint().unwrap())) + Ok(vm.context().new_int(count)) } pub fn list_extend(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { @@ -163,7 +254,7 @@ fn list_index(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { for (index, element) in get_elements(list).iter().enumerate() { let py_equal = vm.call_method(needle, "__eq__", vec![element.clone()])?; if objbool::get_value(&py_equal) { - return Ok(vm.context().new_int(index.to_bigint().unwrap())); + return Ok(vm.context().new_int(index)); } } let needle_str = objstr::get_value(&vm.to_str(needle).unwrap()); @@ -174,7 +265,7 @@ fn list_len(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { trace!("list.len called with: {:?}", args); arg_check!(vm, args, required = [(list, Some(vm.ctx.list_type()))]); let elements = get_elements(list); - Ok(vm.context().new_int(elements.len().to_bigint().unwrap())) + Ok(vm.context().new_int(elements.len())) } fn list_reverse(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { @@ -259,21 +350,29 @@ fn list_mul(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { ] ); - let counter = objint::get_value(&product).to_usize().unwrap(); - - let elements = get_elements(list); - let current_len = elements.len(); - let mut new_elements = Vec::with_capacity(counter * current_len); - - for _ in 0..counter { - new_elements.extend(elements.clone()); - } + let new_elements = seq_mul(&get_elements(list), product); Ok(vm.ctx.new_list(new_elements)) } +fn list_pop(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!(vm, args, required = [(zelf, Some(vm.ctx.list_type()))]); + + let mut elements = get_mut_elements(zelf); + if let Some(result) = elements.pop() { + Ok(result) + } else { + Err(vm.new_index_error("pop from empty list".to_string())) + } +} + pub fn init(context: &PyContext) { - let ref list_type = context.list_type; + let list_type = &context.list_type; + + let list_doc = "Built-in mutable sequence.\n\n\ + If no argument is given, the constructor creates a new empty list.\n\ + The argument must be an iterable if specified."; + context.set_attr(&list_type, "__add__", context.new_rustfunc(list_add)); context.set_attr( &list_type, @@ -281,6 +380,10 @@ pub fn init(context: &PyContext) { context.new_rustfunc(list_contains), ); context.set_attr(&list_type, "__eq__", context.new_rustfunc(list_eq)); + context.set_attr(&list_type, "__lt__", context.new_rustfunc(list_lt)); + context.set_attr(&list_type, "__gt__", context.new_rustfunc(list_gt)); + context.set_attr(&list_type, "__le__", context.new_rustfunc(list_le)); + context.set_attr(&list_type, "__ge__", context.new_rustfunc(list_ge)); context.set_attr( &list_type, "__getitem__", @@ -296,6 +399,7 @@ pub fn init(context: &PyContext) { context.set_attr(&list_type, "__len__", context.new_rustfunc(list_len)); context.set_attr(&list_type, "__new__", context.new_rustfunc(list_new)); context.set_attr(&list_type, "__repr__", context.new_rustfunc(list_repr)); + context.set_attr(&list_type, "__doc__", context.new_str(list_doc.to_string())); context.set_attr(&list_type, "append", context.new_rustfunc(list_append)); context.set_attr(&list_type, "clear", context.new_rustfunc(list_clear)); context.set_attr(&list_type, "count", context.new_rustfunc(list_count)); @@ -303,4 +407,5 @@ pub fn init(context: &PyContext) { context.set_attr(&list_type, "index", context.new_rustfunc(list_index)); context.set_attr(&list_type, "reverse", context.new_rustfunc(list_reverse)); context.set_attr(&list_type, "sort", context.new_rustfunc(list_sort)); + context.set_attr(&list_type, "pop", context.new_rustfunc(list_pop)); } diff --git a/vm/src/obj/objmap.rs b/vm/src/obj/objmap.rs new file mode 100644 index 000000000..79ee7ddf8 --- /dev/null +++ b/vm/src/obj/objmap.rs @@ -0,0 +1,67 @@ +use super::super::pyobject::{ + PyContext, PyFuncArgs, PyObject, PyObjectPayload, PyObjectRef, PyResult, TypeProtocol, +}; +use super::super::vm::VirtualMachine; +use super::objiter; +use super::objtype; // Required for arg_check! to use isinstance + +fn map_new(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { + no_kwargs!(vm, args); + let cls = &args.args[0]; + if args.args.len() < 3 { + Err(vm.new_type_error("map() must have at least two arguments.".to_owned())) + } else { + let function = &args.args[1]; + let iterables = &args.args[2..]; + let iterators = iterables + .into_iter() + .map(|iterable| objiter::get_iter(vm, iterable)) + .collect::, _>>()?; + Ok(PyObject::new( + PyObjectPayload::MapIterator { + mapper: function.clone(), + iterators, + }, + cls.clone(), + )) + } +} + +fn map_next(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!(vm, args, required = [(map, Some(vm.ctx.map_type()))]); + + if let PyObjectPayload::MapIterator { + ref mut mapper, + ref mut iterators, + } = map.borrow_mut().payload + { + let next_objs = iterators + .iter() + .map(|iterator| objiter::call_next(vm, iterator)) + .collect::, _>>()?; + + // the mapper itself can raise StopIteration which does stop the map iteration + vm.invoke( + mapper.clone(), + PyFuncArgs { + args: next_objs, + kwargs: vec![], + }, + ) + } else { + panic!("map doesn't have correct payload"); + } +} + +pub fn init(context: &PyContext) { + let map_type = &context.map_type; + + let map_doc = "map(func, *iterables) --> map object\n\n\ + Make an iterator that computes the function using arguments from\n\ + each of the iterables. Stops when the shortest iterable is exhausted."; + + objiter::iter_type_init(context, map_type); + context.set_attr(&map_type, "__new__", context.new_rustfunc(map_new)); + context.set_attr(&map_type, "__next__", context.new_rustfunc(map_next)); + context.set_attr(&map_type, "__doc__", context.new_str(map_doc.to_string())); +} diff --git a/vm/src/obj/objmemory.rs b/vm/src/obj/objmemory.rs index f9b20e441..6005ba62d 100644 --- a/vm/src/obj/objmemory.rs +++ b/vm/src/obj/objmemory.rs @@ -17,7 +17,7 @@ pub fn new_memory_view(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { } pub fn init(ctx: &PyContext) { - let ref memoryview_type = ctx.memoryview_type; + let memoryview_type = &ctx.memoryview_type; ctx.set_attr( &memoryview_type, "__new__", diff --git a/vm/src/obj/objobject.rs b/vm/src/obj/objobject.rs index 5c425087e..cb5065b20 100644 --- a/vm/src/obj/objobject.rs +++ b/vm/src/obj/objobject.rs @@ -1,25 +1,25 @@ use super::super::pyobject::{ - AttributeProtocol, IdProtocol, PyContext, PyFuncArgs, PyObject, PyObjectPayload, PyObjectRef, - PyResult, TypeProtocol, + AttributeProtocol, IdProtocol, PyContext, PyFuncArgs, PyObjectPayload, PyObjectRef, PyResult, + TypeProtocol, }; use super::super::vm::VirtualMachine; use super::objbool; -use super::objdict; use super::objstr; use super::objtype; +use std::cell::RefCell; +use std::collections::HashMap; pub fn new_instance(vm: &mut VirtualMachine, mut args: PyFuncArgs) -> PyResult { // more or less __new__ operator let type_ref = args.shift(); - let dict = vm.new_dict(); - let obj = PyObject::new(PyObjectPayload::Instance { dict: dict }, type_ref.clone()); + let obj = vm.ctx.new_instance(type_ref.clone(), None); Ok(obj) } -pub fn create_object(type_type: PyObjectRef, object_type: PyObjectRef, dict_type: PyObjectRef) { +pub fn create_object(type_type: PyObjectRef, object_type: PyObjectRef, _dict_type: PyObjectRef) { (*object_type.borrow_mut()).payload = PyObjectPayload::Class { name: String::from("object"), - dict: objdict::new(dict_type), + dict: RefCell::new(HashMap::new()), mro: vec![], }; (*object_type.borrow_mut()).typ = Some(type_type.clone()); @@ -62,15 +62,14 @@ fn object_delattr(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { ] ); - // Get dict: - let dict = match zelf.borrow().payload { - PyObjectPayload::Class { ref dict, .. } => dict.clone(), - PyObjectPayload::Instance { ref dict, .. } => dict.clone(), - _ => return Err(vm.new_type_error("TypeError: no dictionary.".to_string())), - }; - - // Delete attr from dict: - vm.call_method(&dict, "__delitem__", vec![attr.clone()]) + match zelf.borrow().payload { + PyObjectPayload::Class { ref dict, .. } | PyObjectPayload::Instance { ref dict, .. } => { + let attr_name = objstr::get_value(attr); + dict.borrow_mut().remove(&attr_name); + Ok(vm.get_none()) + } + _ => Err(vm.new_type_error("TypeError: no dictionary.".to_string())), + } } fn object_str(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { @@ -86,7 +85,9 @@ fn object_repr(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { } pub fn init(context: &PyContext) { - let ref object = context.object; + let object = &context.object; + let object_doc = "The most base type"; + context.set_attr(&object, "__new__", context.new_rustfunc(new_instance)); context.set_attr(&object, "__init__", context.new_rustfunc(object_init)); context.set_attr(&object, "__eq__", context.new_rustfunc(object_eq)); @@ -105,6 +106,7 @@ pub fn init(context: &PyContext) { "__getattribute__", context.new_rustfunc(object_getattribute), ); + context.set_attr(&object, "__doc__", context.new_str(object_doc.to_string())); } fn object_init(vm: &mut VirtualMachine, _args: PyFuncArgs) -> PyResult { @@ -113,8 +115,13 @@ fn object_init(vm: &mut VirtualMachine, _args: PyFuncArgs) -> PyResult { fn object_dict(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { match args.args[0].borrow().payload { - PyObjectPayload::Class { ref dict, .. } => Ok(dict.clone()), - PyObjectPayload::Instance { ref dict, .. } => Ok(dict.clone()), + PyObjectPayload::Class { ref dict, .. } | PyObjectPayload::Instance { ref dict, .. } => { + let new_dict = vm.new_dict(); + for (attr, value) in dict.borrow().iter() { + vm.ctx.set_item(&new_dict, &attr, value.clone()); + } + Ok(new_dict) + } _ => Err(vm.new_type_error("TypeError: no dictionary.".to_string())), } } @@ -151,21 +158,19 @@ fn object_getattribute(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { Ok(obj_attr) } else if let Some(attr) = cls.get_attr(&name) { vm.call_get_descriptor(attr, obj.clone()) + } else if let Some(getter) = cls.get_attr("__getattr__") { + vm.invoke( + getter, + PyFuncArgs { + args: vec![cls, name_str.clone()], + kwargs: vec![], + }, + ) } else { - if let Some(getter) = cls.get_attr("__getattr__") { - vm.invoke( - getter, - PyFuncArgs { - args: vec![cls, name_str.clone()], - kwargs: vec![], - }, - ) - } else { - let attribute_error = vm.context().exceptions.attribute_error.clone(); - Err(vm.new_exception( - attribute_error, - format!("{} has no attribute '{}'", obj.borrow(), name), - )) - } + let attribute_error = vm.context().exceptions.attribute_error.clone(); + Err(vm.new_exception( + attribute_error, + format!("{} has no attribute '{}'", obj.borrow(), name), + )) } } diff --git a/vm/src/obj/objproperty.rs b/vm/src/obj/objproperty.rs index 71c1b5ab9..ada639270 100644 --- a/vm/src/obj/objproperty.rs +++ b/vm/src/obj/objproperty.rs @@ -2,14 +2,41 @@ */ -use super::super::pyobject::{ - PyContext, PyFuncArgs, PyObject, PyObjectPayload, PyObjectRef, PyResult, TypeProtocol, -}; +use super::super::pyobject::{PyContext, PyFuncArgs, PyObjectRef, PyResult, TypeProtocol}; use super::super::vm::VirtualMachine; use super::objtype; pub fn init(context: &PyContext) { - let ref property_type = context.property_type; + let property_type = &context.property_type; + + let property_doc = + "Property attribute.\n\n \ + fget\n \ + function to be used for getting an attribute value\n \ + fset\n \ + function to be used for setting an attribute value\n \ + fdel\n \ + function to be used for del\'ing an attribute\n \ + doc\n \ + docstring\n\n\ + Typical use is to define a managed attribute x:\n\n\ + class C(object):\n \ + def getx(self): return self._x\n \ + def setx(self, value): self._x = value\n \ + def delx(self): del self._x\n \ + x = property(getx, setx, delx, \"I\'m the \'x\' property.\")\n\n\ + Decorators make defining new properties or modifying existing ones easy:\n\n\ + class C(object):\n \ + @property\n \ + def x(self):\n \"I am the \'x\' property.\"\n \ + return self._x\n \ + @x.setter\n \ + def x(self, value):\n \ + self._x = value\n \ + @x.deleter\n \ + def x(self):\n \ + del self._x"; + context.set_attr( &property_type, "__get__", @@ -20,6 +47,11 @@ pub fn init(context: &PyContext) { "__new__", context.new_rustfunc(property_new), ); + context.set_attr( + &property_type, + "__doc__", + context.new_str(property_doc.to_string()), + ); // TODO: how to handle __set__ ? } @@ -55,12 +87,7 @@ fn property_new(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { trace!("property.__new__ {:?}", args.args); arg_check!(vm, args, required = [(cls, None), (fget, None)]); - let py_obj = PyObject::new( - PyObjectPayload::Instance { - dict: vm.ctx.new_dict(), - }, - cls.clone(), - ); + let py_obj = vm.ctx.new_instance(cls.clone(), None); vm.ctx.set_attr(&py_obj, "fget", fget.clone()); Ok(py_obj) } diff --git a/vm/src/obj/objrange.rs b/vm/src/obj/objrange.rs new file mode 100644 index 000000000..221e98da5 --- /dev/null +++ b/vm/src/obj/objrange.rs @@ -0,0 +1,368 @@ +use super::super::pyobject::{ + PyContext, PyFuncArgs, PyObject, PyObjectPayload, PyObjectRef, PyResult, TypeProtocol, +}; +use super::super::vm::VirtualMachine; +use super::objint; +use super::objtype; +use num_bigint::{BigInt, Sign}; +use num_integer::Integer; +use num_traits::{One, Signed, ToPrimitive, Zero}; +use std::ops::Mul; + +#[derive(Debug, Clone)] +pub struct RangeType { + // Unfortunately Rust's built in range type doesn't support things like indexing + // or ranges where start > end so we need to roll our own. + pub start: BigInt, + pub end: BigInt, + pub step: BigInt, +} + +impl RangeType { + #[inline] + pub fn try_len(&self) -> Option { + match self.step.sign() { + Sign::Plus if self.start < self.end => ((&self.end - &self.start - 1usize) + / &self.step) + .to_usize() + .map(|sz| sz + 1), + Sign::Minus if self.start > self.end => ((&self.start - &self.end - 1usize) + / (-&self.step)) + .to_usize() + .map(|sz| sz + 1), + _ => Some(0), + } + } + + #[inline] + pub fn len(&self) -> usize { + self.try_len().unwrap() + } + + #[inline] + fn offset(&self, value: &BigInt) -> Option { + match self.step.sign() { + Sign::Plus if value >= &self.start && value < &self.end => Some(value - &self.start), + Sign::Minus if value <= &self.start && value > &self.end => Some(&self.start - value), + _ => None, + } + } + + #[inline] + pub fn contains(&self, value: &BigInt) -> bool { + match self.offset(value) { + Some(ref offset) => offset.is_multiple_of(&self.step), + None => false, + } + } + + #[inline] + pub fn index_of(&self, value: &BigInt) -> Option { + match self.offset(value) { + Some(ref offset) if offset.is_multiple_of(&self.step) => { + Some((offset / &self.step).abs()) + } + Some(_) | None => None, + } + } + + #[inline] + pub fn is_empty(&self) -> bool { + (self.start <= self.end && self.step.is_negative()) + || (self.start >= self.end && self.step.is_positive()) + } + + #[inline] + pub fn forward(&self) -> bool { + self.start < self.end + } + + #[inline] + pub fn get<'a, T>(&'a self, index: T) -> Option + where + &'a BigInt: Mul, + { + let result = &self.start + &self.step * index; + + if (self.forward() && !self.is_empty() && result < self.end) + || (!self.forward() && !self.is_empty() && result > self.end) + { + Some(result) + } else { + None + } + } + + #[inline] + pub fn reversed(&self) -> Self { + match self.step.sign() { + Sign::Plus => RangeType { + start: &self.end - 1, + end: &self.start - 1, + step: -&self.step, + }, + Sign::Minus => RangeType { + start: &self.end + 1, + end: &self.start + 1, + step: -&self.step, + }, + Sign::NoSign => unreachable!(), + } + } + + pub fn repr(&self) -> String { + if self.step == BigInt::one() { + format!("range({}, {})", self.start, self.end) + } else { + format!("range({}, {}, {})", self.start, self.end, self.step) + } + } +} + +pub fn init(context: &PyContext) { + let ref range_type = context.range_type; + + let range_doc = "range(stop) -> range object\n\ + range(start, stop[, step]) -> range object\n\n\ + Return an object that produces a sequence of integers from start (inclusive)\n\ + to stop (exclusive) by step. range(i, j) produces i, i+1, i+2, ..., j-1.\n\ + start defaults to 0, and stop is omitted! range(4) produces 0, 1, 2, 3.\n\ + These are exactly the valid indices for a list of 4 elements.\n\ + When step is given, it specifies the increment (or decrement)."; + + context.set_attr(&range_type, "__new__", context.new_rustfunc(range_new)); + context.set_attr(&range_type, "__iter__", context.new_rustfunc(range_iter)); + context.set_attr( + &range_type, + "__reversed__", + context.new_rustfunc(range_reversed), + ); + context.set_attr( + &range_type, + "__doc__", + context.new_str(range_doc.to_string()), + ); + context.set_attr(&range_type, "__len__", context.new_rustfunc(range_len)); + context.set_attr( + &range_type, + "__getitem__", + context.new_rustfunc(range_getitem), + ); + context.set_attr(&range_type, "__repr__", context.new_rustfunc(range_repr)); + context.set_attr(&range_type, "__bool__", context.new_rustfunc(range_bool)); + context.set_attr( + &range_type, + "__contains__", + context.new_rustfunc(range_contains), + ); + context.set_attr(&range_type, "index", context.new_rustfunc(range_index)); +} + +fn range_new(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!( + vm, + args, + required = [(cls, None), (first, Some(vm.ctx.int_type()))], + optional = [ + (second, Some(vm.ctx.int_type())), + (step, Some(vm.ctx.int_type())) + ] + ); + + let start = if let Some(_) = second { + objint::get_value(first) + } else { + BigInt::zero() + }; + + let end = if let Some(pyint) = second { + objint::get_value(pyint) + } else { + objint::get_value(first) + }; + + let step = if let Some(pyint) = step { + objint::get_value(pyint) + } else { + BigInt::one() + }; + + if step.is_zero() { + Err(vm.new_value_error("range with 0 step size".to_string())) + } else { + Ok(PyObject::new( + PyObjectPayload::Range { + range: RangeType { start, end, step }, + }, + cls.clone(), + )) + } +} + +fn range_iter(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!(vm, args, required = [(range, Some(vm.ctx.range_type()))]); + + Ok(PyObject::new( + PyObjectPayload::Iterator { + position: 0, + iterated_obj: range.clone(), + }, + vm.ctx.iter_type(), + )) +} + +fn range_reversed(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!(vm, args, required = [(zelf, Some(vm.ctx.range_type()))]); + + let range = match zelf.borrow().payload { + PyObjectPayload::Range { ref range } => range.reversed(), + _ => unreachable!(), + }; + + Ok(PyObject::new( + PyObjectPayload::Iterator { + position: 0, + iterated_obj: PyObject::new(PyObjectPayload::Range { range }, vm.ctx.range_type()), + }, + vm.ctx.iter_type(), + )) +} + +fn range_len(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!(vm, args, required = [(zelf, Some(vm.ctx.range_type()))]); + + if let Some(len) = match zelf.borrow().payload { + PyObjectPayload::Range { ref range } => range.try_len(), + _ => unreachable!(), + } { + Ok(vm.ctx.new_int(len)) + } else { + Err(vm.new_overflow_error("Python int too large to convert to Rust usize".to_string())) + } +} + +fn range_getitem(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!( + vm, + args, + required = [(zelf, Some(vm.ctx.range_type())), (subscript, None)] + ); + let zrange = if let PyObjectPayload::Range { ref range } = zelf.borrow().payload { + range.clone() + } else { + unreachable!() + }; + + match subscript.borrow().payload { + PyObjectPayload::Integer { ref value } => { + if let Some(int) = zrange.get(value) { + Ok(vm.ctx.new_int(int)) + } else { + Err(vm.new_index_error("range object index out of range".to_string())) + } + } + PyObjectPayload::Slice { + ref start, + ref stop, + ref step, + } => { + let new_start = if let Some(int) = start { + if let Some(i) = zrange.get(int) { + i + } else { + zrange.start.clone() + } + } else { + zrange.start.clone() + }; + + let new_end = if let Some(int) = stop { + if let Some(i) = zrange.get(int) { + i + } else { + zrange.end + } + } else { + zrange.end + }; + + let new_step = if let Some(int) = step { + int * zrange.step + } else { + zrange.step + }; + + Ok(PyObject::new( + PyObjectPayload::Range { + range: RangeType { + start: new_start, + end: new_end, + step: new_step, + }, + }, + vm.ctx.range_type(), + )) + } + + _ => Err(vm.new_type_error("range indices must be integer or slice".to_string())), + } +} + +fn range_repr(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!(vm, args, required = [(zelf, Some(vm.ctx.range_type()))]); + + let s = match zelf.borrow().payload { + PyObjectPayload::Range { ref range } => range.repr(), + _ => unreachable!(), + }; + + Ok(vm.ctx.new_str(s)) +} + +fn range_bool(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!(vm, args, required = [(zelf, Some(vm.ctx.range_type()))]); + + let len = match zelf.borrow().payload { + PyObjectPayload::Range { ref range } => range.len(), + _ => unreachable!(), + }; + + Ok(vm.ctx.new_bool(len > 0)) +} + +fn range_contains(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!( + vm, + args, + required = [(zelf, Some(vm.ctx.range_type())), (needle, None)] + ); + + if let PyObjectPayload::Range { ref range } = zelf.borrow().payload { + Ok(vm.ctx.new_bool(match needle.borrow().payload { + PyObjectPayload::Integer { ref value } => range.contains(value), + _ => false, + })) + } else { + unreachable!() + } +} + +fn range_index(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!( + vm, + args, + required = [(zelf, Some(vm.ctx.range_type())), (needle, None)] + ); + + if let PyObjectPayload::Range { ref range } = zelf.borrow().payload { + match needle.borrow().payload { + PyObjectPayload::Integer { ref value } => match range.index_of(value) { + Some(idx) => Ok(vm.ctx.new_int(idx)), + None => Err(vm.new_value_error(format!("{} is not in range", value))), + }, + _ => Err(vm.new_value_error("sequence.index(x): x not in sequence".to_string())), + } + } else { + unreachable!() + } +} diff --git a/vm/src/obj/objsequence.rs b/vm/src/obj/objsequence.rs index cd26391a9..5d85b12a0 100644 --- a/vm/src/obj/objsequence.rs +++ b/vm/src/obj/objsequence.rs @@ -1,48 +1,98 @@ use super::super::pyobject::{PyObject, PyObjectPayload, PyObjectRef, PyResult, TypeProtocol}; use super::super::vm::VirtualMachine; use super::objbool; -use num_traits::ToPrimitive; +use super::objint; +use num_bigint::BigInt; +use num_traits::{One, Signed, ToPrimitive, Zero}; use std::cell::{Ref, RefMut}; use std::marker::Sized; -use std::ops::{Deref, DerefMut}; +use std::ops::{Deref, DerefMut, Range}; pub trait PySliceableSequence { - fn do_slice(&self, start: usize, stop: usize) -> Self; - fn do_stepped_slice(&self, start: usize, stop: usize, step: usize) -> Self; + fn do_slice(&self, range: Range) -> Self; + fn do_slice_reverse(&self, range: Range) -> Self; + fn do_stepped_slice(&self, range: Range, step: usize) -> Self; + fn do_stepped_slice_reverse(&self, range: Range, step: usize) -> Self; + fn empty() -> Self; fn len(&self) -> usize; - fn get_pos(&self, p: i32) -> usize { + fn get_pos(&self, p: i32) -> Option { if p < 0 { - self.len() - ((-p) as usize) - } else if p as usize > self.len() { - // This is for the slicing case where the end element is greater than the length of the - // sequence - self.len() + if -p as usize > self.len() { + None + } else { + Some(self.len() - ((-p) as usize)) + } + } else if p as usize >= self.len() { + None } else { - p as usize + Some(p as usize) } } - fn get_slice_items(&self, slice: &PyObjectRef) -> Self + + fn get_slice_pos(&self, slice_pos: &BigInt) -> usize { + if let Some(pos) = slice_pos.to_i32() { + if let Some(index) = self.get_pos(pos) { + // within bounds + return index; + } + } + + if slice_pos.is_negative() { + 0 + } else { + self.len() + } + } + + fn get_slice_range(&self, start: &Option, stop: &Option) -> Range { + let start = start.as_ref().map(|x| self.get_slice_pos(x)).unwrap_or(0); + let stop = stop + .as_ref() + .map(|x| self.get_slice_pos(x)) + .unwrap_or(self.len()); + + start..stop + } + + fn get_slice_items( + &self, + vm: &mut VirtualMachine, + slice: &PyObjectRef, + ) -> Result where Self: Sized, { // TODO: we could potentially avoid this copy and use slice match &(slice.borrow()).payload { PyObjectPayload::Slice { start, stop, step } => { - let start = match start { - &Some(start) => self.get_pos(start), - &None => 0, - }; - let stop = match stop { - &Some(stop) => self.get_pos(stop), - &None => self.len() as usize, - }; - match step { - &None | &Some(1) => self.do_slice(start, stop), - &Some(num) => { - if num < 0 { - unimplemented!("negative step indexing not yet supported") - }; - self.do_stepped_slice(start, stop, num as usize) + let step = step.clone().unwrap_or(BigInt::one()); + if step.is_zero() { + Err(vm.new_value_error("slice step cannot be zero".to_string())) + } else if step.is_positive() { + let range = self.get_slice_range(start, stop); + if range.start < range.end { + match step.to_i32() { + Some(1) => Ok(self.do_slice(range)), + Some(num) => Ok(self.do_stepped_slice(range, num as usize)), + None => Ok(self.do_slice(range.start..range.start + 1)), + } + } else { + Ok(Self::empty()) + } + } else { + // calculate the range for the reverse slice, first the bounds needs to be made + // exclusive around stop, the lower number + let start = start.as_ref().map(|x| x + 1); + let stop = stop.as_ref().map(|x| x + 1); + let range = self.get_slice_range(&stop, &start); + if range.start < range.end { + match (-step).to_i32() { + Some(1) => Ok(self.do_slice_reverse(range)), + Some(num) => Ok(self.do_stepped_slice_reverse(range, num as usize)), + None => Ok(self.do_slice(range.end - 1..range.end)), + } + } else { + Ok(Self::empty()) } } } @@ -51,13 +101,29 @@ pub trait PySliceableSequence { } } -impl PySliceableSequence for Vec { - fn do_slice(&self, start: usize, stop: usize) -> Self { - self[start..stop].to_vec() +impl PySliceableSequence for Vec { + fn do_slice(&self, range: Range) -> Self { + self[range].to_vec() } - fn do_stepped_slice(&self, start: usize, stop: usize, step: usize) -> Self { - self[start..stop].iter().step_by(step).cloned().collect() + + fn do_slice_reverse(&self, range: Range) -> Self { + let mut slice = self[range].to_vec(); + slice.reverse(); + slice } + + fn do_stepped_slice(&self, range: Range, step: usize) -> Self { + self[range].iter().step_by(step).cloned().collect() + } + + fn do_stepped_slice_reverse(&self, range: Range, step: usize) -> Self { + self[range].iter().rev().step_by(step).cloned().collect() + } + + fn empty() -> Self { + Vec::new() + } + fn len(&self) -> usize { self.len() } @@ -70,25 +136,24 @@ pub fn get_item( subscript: PyObjectRef, ) -> PyResult { match &(subscript.borrow()).payload { - PyObjectPayload::Integer { value } => { - let value = value.to_i32().unwrap(); - let pos_index = elements.to_vec().get_pos(value); - if pos_index < elements.len() { - let obj = elements[pos_index].clone(); - Ok(obj) - } else { - let value_error = vm.context().exceptions.value_error.clone(); - Err(vm.new_exception(value_error, "Index out of bounds!".to_string())) + PyObjectPayload::Integer { value } => match value.to_i32() { + Some(value) => { + if let Some(pos_index) = elements.to_vec().get_pos(value) { + let obj = elements[pos_index].clone(); + Ok(obj) + } else { + Err(vm.new_index_error("Index out of bounds!".to_string())) + } } - } - PyObjectPayload::Slice { - start: _, - stop: _, - step: _, - } => Ok(PyObject::new( + None => { + Err(vm.new_index_error("cannot fit 'int' into an index-sized integer".to_string())) + } + }, + + PyObjectPayload::Slice { .. } => Ok(PyObject::new( match &(sequence.borrow()).payload { - PyObjectPayload::Sequence { elements: _ } => PyObjectPayload::Sequence { - elements: elements.to_vec().get_slice_items(&subscript), + PyObjectPayload::Sequence { .. } => PyObjectPayload::Sequence { + elements: elements.to_vec().get_slice_items(vm, &subscript)?, }, ref payload => panic!("sequence get_item called for non-sequence: {:?}", payload), }, @@ -103,8 +168,8 @@ pub fn get_item( pub fn seq_equal( vm: &mut VirtualMachine, - zelf: &Vec, - other: &Vec, + zelf: &[PyObjectRef], + other: &[PyObjectRef], ) -> Result { if zelf.len() == other.len() { for (a, b) in Iterator::zip(zelf.iter(), other.iter()) { @@ -120,6 +185,115 @@ pub fn seq_equal( } } +pub fn seq_lt( + vm: &mut VirtualMachine, + zelf: &[PyObjectRef], + other: &[PyObjectRef], +) -> Result { + if zelf.len() == other.len() { + for (a, b) in Iterator::zip(zelf.iter(), other.iter()) { + let lt = vm.call_method(&a.clone(), "__lt__", vec![b.clone()])?; + let value = objbool::boolval(vm, lt)?; + if !value { + return Ok(false); + } + } + Ok(true) + } else { + // This case is more complicated because it can still return true if + // `zelf` is the head of `other` e.g. [1,2,3] < [1,2,3,4] should return true + let mut head = true; // true if `zelf` is the head of `other` + + for (a, b) in Iterator::zip(zelf.iter(), other.iter()) { + let lt = vm.call_method(&a.clone(), "__lt__", vec![b.clone()])?; + let eq = vm.call_method(&a.clone(), "__eq__", vec![b.clone()])?; + let lt_value = objbool::boolval(vm, lt)?; + let eq_value = objbool::boolval(vm, eq)?; + + if !lt_value && !eq_value { + return Ok(false); + } else if !eq_value { + head = false; + } + } + + if head { + Ok(zelf.len() < other.len()) + } else { + Ok(true) + } + } +} + +pub fn seq_gt( + vm: &mut VirtualMachine, + zelf: &[PyObjectRef], + other: &[PyObjectRef], +) -> Result { + if zelf.len() == other.len() { + for (a, b) in Iterator::zip(zelf.iter(), other.iter()) { + let gt = vm.call_method(&a.clone(), "__gt__", vec![b.clone()])?; + let value = objbool::boolval(vm, gt)?; + if !value { + return Ok(false); + } + } + Ok(true) + } else { + let mut head = true; // true if `other` is the head of `zelf` + for (a, b) in Iterator::zip(zelf.iter(), other.iter()) { + // This case is more complicated because it can still return true if + // `other` is the head of `zelf` e.g. [1,2,3,4] > [1,2,3] should return true + let gt = vm.call_method(&a.clone(), "__gt__", vec![b.clone()])?; + let eq = vm.call_method(&a.clone(), "__eq__", vec![b.clone()])?; + let gt_value = objbool::boolval(vm, gt)?; + let eq_value = objbool::boolval(vm, eq)?; + + if !gt_value && !eq_value { + return Ok(false); + } else if !eq_value { + head = false; + } + } + + if head { + Ok(zelf.len() > other.len()) + } else { + Ok(true) + } + } +} + +pub fn seq_ge( + vm: &mut VirtualMachine, + zelf: &[PyObjectRef], + other: &[PyObjectRef], +) -> Result { + Ok(seq_gt(vm, zelf, other)? || seq_equal(vm, zelf, other)?) +} + +pub fn seq_le( + vm: &mut VirtualMachine, + zelf: &[PyObjectRef], + other: &[PyObjectRef], +) -> Result { + Ok(seq_lt(vm, zelf, other)? || seq_equal(vm, zelf, other)?) +} + +pub fn seq_mul(elements: &[PyObjectRef], product: &PyObjectRef) -> Vec { + let counter = objint::get_value(&product).to_isize().unwrap(); + + let current_len = elements.len(); + let new_len = counter.max(0) as usize * current_len; + let mut new_elements = Vec::with_capacity(new_len); + + for _ in 0..counter { + new_elements.extend(elements.clone().to_owned()); + } + + new_elements +} + pub fn get_elements<'a>(obj: &'a PyObjectRef) -> impl Deref> + 'a { Ref::map(obj.borrow(), |x| { if let PyObjectPayload::Sequence { ref elements } = x.payload { diff --git a/vm/src/obj/objset.rs b/vm/src/obj/objset.rs index 14779d718..249004f85 100644 --- a/vm/src/obj/objset.rs +++ b/vm/src/obj/objset.rs @@ -11,7 +11,6 @@ use super::objiter; use super::objstr; use super::objtype; use num_bigint::BigInt; -use num_bigint::ToBigInt; use std::collections::HashMap; pub fn get_elements(obj: &PyObjectRef) -> HashMap { @@ -130,20 +129,15 @@ fn set_new(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { Some(iterable) => { let mut elements = HashMap::new(); let iterator = objiter::get_iter(vm, iterable)?; - loop { - match vm.call_method(&iterator, "__next__", vec![]) { - Ok(v) => { - insert_into_set(vm, &mut elements, &v).unwrap(); - } - _ => break, - } + while let Ok(v) = vm.call_method(&iterator, "__next__", vec![]) { + insert_into_set(vm, &mut elements, &v).unwrap(); } elements } }; Ok(PyObject::new( - PyObjectPayload::Set { elements: elements }, + PyObjectPayload::Set { elements }, cls.clone(), )) } @@ -152,14 +146,14 @@ fn set_len(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { trace!("set.len called with: {:?}", args); arg_check!(vm, args, required = [(s, Some(vm.ctx.set_type()))]); let elements = get_elements(s); - Ok(vm.context().new_int(elements.len().to_bigint().unwrap())) + Ok(vm.context().new_int(elements.len())) } fn set_repr(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(o, Some(vm.ctx.set_type()))]); let elements = get_elements(o); - let s = if elements.len() == 0 { + let s = if elements.is_empty() { "set()".to_string() } else { let mut str_parts = vec![]; @@ -193,11 +187,104 @@ pub fn set_contains(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { Ok(vm.new_bool(false)) } +fn set_eq(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { + return set_compare_inner( + vm, + args, + &|zelf: usize, other: usize| -> bool { zelf != other }, + false, + ); +} + +fn set_ge(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { + return set_compare_inner( + vm, + args, + &|zelf: usize, other: usize| -> bool { zelf < other }, + false, + ); +} + +fn set_gt(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { + return set_compare_inner( + vm, + args, + &|zelf: usize, other: usize| -> bool { zelf <= other }, + false, + ); +} + +fn set_le(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { + return set_compare_inner( + vm, + args, + &|zelf: usize, other: usize| -> bool { zelf < other }, + true, + ); +} + +fn set_lt(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { + return set_compare_inner( + vm, + args, + &|zelf: usize, other: usize| -> bool { zelf <= other }, + true, + ); +} + +fn set_compare_inner( + vm: &mut VirtualMachine, + args: PyFuncArgs, + size_func: &Fn(usize, usize) -> bool, + swap: bool, +) -> PyResult { + arg_check!( + vm, + args, + required = [ + (zelf, Some(vm.ctx.set_type())), + (other, Some(vm.ctx.set_type())) + ] + ); + + let get_zelf = |swap: bool| -> &PyObjectRef { + if swap { + other + } else { + zelf + } + }; + let get_other = |swap: bool| -> &PyObjectRef { + if swap { + zelf + } else { + other + } + }; + + let zelf_elements = get_elements(get_zelf(swap)); + let other_elements = get_elements(get_other(swap)); + if size_func(zelf_elements.len(), other_elements.len()) { + return Ok(vm.new_bool(false)); + } + for element in other_elements.iter() { + match vm.call_method(get_zelf(swap), "__contains__", vec![element.1.clone()]) { + Ok(value) => { + if !objbool::get_value(&value) { + return Ok(vm.new_bool(false)); + } + } + Err(_) => return Err(vm.new_type_error("".to_string())), + } + } + Ok(vm.new_bool(true)) +} + fn frozenset_repr(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(o, Some(vm.ctx.frozenset_type()))]); let elements = get_elements(o); - let s = if elements.len() == 0 { + let s = if elements.is_empty() { "frozenset()".to_string() } else { let mut str_parts = vec![]; @@ -212,7 +299,12 @@ fn frozenset_repr(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { } pub fn init(context: &PyContext) { - let ref set_type = context.set_type; + let set_type = &context.set_type; + + let set_doc = "set() -> new empty set object\n\ + set(iterable) -> new set object\n\n\ + Build an unordered collection of unique elements."; + context.set_attr( &set_type, "__contains__", @@ -221,16 +313,34 @@ pub fn init(context: &PyContext) { context.set_attr(&set_type, "__len__", context.new_rustfunc(set_len)); context.set_attr(&set_type, "__new__", context.new_rustfunc(set_new)); context.set_attr(&set_type, "__repr__", context.new_rustfunc(set_repr)); + context.set_attr(&set_type, "__eq__", context.new_rustfunc(set_eq)); + context.set_attr(&set_type, "__ge__", context.new_rustfunc(set_ge)); + context.set_attr(&set_type, "__gt__", context.new_rustfunc(set_gt)); + context.set_attr(&set_type, "__le__", context.new_rustfunc(set_le)); + context.set_attr(&set_type, "__lt__", context.new_rustfunc(set_lt)); + context.set_attr(&set_type, "issubset", context.new_rustfunc(set_le)); + context.set_attr(&set_type, "issuperset", context.new_rustfunc(set_ge)); + context.set_attr(&set_type, "__doc__", context.new_str(set_doc.to_string())); context.set_attr(&set_type, "add", context.new_rustfunc(set_add)); context.set_attr(&set_type, "remove", context.new_rustfunc(set_remove)); - let ref frozenset_type = context.frozenset_type; + let frozenset_type = &context.frozenset_type; + + let frozenset_doc = "frozenset() -> empty frozenset object\n\ + frozenset(iterable) -> frozenset object\n\n\ + Build an immutable unordered collection of unique elements."; + context.set_attr( &frozenset_type, "__contains__", context.new_rustfunc(set_contains), ); context.set_attr(&frozenset_type, "__len__", context.new_rustfunc(set_len)); + context.set_attr( + &frozenset_type, + "__doc__", + context.new_str(frozenset_doc.to_string()), + ); context.set_attr( &frozenset_type, "__repr__", diff --git a/vm/src/obj/objslice.rs b/vm/src/obj/objslice.rs new file mode 100644 index 000000000..547e252ba --- /dev/null +++ b/vm/src/obj/objslice.rs @@ -0,0 +1,95 @@ +use super::super::pyobject::{ + PyContext, PyFuncArgs, PyObject, PyObjectPayload, PyObjectRef, PyResult, TypeProtocol, +}; +use super::super::vm::VirtualMachine; +use super::objint; +use super::objtype; // Required for arg_check! to use isinstance +use num_bigint::BigInt; + +fn slice_new(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { + no_kwargs!(vm, args); + let (cls, start, stop, step): ( + &PyObjectRef, + Option<&PyObjectRef>, + Option<&PyObjectRef>, + Option<&PyObjectRef>, + ) = match args.args.len() { + 0 | 1 => Err(vm.new_type_error("slice() must have at least one arguments.".to_owned())), + 2 => { + arg_check!( + vm, + args, + required = [ + (cls, Some(vm.ctx.type_type())), + (stop, Some(vm.ctx.int_type())) + ] + ); + Ok((cls, None, Some(stop), None)) + } + _ => { + arg_check!( + vm, + args, + required = [ + (cls, Some(vm.ctx.type_type())), + (start, Some(vm.ctx.int_type())), + (stop, Some(vm.ctx.int_type())) + ], + optional = [(step, Some(vm.ctx.int_type()))] + ); + Ok((cls, Some(start), Some(stop), step)) + } + }?; + Ok(PyObject::new( + PyObjectPayload::Slice { + start: start.map(|x| objint::get_value(x)), + stop: stop.map(|x| objint::get_value(x)), + step: step.map(|x| objint::get_value(x)), + }, + cls.clone(), + )) +} + +fn get_property_value(vm: &mut VirtualMachine, value: &Option) -> PyResult { + if let Some(value) = value { + Ok(vm.ctx.new_int(value.clone())) + } else { + Ok(vm.get_none()) + } +} + +fn slice_start(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!(vm, args, required = [(slice, Some(vm.ctx.slice_type()))]); + if let PyObjectPayload::Slice { start, .. } = &slice.borrow().payload { + get_property_value(vm, start) + } else { + panic!("Slice has incorrect payload."); + } +} + +fn slice_stop(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!(vm, args, required = [(slice, Some(vm.ctx.slice_type()))]); + if let PyObjectPayload::Slice { stop, .. } = &slice.borrow().payload { + get_property_value(vm, stop) + } else { + panic!("Slice has incorrect payload."); + } +} + +fn slice_step(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!(vm, args, required = [(slice, Some(vm.ctx.slice_type()))]); + if let PyObjectPayload::Slice { step, .. } = &slice.borrow().payload { + get_property_value(vm, step) + } else { + panic!("Slice has incorrect payload."); + } +} + +pub fn init(context: &PyContext) { + let zip_type = &context.slice_type; + + context.set_attr(zip_type, "__new__", context.new_rustfunc(slice_new)); + context.set_attr(zip_type, "start", context.new_property(slice_start)); + context.set_attr(zip_type, "stop", context.new_property(slice_stop)); + context.set_attr(zip_type, "step", context.new_property(slice_step)); +} diff --git a/vm/src/obj/objstr.rs b/vm/src/obj/objstr.rs index 707ce42ba..a49e7a2c6 100644 --- a/vm/src/obj/objstr.rs +++ b/vm/src/obj/objstr.rs @@ -6,14 +6,17 @@ use super::super::vm::VirtualMachine; use super::objint; use super::objsequence::PySliceableSequence; use super::objtype; -use num_bigint::ToBigInt; use num_traits::ToPrimitive; use std::hash::{Hash, Hasher}; +use std::ops::Range; // rust's builtin to_lowercase isn't sufficient for casefold extern crate caseless; +extern crate unicode_segmentation; + +use self::unicode_segmentation::UnicodeSegmentation; pub fn init(context: &PyContext) { - let ref str_type = context.str_type; + let str_type = &context.str_type; context.set_attr(&str_type, "__add__", context.new_rustfunc(str_add)); context.set_attr(&str_type, "__eq__", context.new_rustfunc(str_eq)); context.set_attr( @@ -53,6 +56,7 @@ pub fn init(context: &PyContext) { context.set_attr(&str_type, "isalnum", context.new_rustfunc(str_isalnum)); context.set_attr(&str_type, "isnumeric", context.new_rustfunc(str_isnumeric)); context.set_attr(&str_type, "isdigit", context.new_rustfunc(str_isdigit)); + context.set_attr(&str_type, "isdecimal", context.new_rustfunc(str_isdecimal)); context.set_attr(&str_type, "title", context.new_rustfunc(str_title)); context.set_attr(&str_type, "swapcase", context.new_rustfunc(str_swapcase)); context.set_attr(&str_type, "isalpha", context.new_rustfunc(str_isalpha)); @@ -204,7 +208,7 @@ fn str_add(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { } fn str_format(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - if args.args.len() == 0 { + if args.args.is_empty() { return Err( vm.new_type_error("descriptor 'format' of 'str' object needs an argument".to_string()) ); @@ -234,9 +238,9 @@ fn str_format(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { fn call_object_format( vm: &mut VirtualMachine, argument: PyObjectRef, - format_spec: &String, + format_spec: &str, ) -> PyResult { - let returned_type = vm.ctx.new_str(format_spec.clone()); + let returned_type = vm.ctx.new_str(format_spec.to_string()); let result = vm.call_method(&argument, "__format__", vec![returned_type])?; if !objtype::isinstance(&result, &vm.ctx.str_type()) { let result_type = result.typ(); @@ -304,13 +308,13 @@ fn str_hash(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { let mut hasher = std::collections::hash_map::DefaultHasher::new(); value.hash(&mut hasher); let hash = hasher.finish(); - Ok(vm.ctx.new_int(hash.to_bigint().unwrap())) + Ok(vm.ctx.new_int(hash)) } fn str_len(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(s, Some(vm.ctx.str_type()))]); let sv = get_value(s); - Ok(vm.ctx.new_int(sv.len().to_bigint().unwrap())) + Ok(vm.ctx.new_int(sv.chars().count())) } fn str_mul(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { @@ -446,12 +450,8 @@ fn str_isidentifier(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { && !value.chars().nth(0).unwrap().is_digit(10) { for c in value.chars() { - if c != "_".chars().nth(0).unwrap() { - if !c.is_digit(10) { - if !c.is_alphabetic() { - is_identifier = false; - } - } + if c != "_".chars().nth(0).unwrap() && !c.is_digit(10) && !c.is_alphabetic() { + is_identifier = false; } } } else { @@ -464,29 +464,37 @@ fn str_isidentifier(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { // which is why isspace is using is_ascii_whitespace. Same for isupper & islower fn str_isspace(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(s, Some(vm.ctx.str_type()))]); - let is_whitespace = get_value(&s).chars().all(|c| c.is_ascii_whitespace()); - Ok(vm.ctx.new_bool(is_whitespace)) + let value = get_value(&s); + Ok(vm + .ctx + .new_bool(!value.is_empty() && value.chars().all(|c| c.is_ascii_whitespace()))) } fn str_isupper(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(s, Some(vm.ctx.str_type()))]); - let is_upper = get_value(&s) - .chars() - .filter(|x| !x.is_ascii_whitespace()) - .all(|c| c.is_uppercase()); - Ok(vm.ctx.new_bool(is_upper)) + let value = get_value(&s); + Ok(vm.ctx.new_bool( + !value.is_empty() + && value + .chars() + .filter(|x| !x.is_ascii_whitespace()) + .all(|c| c.is_uppercase()), + )) } fn str_islower(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(s, Some(vm.ctx.str_type()))]); - let is_lower = get_value(&s) - .chars() - .filter(|x| !x.is_ascii_whitespace()) - .all(|c| c.is_lowercase()); - Ok(vm.ctx.new_bool(is_lower)) + let value = get_value(&s); + Ok(vm.ctx.new_bool( + !value.is_empty() + && value + .chars() + .filter(|x| !x.is_ascii_whitespace()) + .all(|c| c.is_lowercase()), + )) } -// doesn't implement keep new line delimeter just yet +// doesn't implement keep new line delimiter just yet fn str_splitlines(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(s, Some(vm.ctx.str_type()))]); let elements = get_value(&s) @@ -504,12 +512,11 @@ fn str_zfill(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { ); let value = get_value(&s); let len = objint::get_value(&len).to_usize().unwrap(); - let new_str: String; - if len <= value.len() { - new_str = value; + let new_str = if len <= value.len() { + value } else { - new_str = format!("{}{}", "0".repeat(len - value.len()), value); - } + format!("{}{}", "0".repeat(len - value.len()), value) + }; Ok(vm.ctx.new_str(new_str)) } @@ -546,7 +553,7 @@ fn str_count(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { Err(e) => return Err(vm.new_index_error(e)), }; let num_occur: usize = value[start..end].matches(&sub).count(); - Ok(vm.ctx.new_int(num_occur.to_bigint().unwrap())) + Ok(vm.ctx.new_int(num_occur)) } fn str_index(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { @@ -565,13 +572,13 @@ fn str_index(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { Ok((start, end)) => (start, end), Err(e) => return Err(vm.new_index_error(e)), }; - let ind: usize = match value[start..end + 1].find(&sub) { + let ind: usize = match value[start..=end].find(&sub) { Some(num) => num, None => { return Err(vm.new_value_error("substring not found".to_string())); } }; - Ok(vm.ctx.new_int(ind.to_bigint().unwrap())) + Ok(vm.ctx.new_int(ind)) } fn str_find(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { @@ -590,11 +597,11 @@ fn str_find(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { Ok((start, end)) => (start, end), Err(e) => return Err(vm.new_index_error(e)), }; - let ind: i128 = match value[start..end + 1].find(&sub) { + let ind: i128 = match value[start..=end].find(&sub) { Some(num) => num as i128, None => -1 as i128, }; - Ok(vm.ctx.new_int(ind.to_bigint().unwrap())) + Ok(vm.ctx.new_int(ind)) } // casefold is much more aggresive than lower @@ -785,10 +792,15 @@ fn str_istitle(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(s, Some(vm.ctx.str_type()))]); let value = get_value(&s); let mut is_titled = true; - for word in value.split(" ") { - if word != make_title(&word) { - is_titled = false; - break; + + if value.is_empty() { + is_titled = false; + } else { + for word in value.split(' ') { + if word != make_title(&word) { + is_titled = false; + break; + } } } Ok(vm.ctx.new_bool(is_titled)) @@ -845,14 +857,18 @@ fn str_contains(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { fn str_isalnum(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(s, Some(vm.ctx.str_type()))]); - let is_alnum = get_value(&s).chars().all(|c| c.is_alphanumeric()); - Ok(vm.ctx.new_bool(is_alnum)) + let value = get_value(&s); + Ok(vm + .ctx + .new_bool(!value.is_empty() && value.chars().all(|c| c.is_alphanumeric()))) } fn str_isascii(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(s, Some(vm.ctx.str_type()))]); - let is_ascii = get_value(&s).chars().all(|c| c.is_ascii()); - Ok(vm.ctx.new_bool(is_ascii)) + let value = get_value(&s); + Ok(vm + .ctx + .new_bool(!value.is_empty() && value.chars().all(|c| c.is_ascii()))) } fn str_rindex(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { @@ -871,13 +887,13 @@ fn str_rindex(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { Ok((start, end)) => (start, end), Err(e) => return Err(vm.new_index_error(e)), }; - let ind: i64 = match value[start..end + 1].rfind(&sub) { + let ind: i64 = match value[start..=end].rfind(&sub) { Some(num) => num as i64, None => { return Err(vm.new_value_error("substring not found".to_string())); } }; - Ok(vm.ctx.new_int(ind.to_bigint().unwrap())) + Ok(vm.ctx.new_int(ind)) } fn str_rfind(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { @@ -896,23 +912,27 @@ fn str_rfind(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { Ok((start, end)) => (start, end), Err(e) => return Err(vm.new_index_error(e)), }; - let ind = match value[start..end + 1].rfind(&sub) { + let ind = match value[start..=end].rfind(&sub) { Some(num) => num as i128, None => -1 as i128, }; - Ok(vm.ctx.new_int(ind.to_bigint().unwrap())) + Ok(vm.ctx.new_int(ind)) } fn str_isnumeric(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(s, Some(vm.ctx.str_type()))]); - let is_numeric = get_value(&s).chars().all(|c| c.is_numeric()); - Ok(vm.ctx.new_bool(is_numeric)) + let value = get_value(&s); + Ok(vm + .ctx + .new_bool(!value.is_empty() && value.chars().all(|c| c.is_numeric()))) } fn str_isalpha(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(s, Some(vm.ctx.str_type()))]); - let is_alpha = get_value(&s).chars().all(|c| c.is_alphanumeric()); - Ok(vm.ctx.new_bool(is_alpha)) + let value = get_value(&s); + Ok(vm + .ctx + .new_bool(!value.is_empty() && value.chars().all(|c| c.is_alphanumeric()))) } fn str_isdigit(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { @@ -923,21 +943,41 @@ fn str_isdigit(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { 0x2070, 0x00B9, 0x00B2, 0x00B3, 0x2074, 0x2075, 0x2076, 0x2077, 0x2078, 0x2079, ]; let mut is_digit: bool = true; - for c in value.chars() { - if !c.is_digit(10) { - // checking if char is exponent - let char_as_uni: u16 = c as u16; - if valid_unicodes.contains(&char_as_uni) { - continue; - } else { - is_digit = false; - break; + + if value.is_empty() { + is_digit = false; + } else { + for c in value.chars() { + if !c.is_digit(10) { + // checking if char is exponent + let char_as_uni: u16 = c as u16; + if valid_unicodes.contains(&char_as_uni) { + continue; + } else { + is_digit = false; + break; + } } } } + Ok(vm.ctx.new_bool(is_digit)) } +fn str_isdecimal(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!(vm, args, required = [(s, Some(vm.ctx.str_type()))]); + + let value = get_value(&s); + + let is_decimal = if !value.is_empty() { + value.chars().all(|c| c.is_ascii_digit()) + } else { + false + }; + + Ok(vm.ctx.new_bool(is_decimal)) +} + fn str_getitem(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!( vm, @@ -964,30 +1004,84 @@ fn str_new(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { } impl PySliceableSequence for String { - fn do_slice(&self, start: usize, stop: usize) -> Self { - self[start..stop].to_string() + fn do_slice(&self, range: Range) -> Self { + to_graphemes(self) + .get(range) + .map_or(String::default(), |c| c.join("")) } - fn do_stepped_slice(&self, start: usize, stop: usize, step: usize) -> Self { - self[start..stop].chars().step_by(step).collect() + + fn do_slice_reverse(&self, range: Range) -> Self { + to_graphemes(self) + .get_mut(range) + .map_or(String::default(), |slice| { + slice.reverse(); + slice.join("") + }) } + + fn do_stepped_slice(&self, range: Range, step: usize) -> Self { + if let Some(s) = to_graphemes(self).get(range) { + return s + .iter() + .cloned() + .step_by(step) + .collect::>() + .join(""); + } + String::default() + } + + fn do_stepped_slice_reverse(&self, range: Range, step: usize) -> Self { + if let Some(s) = to_graphemes(self).get(range) { + return s + .iter() + .rev() + .cloned() + .step_by(step) + .collect::>() + .join(""); + } + String::default() + } + + fn empty() -> Self { + String::default() + } + fn len(&self) -> usize { - self.len() + to_graphemes(self).len() } } +/// Convert a string-able `value` to a vec of graphemes +/// represents the string according to user perceived characters +fn to_graphemes>(value: S) -> Vec { + UnicodeSegmentation::graphemes(value.as_ref(), true) + .map(String::from) + .collect() +} + pub fn subscript(vm: &mut VirtualMachine, value: &str, b: PyObjectRef) -> PyResult { - // let value = a if objtype::isinstance(&b, &vm.ctx.int_type()) { - let pos = objint::get_value(&b).to_i32().unwrap(); - let idx = value.to_string().get_pos(pos); - Ok(vm.new_str(value[idx..idx + 1].to_string())) + match objint::get_value(&b).to_i32() { + Some(pos) => { + let graphemes = to_graphemes(value); + if let Some(idx) = graphemes.get_pos(pos) { + Ok(vm.new_str(graphemes[idx].to_string())) + } else { + Err(vm.new_index_error("string index out of range".to_string())) + } + } + None => { + Err(vm.new_index_error("cannot fit 'int' into an index-sized integer".to_string())) + } + } } else { - match &(*b.borrow()).payload { - &PyObjectPayload::Slice { - start: _, - stop: _, - step: _, - } => Ok(vm.new_str(value.to_string().get_slice_items(&b).to_string())), + match (*b.borrow()).payload { + PyObjectPayload::Slice { .. } => { + let string = value.to_string().get_slice_items(vm, &b)?; + Ok(vm.new_str(string)) + } _ => panic!( "TypeError: indexing type {:?} with index {:?} is not supported (yet?)", value, b @@ -1034,5 +1128,5 @@ fn make_title(s: &str) -> String { capitalize_char = true; } } - return titled_str; + titled_str } diff --git a/vm/src/obj/objsuper.rs b/vm/src/obj/objsuper.rs index 850f64c1a..4fa239903 100644 --- a/vm/src/obj/objsuper.rs +++ b/vm/src/obj/objsuper.rs @@ -11,8 +11,28 @@ use super::super::vm::VirtualMachine; use super::objtype; pub fn init(context: &PyContext) { - let ref super_type = context.super_type; + let super_type = &context.super_type; + + let super_doc = "super() -> same as super(__class__, )\n\ + super(type) -> unbound super object\n\ + super(type, obj) -> bound super object; requires isinstance(obj, type)\n\ + super(type, type2) -> bound super object; requires issubclass(type2, type)\n\ + Typical use to call a cooperative superclass method:\n\ + class C(B):\n \ + def meth(self, arg):\n \ + super().meth(arg)\n\ + This works for class methods too:\n\ + class C(B):\n \ + @classmethod\n \ + def cmeth(cls, arg):\n \ + super().cmeth(arg)\n"; + context.set_attr(&super_type, "__init__", context.new_rustfunc(super_init)); + context.set_attr( + &super_type, + "__doc__", + context.new_str(super_doc.to_string()), + ); } fn super_init(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { @@ -53,9 +73,9 @@ fn super_init(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { // Check obj type: if !(objtype::isinstance(&py_obj, &py_type) || objtype::issubclass(&py_obj, &py_type)) { - return Err(vm.new_type_error(format!( - "super(type, obj): obj must be an instance or subtype of type" - ))); + return Err(vm.new_type_error( + "super(type, obj): obj must be an instance or subtype of type".to_string(), + )); } // TODO: how to store those types? diff --git a/vm/src/obj/objtuple.rs b/vm/src/obj/objtuple.rs index 9657328a1..2d99db2d4 100644 --- a/vm/src/obj/objtuple.rs +++ b/vm/src/obj/objtuple.rs @@ -4,12 +4,122 @@ use super::super::pyobject::{ use super::super::vm::VirtualMachine; use super::objbool; use super::objint; -use super::objsequence::{get_elements, get_item, seq_equal}; +use super::objsequence::{ + get_elements, get_item, seq_equal, seq_ge, seq_gt, seq_le, seq_lt, seq_mul, +}; use super::objstr; use super::objtype; -use num_bigint::ToBigInt; use std::hash::{Hash, Hasher}; +fn tuple_lt(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!( + vm, + args, + required = [(zelf, Some(vm.ctx.tuple_type())), (other, None)] + ); + + let result = if objtype::isinstance(other, &vm.ctx.tuple_type()) { + let zelf = get_elements(zelf); + let other = get_elements(other); + seq_lt(vm, &zelf, &other)? + } else { + return Err(vm.new_type_error(format!( + "Cannot compare {} and {} using '<'", + zelf.borrow(), + other.borrow() + ))); + }; + + Ok(vm.ctx.new_bool(result)) +} + +fn tuple_gt(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!( + vm, + args, + required = [(zelf, Some(vm.ctx.tuple_type())), (other, None)] + ); + + let result = if objtype::isinstance(other, &vm.ctx.tuple_type()) { + let zelf = get_elements(zelf); + let other = get_elements(other); + seq_gt(vm, &zelf, &other)? + } else { + return Err(vm.new_type_error(format!( + "Cannot compare {} and {} using '>'", + zelf.borrow(), + other.borrow() + ))); + }; + + Ok(vm.ctx.new_bool(result)) +} + +fn tuple_ge(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!( + vm, + args, + required = [(zelf, Some(vm.ctx.tuple_type())), (other, None)] + ); + + let result = if objtype::isinstance(other, &vm.ctx.tuple_type()) { + let zelf = get_elements(zelf); + let other = get_elements(other); + seq_ge(vm, &zelf, &other)? + } else { + return Err(vm.new_type_error(format!( + "Cannot compare {} and {} using '>='", + zelf.borrow(), + other.borrow() + ))); + }; + + Ok(vm.ctx.new_bool(result)) +} + +fn tuple_le(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!( + vm, + args, + required = [(zelf, Some(vm.ctx.tuple_type())), (other, None)] + ); + + let result = if objtype::isinstance(other, &vm.ctx.tuple_type()) { + let zelf = get_elements(zelf); + let other = get_elements(other); + seq_le(vm, &zelf, &other)? + } else { + return Err(vm.new_type_error(format!( + "Cannot compare {} and {} using '<='", + zelf.borrow(), + other.borrow() + ))); + }; + + Ok(vm.ctx.new_bool(result)) +} + +fn tuple_add(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!( + vm, + args, + required = [(zelf, Some(vm.ctx.tuple_type())), (other, None)] + ); + + if objtype::isinstance(other, &vm.ctx.tuple_type()) { + let e1 = get_elements(zelf); + let e2 = get_elements(other); + let elements = e1.iter().chain(e2.iter()).cloned().collect(); + Ok(vm.ctx.new_tuple(elements)) + } else { + Err(vm.new_type_error(format!( + "Cannot add {} and {}", + zelf.borrow(), + other.borrow() + ))) + } +} + fn tuple_count(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!( vm, @@ -21,10 +131,10 @@ fn tuple_count(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { for element in elements.iter() { let is_eq = vm._eq(element, value.clone())?; if objbool::boolval(vm, is_eq)? { - count = count + 1; + count += 1; } } - Ok(vm.context().new_int(count.to_bigint().unwrap())) + Ok(vm.context().new_int(count)) } fn tuple_eq(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { @@ -53,7 +163,7 @@ fn tuple_hash(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { element_hash.hash(&mut hasher); } let hash = hasher.finish(); - Ok(vm.ctx.new_int(hash.to_bigint().unwrap())) + Ok(vm.ctx.new_int(hash)) } fn tuple_iter(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { @@ -73,7 +183,7 @@ fn tuple_iter(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { fn tuple_len(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(zelf, Some(vm.ctx.tuple_type()))]); let elements = get_elements(zelf); - Ok(vm.context().new_int(elements.len().to_bigint().unwrap())) + Ok(vm.context().new_int(elements.len())) } fn tuple_new(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { @@ -95,7 +205,7 @@ fn tuple_new(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { }; Ok(PyObject::new( - PyObjectPayload::Sequence { elements: elements }, + PyObjectPayload::Sequence { elements }, cls.clone(), )) } @@ -119,6 +229,21 @@ fn tuple_repr(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { Ok(vm.new_str(s)) } +fn tuple_mul(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!( + vm, + args, + required = [ + (zelf, Some(vm.ctx.tuple_type())), + (product, Some(vm.ctx.int_type())) + ] + ); + + let new_elements = seq_mul(&get_elements(zelf), product); + + Ok(vm.ctx.new_tuple(new_elements)) +} + fn tuple_getitem(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!( vm, @@ -128,6 +253,21 @@ fn tuple_getitem(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { get_item(vm, tuple, &get_elements(&tuple), needle.clone()) } +pub fn tuple_index(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!( + vm, + args, + required = [(tuple, Some(vm.ctx.tuple_type())), (needle, None)] + ); + for (index, element) in get_elements(tuple).iter().enumerate() { + let py_equal = vm.call_method(needle, "__eq__", vec![element.clone()])?; + if objbool::get_value(&py_equal) { + return Ok(vm.context().new_int(index)); + } + } + Err(vm.new_value_error("tuple.index(x): x not in tuple".to_string())) +} + pub fn tuple_contains(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!( vm, @@ -149,7 +289,12 @@ pub fn tuple_contains(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { } pub fn init(context: &PyContext) { - let ref tuple_type = context.tuple_type; + let tuple_type = &context.tuple_type; + let tuple_doc = "tuple() -> empty tuple +tuple(iterable) -> tuple initialized from iterable's items + +If the argument is a tuple, the return value is the same object."; + context.set_attr(&tuple_type, "__add__", context.new_rustfunc(tuple_add)); context.set_attr(&tuple_type, "__eq__", context.new_rustfunc(tuple_eq)); context.set_attr( &tuple_type, @@ -165,6 +310,17 @@ pub fn init(context: &PyContext) { context.set_attr(&tuple_type, "__iter__", context.new_rustfunc(tuple_iter)); context.set_attr(&tuple_type, "__len__", context.new_rustfunc(tuple_len)); context.set_attr(&tuple_type, "__new__", context.new_rustfunc(tuple_new)); + context.set_attr(&tuple_type, "__mul__", context.new_rustfunc(tuple_mul)); context.set_attr(&tuple_type, "__repr__", context.new_rustfunc(tuple_repr)); context.set_attr(&tuple_type, "count", context.new_rustfunc(tuple_count)); + context.set_attr(&tuple_type, "__lt__", context.new_rustfunc(tuple_lt)); + context.set_attr(&tuple_type, "__le__", context.new_rustfunc(tuple_le)); + context.set_attr(&tuple_type, "__gt__", context.new_rustfunc(tuple_gt)); + context.set_attr(&tuple_type, "__ge__", context.new_rustfunc(tuple_ge)); + context.set_attr( + &tuple_type, + "__doc__", + context.new_str(tuple_doc.to_string()), + ); + context.set_attr(&tuple_type, "index", context.new_rustfunc(tuple_index)); } diff --git a/vm/src/obj/objtype.rs b/vm/src/obj/objtype.rs index 059b59ff4..dd5fb3b2c 100644 --- a/vm/src/obj/objtype.rs +++ b/vm/src/obj/objtype.rs @@ -1,28 +1,34 @@ use super::super::pyobject::{ - AttributeProtocol, IdProtocol, PyContext, PyFuncArgs, PyObject, PyObjectPayload, PyObjectRef, - PyResult, TypeProtocol, + AttributeProtocol, IdProtocol, PyAttributes, PyContext, PyFuncArgs, PyObject, PyObjectPayload, + PyObjectRef, PyResult, TypeProtocol, }; use super::super::vm::VirtualMachine; use super::objdict; use super::objstr; use super::objtype; // Required for arg_check! to use isinstance +use std::cell::RefCell; use std::collections::HashMap; /* * The magical type type */ -pub fn create_type(type_type: PyObjectRef, object_type: PyObjectRef, dict_type: PyObjectRef) { +pub fn create_type(type_type: PyObjectRef, object_type: PyObjectRef, _dict_type: PyObjectRef) { (*type_type.borrow_mut()).payload = PyObjectPayload::Class { name: String::from("type"), - dict: objdict::new(dict_type), + dict: RefCell::new(PyAttributes::new()), mro: vec![object_type], }; (*type_type.borrow_mut()).typ = Some(type_type.clone()); } pub fn init(context: &PyContext) { - let ref type_type = context.type_type; + let type_type = &context.type_type; + + let type_doc = "type(object_or_name, bases, dict)\n\ + type(object) -> the object's type\n\ + type(name, bases, dict) -> a new type"; + context.set_attr(&type_type, "__call__", context.new_rustfunc(type_call)); context.set_attr(&type_type, "__new__", context.new_rustfunc(type_new)); context.set_attr( @@ -46,6 +52,7 @@ pub fn init(context: &PyContext) { "__getattribute__", context.new_rustfunc(type_getattribute), ); + context.set_attr(&type_type, "__doc__", context.new_str(type_doc.to_string())); } fn type_mro(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { @@ -89,12 +96,7 @@ pub fn issubclass(typ: &PyObjectRef, cls: &PyObjectRef) -> bool { } pub fn get_type_name(typ: &PyObjectRef) -> String { - if let PyObjectPayload::Class { - name, - dict: _, - mro: _, - } = &typ.borrow().payload - { + if let PyObjectPayload::Class { name, .. } = &typ.borrow().payload { name.clone() } else { panic!("Cannot get type_name of non-type type {:?}", typ); @@ -124,12 +126,22 @@ pub fn type_new(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { let mut bases = vm.extract_elements(bases)?; bases.push(vm.context().object()); let name = objstr::get_value(name); - new(typ.clone(), &name, bases, dict.clone()) + new(typ.clone(), &name, bases, py_dict_to_attributes(dict)) } else { Err(vm.new_type_error(format!(": type_new: {:?}", args))) } } +/// Take a python dictionary and convert it to attributes. +fn py_dict_to_attributes(dict: &PyObjectRef) -> PyAttributes { + let mut attrs = PyAttributes::new(); + for (key, value) in objdict::get_key_value_pairs(dict) { + let key = objstr::get_value(&key); + attrs.insert(key, value); + } + attrs +} + pub fn type_call(vm: &mut VirtualMachine, mut args: PyFuncArgs) -> PyResult { debug!("type_call: {:?}", args); let cls = args.shift(); @@ -192,42 +204,33 @@ pub fn type_getattribute(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult Ok(cls_attr) } else if let Some(attr) = mcl.get_attr(&name) { vm.call_get_descriptor(attr, cls.clone()) + } else if let Some(getter) = cls.get_attr("__getattr__") { + vm.invoke( + getter, + PyFuncArgs { + args: vec![mcl, name_str.clone()], + kwargs: vec![], + }, + ) } else { - if let Some(getter) = cls.get_attr("__getattr__") { - vm.invoke( - getter, - PyFuncArgs { - args: vec![mcl, name_str.clone()], - kwargs: vec![], - }, - ) - } else { - let attribute_error = vm.context().exceptions.attribute_error.clone(); - Err(vm.new_exception( - attribute_error, - format!("{} has no attribute '{}'", cls.borrow(), name), - )) - } + let attribute_error = vm.context().exceptions.attribute_error.clone(); + Err(vm.new_exception( + attribute_error, + format!("{} has no attribute '{}'", cls.borrow(), name), + )) } } -pub fn get_attributes(obj: &PyObjectRef) -> HashMap { +pub fn get_attributes(obj: &PyObjectRef) -> PyAttributes { // Gather all members here: - let mut attributes: HashMap = HashMap::new(); + let mut attributes = PyAttributes::new(); // Get class attributes: let mut base_classes = objtype::base_classes(obj); base_classes.reverse(); for bc in base_classes { - if let PyObjectPayload::Class { - name: _, - dict, - mro: _, - } = &bc.borrow().payload - { - let elements = objdict::get_key_value_pairs(dict); - for (name, value) in elements.iter() { - let name = objstr::get_value(name); + if let PyObjectPayload::Class { dict, .. } = &bc.borrow().payload { + for (name, value) in dict.borrow().iter() { attributes.insert(name.to_string(), value.clone()); } } @@ -235,9 +238,7 @@ pub fn get_attributes(obj: &PyObjectRef) -> HashMap { // Get instance attributes: if let PyObjectPayload::Instance { dict } = &obj.borrow().payload { - let elements = objdict::get_key_value_pairs(dict); - for (name, value) in elements.iter() { - let name = objstr::get_value(name); + for (name, value) in dict.borrow().iter() { attributes.insert(name.to_string(), value.clone()); } } @@ -254,8 +255,8 @@ fn take_next_base( for base in &bases { let head = base[0].clone(); if !(&bases) - .into_iter() - .any(|x| x[1..].into_iter().any(|x| x.get_id() == head.get_id())) + .iter() + .any(|x| x[1..].iter().any(|x| x.get_id() == head.get_id())) { next = Some(head); break; @@ -263,7 +264,7 @@ fn take_next_base( } if let Some(head) = next { - for ref mut item in &mut bases { + for item in &mut bases { if item[0].get_id() == head.get_id() { item.remove(0); } @@ -277,7 +278,7 @@ fn linearise_mro(mut bases: Vec>) -> Option> { debug!("Linearising MRO: {:?}", bases); let mut result = vec![]; loop { - if (&bases).into_iter().all(|x| x.is_empty()) { + if (&bases).iter().all(|x| x.is_empty()) { break; } match take_next_base(bases) { @@ -291,14 +292,19 @@ fn linearise_mro(mut bases: Vec>) -> Option> { Some(result) } -pub fn new(typ: PyObjectRef, name: &str, bases: Vec, dict: PyObjectRef) -> PyResult { +pub fn new( + typ: PyObjectRef, + name: &str, + bases: Vec, + dict: HashMap, +) -> PyResult { let mros = bases.into_iter().map(|x| _mro(x).unwrap()).collect(); let mro = linearise_mro(mros).unwrap(); Ok(PyObject::new( PyObjectPayload::Class { name: String::from(name), - dict: dict, - mro: mro, + dict: RefCell::new(dict), + mro, }, typ, )) @@ -317,7 +323,7 @@ fn type_prepare(vm: &mut VirtualMachine, _args: PyFuncArgs) -> PyResult { #[cfg(test)] mod tests { use super::{linearise_mro, new}; - use super::{IdProtocol, PyContext, PyObjectRef}; + use super::{HashMap, IdProtocol, PyContext, PyObjectRef}; fn map_ids(obj: Option>) -> Option> { match obj { @@ -332,20 +338,8 @@ mod tests { let object = context.object; let type_type = context.type_type; - let a = new( - type_type.clone(), - "A", - vec![object.clone()], - type_type.clone(), - ) - .unwrap(); - let b = new( - type_type.clone(), - "B", - vec![object.clone()], - type_type.clone(), - ) - .unwrap(); + let a = new(type_type.clone(), "A", vec![object.clone()], HashMap::new()).unwrap(); + let b = new(type_type.clone(), "B", vec![object.clone()], HashMap::new()).unwrap(); assert_eq!( map_ids(linearise_mro(vec![ diff --git a/vm/src/obj/objzip.rs b/vm/src/obj/objzip.rs new file mode 100644 index 000000000..471df9c2b --- /dev/null +++ b/vm/src/obj/objzip.rs @@ -0,0 +1,46 @@ +use super::super::pyobject::{ + PyContext, PyFuncArgs, PyObject, PyObjectPayload, PyObjectRef, PyResult, TypeProtocol, +}; +use super::super::vm::VirtualMachine; +use super::objiter; +use super::objtype; // Required for arg_check! to use isinstance + +fn zip_new(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { + no_kwargs!(vm, args); + let cls = &args.args[0]; + let iterables = &args.args[1..]; + let iterators = iterables + .into_iter() + .map(|iterable| objiter::get_iter(vm, iterable)) + .collect::, _>>()?; + Ok(PyObject::new( + PyObjectPayload::ZipIterator { iterators }, + cls.clone(), + )) +} + +fn zip_next(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!(vm, args, required = [(zip, Some(vm.ctx.zip_type()))]); + + if let PyObjectPayload::ZipIterator { ref mut iterators } = zip.borrow_mut().payload { + if iterators.is_empty() { + Err(objiter::new_stop_iteration(vm)) + } else { + let next_objs = iterators + .iter() + .map(|iterator| objiter::call_next(vm, iterator)) + .collect::, _>>()?; + + Ok(vm.ctx.new_tuple(next_objs)) + } + } else { + panic!("zip doesn't have correct payload"); + } +} + +pub fn init(context: &PyContext) { + let zip_type = &context.zip_type; + objiter::iter_type_init(context, zip_type); + context.set_attr(zip_type, "__new__", context.new_rustfunc(zip_new)); + context.set_attr(zip_type, "__next__", context.new_rustfunc(zip_next)); +} diff --git a/vm/src/pyobject.rs b/vm/src/pyobject.rs index cb73f767d..c97eb4cbb 100644 --- a/vm/src/pyobject.rs +++ b/vm/src/pyobject.rs @@ -7,6 +7,8 @@ use super::obj::objbytes; use super::obj::objcode; use super::obj::objcomplex; use super::obj::objdict; +use super::obj::objenumerate; +use super::obj::objfilter; use super::obj::objfloat; use super::obj::objframe; use super::obj::objfunction; @@ -14,16 +16,21 @@ use super::obj::objgenerator; use super::obj::objint; use super::obj::objiter; use super::obj::objlist; +use super::obj::objmap; use super::obj::objmemory; use super::obj::objobject; use super::obj::objproperty; +use super::obj::objrange; use super::obj::objset; +use super::obj::objslice; use super::obj::objstr; use super::obj::objsuper; use super::obj::objtuple; use super::obj::objtype; +use super::obj::objzip; use super::vm::VirtualMachine; use num_bigint::BigInt; +use num_bigint::ToBigInt; use num_complex::Complex64; use num_traits::{One, Zero}; use std::cell::RefCell; @@ -66,6 +73,10 @@ pub type PyObjectWeakRef = Weak>; /// since exceptions are also python objects. pub type PyResult = Result; // A valid value, or an exception +/// For attributes we do not use a dict, but a hashmap. This is probably +/// faster, unordered, and only supports strings as keys. +pub type PyAttributes = HashMap; + impl fmt::Display for PyObject { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { use self::TypeProtocol; @@ -105,6 +116,8 @@ pub struct PyContext { pub classmethod_type: PyObjectRef, pub code_type: PyObjectRef, pub dict_type: PyObjectRef, + pub enumerate_type: PyObjectRef, + pub filter_type: PyObjectRef, pub float_type: PyObjectRef, pub frame_type: PyObjectRef, pub frozenset_type: PyObjectRef, @@ -115,6 +128,7 @@ pub struct PyContext { pub true_value: PyObjectRef, pub false_value: PyObjectRef, pub list_type: PyObjectRef, + pub map_type: PyObjectRef, pub memoryview_type: PyObjectRef, pub none: PyObjectRef, pub tuple_type: PyObjectRef, @@ -122,7 +136,10 @@ pub struct PyContext { pub staticmethod_type: PyObjectRef, pub super_type: PyObjectRef, pub str_type: PyObjectRef, + pub range_type: PyObjectRef, + pub slice_type: PyObjectRef, pub type_type: PyObjectRef, + pub zip_type: PyObjectRef, pub function_type: PyObjectRef, pub property_type: PyObjectRef, pub module_type: PyObjectRef, @@ -154,14 +171,9 @@ pub fn create_type( name: &str, type_type: &PyObjectRef, base: &PyObjectRef, - dict_type: &PyObjectRef, + _dict_type: &PyObjectRef, ) -> PyObjectRef { - let dict = PyObject::new( - PyObjectPayload::Dict { - elements: HashMap::new(), - }, - dict_type.clone(), - ); + let dict = PyAttributes::new(); objtype::new(type_type.clone(), name, vec![base.clone()], dict).unwrap() } @@ -198,9 +210,15 @@ impl PyContext { let bytearray_type = create_type("bytearray", &type_type, &object_type, &dict_type); let tuple_type = create_type("tuple", &type_type, &object_type, &dict_type); let iter_type = create_type("iter", &type_type, &object_type, &dict_type); + let enumerate_type = create_type("enumerate", &type_type, &object_type, &dict_type); + let filter_type = create_type("filter", &type_type, &object_type, &dict_type); + let map_type = create_type("map", &type_type, &object_type, &dict_type); + let zip_type = create_type("zip", &type_type, &object_type, &dict_type); let bool_type = create_type("bool", &type_type, &int_type, &dict_type); let memoryview_type = create_type("memoryview", &type_type, &object_type, &dict_type); let code_type = create_type("code", &type_type, &int_type, &dict_type); + let range_type = create_type("range", &type_type, &object_type, &dict_type); + let slice_type = create_type("slice", &type_type, &object_type, &dict_type); let exceptions = exceptions::ExceptionZoo::new(&type_type, &object_type, &dict_type); let none = PyObject::new( @@ -219,37 +237,43 @@ impl PyContext { bool_type.clone(), ); let context = PyContext { - bool_type: bool_type, - memoryview_type: memoryview_type, - bytearray_type: bytearray_type, - bytes_type: bytes_type, - code_type: code_type, - complex_type: complex_type, - classmethod_type: classmethod_type, - int_type: int_type, - float_type: float_type, - frame_type: frame_type, - staticmethod_type: staticmethod_type, - list_type: list_type, - set_type: set_type, - frozenset_type: frozenset_type, - true_value: true_value, - false_value: false_value, - tuple_type: tuple_type, - iter_type: iter_type, - dict_type: dict_type, - none: none, - str_type: str_type, + bool_type, + memoryview_type, + bytearray_type, + bytes_type, + code_type, + complex_type, + classmethod_type, + int_type, + float_type, + frame_type, + staticmethod_type, + list_type, + set_type, + frozenset_type, + true_value, + false_value, + tuple_type, + iter_type, + enumerate_type, + filter_type, + map_type, + zip_type, + dict_type, + none, + str_type, + range_type, + slice_type, object: object_type, - function_type: function_type, - super_type: super_type, - property_type: property_type, - generator_type: generator_type, - module_type: module_type, - bound_method_type: bound_method_type, - member_descriptor_type: member_descriptor_type, - type_type: type_type, - exceptions: exceptions, + function_type, + super_type, + property_type, + generator_type, + module_type, + bound_method_type, + member_descriptor_type, + type_type, + exceptions, }; objtype::init(&context); objlist::init(&context); @@ -267,9 +291,15 @@ impl PyContext { objproperty::init(&context); objmemory::init(&context); objstr::init(&context); + objrange::init(&context); + objslice::init(&context); objsuper::init(&context); objtuple::init(&context); objiter::init(&context); + objenumerate::init(&context); + objfilter::init(&context); + objmap::init(&context); + objzip::init(&context); objbool::init(&context); objcode::init(&context); objframe::init(&context); @@ -317,6 +347,14 @@ impl PyContext { self.set_type.clone() } + pub fn range_type(&self) -> PyObjectRef { + self.range_type.clone() + } + + pub fn slice_type(&self) -> PyObjectRef { + self.slice_type.clone() + } + pub fn frozenset_type(&self) -> PyObjectRef { self.frozenset_type.clone() } @@ -337,6 +375,22 @@ impl PyContext { self.iter_type.clone() } + pub fn enumerate_type(&self) -> PyObjectRef { + self.enumerate_type.clone() + } + + pub fn filter_type(&self) -> PyObjectRef { + self.filter_type.clone() + } + + pub fn map_type(&self) -> PyObjectRef { + self.map_type.clone() + } + + pub fn zip_type(&self) -> PyObjectRef { + self.zip_type.clone() + } + pub fn str_type(&self) -> PyObjectRef { self.str_type.clone() } @@ -383,16 +437,16 @@ impl PyContext { } pub fn new_object(&self) -> PyObjectRef { - PyObject::new( - PyObjectPayload::Instance { - dict: self.new_dict(), - }, - self.object(), - ) + self.new_instance(self.object(), None) } - pub fn new_int(&self, i: BigInt) -> PyObjectRef { - PyObject::new(PyObjectPayload::Integer { value: i }, self.int_type()) + pub fn new_int(&self, i: T) -> PyObjectRef { + PyObject::new( + PyObjectPayload::Integer { + value: i.to_bigint().unwrap(), + }, + self.int_type(), + ) } pub fn new_float(&self, i: f64) -> PyObjectRef { @@ -427,17 +481,11 @@ impl PyContext { } pub fn new_tuple(&self, elements: Vec) -> PyObjectRef { - PyObject::new( - PyObjectPayload::Sequence { elements: elements }, - self.tuple_type(), - ) + PyObject::new(PyObjectPayload::Sequence { elements }, self.tuple_type()) } pub fn new_list(&self, elements: Vec) -> PyObjectRef { - PyObject::new( - PyObjectPayload::Sequence { elements: elements }, - self.list_type(), - ) + PyObject::new(PyObjectPayload::Sequence { elements }, self.list_type()) } pub fn new_set(&self) -> PyObjectRef { @@ -457,17 +505,14 @@ impl PyContext { } pub fn new_class(&self, name: &str, base: PyObjectRef) -> PyObjectRef { - objtype::new(self.type_type(), name, vec![base], self.new_dict()).unwrap() + objtype::new(self.type_type(), name, vec![base], PyAttributes::new()).unwrap() } pub fn new_scope(&self, parent: Option) -> PyObjectRef { let locals = self.new_dict(); - let scope = Scope { - locals: locals, - parent: parent, - }; + let scope = Scope { locals, parent }; PyObject { - payload: PyObjectPayload::Scope { scope: scope }, + payload: PyObjectPayload::Scope { scope }, typ: None, } .into_ref() @@ -506,7 +551,7 @@ impl PyContext { } pub fn new_frame(&self, frame: Frame) -> PyObjectRef { - PyObject::new(PyObjectPayload::Frame { frame: frame }, self.frame_type()) + PyObject::new(PyObjectPayload::Frame { frame }, self.frame_type()) } pub fn new_property PyResult>( @@ -514,12 +559,7 @@ impl PyContext { function: F, ) -> PyObjectRef { let fget = self.new_rustfunc(function); - let py_obj = PyObject::new( - PyObjectPayload::Instance { - dict: self.new_dict(), - }, - self.property_type(), - ); + let py_obj = self.new_instance(self.property_type(), None); self.set_attr(&py_obj, "fget", fget.clone()); py_obj } @@ -537,8 +577,8 @@ impl PyContext { PyObject::new( PyObjectPayload::Function { code: code_obj, - scope: scope, - defaults: defaults, + scope, + defaults, }, self.function_type(), ) @@ -546,10 +586,7 @@ impl PyContext { pub fn new_bound_method(&self, function: PyObjectRef, object: PyObjectRef) -> PyObjectRef { PyObject::new( - PyObjectPayload::BoundMethod { - function: function, - object: object, - }, + PyObjectPayload::BoundMethod { function, object }, self.bound_method_type(), ) } @@ -558,13 +595,23 @@ impl PyContext { &self, function: F, ) -> PyObjectRef { - let dict = self.new_dict(); - self.set_item(&dict, "function", self.new_rustfunc(function)); - self.new_instance(dict, self.member_descriptor_type()) + let mut dict = PyAttributes::new(); + dict.insert("function".to_string(), self.new_rustfunc(function)); + self.new_instance(self.member_descriptor_type(), Some(dict)) } - pub fn new_instance(&self, dict: PyObjectRef, class: PyObjectRef) -> PyObjectRef { - PyObject::new(PyObjectPayload::Instance { dict: dict }, class) + pub fn new_instance(&self, class: PyObjectRef, dict: Option) -> PyObjectRef { + let dict = if let Some(dict) = dict { + dict + } else { + PyAttributes::new() + }; + PyObject::new( + PyObjectPayload::Instance { + dict: RefCell::new(dict), + }, + class, + ) } // Item set/get: @@ -574,10 +621,7 @@ impl PyContext { let key = self.new_str(key.to_string()); objdict::set_item_in_content(elements, &key, &v); } - PyObjectPayload::Module { - name: _, - ref mut dict, - } => self.set_item(dict, key, v), + PyObjectPayload::Module { ref mut dict, .. } => self.set_item(dict, key, v), PyObjectPayload::Scope { ref mut scope } => { self.set_item(&scope.locals, key, v); } @@ -594,13 +638,10 @@ impl PyContext { pub fn set_attr(&self, obj: &PyObjectRef, attr_name: &str, value: PyObjectRef) { match obj.borrow().payload { - PyObjectPayload::Module { name: _, ref dict } => self.set_item(dict, attr_name, value), - PyObjectPayload::Instance { ref dict } => self.set_item(dict, attr_name, value), - PyObjectPayload::Class { - name: _, - ref dict, - mro: _, - } => self.set_item(dict, attr_name, value), + PyObjectPayload::Module { ref dict, .. } => self.set_item(dict, attr_name, value), + PyObjectPayload::Instance { ref dict } | PyObjectPayload::Class { ref dict, .. } => { + dict.borrow_mut().insert(attr_name.to_string(), value); + } ref payload => unimplemented!("set_attr unimplemented for: {:?}", payload), }; } @@ -661,10 +702,7 @@ pub trait ParentProtocol { impl ParentProtocol for PyObjectRef { fn has_parent(&self) -> bool { match self.borrow().payload { - PyObjectPayload::Scope { ref scope } => match scope.parent { - Some(_) => true, - None => false, - }, + PyObjectPayload::Scope { ref scope } => scope.parent.is_some(), _ => panic!("Only scopes have parent (not {:?}", self), } } @@ -688,7 +726,7 @@ pub trait AttributeProtocol { fn class_get_item(class: &PyObjectRef, attr_name: &str) -> Option { let class = class.borrow(); match class.payload { - PyObjectPayload::Class { ref dict, .. } => dict.get_item(attr_name), + PyObjectPayload::Class { ref dict, .. } => dict.borrow().get(attr_name).map(|v| v.clone()), _ => panic!("Only classes should be in MRO!"), } } @@ -696,7 +734,7 @@ fn class_get_item(class: &PyObjectRef, attr_name: &str) -> Option { fn class_has_item(class: &PyObjectRef, attr_name: &str) -> bool { let class = class.borrow(); match class.payload { - PyObjectPayload::Class { ref dict, .. } => dict.contains_key(attr_name), + PyObjectPayload::Class { ref dict, .. } => dict.borrow().contains_key(attr_name), _ => panic!("Only classes should be in MRO!"), } } @@ -710,14 +748,16 @@ impl AttributeProtocol for PyObjectRef { if let Some(item) = class_get_item(self, attr_name) { return Some(item); } - for ref class in mro { + for class in mro { if let Some(item) = class_get_item(class, attr_name) { return Some(item); } } None } - PyObjectPayload::Instance { ref dict } => dict.get_item(attr_name), + PyObjectPayload::Instance { ref dict } => { + dict.borrow().get(attr_name).map(|v| v.clone()) + } _ => None, } } @@ -725,12 +765,11 @@ impl AttributeProtocol for PyObjectRef { fn has_attr(&self, attr_name: &str) -> bool { let obj = self.borrow(); match obj.payload { - PyObjectPayload::Module { name: _, ref dict } => dict.contains_key(attr_name), + PyObjectPayload::Module { ref dict, .. } => dict.contains_key(attr_name), PyObjectPayload::Class { ref mro, .. } => { - class_has_item(self, attr_name) - || mro.into_iter().any(|d| class_has_item(d, attr_name)) + class_has_item(self, attr_name) || mro.iter().any(|d| class_has_item(d, attr_name)) } - PyObjectPayload::Instance { ref dict } => dict.contains_key(attr_name), + PyObjectPayload::Instance { ref dict } => dict.borrow().contains_key(attr_name), _ => false, } } @@ -748,7 +787,7 @@ impl DictProtocol for PyObjectRef { PyObjectPayload::Dict { ref elements } => { objdict::content_contains_key_str(elements, k) } - PyObjectPayload::Module { name: _, ref dict } => dict.contains_key(k), + PyObjectPayload::Module { ref dict, .. } => dict.contains_key(k), PyObjectPayload::Scope { ref scope } => scope.locals.contains_key(k), ref payload => unimplemented!("TODO {:?}", payload), } @@ -757,7 +796,7 @@ impl DictProtocol for PyObjectRef { fn get_item(&self, k: &str) -> Option { match self.borrow().payload { PyObjectPayload::Dict { ref elements } => objdict::content_get_key_str(elements, k), - PyObjectPayload::Module { name: _, ref dict } => dict.get_item(k), + PyObjectPayload::Module { ref dict, .. } => dict.get_item(k), PyObjectPayload::Scope { ref scope } => scope.locals.get_item(k), _ => panic!("TODO"), } @@ -765,8 +804,8 @@ impl DictProtocol for PyObjectRef { fn get_key_value_pairs(&self) -> Vec<(PyObjectRef, PyObjectRef)> { match self.borrow().payload { - PyObjectPayload::Dict { elements: _ } => objdict::get_key_value_pairs(self), - PyObjectPayload::Module { name: _, ref dict } => dict.get_key_value_pairs(), + PyObjectPayload::Dict { .. } => objdict::get_key_value_pairs(self), + PyObjectPayload::Module { ref dict, .. } => dict.get_key_value_pairs(), PyObjectPayload::Scope { ref scope } => scope.locals.get_key_value_pairs(), _ => panic!("TODO"), } @@ -808,10 +847,7 @@ impl PyFuncArgs { for name in kwarg_names.iter().rev() { kwargs.push((name.clone(), args.pop().unwrap())); } - PyFuncArgs { - args: args, - kwargs: kwargs, - } + PyFuncArgs { args, kwargs } } pub fn insert(&self, item: PyObjectRef) -> PyFuncArgs { @@ -820,7 +856,7 @@ impl PyFuncArgs { kwargs: self.kwargs.clone(), }; args.args.insert(0, item); - return args; + args } pub fn shift(&mut self) -> PyObjectRef { @@ -879,10 +915,28 @@ pub enum PyObjectPayload { position: usize, iterated_obj: PyObjectRef, }, + EnumerateIterator { + counter: BigInt, + iterator: PyObjectRef, + }, + FilterIterator { + predicate: PyObjectRef, + iterator: PyObjectRef, + }, + MapIterator { + mapper: PyObjectRef, + iterators: Vec, + }, + ZipIterator { + iterators: Vec, + }, Slice { - start: Option, - stop: Option, - step: Option, + start: Option, + stop: Option, + step: Option, + }, + Range { + range: objrange::RangeType, }, MemoryView { obj: PyObjectRef, @@ -915,14 +969,14 @@ pub enum PyObjectPayload { None, Class { name: String, - dict: PyObjectRef, + dict: RefCell, mro: Vec, }, WeakRef { referent: PyObjectWeakRef, }, Instance { - dict: PyObjectRef, + dict: RefCell, }, RustFunction { function: Box PyResult>, @@ -932,43 +986,37 @@ pub enum PyObjectPayload { impl fmt::Debug for PyObjectPayload { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { - &PyObjectPayload::String { ref value } => write!(f, "str \"{}\"", value), - &PyObjectPayload::Integer { ref value } => write!(f, "int {}", value), - &PyObjectPayload::Float { ref value } => write!(f, "float {}", value), - &PyObjectPayload::Complex { ref value } => write!(f, "complex {}", value), - &PyObjectPayload::Bytes { ref value } => write!(f, "bytes/bytearray {:?}", value), - &PyObjectPayload::MemoryView { ref obj } => write!(f, "bytes/bytearray {:?}", obj), - &PyObjectPayload::Sequence { elements: _ } => write!(f, "list or tuple"), - &PyObjectPayload::Dict { elements: _ } => write!(f, "dict"), - &PyObjectPayload::Set { elements: _ } => write!(f, "set"), - &PyObjectPayload::WeakRef { .. } => write!(f, "weakref"), - &PyObjectPayload::Iterator { - position: _, - iterated_obj: _, - } => write!(f, "iterator"), - &PyObjectPayload::Slice { - start: _, - stop: _, - step: _, - } => write!(f, "slice"), - &PyObjectPayload::Code { ref code } => write!(f, "code: {:?}", code), - &PyObjectPayload::Function { .. } => write!(f, "function"), - &PyObjectPayload::Generator { .. } => write!(f, "generator"), - &PyObjectPayload::BoundMethod { + PyObjectPayload::String { ref value } => write!(f, "str \"{}\"", value), + PyObjectPayload::Integer { ref value } => write!(f, "int {}", value), + PyObjectPayload::Float { ref value } => write!(f, "float {}", value), + PyObjectPayload::Complex { ref value } => write!(f, "complex {}", value), + PyObjectPayload::Bytes { ref value } => write!(f, "bytes/bytearray {:?}", value), + PyObjectPayload::MemoryView { ref obj } => write!(f, "bytes/bytearray {:?}", obj), + PyObjectPayload::Sequence { .. } => write!(f, "list or tuple"), + PyObjectPayload::Dict { .. } => write!(f, "dict"), + PyObjectPayload::Set { .. } => write!(f, "set"), + PyObjectPayload::WeakRef { .. } => write!(f, "weakref"), + PyObjectPayload::Range { .. } => write!(f, "range"), + PyObjectPayload::Iterator { .. } => write!(f, "iterator"), + PyObjectPayload::EnumerateIterator { .. } => write!(f, "enumerate"), + PyObjectPayload::FilterIterator { .. } => write!(f, "filter"), + PyObjectPayload::MapIterator { .. } => write!(f, "map"), + PyObjectPayload::ZipIterator { .. } => write!(f, "zip"), + PyObjectPayload::Slice { .. } => write!(f, "slice"), + PyObjectPayload::Code { ref code } => write!(f, "code: {:?}", code), + PyObjectPayload::Function { .. } => write!(f, "function"), + PyObjectPayload::Generator { .. } => write!(f, "generator"), + PyObjectPayload::BoundMethod { ref function, ref object, } => write!(f, "bound-method: {:?} of {:?}", function, object), - &PyObjectPayload::Module { name: _, dict: _ } => write!(f, "module"), - &PyObjectPayload::Scope { scope: _ } => write!(f, "scope"), - &PyObjectPayload::None => write!(f, "None"), - &PyObjectPayload::Class { - ref name, - dict: _, - mro: _, - } => write!(f, "class {:?}", name), - &PyObjectPayload::Instance { dict: _ } => write!(f, "instance"), - &PyObjectPayload::RustFunction { function: _ } => write!(f, "rust function"), - &PyObjectPayload::Frame { .. } => write!(f, "frame"), + PyObjectPayload::Module { .. } => write!(f, "module"), + PyObjectPayload::Scope { .. } => write!(f, "scope"), + PyObjectPayload::None => write!(f, "None"), + PyObjectPayload::Class { ref name, .. } => write!(f, "class {:?}", name), + PyObjectPayload::Instance { .. } => write!(f, "instance"), + PyObjectPayload::RustFunction { .. } => write!(f, "rust function"), + PyObjectPayload::Frame { .. } => write!(f, "frame"), } } } @@ -979,7 +1027,7 @@ impl PyObject { /* dict: PyObjectRef,*/ typ: PyObjectRef, ) -> PyObjectRef { PyObject { - payload: payload, + payload, typ: Some(typ), // dict: HashMap::new(), // dict, } @@ -1024,22 +1072,23 @@ impl PyObject { PyObjectPayload::Class { ref name, dict: ref _dict, - mro: _, + .. } => format!("", name), - PyObjectPayload::Instance { dict: _ } => format!(""), - PyObjectPayload::Code { code: _ } => format!(""), - PyObjectPayload::Function { .. } => format!(""), - PyObjectPayload::Generator { .. } => format!(""), - PyObjectPayload::Frame { .. } => format!(""), - PyObjectPayload::BoundMethod { .. } => format!(""), - PyObjectPayload::RustFunction { function: _ } => format!(""), - PyObjectPayload::Module { ref name, dict: _ } => format!("", name), + PyObjectPayload::Instance { .. } => "".to_string(), + PyObjectPayload::Code { .. } => "".to_string(), + PyObjectPayload::Function { .. } => "".to_string(), + PyObjectPayload::Generator { .. } => "".to_string(), + PyObjectPayload::Frame { .. } => "".to_string(), + PyObjectPayload::BoundMethod { .. } => "".to_string(), + PyObjectPayload::RustFunction { .. } => "".to_string(), + PyObjectPayload::Module { ref name, .. } => format!("", name), PyObjectPayload::Scope { ref scope } => format!("", scope), PyObjectPayload::Slice { ref start, ref stop, ref step, } => format!("", start, stop, step), + PyObjectPayload::Range { ref range } => format!("", range), PyObjectPayload::Iterator { ref position, ref iterated_obj, @@ -1048,6 +1097,10 @@ impl PyObject { position, iterated_obj.borrow_mut().str() ), + PyObjectPayload::EnumerateIterator { .. } => format!(""), + PyObjectPayload::FilterIterator { .. } => format!(""), + PyObjectPayload::MapIterator { .. } => format!(""), + PyObjectPayload::ZipIterator { .. } => format!(""), } } diff --git a/vm/src/stdlib/ast.rs b/vm/src/stdlib/ast.rs index 107cc0ac9..6703534f4 100644 --- a/vm/src/stdlib/ast.rs +++ b/vm/src/stdlib/ast.rs @@ -9,9 +9,7 @@ use self::rustpython_parser::{ast, parser}; use super::super::obj::{objstr, objtype}; use super::super::pyobject::{PyContext, PyFuncArgs, PyObjectRef, PyResult, TypeProtocol}; use super::super::VirtualMachine; -use num_bigint::ToBigInt; use num_complex::Complex64; -use num_traits::One; use std::ops::Deref; /* @@ -47,8 +45,7 @@ fn program_to_ast(ctx: &PyContext, program: &ast::Program) -> PyObjectRef { fn create_node(ctx: &PyContext, _name: &str) -> PyObjectRef { // TODO: instantiate a class of type given by name // TODO: lookup in the current module? - let node = ctx.new_object(); - node + ctx.new_object() } fn statements_to_ast(ctx: &PyContext, statements: &[ast::LocatedStatement]) -> PyObjectRef { @@ -64,9 +61,8 @@ fn statement_to_ast(ctx: &PyContext, statement: &ast::LocatedStatement) -> PyObj ast::Statement::ClassDef { name, body, - bases: _, - keywords: _, decorator_list, + .. } => { let node = create_node(ctx, "ClassDef"); @@ -102,18 +98,9 @@ fn statement_to_ast(ctx: &PyContext, statement: &ast::LocatedStatement) -> PyObj ctx.set_attr(&node, "decorator_list", py_decorator_list); node } - ast::Statement::Continue => { - let node = create_node(ctx, "Continue"); - node - } - ast::Statement::Break => { - let node = create_node(ctx, "Break"); - node - } - ast::Statement::Pass => { - let node = create_node(ctx, "Pass"); - node - } + ast::Statement::Continue => create_node(ctx, "Continue"), + ast::Statement::Break => create_node(ctx, "Break"), + ast::Statement::Pass => create_node(ctx, "Pass"), ast::Statement::Assert { test, msg } => { let node = create_node(ctx, "Pass"); @@ -130,12 +117,8 @@ fn statement_to_ast(ctx: &PyContext, statement: &ast::LocatedStatement) -> PyObj ast::Statement::Delete { targets } => { let node = create_node(ctx, "Delete"); - let py_targets = ctx.new_tuple( - targets - .into_iter() - .map(|v| expression_to_ast(ctx, v)) - .collect(), - ); + let py_targets = + ctx.new_tuple(targets.iter().map(|v| expression_to_ast(ctx, v)).collect()); ctx.set_attr(&node, "targets", py_targets); node @@ -144,12 +127,7 @@ fn statement_to_ast(ctx: &PyContext, statement: &ast::LocatedStatement) -> PyObj let node = create_node(ctx, "Return"); let py_value = if let Some(value) = value { - ctx.new_tuple( - value - .into_iter() - .map(|v| expression_to_ast(ctx, v)) - .collect(), - ) + ctx.new_tuple(value.iter().map(|v| expression_to_ast(ctx, v)).collect()) } else { ctx.none() }; @@ -233,13 +211,13 @@ fn statement_to_ast(ctx: &PyContext, statement: &ast::LocatedStatement) -> PyObj }; // set lineno on node: - let lineno = ctx.new_int(statement.location.get_row().to_bigint().unwrap()); + let lineno = ctx.new_int(statement.location.get_row()); ctx.set_attr(&node, "lineno", lineno); node } -fn expressions_to_ast(ctx: &PyContext, expressions: &Vec) -> PyObjectRef { +fn expressions_to_ast(ctx: &PyContext, expressions: &[ast::Expression]) -> PyObjectRef { let mut py_expression_nodes = vec![]; for expression in expressions { py_expression_nodes.push(expression_to_ast(ctx, expression)); @@ -249,11 +227,7 @@ fn expressions_to_ast(ctx: &PyContext, expressions: &Vec) -> Py fn expression_to_ast(ctx: &PyContext, expression: &ast::Expression) -> PyObjectRef { let node = match &expression { - ast::Expression::Call { - function, - args, - keywords: _, - } => { + ast::Expression::Call { function, args, .. } => { let node = create_node(ctx, "Call"); let py_func_ast = expression_to_ast(ctx, function); @@ -390,7 +364,7 @@ fn expression_to_ast(ctx: &PyContext, expression: &ast::Expression) -> PyObjectR let node = create_node(ctx, "Num"); let py_n = match value { - ast::Number::Integer { value } => ctx.new_int(value.to_bigint().unwrap()), + ast::Number::Integer { value } => ctx.new_int(value.clone()), ast::Number::Float { value } => ctx.new_float(*value), ast::Number::Complex { real, imag } => { ctx.new_complex(Complex64::new(*real, *imag)) @@ -555,7 +529,7 @@ fn expression_to_ast(ctx: &PyContext, expression: &ast::Expression) -> PyObjectR }; // TODO: retrieve correct lineno: - let lineno = ctx.new_int(One::one()); + let lineno = ctx.new_int(1); ctx.set_attr(&node, "lineno", lineno); node diff --git a/vm/src/stdlib/io.rs b/vm/src/stdlib/io.rs index c639195ec..e254b2417 100644 --- a/vm/src/stdlib/io.rs +++ b/vm/src/stdlib/io.rs @@ -2,23 +2,23 @@ * I/O core tools. */ +//library imports use std::collections::HashSet; - -use std::io::prelude::*; -use std::os::unix::io::{FromRawFd, IntoRawFd}; - use std::fs::File; +use std::io::prelude::*; use std::io::BufReader; +//3rd party imports +use num_bigint::ToBigInt; +use num_traits::ToPrimitive; + +//custom imports use super::super::obj::objbytes; use super::super::obj::objint; use super::super::obj::objstr; use super::super::obj::objtype; use super::os; -use num_bigint::ToBigInt; -use num_traits::ToPrimitive; - use super::super::pyobject::{ AttributeProtocol, BufferProtocol, PyContext, PyFuncArgs, PyObjectPayload, PyObjectRef, PyResult, TypeProtocol, @@ -26,8 +26,8 @@ use super::super::pyobject::{ use super::super::vm::VirtualMachine; -fn compute_c_flag(mode: &String) -> u16 { - match mode.as_ref() { +fn compute_c_flag(mode: &str) -> u16 { + match mode { "w" => 512, "x" => 512, "a" => 8, @@ -81,17 +81,12 @@ fn buffered_reader_read(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { //bytes are returned (when the end of the file is reached). while length == buff_size { let raw_read = vm.get_method(raw.clone(), &"readinto".to_string()).unwrap(); - match vm.invoke(raw_read, PyFuncArgs::new(vec![buffer.clone()], vec![])) { - Ok(_) => {} - Err(_) => return Err(vm.new_value_error("IO Error".to_string())), - } + vm.invoke(raw_read, PyFuncArgs::new(vec![buffer.clone()], vec![])) + .map_err(|_| vm.new_value_error("IO Error".to_string()))?; //Copy bytes from the buffer vector into the results vector - match buffer.borrow_mut().payload { - PyObjectPayload::Bytes { ref mut value } => { - result.extend(value.iter().cloned()); - } - _ => {} + if let PyObjectPayload::Bytes { ref mut value } = buffer.borrow_mut().payload { + result.extend(value.iter().cloned()); }; let len = vm.get_method(buffer.clone(), &"__len__".to_string()); @@ -110,18 +105,18 @@ fn file_io_init(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { optional = [(mode, Some(vm.ctx.str_type()))] ); - let rust_mode = match mode { - Some(m) => objstr::get_value(m), - None => "r".to_string(), - }; + let rust_mode = mode.map_or("r".to_string(), |m| objstr::get_value(m)); match compute_c_flag(&rust_mode).to_bigint() { Some(os_mode) => { let args = vec![name.clone(), vm.ctx.new_int(os_mode)]; - let fileno = os::os_open(vm, PyFuncArgs::new(args, vec![])); + let file_no = os::os_open(vm, PyFuncArgs::new(args, vec![]))?; vm.ctx.set_attr(&file_io, "name", name.clone()); - vm.ctx.set_attr(&file_io, "fileno", fileno.unwrap()); + vm.ctx.set_attr(&file_io, "fileno", file_no); + vm.ctx.set_attr(&file_io, "closefd", vm.new_bool(false)); + vm.ctx.set_attr(&file_io, "closed", vm.new_bool(false)); + Ok(vm.get_none()) } None => Err(vm.new_type_error(format!("invalid mode {}", rust_mode))), @@ -166,30 +161,25 @@ fn file_io_readinto(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { let py_length = vm.invoke(len_method.unwrap(), PyFuncArgs::default()); let length = objint::get_value(&py_length.unwrap()).to_u64().unwrap(); - let fileno = file_io.get_attr("fileno").unwrap(); - let raw_fd = objint::get_value(&fileno).to_i32().unwrap(); + let file_no = file_io.get_attr("fileno").unwrap(); + let raw_fd = objint::get_value(&file_no).to_i64().unwrap(); - //unsafe block - creates file handle from the UNIX file descriptor - //raw_fd is supported on UNIX only. This will need to be extended - //to support windows - i.e. raw file_handles - let handle = unsafe { File::from_raw_fd(raw_fd) }; + //extract unix file descriptor. + let handle = os::rust_file(raw_fd); let mut f = handle.take(length); - match obj.borrow_mut().payload { + if let PyObjectPayload::Bytes { ref mut value } = obj.borrow_mut().payload { //TODO: Implement for MemoryView - PyObjectPayload::Bytes { ref mut value } => { - value.clear(); - match f.read_to_end(&mut *value) { - Ok(_) => {} - Err(_) => return Err(vm.new_value_error("Error reading from Take".to_string())), - } + + value.clear(); + match f.read_to_end(&mut *value) { + Ok(_) => {} + Err(_) => return Err(vm.new_value_error("Error reading from Take".to_string())), } - _ => {} }; - let new_handle = f.into_inner().into_raw_fd().to_bigint(); - vm.ctx - .set_attr(&file_io, "fileno", vm.ctx.new_int(new_handle.unwrap())); + let updated = os::raw_file_number(f.into_inner()); + vm.ctx.set_attr(&file_io, "fileno", vm.ctx.new_int(updated)); Ok(vm.get_none()) } @@ -200,25 +190,24 @@ fn file_io_write(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { required = [(file_io, None), (obj, Some(vm.ctx.bytes_type()))] ); - let fileno = file_io.get_attr("fileno").unwrap(); - let raw_fd = objint::get_value(&fileno).to_i32().unwrap(); + let file_no = file_io.get_attr("fileno").unwrap(); + let raw_fd = objint::get_value(&file_no).to_i64().unwrap(); //unsafe block - creates file handle from the UNIX file descriptor //raw_fd is supported on UNIX only. This will need to be extended //to support windows - i.e. raw file_handles - let mut handle = unsafe { File::from_raw_fd(raw_fd) }; + let mut handle = os::rust_file(raw_fd); match obj.borrow_mut().payload { PyObjectPayload::Bytes { ref mut value } => { match handle.write(&value[..]) { Ok(len) => { //reset raw fd on the FileIO object - let new_handle = handle.into_raw_fd().to_bigint(); - vm.ctx - .set_attr(&file_io, "fileno", vm.ctx.new_int(new_handle.unwrap())); + let updated = os::raw_file_number(handle); + vm.ctx.set_attr(&file_io, "fileno", vm.ctx.new_int(updated)); //return number of bytes written - Ok(vm.ctx.new_int(len.to_bigint().unwrap())) + Ok(vm.ctx.new_int(len)) } Err(_) => Err(vm.new_value_error("Error Writing Bytes to Handle".to_string())), } @@ -280,15 +269,11 @@ pub fn io_open(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { let module = mk_module(&vm.ctx); //mode is optional: 'rt' is the default mode (open from reading text) - let rust_mode = if let Some(m) = mode { - objstr::get_value(m) - } else { - "rt".to_string() - }; + let rust_mode = mode.map_or("rt".to_string(), |m| objstr::get_value(m)); let mut raw_modes = HashSet::new(); - // Add some books. + //add raw modes raw_modes.insert("a".to_string()); raw_modes.insert("r".to_string()); raw_modes.insert("x".to_string()); @@ -303,7 +288,7 @@ pub fn io_open(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { .filter(|a| raw_modes.contains(&a.to_string())) .collect(); - if modes.len() == 0 || modes.len() > 1 { + if modes.is_empty() || modes.len() > 1 { return Err(vm.new_value_error("Invalid Mode".to_string())); } @@ -322,13 +307,13 @@ pub fn io_open(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { vec![file.clone(), vm.ctx.new_str(modes[0].to_string())], vec![], ); - let file_io = vm.invoke(file_io_class, file_args).unwrap(); + let file_io = vm.invoke(file_io_class, file_args)?; //Create Buffered class to consume FileIO. The type of buffered class depends on //the operation in the mode. //There are 3 possible classes here, each inheriting from the RawBaseIO // creating || writing || appending => BufferedWriter - let buffered = if rust_mode.contains("w") { + let buffered = if rust_mode.contains('w') { vm.invoke( buffered_writer_class, PyFuncArgs::new(vec![file_io.clone()], vec![]), @@ -342,7 +327,7 @@ pub fn io_open(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { //TODO: updating => PyBufferedRandom }; - if rust_mode.contains("t") { + if rust_mode.contains('t') { //If the mode is text this buffer type is consumed on construction of //a TextIOWrapper which is subsequently returned. vm.invoke( diff --git a/vm/src/stdlib/json.rs b/vm/src/stdlib/json.rs index 1101a8713..1e7ec411e 100644 --- a/vm/src/stdlib/json.rs +++ b/vm/src/stdlib/json.rs @@ -11,7 +11,6 @@ use super::super::pyobject::{ TypeProtocol, }; use super::super::VirtualMachine; -use num_bigint::ToBigInt; use num_traits::cast::ToPrimitive; // We need to have a VM available to serialise a PyObject based on its subclass, so we implement @@ -52,12 +51,11 @@ impl<'s> serde::Serialize for PyObjectSerializer<'s> { } else if objtype::isinstance(self.pyobject, &self.ctx.int_type()) { let v = objint::get_value(self.pyobject); serializer.serialize_i64(v.to_i64().unwrap()) - // Allthough this may seem nice, it does not give the right result: + // Although this may seem nice, it does not give the right result: // v.serialize(serializer) - } else if objtype::isinstance(self.pyobject, &self.ctx.list_type()) { - let elements = objsequence::get_elements(self.pyobject); - serialize_seq_elements(serializer, &elements) - } else if objtype::isinstance(self.pyobject, &self.ctx.tuple_type()) { + } else if objtype::isinstance(self.pyobject, &self.ctx.list_type()) + || objtype::isinstance(self.pyobject, &self.ctx.tuple_type()) + { let elements = objsequence::get_elements(self.pyobject); serialize_seq_elements(serializer, &elements) } else if objtype::isinstance(self.pyobject, &self.ctx.dict_type()) { @@ -119,7 +117,7 @@ impl<'de> serde::de::DeserializeSeed<'de> for PyObjectDeserializer<'de> { { // The JSON deserialiser always uses the i64/u64 deserialisers, so we only need to // implement those for now - Ok(self.ctx.new_int(value.to_bigint().unwrap())) + Ok(self.ctx.new_int(value)) } fn visit_u64(self, value: u64) -> Result @@ -128,7 +126,7 @@ impl<'de> serde::de::DeserializeSeed<'de> for PyObjectDeserializer<'de> { { // The JSON deserialiser always uses the i64/u64 deserialisers, so we only need to // implement those for now - Ok(self.ctx.new_int(value.to_bigint().unwrap())) + Ok(self.ctx.new_int(value)) } fn visit_f64(self, value: f64) -> Result @@ -223,10 +221,8 @@ fn loads(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { .get_item("JSONDecodeError") .unwrap(); let exc = vm.new_exception(json_decode_error, format!("{}", err)); - vm.ctx - .set_item(&exc, "lineno", vm.ctx.new_int(err.line().into())); - vm.ctx - .set_item(&exc, "colno", vm.ctx.new_int(err.column().into())); + vm.ctx.set_item(&exc, "lineno", vm.ctx.new_int(err.line())); + vm.ctx.set_item(&exc, "colno", vm.ctx.new_int(err.column())); exc }) } diff --git a/vm/src/stdlib/math.rs b/vm/src/stdlib/math.rs index 50e7dbde9..eedae4f78 100644 --- a/vm/src/stdlib/math.rs +++ b/vm/src/stdlib/math.rs @@ -14,8 +14,8 @@ use std; macro_rules! make_math_func { ( $fname:ident, $fun:ident ) => { fn $fname(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(value, Some(vm.ctx.float_type()))]); - let value = objfloat::get_value(value); + arg_check!(vm, args, required = [(value, None)]); + let value = objfloat::make_float(vm, value)?; let value = value.$fun(); let value = vm.ctx.new_float(value); Ok(value) @@ -27,20 +27,20 @@ macro_rules! make_math_func { make_math_func!(math_fabs, abs); fn math_isfinite(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(value, Some(vm.ctx.float_type()))]); - let value = objfloat::get_value(value).is_finite(); + arg_check!(vm, args, required = [(value, None)]); + let value = objfloat::make_float(vm, value)?.is_finite(); Ok(vm.ctx.new_bool(value)) } fn math_isinf(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(value, Some(vm.ctx.float_type()))]); - let value = objfloat::get_value(value).is_infinite(); + arg_check!(vm, args, required = [(value, None)]); + let value = objfloat::make_float(vm, value)?.is_infinite(); Ok(vm.ctx.new_bool(value)) } fn math_isnan(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(value, Some(vm.ctx.float_type()))]); - let value = objfloat::get_value(value).is_nan(); + arg_check!(vm, args, required = [(value, None)]); + let value = objfloat::make_float(vm, value)?.is_nan(); Ok(vm.ctx.new_bool(value)) } @@ -49,25 +49,20 @@ make_math_func!(math_exp, exp); make_math_func!(math_expm1, exp_m1); fn math_log(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(x, Some(vm.ctx.float_type()))], - optional = [(base, Some(vm.ctx.float_type()))] - ); - let x = objfloat::get_value(x); + arg_check!(vm, args, required = [(x, None)], optional = [(base, None)]); + let x = objfloat::make_float(vm, x)?; match base { None => Ok(vm.ctx.new_float(x.ln())), Some(base) => { - let base = objfloat::get_value(base); + let base = objfloat::make_float(vm, base)?; Ok(vm.ctx.new_float(x.log(base))) } } } fn math_log1p(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(x, Some(vm.ctx.float_type()))]); - let x = objfloat::get_value(x); + arg_check!(vm, args, required = [(x, None)]); + let x = objfloat::make_float(vm, x)?; Ok(vm.ctx.new_float((x + 1.0).ln())) } @@ -75,16 +70,9 @@ make_math_func!(math_log2, log2); make_math_func!(math_log10, log10); fn math_pow(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [ - (x, Some(vm.ctx.float_type())), - (y, Some(vm.ctx.float_type())) - ] - ); - let x = objfloat::get_value(x); - let y = objfloat::get_value(y); + arg_check!(vm, args, required = [(x, None), (y, None)]); + let x = objfloat::make_float(vm, x)?; + let y = objfloat::make_float(vm, y)?; Ok(vm.ctx.new_float(x.powf(y))) } @@ -96,32 +84,18 @@ make_math_func!(math_asin, asin); make_math_func!(math_atan, atan); fn math_atan2(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [ - (y, Some(vm.ctx.float_type())), - (x, Some(vm.ctx.float_type())) - ] - ); - let y = objfloat::get_value(y); - let x = objfloat::get_value(x); + arg_check!(vm, args, required = [(y, None), (x, None)]); + let y = objfloat::make_float(vm, y)?; + let x = objfloat::make_float(vm, x)?; Ok(vm.ctx.new_float(y.atan2(x))) } make_math_func!(math_cos, cos); fn math_hypot(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [ - (x, Some(vm.ctx.float_type())), - (y, Some(vm.ctx.float_type())) - ] - ); - let x = objfloat::get_value(x); - let y = objfloat::get_value(y); + arg_check!(vm, args, required = [(x, None), (y, None)]); + let x = objfloat::make_float(vm, x)?; + let y = objfloat::make_float(vm, y)?; Ok(vm.ctx.new_float(x.hypot(y))) } @@ -129,14 +103,14 @@ make_math_func!(math_sin, sin); make_math_func!(math_tan, tan); fn math_degrees(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(value, Some(vm.ctx.float_type()))]); - let x = objfloat::get_value(value); + arg_check!(vm, args, required = [(value, None)]); + let x = objfloat::make_float(vm, value)?; Ok(vm.ctx.new_float(x * (180.0 / std::f64::consts::PI))) } fn math_radians(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(value, Some(vm.ctx.float_type()))]); - let x = objfloat::get_value(value); + arg_check!(vm, args, required = [(value, None)]); + let x = objfloat::make_float(vm, value)?; Ok(vm.ctx.new_float(x * (std::f64::consts::PI / 180.0))) } @@ -150,8 +124,8 @@ make_math_func!(math_tanh, tanh); // Special functions: fn math_erf(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(value, Some(vm.ctx.float_type()))]); - let x = objfloat::get_value(value); + arg_check!(vm, args, required = [(value, None)]); + let x = objfloat::make_float(vm, value)?; if x.is_nan() { Ok(vm.ctx.new_float(x)) @@ -161,8 +135,8 @@ fn math_erf(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { } fn math_erfc(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(value, Some(vm.ctx.float_type()))]); - let x = objfloat::get_value(value); + arg_check!(vm, args, required = [(value, None)]); + let x = objfloat::make_float(vm, value)?; if x.is_nan() { Ok(vm.ctx.new_float(x)) @@ -172,32 +146,28 @@ fn math_erfc(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { } fn math_gamma(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(value, Some(vm.ctx.float_type()))]); - let x = objfloat::get_value(value); + arg_check!(vm, args, required = [(value, None)]); + let x = objfloat::make_float(vm, value)?; if x.is_finite() { Ok(vm.ctx.new_float(gamma(x))) + } else if x.is_nan() || x.is_sign_positive() { + Ok(vm.ctx.new_float(x)) } else { - if x.is_nan() || x.is_sign_positive() { - Ok(vm.ctx.new_float(x)) - } else { - Ok(vm.ctx.new_float(std::f64::NAN)) - } + Ok(vm.ctx.new_float(std::f64::NAN)) } } fn math_lgamma(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(value, Some(vm.ctx.float_type()))]); - let x = objfloat::get_value(value); + arg_check!(vm, args, required = [(value, None)]); + let x = objfloat::make_float(vm, value)?; if x.is_finite() { Ok(vm.ctx.new_float(ln_gamma(x))) + } else if x.is_nan() { + Ok(vm.ctx.new_float(x)) } else { - if x.is_nan() { - Ok(vm.ctx.new_float(x)) - } else { - Ok(vm.ctx.new_float(std::f64::INFINITY)) - } + Ok(vm.ctx.new_float(std::f64::INFINITY)) } } diff --git a/vm/src/stdlib/os.rs b/vm/src/stdlib/os.rs index 443952cb0..4671f5fe9 100644 --- a/vm/src/stdlib/os.rs +++ b/vm/src/stdlib/os.rs @@ -1,17 +1,65 @@ +//library imports +use std::fs::File; use std::fs::OpenOptions; -use std::os::unix::io::IntoRawFd; +use std::io::ErrorKind; -use num_bigint::ToBigInt; +//3rd party imports use num_traits::cast::ToPrimitive; +//custom imports use super::super::obj::objint; use super::super::obj::objstr; use super::super::obj::objtype; use super::super::pyobject::{PyContext, PyFuncArgs, PyObjectRef, PyResult, TypeProtocol}; - use super::super::vm::VirtualMachine; +#[cfg(target_family = "unix")] +pub fn raw_file_number(handle: File) -> i64 { + use std::os::unix::io::IntoRawFd; + + handle.into_raw_fd() as i64 +} + +#[cfg(target_family = "unix")] +pub fn rust_file(raw_fileno: i64) -> File { + use std::os::unix::io::FromRawFd; + + unsafe { File::from_raw_fd(raw_fileno as i32) } +} + +#[cfg(target_family = "windows")] +pub fn raw_file_number(handle: File) -> i64 { + use std::os::windows::io::IntoRawHandle; + + handle.into_raw_handle() as i64 +} + +#[cfg(target_family = "windows")] +pub fn rust_file(raw_fileno: i64) -> File { + use std::ffi::c_void; + use std::os::windows::io::FromRawHandle; + + //TODO: This is untested and (very) unsafe handling or + //raw pointers - This should be patched urgently by + //comparison to the cpython handling of the equivalent fileno + //fields for windows + unsafe { File::from_raw_handle(raw_fileno as *mut c_void) } +} + +pub fn os_close(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!(vm, args, required = [(fileno, Some(vm.ctx.int_type()))]); + + let raw_fileno = objint::get_value(&fileno); + + //The File type automatically closes when it goes out of scope. + //To enable us to close these file descriptors (and hence prevent leaks) + //we seek to create the relevant File and simply let it pass out of scope! + rust_file(raw_fileno.to_i64().unwrap()); + + Ok(vm.get_none()) +} + pub fn os_open(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!( vm, @@ -22,35 +70,39 @@ pub fn os_open(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { ] ); - let handle = match objint::get_value(mode).to_u16().unwrap() { - 0 => OpenOptions::new().read(true).open(objstr::get_value(&name)), - 1 => OpenOptions::new() - .write(true) - .open(objstr::get_value(&name)), - 512 => OpenOptions::new() - .write(true) - .create(true) - .open(objstr::get_value(&name)), - _ => OpenOptions::new().read(true).open(objstr::get_value(&name)), - }; + let fname = objstr::get_value(&name); - //raw_fd is supported on UNIX only. This will need to be extended - //to support windows - i.e. raw file_handles - if let Ok(f) = handle { - Ok(vm.ctx.new_int(f.into_raw_fd().to_bigint().unwrap())) - } else { - Err(vm.new_value_error("Bad file descriptor".to_string())) + let handle = match objint::get_value(mode).to_u16().unwrap() { + 0 => OpenOptions::new().read(true).open(&fname), + 1 => OpenOptions::new().write(true).open(&fname), + 512 => OpenOptions::new().write(true).create(true).open(&fname), + _ => OpenOptions::new().read(true).open(&fname), } + .map_err(|err| match err.kind() { + ErrorKind::NotFound => { + let exc_type = vm.ctx.exceptions.file_not_found_error.clone(); + vm.new_exception(exc_type, format!("No such file or directory: {}", &fname)) + } + ErrorKind::PermissionDenied => { + let exc_type = vm.ctx.exceptions.permission_error.clone(); + vm.new_exception(exc_type, format!("Permission denied: {}", &fname)) + } + _ => vm.new_value_error("Unhandled file IO error".to_string()), + })?; + + Ok(vm.ctx.new_int(raw_file_number(handle))) } pub fn mk_module(ctx: &PyContext) -> PyObjectRef { let py_mod = ctx.new_module(&"io".to_string(), ctx.new_scope(None)); ctx.set_attr(&py_mod, "open", ctx.new_rustfunc(os_open)); - ctx.set_attr(&py_mod, "O_RDONLY", ctx.new_int(0.to_bigint().unwrap())); - ctx.set_attr(&py_mod, "O_WRONLY", ctx.new_int(1.to_bigint().unwrap())); - ctx.set_attr(&py_mod, "O_RDWR", ctx.new_int(2.to_bigint().unwrap())); - ctx.set_attr(&py_mod, "O_NONBLOCK", ctx.new_int(4.to_bigint().unwrap())); - ctx.set_attr(&py_mod, "O_APPEND", ctx.new_int(8.to_bigint().unwrap())); - ctx.set_attr(&py_mod, "O_CREAT", ctx.new_int(512.to_bigint().unwrap())); + ctx.set_attr(&py_mod, "close", ctx.new_rustfunc(os_close)); + + ctx.set_attr(&py_mod, "O_RDONLY", ctx.new_int(0)); + ctx.set_attr(&py_mod, "O_WRONLY", ctx.new_int(1)); + ctx.set_attr(&py_mod, "O_RDWR", ctx.new_int(2)); + ctx.set_attr(&py_mod, "O_NONBLOCK", ctx.new_int(4)); + ctx.set_attr(&py_mod, "O_APPEND", ctx.new_int(8)); + ctx.set_attr(&py_mod, "O_CREAT", ctx.new_int(512)); py_mod } diff --git a/vm/src/stdlib/pystruct.rs b/vm/src/stdlib/pystruct.rs index fda1854f4..166007197 100644 --- a/vm/src/stdlib/pystruct.rs +++ b/vm/src/stdlib/pystruct.rs @@ -13,7 +13,7 @@ use self::byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; use super::super::obj::{objbool, objbytes, objfloat, objint, objstr, objtype}; use super::super::pyobject::{PyContext, PyFuncArgs, PyObjectRef, PyResult, TypeProtocol}; use super::super::VirtualMachine; -use num_bigint::{BigInt, ToBigInt}; +use num_bigint::BigInt; use num_traits::ToPrimitive; use std::io::{Cursor, Read, Write}; @@ -76,7 +76,7 @@ fn pack_bool( data.write_u8(v).unwrap(); Ok(()) } else { - Err(vm.new_type_error(format!("Expected boolean"))) + Err(vm.new_type_error("Expected boolean".to_string())) } } @@ -150,7 +150,7 @@ fn pack_f32( data.write_f32::(v).unwrap(); Ok(()) } else { - Err(vm.new_type_error(format!("Expected float"))) + Err(vm.new_type_error("Expected float".to_string())) } } @@ -164,12 +164,12 @@ fn pack_f64( data.write_f64::(v).unwrap(); Ok(()) } else { - Err(vm.new_type_error(format!("Expected float"))) + Err(vm.new_type_error("Expected float".to_string())) } } fn struct_pack(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - if args.args.len() < 1 { + if args.args.is_empty() { Err(vm.new_type_error(format!( "Expected at least 1 argument (got: {})", args.args.len() @@ -216,7 +216,7 @@ fn struct_pack(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { ))) } } else { - Err(vm.new_type_error(format!("First argument must be of str type"))) + Err(vm.new_type_error("First argument must be of str type".to_string())) } } } @@ -224,14 +224,14 @@ fn struct_pack(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { fn unpack_i8(vm: &mut VirtualMachine, rdr: &mut Read) -> PyResult { match rdr.read_i8() { Err(err) => panic!("Error in reading {:?}", err), - Ok(v) => Ok(vm.ctx.new_int(v.to_bigint().unwrap())), + Ok(v) => Ok(vm.ctx.new_int(v)), } } fn unpack_u8(vm: &mut VirtualMachine, rdr: &mut Read) -> PyResult { match rdr.read_u8() { Err(err) => panic!("Error in reading {:?}", err), - Ok(v) => Ok(vm.ctx.new_int(v.to_bigint().unwrap())), + Ok(v) => Ok(vm.ctx.new_int(v)), } } @@ -245,42 +245,42 @@ fn unpack_bool(vm: &mut VirtualMachine, rdr: &mut Read) -> PyResult { fn unpack_i16(vm: &mut VirtualMachine, rdr: &mut Read) -> PyResult { match rdr.read_i16::() { Err(err) => panic!("Error in reading {:?}", err), - Ok(v) => Ok(vm.ctx.new_int(v.to_bigint().unwrap())), + Ok(v) => Ok(vm.ctx.new_int(v)), } } fn unpack_u16(vm: &mut VirtualMachine, rdr: &mut Read) -> PyResult { match rdr.read_u16::() { Err(err) => panic!("Error in reading {:?}", err), - Ok(v) => Ok(vm.ctx.new_int(v.to_bigint().unwrap())), + Ok(v) => Ok(vm.ctx.new_int(v)), } } fn unpack_i32(vm: &mut VirtualMachine, rdr: &mut Read) -> PyResult { match rdr.read_i32::() { Err(err) => panic!("Error in reading {:?}", err), - Ok(v) => Ok(vm.ctx.new_int(v.to_bigint().unwrap())), + Ok(v) => Ok(vm.ctx.new_int(v)), } } fn unpack_u32(vm: &mut VirtualMachine, rdr: &mut Read) -> PyResult { match rdr.read_u32::() { Err(err) => panic!("Error in reading {:?}", err), - Ok(v) => Ok(vm.ctx.new_int(v.to_bigint().unwrap())), + Ok(v) => Ok(vm.ctx.new_int(v)), } } fn unpack_i64(vm: &mut VirtualMachine, rdr: &mut Read) -> PyResult { match rdr.read_i64::() { Err(err) => panic!("Error in reading {:?}", err), - Ok(v) => Ok(vm.ctx.new_int(v.to_bigint().unwrap())), + Ok(v) => Ok(vm.ctx.new_int(v)), } } fn unpack_u64(vm: &mut VirtualMachine, rdr: &mut Read) -> PyResult { match rdr.read_u64::() { Err(err) => panic!("Error in reading {:?}", err), - Ok(v) => Ok(vm.ctx.new_int(v.to_bigint().unwrap())), + Ok(v) => Ok(vm.ctx.new_int(v)), } } diff --git a/vm/src/stdlib/types.rs b/vm/src/stdlib/types.rs index 2060ad1f1..a744d89b7 100644 --- a/vm/src/stdlib/types.rs +++ b/vm/src/stdlib/types.rs @@ -3,7 +3,9 @@ */ use super::super::obj::{objsequence, objstr, objtype}; -use super::super::pyobject::{PyContext, PyFuncArgs, PyObjectRef, PyResult, TypeProtocol}; +use super::super::pyobject::{ + PyAttributes, PyContext, PyFuncArgs, PyObjectRef, PyResult, TypeProtocol, +}; use super::super::VirtualMachine; fn types_new_class(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { @@ -15,7 +17,6 @@ fn types_new_class(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { ); let name = objstr::get_value(name); - let dict = vm.ctx.new_dict(); let bases = match bases { Some(b) => { @@ -28,7 +29,7 @@ fn types_new_class(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { None => vec![vm.ctx.object()], }; - objtype::new(vm.ctx.type_type(), &name, bases, dict) + objtype::new(vm.ctx.type_type(), &name, bases, PyAttributes::new()) } pub fn mk_module(ctx: &PyContext) -> PyObjectRef { diff --git a/vm/src/stdlib/weakref.rs b/vm/src/stdlib/weakref.rs index f1294b3c0..8f407675b 100644 --- a/vm/src/stdlib/weakref.rs +++ b/vm/src/stdlib/weakref.rs @@ -28,7 +28,7 @@ fn ref_new(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(cls, None), (referent, None)]); let referent = Rc::downgrade(referent); Ok(PyObject::new( - PyObjectPayload::WeakRef { referent: referent }, + PyObjectPayload::WeakRef { referent }, cls.clone(), )) } diff --git a/vm/src/sysmodule.rs b/vm/src/sysmodule.rs index e747fda5a..913e4d4c3 100644 --- a/vm/src/sysmodule.rs +++ b/vm/src/sysmodule.rs @@ -1,4 +1,3 @@ -use num_bigint::ToBigInt; use obj::objtype; use pyobject::{PyContext, PyFuncArgs, PyObjectRef, PyResult, TypeProtocol}; use std::rc::Rc; @@ -26,14 +25,14 @@ fn getframe(vm: &mut VirtualMachine, _args: PyFuncArgs) -> PyResult { fn sys_getrefcount(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(object, None)]); let size = Rc::strong_count(&object); - Ok(vm.ctx.new_int(size.to_bigint().unwrap())) + Ok(vm.ctx.new_int(size)) } fn sys_getsizeof(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(object, None)]); // TODO: implement default optional argument. let size = mem::size_of_val(&object.borrow()); - Ok(vm.ctx.new_int(size.to_bigint().unwrap())) + Ok(vm.ctx.new_int(size)) } pub fn mk_module(ctx: &PyContext) -> PyObjectRef { @@ -54,6 +53,75 @@ pub fn mk_module(ctx: &PyContext) -> PyObjectRef { let modules = ctx.new_dict(); let sys_name = "sys"; + let sys_doc = "This module provides access to some objects used or maintained by the +interpreter and to functions that interact strongly with the interpreter. + +Dynamic objects: + +argv -- command line arguments; argv[0] is the script pathname if known +path -- module search path; path[0] is the script directory, else '' +modules -- dictionary of loaded modules + +displayhook -- called to show results in an interactive session +excepthook -- called to handle any uncaught exception other than SystemExit + To customize printing in an interactive session or to install a custom + top-level exception handler, assign other functions to replace these. + +stdin -- standard input file object; used by input() +stdout -- standard output file object; used by print() +stderr -- standard error object; used for error messages + By assigning other file objects (or objects that behave like files) + to these, it is possible to redirect all of the interpreter's I/O. + +last_type -- type of last uncaught exception +last_value -- value of last uncaught exception +last_traceback -- traceback of last uncaught exception + These three are only available in an interactive session after a + traceback has been printed. + +Static objects: + +builtin_module_names -- tuple of module names built into this interpreter +copyright -- copyright notice pertaining to this interpreter +exec_prefix -- prefix used to find the machine-specific Python library +executable -- absolute path of the executable binary of the Python interpreter +float_info -- a struct sequence with information about the float implementation. +float_repr_style -- string indicating the style of repr() output for floats +hash_info -- a struct sequence with information about the hash algorithm. +hexversion -- version information encoded as a single integer +implementation -- Python implementation information. +int_info -- a struct sequence with information about the int implementation. +maxsize -- the largest supported length of containers. +maxunicode -- the value of the largest Unicode code point +platform -- platform identifier +prefix -- prefix used to find the Python library +thread_info -- a struct sequence with information about the thread implementation. +version -- the version of this interpreter as a string +version_info -- version information as a named tuple +__stdin__ -- the original stdin; don't touch! +__stdout__ -- the original stdout; don't touch! +__stderr__ -- the original stderr; don't touch! +__displayhook__ -- the original displayhook; don't touch! +__excepthook__ -- the original excepthook; don't touch! + +Functions: + +displayhook() -- print an object to the screen, and save it in builtins._ +excepthook() -- print an exception and its traceback to sys.stderr +exc_info() -- return thread-safe information about the current exception +exit() -- exit the interpreter by raising SystemExit +getdlopenflags() -- returns flags to be used for dlopen() calls +getprofile() -- get the global profiling function +getrefcount() -- return the reference count for an object (plus one :-) +getrecursionlimit() -- return the max recursion depth for the interpreter +getsizeof() -- return the size of an object in bytes +gettrace() -- get the global debug tracing function +setcheckinterval() -- control how often the interpreter checks for events +setdlopenflags() -- set the flags to be used for dlopen() calls +setprofile() -- set the global profiling function +setrecursionlimit() -- set the max recursion depth for the interpreter +settrace() -- set the global debug tracing function +"; let sys_mod = ctx.new_module(&sys_name, ctx.new_scope(None)); ctx.set_item(&modules, sys_name, sys_mod.clone()); @@ -62,14 +130,11 @@ pub fn mk_module(ctx: &PyContext) -> PyObjectRef { ctx.set_item(&sys_mod, "argv", argv(ctx)); ctx.set_item(&sys_mod, "getrefcount", ctx.new_rustfunc(sys_getrefcount)); ctx.set_item(&sys_mod, "getsizeof", ctx.new_rustfunc(sys_getsizeof)); - ctx.set_item( - &sys_mod, - "maxsize", - ctx.new_int(std::usize::MAX.to_bigint().unwrap()), - ); + ctx.set_item(&sys_mod, "maxsize", ctx.new_int(std::usize::MAX)); ctx.set_item(&sys_mod, "path", path); ctx.set_item(&sys_mod, "ps1", ctx.new_str(">>>>> ".to_string())); ctx.set_item(&sys_mod, "ps2", ctx.new_str("..... ".to_string())); + ctx.set_item(&sys_mod, "__doc__", ctx.new_str(sys_doc.to_string())); ctx.set_item(&sys_mod, "_getframe", ctx.new_rustfunc(getframe)); sys_mod diff --git a/vm/src/vm.rs b/vm/src/vm.rs index 2dea87aa2..cd7567c8e 100644 --- a/vm/src/vm.rs +++ b/vm/src/vm.rs @@ -11,7 +11,7 @@ use std::collections::hash_map::HashMap; use super::builtins; use super::bytecode; use super::frame::Frame; -use super::obj::objcode::copy_code; +use super::obj::objcode; use super::obj::objgenerator; use super::obj::objiter; use super::obj::objsequence; @@ -53,10 +53,10 @@ impl VirtualMachine { let stdlib_inits = stdlib::get_module_inits(); VirtualMachine { - builtins: builtins, + builtins, sys_module: sysmod, stdlib_inits, - ctx: ctx, + ctx, current_frame: None, } } @@ -86,13 +86,12 @@ impl VirtualMachine { let pymsg = self.new_str(msg); let args: Vec = vec![pymsg]; let args = PyFuncArgs { - args: args, + args, kwargs: vec![], }; // Call function: - let exception = self.invoke(exc_type, args).unwrap(); - exception + self.invoke(exc_type, args).unwrap() } pub fn new_type_error(&mut self, msg: String) -> PyObjectRef { @@ -100,6 +99,16 @@ impl VirtualMachine { self.new_exception(type_error, msg) } + pub fn new_os_error(&mut self, msg: String) -> PyObjectRef { + let os_error = self.ctx.exceptions.os_error.clone(); + self.new_exception(os_error, msg) + } + + pub fn new_overflow_error(&mut self, msg: String) -> PyObjectRef { + let overflow_error = self.ctx.exceptions.overflow_error.clone(); + self.new_exception(overflow_error, msg) + } + /// Create a new python ValueError object. Useful for raising errors from /// python functions implemented in rust. pub fn new_value_error(&mut self, msg: String) -> PyObjectRef { @@ -118,8 +127,13 @@ impl VirtualMachine { } pub fn new_not_implemented_error(&mut self, msg: String) -> PyObjectRef { - let value_error = self.ctx.exceptions.not_implemented_error.clone(); - self.new_exception(value_error, msg) + let not_implemented_error = self.ctx.exceptions.not_implemented_error.clone(); + self.new_exception(not_implemented_error, msg) + } + + pub fn new_zero_division_error(&mut self, msg: String) -> PyObjectRef { + let zero_division_error = self.ctx.exceptions.zero_division_error.clone(); + self.new_exception(zero_division_error, msg) } pub fn new_scope(&mut self, parent_scope: Option) -> PyObjectRef { @@ -159,7 +173,7 @@ impl VirtualMachine { pub fn get_builtin_scope(&mut self) -> PyObjectRef { let a2 = &*self.builtins.borrow(); match a2.payload { - PyObjectPayload::Module { name: _, ref dict } => dict.clone(), + PyObjectPayload::Module { ref dict, .. } => dict.clone(), _ => { panic!("OMG"); } @@ -206,7 +220,7 @@ impl VirtualMachine { obj, method_name, PyFuncArgs { - args: args, + args, kwargs: vec![], }, ) @@ -245,11 +259,7 @@ impl VirtualMachine { ref scope, ref defaults, } => self.invoke_python_function(code, scope, defaults, args), - PyObjectPayload::Class { - name: _, - dict: _, - mro: _, - } => self.call_method_pyargs(&func_ref, "__call__", args), + PyObjectPayload::Class { .. } => self.call_method_pyargs(&func_ref, "__call__", args), PyObjectPayload::BoundMethod { ref function, ref object, @@ -272,7 +282,7 @@ impl VirtualMachine { defaults: &PyObjectRef, args: PyFuncArgs, ) -> PyResult { - let code_object = copy_code(code); + let code_object = objcode::get_value(code); let scope = self.ctx.new_scope(Some(scope.clone())); self.fill_scope_from_args(&code_object, &scope, args, defaults)?; @@ -439,9 +449,9 @@ impl VirtualMachine { value: &PyObjectRef, ) -> Result, PyObjectRef> { // Extract elements from item, if possible: - let elements = if objtype::isinstance(value, &self.ctx.tuple_type()) { - objsequence::get_elements(value).to_vec() - } else if objtype::isinstance(value, &self.ctx.list_type()) { + let elements = if objtype::isinstance(value, &self.ctx.tuple_type()) + || objtype::isinstance(value, &self.ctx.list_type()) + { objsequence::get_elements(value).to_vec() } else { let iter = objiter::get_iter(self, value)?; @@ -549,27 +559,27 @@ impl VirtualMachine { } pub fn _add(&mut self, a: PyObjectRef, b: PyObjectRef) -> PyResult { - self.call_method(&a, "__add__", vec![b]) + self.call_or_unsupported(a, b, "__add__", "__radd__", "+") } pub fn _mul(&mut self, a: PyObjectRef, b: PyObjectRef) -> PyResult { - self.call_method(&a, "__mul__", vec![b]) + self.call_or_unsupported(a, b, "__mul__", "__rmul__", "*") } pub fn _div(&mut self, a: PyObjectRef, b: PyObjectRef) -> PyResult { - self.call_method(&a, "__truediv__", vec![b]) + self.call_or_unsupported(a, b, "__truediv__", "__rtruediv__", "/") } pub fn _pow(&mut self, a: PyObjectRef, b: PyObjectRef) -> PyResult { - self.call_method(&a, "__pow__", vec![b]) + self.call_or_unsupported(a, b, "__pow__", "__rpow__", "**") } pub fn _modulo(&mut self, a: PyObjectRef, b: PyObjectRef) -> PyResult { - self.call_method(&a, "__mod__", vec![b]) + self.call_or_unsupported(a, b, "__mod__", "__rmod__", "%") } pub fn _xor(&mut self, a: PyObjectRef, b: PyObjectRef) -> PyResult { - self.call_method(&a, "__xor__", vec![b]) + self.call_or_unsupported(a, b, "__xor__", "__rxor__", "^") } pub fn _or(&mut self, a: PyObjectRef, b: PyObjectRef) -> PyResult { @@ -614,8 +624,8 @@ mod tests { #[test] fn test_add_py_integers() { let mut vm = VirtualMachine::new(); - let a = vm.ctx.new_int(33_i32.to_bigint().unwrap()); - let b = vm.ctx.new_int(12_i32.to_bigint().unwrap()); + let a = vm.ctx.new_int(33_i32); + let b = vm.ctx.new_int(12_i32); let res = vm._add(a, b).unwrap(); let value = objint::get_value(&res); assert_eq!(value, 45_i32.to_bigint().unwrap()); @@ -625,7 +635,7 @@ mod tests { fn test_multiply_str() { let mut vm = VirtualMachine::new(); let a = vm.ctx.new_str(String::from("Hello ")); - let b = vm.ctx.new_int(4_i32.to_bigint().unwrap()); + let b = vm.ctx.new_int(4_i32); let res = vm._mul(a, b).unwrap(); let value = objstr::get_value(&res); assert_eq!(value, String::from("Hello Hello Hello Hello ")) diff --git a/wasm/demo/src/index.html b/wasm/demo/src/index.html index d87853bde..639bf1f7d 100644 --- a/wasm/demo/src/index.html +++ b/wasm/demo/src/index.html @@ -7,7 +7,7 @@

RustPython Demo

- RustPython is a Python interpreter writter in Rust. This demo is + RustPython is a Python interpreter written in Rust. This demo is compiled from Rust to WebAssembly so it runs in the browser.
Please input your Python code below and click Run, or you can open up your browser's devtools and play with @@ -44,7 +44,7 @@ while count < until:

  • stdout: either a string with a css selector - to a textarea element or a function that recieves a + to a textarea element or a function that receives a string when the print function is called in python. The default value is console.log.
  • @@ -57,7 +57,7 @@ while count < until:
  • - JS functions that get passed to python will recieve positional + JS functions that get passed to python will receive positional args as positional args and kwargs as the this argument
  • diff --git a/wasm/lib/README.md b/wasm/lib/README.md index b278e8cb9..6d2f89d93 100644 --- a/wasm/lib/README.md +++ b/wasm/lib/README.md @@ -28,7 +28,7 @@ pyEval(code, options?); - `vars?`: `{ [key: string]: any }`: Variables passed to the VM that can be accessed in Python with the variable `js_vars`. Functions do work, and - recieve the Python kwargs as the `this` argument. + receive the Python kwargs as the `this` argument. - `stdout?`: `(out: string) => void`: A function to replace the native print function, by default `console.log`. diff --git a/wasm/lib/src/lib.rs b/wasm/lib/src/lib.rs index 4bc0891b2..7ad3f1956 100644 --- a/wasm/lib/src/lib.rs +++ b/wasm/lib/src/lib.rs @@ -11,7 +11,7 @@ use rustpython_vm::pyobject::{self, PyFuncArgs, PyObjectRef, PyResult}; use rustpython_vm::VirtualMachine; use wasm_bindgen::{prelude::*, JsCast}; -// Hack to comment out wasm-bindgen's typescript definitons +// Hack to comment out wasm-bindgen's typescript definitions #[wasm_bindgen(typescript_custom_section)] const TS_CMT_START: &'static str = "/*"; @@ -123,7 +123,7 @@ fn eval(vm: &mut VirtualMachine, source: &str, vars: PyObjectRef) -> PyResult { source.push('\n'); } - let code_obj = compile::compile(vm, &source, compile::Mode::Exec, None)?; + let code_obj = compile::compile(vm, &source, &compile::Mode::Exec, "".to_string())?; vm.run_code_obj(code_obj, vars) } @@ -141,7 +141,7 @@ fn eval(vm: &mut VirtualMachine, source: &str, vars: PyObjectRef) -> PyResult { /// /// - `vars?`: `{ [key: string]: any }`: Variables passed to the VM that can be /// accessed in Python with the variable `js_vars`. Functions do work, and -/// recieve the Python kwargs as the `this` argument. +/// receive the Python kwargs as the `this` argument. /// - `stdout?`: `(out: string) => void`: A function to replace the native print /// function, by default `console.log`. pub fn eval_py(source: &str, options: Option) -> Result {