diff --git a/Cargo.lock b/Cargo.lock index 25a156c8c..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" diff --git a/parser/src/lexer.rs b/parser/src/lexer.rs index 0d721f7d1..58660a901 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; 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 a5672e700..e6d5e5684 100644 --- a/src/main.rs +++ b/src/main.rs @@ -68,7 +68,7 @@ fn main() { handle_exception(&mut vm, result); } -fn _run_string(vm: &mut VirtualMachine, source: &str, source_path: Option) -> PyResult { +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(); @@ -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) => { @@ -177,7 +177,7 @@ fn run_shell(vm: &mut VirtualMachine) -> PyResult { } 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(), diff --git a/tests/snippets/bools.py b/tests/snippets/bools.py index 50bd39f7e..2aa817ca4 100644 --- a/tests/snippets/bools.py +++ b/tests/snippets/bools.py @@ -40,8 +40,6 @@ assert (True and True) assert not (False and fake) assert (True and 5) == 5 -assert bool.__doc__ == "bool(x) -> bool\n\nReturns True when the argument x is true, False otherwise.\nThe builtins True and False are the only two instances of the class bool.\nThe class bool is a subclass of the class int, and cannot be subclassed." - # Bools are also ints. assert isinstance(True, int) assert True + True == 2 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_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 index c822ce7e7..c8efb189d 100644 --- a/tests/snippets/builtin_range.py +++ b/tests/snippets/builtin_range.py @@ -18,6 +18,11 @@ def assert_raises(expr, exc_type): 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 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 539b49ef7..76b28a7b9 100644 --- a/tests/snippets/builtins.py +++ b/tests/snippets/builtins.py @@ -5,12 +5,8 @@ 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 3 == eval('1+2') code = compile('5+3', 'x.py', 'eval') 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/numbers.py b/tests/snippets/numbers.py index 12e892385..c36602ee1 100644 --- a/tests/snippets/numbers.py +++ b/tests/snippets/numbers.py @@ -2,8 +2,6 @@ x = 5 x.__init__(6) assert x == 5 -assert int.__doc__ == "int(x=0) -> integer\nint(x, base=10) -> integer\n\nConvert a number or string to an integer, or return 0 if no arguments\nare given. If x is a number, return x.__int__(). For floating point\nnumbers, this truncates towards zero.\n\nIf x is not a number or if base is given, then x must be a string,\nbytes, or bytearray instance representing an integer literal in the\ngiven base. The literal can be preceded by '+' or '-' and be surrounded\nby whitespace. The base defaults to 10. Valid bases are 0 and 2-36.\nBase 0 means to interpret the base from the string as an integer literal.\n>>> int('0b100', base=0)\n4" - class A(int): pass @@ -13,6 +11,23 @@ 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 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/tuple.py b/tests/snippets/tuple.py index 38617d036..eb5102fa3 100644 --- a/tests/snippets/tuple.py +++ b/tests/snippets/tuple.py @@ -19,5 +19,3 @@ assert x > y, "tuple __gt__ failed" b = (1,2,3) assert b.index(2) == 1 - -assert b.__doc__ == "tuple() -> empty tuple\ntuple(iterable) -> tuple initialized from iterable's items\n\nIf the argument is a tuple, the return value is the same object." diff --git a/vm/src/builtins.rs b/vm/src/builtins.rs index 1db5c7e77..75cd7afaf 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(); @@ -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,6 @@ 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 { @@ -224,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())); }; @@ -270,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 { @@ -612,7 +588,24 @@ fn builtin_repr(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { vm.to_repr(obj) } // 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!( @@ -641,32 +634,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 { @@ -692,7 +659,7 @@ 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()); @@ -725,15 +692,17 @@ pub fn make_module(ctx: &PyContext) -> PyObjectRef { ctx.set_attr(&py_mod, "property", ctx.property_type()); 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, "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( @@ -742,6 +711,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", @@ -753,6 +727,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", @@ -767,11 +746,21 @@ pub fn make_module(ctx: &PyContext) -> PyObjectRef { 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 77b12917a..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,7 +34,8 @@ impl CodeObject { varargs: Option>, kwonlyarg_names: Vec, varkeywords: Option>, - source_path: Option, + source_path: String, + first_line_number: usize, obj_name: String, ) -> CodeObject { CodeObject { @@ -45,6 +47,7 @@ impl CodeObject { 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 9050e59ce..1aa8cce27 100644 --- a/vm/src/compile.rs +++ b/vm/src/compile.rs @@ -24,10 +24,10 @@ pub fn compile( vm: &mut VirtualMachine, source: &str, mode: &Mode, - source_path: Option, + 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 { @@ -78,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, )); } @@ -453,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 { @@ -653,12 +657,14 @@ 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(), + self.source_path.clone().unwrap(), + line_number, name.to_string(), )); @@ -1162,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(), )); @@ -1338,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; } @@ -1352,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 f5f690e95..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 305297919..be3652a73 100644 --- a/vm/src/exceptions.rs +++ b/vm/src/exceptions.rs @@ -81,6 +81,7 @@ fn exception_str(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { #[derive(Debug)] pub struct ExceptionZoo { + pub arithmetic_error: PyObjectRef, pub assertion_error: PyObjectRef, pub attribute_error: PyObjectRef, pub base_exception_type: PyObjectRef, @@ -93,12 +94,14 @@ pub struct ExceptionZoo { pub name_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 zero_division_error: PyObjectRef, } impl ExceptionZoo { @@ -113,6 +116,8 @@ impl ExceptionZoo { let exception_type = create_type("Exception", &type_type, &base_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 = @@ -128,8 +133,18 @@ impl ExceptionZoo { 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, @@ -142,6 +157,7 @@ impl ExceptionZoo { let permission_error = create_type("PermissionError", &type_type, &os_error, &dict_type); ExceptionZoo { + arithmetic_error, assertion_error, attribute_error, base_exception_type, @@ -154,12 +170,14 @@ impl ExceptionZoo { name_error, not_implemented_error, os_error, + overflow_error, permission_error, runtime_error, stop_iteration, syntax_error, type_error, value_error, + zero_division_error, } } } diff --git a/vm/src/frame.rs b/vm/src/frame.rs index 54fbf415b..474b6d890 100644 --- a/vm/src/frame.rs +++ b/vm/src/frame.rs @@ -20,7 +20,7 @@ use super::pyobject::{ PyResult, TypeProtocol, }; use super::vm::VirtualMachine; -use num_traits::ToPrimitive; +use num_bigint::BigInt; #[derive(Clone, Debug)] enum Block { @@ -70,7 +70,7 @@ 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 @@ -88,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()))); @@ -262,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) @@ -503,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); @@ -661,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)?; @@ -678,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 diff --git a/vm/src/import.rs b/vm/src/import.rs index be8807881..d7393bd67 100644 --- a/vm/src/import.rs +++ b/vm/src/import.rs @@ -35,7 +35,7 @@ fn import_uncached_module( vm, &source, &compile::Mode::Exec, - Some(filepath.to_str().unwrap().to_string()), + filepath.to_str().unwrap().to_string(), )?; // trace!("Code object: {:?}", code_obj); diff --git a/vm/src/obj/mod.rs b/vm/src/obj/mod.rs index a60ce79ff..f08998fcb 100644 --- a/vm/src/obj/mod.rs +++ b/vm/src/obj/mod.rs @@ -6,6 +6,7 @@ pub mod objbytes; pub mod objcode; pub mod objcomplex; pub mod objdict; +pub mod objenumerate; pub mod objfilter; pub mod objfloat; pub mod objframe; @@ -21,7 +22,9 @@ 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/objbytearray.rs b/vm/src/obj/objbytearray.rs index c6fc8c35c..b4afaa639 100644 --- a/vm/src/obj/objbytearray.rs +++ b/vm/src/obj/objbytearray.rs @@ -16,6 +16,20 @@ use num_traits::ToPrimitive; /// Fill bytearray class methods dictionary. pub fn init(context: &PyContext) { 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__", @@ -36,6 +50,11 @@ 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", @@ -203,11 +222,12 @@ fn bytearray_istitle(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { } }; - if is_cased(current) && next.is_uppercase() && !prev_cased { - return Ok(vm.new_bool(false)); - } else if !is_cased(current) && next.is_lowercase() { + 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); } diff --git a/vm/src/obj/objcode.rs b/vm/src/obj/objcode.rs index 70d9f2d54..2649c1190 100644 --- a/vm/src/obj/objcode.rs +++ b/vm/src/obj/objcode.rs @@ -4,7 +4,7 @@ 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; @@ -13,15 +13,26 @@ pub fn init(context: &PyContext) { 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) } } @@ -33,18 +44,54 @@ fn code_new(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { 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 = ", line 1".to_string(); - - 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 bc624d50f..353b89b7f 100644 --- a/vm/src/obj/objcomplex.rs +++ b/vm/src/obj/objcomplex.rs @@ -8,8 +8,18 @@ use num_complex::Complex64; pub fn init(context: &PyContext) { 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__", 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 index 009d2ad2e..b4bc4ff5e 100644 --- a/vm/src/obj/objfilter.rs +++ b/vm/src/obj/objfilter.rs @@ -7,7 +7,7 @@ use super::objbool; use super::objiter; use super::objtype; // Required for arg_check! to use isinstance -pub fn filter_new(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn filter_new(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!( vm, args, @@ -23,21 +23,6 @@ pub fn filter_new(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { )) } -fn filter_iter(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(filter, Some(vm.ctx.filter_type()))]); - // Return self: - Ok(filter.clone()) -} - -fn filter_contains(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(filter, Some(vm.ctx.filter_type())), (needle, None)] - ); - objiter::contains(vm, filter, needle) -} - fn filter_next(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(filter, Some(vm.ctx.filter_type()))]); @@ -72,12 +57,7 @@ fn filter_next(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { pub fn init(context: &PyContext) { let filter_type = &context.filter_type; - context.set_attr( - &filter_type, - "__contains__", - context.new_rustfunc(filter_contains), - ); - context.set_attr(&filter_type, "__iter__", context.new_rustfunc(filter_iter)); + 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 1bb57e572..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,6 +282,53 @@ 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 float_type = &context.float_type; @@ -299,4 +362,10 @@ pub fn init(context: &PyContext) { "__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/objint.rs b/vm/src/obj/objint.rs index 6894a0492..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 @@ -289,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 {}", @@ -299,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, @@ -366,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)) } } @@ -388,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()))) } @@ -433,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 {}", @@ -549,6 +594,12 @@ Base 0 means to interpret the base from the string as an integer literal. 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__", diff --git a/vm/src/obj/objiter.rs b/vm/src/obj/objiter.rs index 7d4507212..40633e984 100644 --- a/vm/src/obj/objiter.rs +++ b/vm/src/obj/objiter.rs @@ -9,7 +9,6 @@ use super::super::vm::VirtualMachine; use super::objbool; // use super::objstr; use super::objtype; // Required for arg_check! to use isinstance -use num_bigint::BigInt; /* * This helper function is called at multiple places. First, it is called @@ -65,7 +64,17 @@ pub fn get_all( Ok(elements) } -pub fn contains(vm: &mut VirtualMachine, iter: &PyObjectRef, needle: &PyObjectRef) -> PyResult { +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()])?; @@ -80,6 +89,34 @@ pub fn contains(vm: &mut VirtualMachine, iter: &PyObjectRef, needle: &PyObjectRe } } +/// 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)]); @@ -87,21 +124,6 @@ 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)] - ); - contains(vm, iter, needle) -} - fn iter_next(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(iter, Some(vm.ctx.iter_type()))]); @@ -118,22 +140,16 @@ 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(BigInt::from(*position)) { + if let Some(int) = range.get(*position) { *position += 1; Ok(vm.ctx.new_int(int)) } 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)) } } @@ -143,10 +159,7 @@ 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)) } } @@ -161,12 +174,7 @@ fn iter_next(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { pub fn init(context: &PyContext) { let 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)); + 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 ed8e552bc..369ed9fc5 100644 --- a/vm/src/obj/objlist.rs +++ b/vm/src/obj/objlist.rs @@ -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?)", diff --git a/vm/src/obj/objmap.rs b/vm/src/obj/objmap.rs index 14cfe1d34..79ee7ddf8 100644 --- a/vm/src/obj/objmap.rs +++ b/vm/src/obj/objmap.rs @@ -5,7 +5,7 @@ use super::super::vm::VirtualMachine; use super::objiter; use super::objtype; // Required for arg_check! to use isinstance -pub fn map_new(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { +fn map_new(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { no_kwargs!(vm, args); let cls = &args.args[0]; if args.args.len() < 3 { @@ -27,21 +27,6 @@ pub fn map_new(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { } } -fn map_iter(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(map, Some(vm.ctx.map_type()))]); - // Return self: - Ok(map.clone()) -} - -fn map_contains(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(map, Some(vm.ctx.map_type())), (needle, None)] - ); - objiter::contains(vm, map, needle) -} - fn map_next(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(map, Some(vm.ctx.map_type()))]); @@ -75,12 +60,7 @@ pub fn init(context: &PyContext) { Make an iterator that computes the function using arguments from\n\ each of the iterables. Stops when the shortest iterable is exhausted."; - context.set_attr( - &map_type, - "__contains__", - context.new_rustfunc(map_contains), - ); - context.set_attr(&map_type, "__iter__", context.new_rustfunc(map_iter)); + 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/objproperty.rs b/vm/src/obj/objproperty.rs index 7ec52a9fd..ada639270 100644 --- a/vm/src/obj/objproperty.rs +++ b/vm/src/obj/objproperty.rs @@ -8,6 +8,35 @@ use super::objtype; pub fn init(context: &PyContext) { 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__", @@ -18,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__ ? } diff --git a/vm/src/obj/objrange.rs b/vm/src/obj/objrange.rs index cd897ff34..717027403 100644 --- a/vm/src/obj/objrange.rs +++ b/vm/src/obj/objrange.rs @@ -7,6 +7,7 @@ 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 { @@ -18,12 +19,24 @@ pub struct RangeType { } 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.end.clone() - self.start.clone()) / self.step.clone()) - .abs() - .to_usize() - .unwrap() + self.try_len().unwrap() } #[inline] @@ -65,29 +78,56 @@ impl RangeType { } #[inline] - pub fn get(&self, index: BigInt) -> Option { - let result = self.start.clone() + self.step.clone() * index; + 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 { - Some(result) - } else if !self.forward() && !self.is_empty() && result > self.end { + if (self.forward() && !self.is_empty() && result < self.end) + || (!self.forward() && !self.is_empty() && result > self.end) + { Some(result) } else { None } } + + #[inline] + 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, + "__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, @@ -153,12 +193,14 @@ fn range_iter(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { fn range_len(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(), + if let Some(len) = match zelf.borrow().payload { + PyObjectPayload::Range { ref range } => range.try_len(), _ => unreachable!(), - }; - - Ok(vm.ctx.new_int(len)) + } { + 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 { @@ -175,15 +217,19 @@ fn range_getitem(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { match subscript.borrow().payload { PyObjectPayload::Integer { ref value } => { - if let Some(int) = zrange.get(value.clone()) { + 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 { start, stop, step } => { + PyObjectPayload::Slice { + ref start, + ref stop, + ref step, + } => { let new_start = if let Some(int) = start { - if let Some(i) = zrange.get(int.into()) { + if let Some(i) = zrange.get(int) { i } else { zrange.start.clone() @@ -193,7 +239,7 @@ fn range_getitem(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { }; let new_end = if let Some(int) = stop { - if let Some(i) = zrange.get(int.into()) { + if let Some(i) = zrange.get(int) { i } else { zrange.end @@ -203,7 +249,7 @@ fn range_getitem(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { }; let new_step = if let Some(int) = step { - (int as i64) * zrange.step + int * zrange.step } else { zrange.step }; @@ -224,6 +270,17 @@ fn range_getitem(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { } } +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()))]); diff --git a/vm/src/obj/objsequence.rs b/vm/src/obj/objsequence.rs index d2fbb2a96..5d85b12a0 100644 --- a/vm/src/obj/objsequence.rs +++ b/vm/src/obj/objsequence.rs @@ -2,53 +2,97 @@ use super::super::pyobject::{PyObject, PyObjectPayload, PyObjectRef, PyResult, T use super::super::vm::VirtualMachine; use super::objbool; use super::objint; -use num_traits::ToPrimitive; +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 { if -p as usize > self.len() { - // return something that is out of bounds so `get_item` raises an IndexError - self.len() + 1 + None } else { - self.len() - ((-p) as usize) + Some(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() + } 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()) } } } @@ -58,12 +102,28 @@ pub trait PySliceableSequence { } impl PySliceableSequence for Vec { - fn do_slice(&self, start: usize, stop: usize) -> Self { - self[start..stop].to_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() } @@ -78,8 +138,7 @@ pub fn get_item( match &(subscript.borrow()).payload { PyObjectPayload::Integer { value } => match value.to_i32() { Some(value) => { - let pos_index = elements.to_vec().get_pos(value); - if pos_index < elements.len() { + if let Some(pos_index) = elements.to_vec().get_pos(value) { let obj = elements[pos_index].clone(); Ok(obj) } else { @@ -94,7 +153,7 @@ pub fn get_item( PyObjectPayload::Slice { .. } => Ok(PyObject::new( match &(sequence.borrow()).payload { PyObjectPayload::Sequence { .. } => PyObjectPayload::Sequence { - elements: elements.to_vec().get_slice_items(&subscript), + elements: elements.to_vec().get_slice_items(vm, &subscript)?, }, ref payload => panic!("sequence get_item called for non-sequence: {:?}", payload), }, diff --git a/vm/src/obj/objset.rs b/vm/src/obj/objset.rs index 5ac7507d6..2648fb4f3 100644 --- a/vm/src/obj/objset.rs +++ b/vm/src/obj/objset.rs @@ -126,6 +126,99 @@ 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()))]); @@ -159,6 +252,13 @@ 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)); 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 fbdf58fc4..a49e7a2c6 100644 --- a/vm/src/obj/objstr.rs +++ b/vm/src/obj/objstr.rs @@ -8,6 +8,7 @@ use super::objsequence::PySliceableSequence; use super::objtype; 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; @@ -493,7 +494,7 @@ fn str_islower(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { )) } -// 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) @@ -1003,14 +1004,23 @@ fn str_new(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { } impl PySliceableSequence for String { - fn do_slice(&self, start: usize, stop: usize) -> Self { + fn do_slice(&self, range: Range) -> Self { to_graphemes(self) - .get(start..stop) + .get(range) .map_or(String::default(), |c| c.join("")) } - fn do_stepped_slice(&self, start: usize, stop: usize, step: usize) -> Self { - if let Some(s) = to_graphemes(self).get(start..stop) { + 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() @@ -1021,6 +1031,23 @@ impl PySliceableSequence for String { 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 { to_graphemes(self).len() } @@ -1039,11 +1066,11 @@ pub fn subscript(vm: &mut VirtualMachine, value: &str, b: PyObjectRef) -> PyResu match objint::get_value(&b).to_i32() { Some(pos) => { let graphemes = to_graphemes(value); - let idx = graphemes.get_pos(pos); - graphemes - .get(idx) - .map(|c| vm.new_str(c.to_string())) - .ok_or(vm.new_index_error("string index out of range".to_string())) + 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())) @@ -1052,7 +1079,8 @@ pub fn subscript(vm: &mut VirtualMachine, value: &str, b: PyObjectRef) -> PyResu } else { match (*b.borrow()).payload { PyObjectPayload::Slice { .. } => { - Ok(vm.new_str(value.to_string().get_slice_items(&b).to_string())) + 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?)", diff --git a/vm/src/obj/objsuper.rs b/vm/src/obj/objsuper.rs index 8a0830503..4fa239903 100644 --- a/vm/src/obj/objsuper.rs +++ b/vm/src/obj/objsuper.rs @@ -12,7 +12,27 @@ use super::objtype; pub fn init(context: &PyContext) { 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 { diff --git a/vm/src/obj/objtype.rs b/vm/src/obj/objtype.rs index ce98e0f4c..dd5fb3b2c 100644 --- a/vm/src/obj/objtype.rs +++ b/vm/src/obj/objtype.rs @@ -24,6 +24,11 @@ pub fn create_type(type_type: PyObjectRef, object_type: PyObjectRef, _dict_type: pub fn init(context: &PyContext) { 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( @@ -47,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 { 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 424cf7646..212757671 100644 --- a/vm/src/pyobject.rs +++ b/vm/src/pyobject.rs @@ -7,6 +7,7 @@ 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; @@ -21,10 +22,12 @@ 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; @@ -113,6 +116,7 @@ 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, @@ -133,7 +137,9 @@ pub struct PyContext { 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, @@ -204,12 +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( @@ -246,12 +255,15 @@ impl PyContext { 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, super_type, @@ -280,11 +292,14 @@ impl PyContext { 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); @@ -336,6 +351,10 @@ impl PyContext { self.range_type.clone() } + pub fn slice_type(&self) -> PyObjectRef { + self.slice_type.clone() + } + pub fn frozenset_type(&self) -> PyObjectRef { self.frozenset_type.clone() } @@ -356,6 +375,10 @@ 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() } @@ -364,6 +387,10 @@ impl PyContext { self.map_type.clone() } + pub fn zip_type(&self) -> PyObjectRef { + self.zip_type.clone() + } + pub fn str_type(&self) -> PyObjectRef { self.str_type.clone() } @@ -886,6 +913,10 @@ pub enum PyObjectPayload { position: usize, iterated_obj: PyObjectRef, }, + EnumerateIterator { + counter: BigInt, + iterator: PyObjectRef, + }, FilterIterator { predicate: PyObjectRef, iterator: PyObjectRef, @@ -894,10 +925,13 @@ pub enum PyObjectPayload { mapper: PyObjectRef, iterators: Vec, }, + ZipIterator { + iterators: Vec, + }, Slice { - start: Option, - stop: Option, - step: Option, + start: Option, + stop: Option, + step: Option, }, Range { range: objrange::RangeType, @@ -962,8 +996,10 @@ impl fmt::Debug for PyObjectPayload { 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"), @@ -1059,8 +1095,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/json.rs b/vm/src/stdlib/json.rs index e284c2c31..1e7ec411e 100644 --- a/vm/src/stdlib/json.rs +++ b/vm/src/stdlib/json.rs @@ -51,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()) { diff --git a/vm/src/stdlib/os.rs b/vm/src/stdlib/os.rs index a9ba6fe70..4671f5fe9 100644 --- a/vm/src/stdlib/os.rs +++ b/vm/src/stdlib/os.rs @@ -53,7 +53,7 @@ pub fn os_close(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { 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 desciptors (and hence prevent leaks) + //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()); diff --git a/vm/src/vm.rs b/vm/src/vm.rs index 9bbd94d64..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; @@ -104,6 +104,11 @@ impl VirtualMachine { 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 { @@ -122,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 { @@ -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)?; diff --git a/wasm/lib/src/lib.rs b/wasm/lib/src/lib.rs index f7da7edd1..7ad3f1956 100644 --- a/wasm/lib/src/lib.rs +++ b/wasm/lib/src/lib.rs @@ -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) }