From 7f75e3ee8a51ab4955c7cd875be5b412f73de243 Mon Sep 17 00:00:00 2001 From: Joey Hain Date: Wed, 27 Feb 2019 19:53:20 -0800 Subject: [PATCH 01/26] Significant improvements to new function definition style - PyRef type for accepting references to payloads. - Args type for consuming remaining positional args (mirrors python `*args`). - KwArgs type for consuming remaining keyword args (mirrors python `*kwargs`). - OptArg type for consuming remaining keyword args (no python code equivalent, only possible in native functions like in cpython). - PyIterable for accepting an iterator over a sequence of Ts. - Arity checking (but TypeError messages need work) --- vm/src/function.rs | 41 +++++ vm/src/lib.rs | 1 + vm/src/obj/objint.rs | 22 ++- vm/src/obj/objrange.rs | 33 ++-- vm/src/obj/objstr.rs | 23 ++- vm/src/pyobject.rs | 392 ++++++++++++++++++++++++++--------------- 6 files changed, 336 insertions(+), 176 deletions(-) create mode 100644 vm/src/function.rs diff --git a/vm/src/function.rs b/vm/src/function.rs new file mode 100644 index 000000000..434f995bb --- /dev/null +++ b/vm/src/function.rs @@ -0,0 +1,41 @@ +use std::marker::PhantomData; +use std::ops::Deref; + +use crate::obj::objtype; +use crate::pyobject::{PyObjectPayload2, PyObjectRef, PyResult, TryFromObject}; +use crate::vm::VirtualMachine; + +// TODO: Move PyFuncArgs, FromArgs, etc. here + +pub struct PyRef { + // invariant: this obj must always have payload of type T + obj: PyObjectRef, + _payload: PhantomData, +} + +impl Deref for PyRef +where + T: PyObjectPayload2, +{ + type Target = T; + + fn deref(&self) -> &T { + self.obj.payload().expect("unexpected payload for type") + } +} + +impl TryFromObject for PyRef +where + T: PyObjectPayload2, +{ + fn try_from_object(vm: &mut VirtualMachine, obj: PyObjectRef) -> PyResult { + if objtype::isinstance(&obj, &T::required_type(&vm.ctx)) { + Ok(PyRef { + obj, + _payload: PhantomData, + }) + } else { + Err(vm.new_type_error("wrong type".to_string())) // TODO: better message + } + } +} diff --git a/vm/src/lib.rs b/vm/src/lib.rs index f785e294d..02fec6d3f 100644 --- a/vm/src/lib.rs +++ b/vm/src/lib.rs @@ -38,6 +38,7 @@ pub mod eval; mod exceptions; pub mod format; mod frame; +pub mod function; pub mod import; pub mod obj; pub mod pyobject; diff --git a/vm/src/obj/objint.rs b/vm/src/obj/objint.rs index 9f5e48893..5f644750a 100644 --- a/vm/src/obj/objint.rs +++ b/vm/src/obj/objint.rs @@ -4,7 +4,7 @@ use super::objtype; use crate::format::FormatSpec; use crate::pyobject::{ FromPyObjectRef, IntoPyObject, PyContext, PyFuncArgs, PyObject, PyObjectPayload, PyObjectRef, - PyResult, TypeProtocol, + PyResult, TryFromObject, TypeProtocol, }; use crate::vm::VirtualMachine; use num_bigint::{BigInt, ToBigInt}; @@ -31,6 +31,26 @@ impl IntoPyObject for usize { } } +impl TryFromObject for usize { + fn try_from_object(vm: &mut VirtualMachine, obj: PyObjectRef) -> PyResult { + // FIXME: don't use get_value + match get_value(&obj).to_usize() { + Some(value) => Ok(value), + None => Err(vm.new_overflow_error("Int value cannot fit into Rust usize".to_string())), + } + } +} + +impl TryFromObject for isize { + fn try_from_object(vm: &mut VirtualMachine, obj: PyObjectRef) -> PyResult { + // FIXME: don't use get_value + match get_value(&obj).to_isize() { + Some(value) => Ok(value), + None => Err(vm.new_overflow_error("Int value cannot fit into Rust isize".to_string())), + } + } +} + fn int_repr(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(int, Some(vm.ctx.int_type()))]); let v = get_value(int); diff --git a/vm/src/obj/objrange.rs b/vm/src/obj/objrange.rs index d80b6aa01..daa5aab52 100644 --- a/vm/src/obj/objrange.rs +++ b/vm/src/obj/objrange.rs @@ -4,8 +4,7 @@ use std::ops::Mul; use super::objint; use super::objtype; use crate::pyobject::{ - FromPyObject, PyContext, PyFuncArgs, PyObject, PyObjectPayload, PyObjectRef, PyResult, - TypeProtocol, + PyContext, PyFuncArgs, PyObject, PyObjectPayload, PyObjectRef, PyResult, TypeProtocol, }; use crate::vm::VirtualMachine; use num_bigint::{BigInt, Sign}; @@ -21,18 +20,6 @@ pub struct RangeType { pub step: BigInt, } -type PyRange = RangeType; - -impl FromPyObject for PyRange { - fn typ(ctx: &PyContext) -> Option { - Some(ctx.range_type()) - } - - fn from_pyobject(obj: PyObjectRef) -> PyResult { - Ok(get_value(&obj)) - } -} - impl RangeType { #[inline] pub fn try_len(&self) -> Option { @@ -360,12 +347,22 @@ fn range_bool(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { Ok(vm.ctx.new_bool(len > 0)) } -fn range_contains(vm: &mut VirtualMachine, zelf: PyRange, needle: PyObjectRef) -> bool { - if objtype::isinstance(&needle, &vm.ctx.int_type()) { - zelf.contains(&objint::get_value(&needle)) +fn range_contains(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!( + vm, + args, + required = [(zelf, Some(vm.ctx.range_type())), (needle, None)] + ); + + let range = get_value(zelf); + + let result = if objtype::isinstance(needle, &vm.ctx.int_type()) { + range.contains(&objint::get_value(needle)) } else { false - } + }; + + Ok(vm.ctx.new_bool(result)) } fn range_index(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { diff --git a/vm/src/obj/objstr.rs b/vm/src/obj/objstr.rs index 580ab2b80..0ddb455f0 100644 --- a/vm/src/obj/objstr.rs +++ b/vm/src/obj/objstr.rs @@ -3,7 +3,7 @@ use super::objsequence::PySliceableSequence; use super::objtype; use crate::format::{FormatParseError, FormatPart, FormatString}; use crate::pyobject::{ - FromPyObject, PyContext, PyFuncArgs, PyObjectPayload, PyObjectRef, PyResult, TypeProtocol, + PyContext, PyFuncArgs, PyObjectPayload, PyObjectRef, PyResult, TypeProtocol, }; use crate::vm::VirtualMachine; use num_traits::ToPrimitive; @@ -16,16 +16,6 @@ extern crate unicode_segmentation; use self::unicode_segmentation::UnicodeSegmentation; -impl FromPyObject for String { - fn typ(ctx: &PyContext) -> Option { - Some(ctx.str_type()) - } - - fn from_pyobject(obj: PyObjectRef) -> PyResult { - Ok(get_value(&obj)) - } -} - pub fn init(context: &PyContext) { let str_type = &context.str_type; context.set_attr(&str_type, "__add__", context.new_rustfunc(str_add)); @@ -477,8 +467,15 @@ fn str_rstrip(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { Ok(vm.ctx.new_str(value)) } -fn str_endswith(_vm: &mut VirtualMachine, zelf: String, suffix: String) -> bool { - zelf.ends_with(&suffix) +fn str_endswith(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!( + vm, + args, + required = [(s, Some(vm.ctx.str_type())), (pat, Some(vm.ctx.str_type()))] + ); + let value = get_value(&s); + let pat = get_value(&pat); + Ok(vm.ctx.new_bool(value.ends_with(pat.as_str()))) } fn str_isidentifier(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { diff --git a/vm/src/pyobject.rs b/vm/src/pyobject.rs index 54201d643..0b878c715 100644 --- a/vm/src/pyobject.rs +++ b/vm/src/pyobject.rs @@ -1,3 +1,9 @@ +use std::cell::{Cell, RefCell}; +use std::collections::HashMap; +use std::fmt; +use std::iter; +use std::rc::{Rc, Weak}; + use crate::bytecode; use crate::exceptions; use crate::frame::Frame; @@ -35,10 +41,6 @@ use num_bigint::BigInt; use num_bigint::ToBigInt; use num_complex::Complex64; use num_traits::{One, Zero}; -use std::cell::{Cell, RefCell}; -use std::collections::HashMap; -use std::fmt; -use std::rc::{Rc, Weak}; /* Python objects and references. @@ -575,7 +577,7 @@ impl PyContext { { PyObject::new( PyObjectPayload::RustFunction { - function: factory.create(self), + function: factory.create(), }, self.builtin_function_or_method_type(), ) @@ -957,24 +959,191 @@ impl PyFuncArgs { None => Ok(None), } } -} -pub trait FromPyObject: Sized { - fn typ(ctx: &PyContext) -> Option; - - fn from_pyobject(obj: PyObjectRef) -> PyResult; -} - -impl FromPyObject for PyObjectRef { - fn typ(_ctx: &PyContext) -> Option { - None + fn into_iter(self) -> impl Iterator { + self.args.into_iter().map(PyArg::Positional).chain( + self.kwargs + .into_iter() + .map(|(name, value)| PyArg::Keyword(name, value)), + ) } - fn from_pyobject(obj: PyObjectRef) -> PyResult { + fn bind(self, vm: &mut VirtualMachine) -> PyResult { + let mut args = self.into_iter().peekable(); + let bound = T::from_args(vm, &mut args)?; + + if args.next().is_none() { + Ok(bound) + } else { + Err(vm.new_type_error("too many args".to_string())) // TODO: improve error message + } + } +} + +pub trait FromArgs: Sized { + fn from_args(vm: &mut VirtualMachine, args: &mut iter::Peekable) -> PyResult + where + I: Iterator; +} + +pub struct PyIterable { + obj: PyObjectRef, + method: PyObjectRef, + _item: std::marker::PhantomData, +} + +impl PyIterable { + pub fn iter<'a>(&self, vm: &'a mut VirtualMachine) -> PyResult> { + let iter_obj = vm.invoke( + self.method.clone(), + PyFuncArgs { + args: vec![], + kwargs: vec![], + }, + )?; + + Ok(PyIterator { + vm, + obj: iter_obj, + _item: std::marker::PhantomData, + }) + } +} + +pub struct PyIterator<'a, T> { + vm: &'a mut VirtualMachine, + obj: PyObjectRef, + _item: std::marker::PhantomData, +} + +impl<'a, T> Iterator for PyIterator<'a, T> +where + T: TryFromObject, +{ + type Item = PyResult; + + fn next(&mut self) -> Option { + match self.vm.call_method(&self.obj, "__next__", vec![]) { + Ok(value) => Some(T::try_from_object(self.vm, value)), + Err(ref err) if objtype::isinstance(err, &self.vm.ctx.exceptions.stop_iteration) => { + None + } + Err(err) => Some(Err(err)), + } + } +} + +impl TryFromObject for PyIterable +where + T: TryFromObject, +{ + fn try_from_object(vm: &mut VirtualMachine, obj: PyObjectRef) -> PyResult { + Ok(PyIterable { + obj: obj.clone(), + method: vm.get_method(obj, "__iter__")?, + _item: std::marker::PhantomData, + }) + } +} + +impl TryFromObject for PyObjectRef { + fn try_from_object(_vm: &mut VirtualMachine, obj: PyObjectRef) -> PyResult { Ok(obj) } } +pub struct KwArgs(HashMap); + +impl FromArgs for KwArgs +where + T: TryFromObject, +{ + fn from_args(vm: &mut VirtualMachine, args: &mut iter::Peekable) -> PyResult + where + I: Iterator, + { + let mut kwargs = HashMap::new(); + while let Some(PyArg::Keyword(name, value)) = args.next() { + kwargs.insert(name, T::try_from_object(vm, value)?); + } + Ok(KwArgs(kwargs)) + } +} + +pub struct Args(Vec); + +impl FromArgs for Args +where + T: TryFromObject, +{ + fn from_args(vm: &mut VirtualMachine, args: &mut iter::Peekable) -> PyResult + where + I: Iterator, + { + let mut varargs = Vec::new(); + while let Some(PyArg::Positional(value)) = args.next() { + varargs.push(T::try_from_object(vm, value)?); + } + Ok(Args(varargs)) + } +} + +impl FromArgs for T +where + T: TryFromObject, +{ + fn from_args(vm: &mut VirtualMachine, args: &mut iter::Peekable) -> PyResult + where + I: Iterator, + { + if let Some(PyArg::Positional(value)) = args.next() { + Ok(T::try_from_object(vm, value)?) + } else { + Err(vm.new_type_error("not enough args".to_string())) // TODO: improve error message + } + } +} + +pub struct OptArg(Option); + +impl std::ops::Deref for OptArg { + type Target = Option; + + fn deref(&self) -> &Option { + &self.0 + } +} + +impl FromArgs for OptArg +where + T: TryFromObject, +{ + fn from_args(vm: &mut VirtualMachine, args: &mut iter::Peekable) -> PyResult + where + I: Iterator, + { + Ok(OptArg(if let Some(PyArg::Positional(_)) = args.peek() { + let value = if let Some(PyArg::Positional(value)) = args.next() { + value + } else { + unreachable!() + }; + Some(T::try_from_object(vm, value)?) + } else { + None + })) + } +} + +pub enum PyArg { + Positional(PyObjectRef), + Keyword(String, PyObjectRef), +} + +pub trait TryFromObject: Sized { + fn try_from_object(vm: &mut VirtualMachine, obj: PyObjectRef) -> PyResult; +} + pub trait IntoPyObject { fn into_pyobject(self, ctx: &PyContext) -> PyResult; } @@ -985,30 +1154,46 @@ impl IntoPyObject for PyObjectRef { } } -impl IntoPyObject for PyResult { - fn into_pyobject(self, _ctx: &PyContext) -> PyResult { - self +impl IntoPyObject for PyResult +where + T: IntoPyObject, +{ + fn into_pyobject(self, ctx: &PyContext) -> PyResult { + self.and_then(|res| T::into_pyobject(res, ctx)) } } -pub trait FromPyFuncArgs: Sized { - fn required_params(ctx: &PyContext) -> Vec; +impl IntoPyObject for () { + fn into_pyobject(self, ctx: &PyContext) -> PyResult { + Ok(ctx.none()) + } +} - fn from_py_func_args(args: &mut PyFuncArgs) -> PyResult; +impl IntoPyObject for T +where + T: PyObjectPayload2 + Sized, +{ + fn into_pyobject(self, ctx: &PyContext) -> PyResult { + Ok(PyObject::new( + PyObjectPayload::AnyRustValue { + value: Box::new(self), + }, + T::required_type(ctx), + )) + } } macro_rules! tuple_from_py_func_args { ($($T:ident),+) => { - impl<$($T),+> FromPyFuncArgs for ($($T,)+) + impl<$($T),+> FromArgs for ($($T,)+) where - $($T: FromPyFuncArgs),+ + $($T: FromArgs),+ { - fn required_params(ctx: &PyContext) -> Vec { - vec![$($T::required_params(ctx),)+].into_iter().flatten().collect() - } - - fn from_py_func_args(args: &mut PyFuncArgs) -> PyResult { - Ok(($($T::from_py_func_args(args)?,)+)) + fn from_args(vm: &mut VirtualMachine, args: &mut iter::Peekable) -> PyResult + where + I: Iterator + { + Ok(($($T::from_args(vm, args)?,)+)) } } }; @@ -1020,141 +1205,46 @@ tuple_from_py_func_args!(A, B, C); tuple_from_py_func_args!(A, B, C, D); tuple_from_py_func_args!(A, B, C, D, E); -impl FromPyFuncArgs for T -where - T: FromPyObject, -{ - fn required_params(ctx: &PyContext) -> Vec { - vec![Parameter { - kind: PositionalOnly, - typ: T::typ(ctx), - }] - } - - fn from_py_func_args(args: &mut PyFuncArgs) -> PyResult { - Self::from_pyobject(args.shift()) - } -} - pub type PyNativeFunc = Box PyResult>; pub trait PyNativeFuncFactory { - fn create(self, ctx: &PyContext) -> PyNativeFunc; + fn create(self) -> PyNativeFunc; } impl PyNativeFuncFactory for F where F: Fn(&mut VirtualMachine, PyFuncArgs) -> PyResult + 'static, { - fn create(self, _ctx: &PyContext) -> PyNativeFunc { + fn create(self) -> PyNativeFunc { Box::new(self) } } -macro_rules! tuple_py_native_func_factory { - ($($T:ident),+) => { +macro_rules! py_native_func_factory_tuple { + ($(($n:tt, $T:ident)),+) => { impl PyNativeFuncFactory<($($T,)+), R> for F where - F: Fn(&mut VirtualMachine, $($T),+) -> R + 'static, - $($T: FromPyFuncArgs,)+ + F: Fn($($T,)+ &mut VirtualMachine) -> R + 'static, + $($T: FromArgs,)+ + ($($T,)+): FromArgs, R: IntoPyObject, { - fn create(self, ctx: &PyContext) -> PyNativeFunc { - let parameters = vec![$($T::required_params(ctx)),+] - .into_iter() - .flatten() - .collect(); - let signature = Signature::new(parameters); + fn create(self) -> PyNativeFunc { + Box::new(move |vm, args| { + let ($($n,)+) = args.bind::<($($T,)+)>(vm)?; - Box::new(move |vm, mut args| { - signature.check(vm, &mut args)?; - - (self)(vm, $($T::from_py_func_args(&mut args)?,)+) - .into_pyobject(&vm.ctx) + (self)($($n,)+ vm).into_pyobject(&vm.ctx) }) } } }; } -tuple_py_native_func_factory!(A); -tuple_py_native_func_factory!(A, B); -tuple_py_native_func_factory!(A, B, C); -tuple_py_native_func_factory!(A, B, C, D); -tuple_py_native_func_factory!(A, B, C, D, E); - -#[derive(Debug)] -pub struct Signature { - positional_params: Vec, - keyword_params: HashMap, -} - -impl Signature { - fn new(params: Vec) -> Self { - let mut positional_params = Vec::new(); - let mut keyword_params = HashMap::new(); - for param in params { - match param.kind { - PositionalOnly => { - positional_params.push(param); - } - KeywordOnly { ref name } => { - keyword_params.insert(name.clone(), param); - } - } - } - - Self { - positional_params, - keyword_params, - } - } - - fn arg_type(&self, pos: usize) -> Option<&PyObjectRef> { - self.positional_params[pos].typ.as_ref() - } - - #[allow(unused)] - fn kwarg_type(&self, name: &str) -> Option<&PyObjectRef> { - self.keyword_params[name].typ.as_ref() - } - - fn check(&self, vm: &mut VirtualMachine, args: &PyFuncArgs) -> PyResult<()> { - // TODO: check arity - - for (pos, arg) in args.args.iter().enumerate() { - if let Some(expected_type) = self.arg_type(pos) { - if !objtype::isinstance(arg, expected_type) { - let arg_typ = arg.typ(); - let expected_type_name = vm.to_pystr(&expected_type)?; - let actual_type = vm.to_pystr(&arg_typ)?; - return Err(vm.new_type_error(format!( - "argument of type {} is required for parameter {} (got: {})", - expected_type_name, - pos + 1, - actual_type - ))); - } - } - } - - Ok(()) - } -} - -#[derive(Debug)] -pub struct Parameter { - typ: Option, - kind: ParameterKind, -} - -#[derive(Debug)] -pub enum ParameterKind { - PositionalOnly, - KeywordOnly { name: String }, -} - -use self::ParameterKind::*; +py_native_func_factory_tuple!((a, A)); +py_native_func_factory_tuple!((a, A), (b, B)); +py_native_func_factory_tuple!((a, A), (b, B), (c, C)); +py_native_func_factory_tuple!((a, A), (b, B), (c, C), (d, D)); +py_native_func_factory_tuple!((a, A), (b, B), (c, C), (d, D), (e, E)); /// Rather than determining the type of a python object, this enum is more /// a holder for the rust payload of a python object. It is more a carrier @@ -1182,9 +1272,6 @@ pub enum PyObjectPayload { Dict { elements: RefCell, }, - Set { - elements: RefCell>, - }, Iterator { position: Cell, iterated_obj: PyObjectRef, @@ -1247,6 +1334,9 @@ pub enum PyObjectPayload { dict: RefCell, mro: Vec, }, + Set { + elements: RefCell>, + }, WeakRef { referent: PyObjectWeakRef, }, @@ -1322,6 +1412,20 @@ impl PyObject { pub fn into_ref(self) -> PyObjectRef { Rc::new(self) } + + pub fn payload(&self) -> Option<&T> { + if let PyObjectPayload::AnyRustValue { ref value } = self.payload { + value.downcast_ref() + } else { + None + } + } +} + +// The intention is for this to replace `PyObjectPayload` once everything is +// converted to use `PyObjectPayload::AnyRustvalue`. +pub trait PyObjectPayload2: std::any::Any + fmt::Debug { + fn required_type(ctx: &PyContext) -> PyObjectRef; } #[cfg(test)] From 713edc57ee799bc335167d78e19e45e560315703 Mon Sep 17 00:00:00 2001 From: Joey Hain Date: Wed, 27 Feb 2019 19:53:31 -0800 Subject: [PATCH 02/26] Convert some objstring methods to new style. --- vm/src/obj/objbool.rs | 3 +- vm/src/obj/objstr.rs | 148 +++++++++++++++++++++++------------------- vm/src/pyobject.rs | 11 ++-- vm/src/stdlib/json.rs | 5 +- 4 files changed, 93 insertions(+), 74 deletions(-) diff --git a/vm/src/obj/objbool.rs b/vm/src/obj/objbool.rs index cf16e1f5f..02ae3cc2f 100644 --- a/vm/src/obj/objbool.rs +++ b/vm/src/obj/objbool.rs @@ -17,7 +17,8 @@ pub fn boolval(vm: &mut VirtualMachine, obj: PyObjectRef) -> Result value != 0.0, PyObjectPayload::Sequence { ref elements } => !elements.borrow().is_empty(), PyObjectPayload::Dict { ref elements } => !elements.borrow().is_empty(), - PyObjectPayload::String { ref value } => !value.is_empty(), + // FIXME + //PyObjectPayload::String { ref value } => !value.is_empty(), PyObjectPayload::None { .. } => false, _ => { if let Ok(f) = vm.get_method(obj.clone(), "__bool__") { diff --git a/vm/src/obj/objstr.rs b/vm/src/obj/objstr.rs index 0ddb455f0..b24f389b3 100644 --- a/vm/src/obj/objstr.rs +++ b/vm/src/obj/objstr.rs @@ -2,8 +2,10 @@ use super::objint; use super::objsequence::PySliceableSequence; use super::objtype; use crate::format::{FormatParseError, FormatPart, FormatString}; +use crate::function::PyRef; use crate::pyobject::{ - PyContext, PyFuncArgs, PyObjectPayload, PyObjectRef, PyResult, TypeProtocol, + OptArg, PyContext, PyFuncArgs, PyIterable, PyObjectPayload, PyObjectPayload2, PyObjectRef, + PyResult, TypeProtocol, }; use crate::vm::VirtualMachine; use num_traits::ToPrimitive; @@ -16,6 +18,74 @@ extern crate unicode_segmentation; use self::unicode_segmentation::UnicodeSegmentation; +#[derive(Clone, Debug)] +pub struct PyString { + // TODO: shouldn't be public + pub value: String, +} + +impl PyString { + pub fn endswith( + zelf: PyRef, + suffix: PyRef, + start: OptArg, + end: OptArg, + _vm: &mut VirtualMachine, + ) -> bool { + let start = start.unwrap_or(0); + let end = end.unwrap_or(zelf.value.len()); + zelf.value[start..end].ends_with(&suffix.value) + } + + pub fn startswith( + zelf: PyRef, + prefix: PyRef, + start: OptArg, + end: OptArg, + _vm: &mut VirtualMachine, + ) -> bool { + let start = start.unwrap_or(0); + let end = end.unwrap_or(zelf.value.len()); + zelf.value[start..end].starts_with(&prefix.value) + } + + fn upper(zelf: PyRef, _vm: &mut VirtualMachine) -> PyString { + PyString { + value: zelf.value.to_uppercase(), + } + } + + fn lower(zelf: PyRef, _vm: &mut VirtualMachine) -> PyString { + PyString { + value: zelf.value.to_lowercase(), + } + } + + fn join( + zelf: PyRef, + iterable: PyIterable>, + vm: &mut VirtualMachine, + ) -> PyResult { + let mut joined = String::new(); + + for (idx, elem) in iterable.iter(vm)?.enumerate() { + let elem = elem?; + if idx != 0 { + joined.push_str(&zelf.value); + } + joined.push_str(&elem.value) + } + + Ok(PyString { value: joined }) + } +} + +impl PyObjectPayload2 for PyString { + fn required_type(ctx: &PyContext) -> PyObjectRef { + ctx.str_type() + } +} + pub fn init(context: &PyContext) { let str_type = &context.str_type; context.set_attr(&str_type, "__add__", context.new_rustfunc(str_add)); @@ -37,9 +107,9 @@ pub fn init(context: &PyContext) { context.set_attr(&str_type, "__str__", context.new_rustfunc(str_str)); context.set_attr(&str_type, "__repr__", context.new_rustfunc(str_repr)); context.set_attr(&str_type, "format", context.new_rustfunc(str_format)); - context.set_attr(&str_type, "lower", context.new_rustfunc(str_lower)); + context.set_attr(&str_type, "lower", context.new_rustfunc(PyString::lower)); context.set_attr(&str_type, "casefold", context.new_rustfunc(str_casefold)); - context.set_attr(&str_type, "upper", context.new_rustfunc(str_upper)); + context.set_attr(&str_type, "upper", context.new_rustfunc(PyString::upper)); context.set_attr( &str_type, "capitalize", @@ -50,11 +120,15 @@ pub fn init(context: &PyContext) { context.set_attr(&str_type, "strip", context.new_rustfunc(str_strip)); context.set_attr(&str_type, "lstrip", context.new_rustfunc(str_lstrip)); context.set_attr(&str_type, "rstrip", context.new_rustfunc(str_rstrip)); - context.set_attr(&str_type, "endswith", context.new_rustfunc(str_endswith)); + context.set_attr( + &str_type, + "endswith", + context.new_rustfunc(PyString::endswith), + ); context.set_attr( &str_type, "startswith", - context.new_rustfunc(str_startswith), + context.new_rustfunc(PyString::startswith), ); context.set_attr(&str_type, "isalnum", context.new_rustfunc(str_isalnum)); context.set_attr(&str_type, "isnumeric", context.new_rustfunc(str_isnumeric)); @@ -74,7 +148,7 @@ pub fn init(context: &PyContext) { "splitlines", context.new_rustfunc(str_splitlines), ); - context.set_attr(&str_type, "join", context.new_rustfunc(str_join)); + context.set_attr(&str_type, "join", context.new_rustfunc(PyString::join)); context.set_attr(&str_type, "find", context.new_rustfunc(str_find)); context.set_attr(&str_type, "rfind", context.new_rustfunc(str_rfind)); context.set_attr(&str_type, "index", context.new_rustfunc(str_index)); @@ -103,19 +177,11 @@ pub fn init(context: &PyContext) { } pub fn get_value(obj: &PyObjectRef) -> String { - if let PyObjectPayload::String { value } = &obj.payload { - value.to_string() - } else { - panic!("Inner error getting str"); - } + obj.payload::().unwrap().value.clone() } pub fn borrow_value(obj: &PyObjectRef) -> &str { - if let PyObjectPayload::String { value } = &obj.payload { - value.as_str() - } else { - panic!("Inner error getting str"); - } + &obj.payload::().unwrap().value } fn str_eq(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { @@ -377,18 +443,6 @@ fn str_mul(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { } } -fn str_upper(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(s, Some(vm.ctx.str_type()))]); - let value = get_value(&s).to_uppercase(); - Ok(vm.ctx.new_str(value)) -} - -fn str_lower(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(s, Some(vm.ctx.str_type()))]); - let value = get_value(&s).to_lowercase(); - Ok(vm.ctx.new_str(value)) -} - fn str_capitalize(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(s, Some(vm.ctx.str_type()))]); let value = get_value(&s); @@ -467,17 +521,6 @@ fn str_rstrip(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { Ok(vm.ctx.new_str(value)) } -fn str_endswith(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(s, Some(vm.ctx.str_type())), (pat, Some(vm.ctx.str_type()))] - ); - let value = get_value(&s); - let pat = get_value(&pat); - Ok(vm.ctx.new_bool(value.ends_with(pat.as_str()))) -} - fn str_isidentifier(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(s, Some(vm.ctx.str_type()))]); let value = get_value(&s); @@ -557,22 +600,6 @@ fn str_zfill(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { Ok(vm.ctx.new_str(new_str)) } -fn str_join(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(s, Some(vm.ctx.str_type())), (iterable, None)] - ); - let value = get_value(&s); - let elements: Vec = vm - .extract_elements(iterable)? - .iter() - .map(|w| get_value(&w)) - .collect(); - let joined = elements.join(&value); - Ok(vm.ctx.new_str(joined)) -} - fn str_count(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!( vm, @@ -862,17 +889,6 @@ fn str_center(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { Ok(vm.ctx.new_str(new_str)) } -fn str_startswith(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(s, Some(vm.ctx.str_type())), (pat, Some(vm.ctx.str_type()))] - ); - let value = get_value(&s); - let pat = get_value(&pat); - Ok(vm.ctx.new_bool(value.starts_with(pat.as_str()))) -} - fn str_contains(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!( vm, diff --git a/vm/src/pyobject.rs b/vm/src/pyobject.rs index 0b878c715..77b3ca7aa 100644 --- a/vm/src/pyobject.rs +++ b/vm/src/pyobject.rs @@ -480,7 +480,12 @@ impl PyContext { } pub fn new_str(&self, s: String) -> PyObjectRef { - PyObject::new(PyObjectPayload::String { value: s }, self.str_type()) + PyObject::new( + PyObjectPayload::AnyRustValue { + value: Box::new(objstr::PyString { value: s }), + }, + self.str_type(), + ) } pub fn new_bytes(&self, data: Vec) -> PyObjectRef { @@ -1251,9 +1256,6 @@ py_native_func_factory_tuple!((a, A), (b, B), (c, C), (d, D), (e, E)); /// of rust data for a particular python object. Determine the python type /// by using for example the `.typ()` method on a python object. pub enum PyObjectPayload { - String { - value: String, - }, Integer { value: BigInt, }, @@ -1357,7 +1359,6 @@ pub enum PyObjectPayload { impl fmt::Debug for PyObjectPayload { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { - PyObjectPayload::String { ref value } => write!(f, "str \"{}\"", value), PyObjectPayload::Integer { ref value } => write!(f, "int {}", value), PyObjectPayload::Float { ref value } => write!(f, "float {}", value), PyObjectPayload::Complex { ref value } => write!(f, "complex {}", value), diff --git a/vm/src/stdlib/json.rs b/vm/src/stdlib/json.rs index 78fc05dc9..10ebf3859 100644 --- a/vm/src/stdlib/json.rs +++ b/vm/src/stdlib/json.rs @@ -167,8 +167,9 @@ impl<'de> Visitor<'de> for PyObjectDeserializer<'de> { // than wrapping the given object up and then unwrapping it to determine whether or // not it is a string while let Some((key_obj, value)) = access.next_entry_seed(self.clone(), self.clone())? { - let key = match key_obj.payload { - PyObjectPayload::String { ref value } => value.clone(), + let key: String = match key_obj.payload { + // FIXME + // PyObjectPayload::String { ref value } => value.clone(), _ => unimplemented!("map keys must be strings"), }; self.vm.ctx.set_item(&dict, &key, value); From 67f8c020875e7cfecc934cfb574420a4524253db Mon Sep 17 00:00:00 2001 From: Joey Hain Date: Wed, 27 Feb 2019 20:05:04 -0800 Subject: [PATCH 03/26] cleanup --- vm/src/obj/objbool.rs | 6 ++++-- vm/src/stdlib/json.rs | 11 +++++++---- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/vm/src/obj/objbool.rs b/vm/src/obj/objbool.rs index 02ae3cc2f..8550099f6 100644 --- a/vm/src/obj/objbool.rs +++ b/vm/src/obj/objbool.rs @@ -1,3 +1,4 @@ +use super::objstr::PyString; use super::objtype; use crate::pyobject::{ IntoPyObject, PyContext, PyFuncArgs, PyObjectPayload, PyObjectRef, PyResult, TypeProtocol, @@ -12,13 +13,14 @@ impl IntoPyObject for bool { } pub fn boolval(vm: &mut VirtualMachine, obj: PyObjectRef) -> Result { + if let Some(s) = obj.payload::() { + return Ok(!s.value.is_empty()); + } let result = match obj.payload { PyObjectPayload::Integer { ref value } => !value.is_zero(), PyObjectPayload::Float { value } => value != 0.0, PyObjectPayload::Sequence { ref elements } => !elements.borrow().is_empty(), PyObjectPayload::Dict { ref elements } => !elements.borrow().is_empty(), - // FIXME - //PyObjectPayload::String { ref value } => !value.is_empty(), PyObjectPayload::None { .. } => false, _ => { if let Ok(f) = vm.get_method(obj.clone(), "__bool__") { diff --git a/vm/src/stdlib/json.rs b/vm/src/stdlib/json.rs index 10ebf3859..c8ea3c9bb 100644 --- a/vm/src/stdlib/json.rs +++ b/vm/src/stdlib/json.rs @@ -5,7 +5,11 @@ use serde::de::{DeserializeSeed, Visitor}; use serde::ser::{SerializeMap, SerializeSeq}; use serde_json; -use crate::obj::{objbool, objdict, objfloat, objint, objsequence, objstr, objtype}; +use crate::obj::{ + objbool, objdict, objfloat, objint, objsequence, + objstr::{self, PyString}, + objtype, +}; use crate::pyobject::{ create_type, DictProtocol, PyContext, PyFuncArgs, PyObjectPayload, PyObjectRef, PyResult, TypeProtocol, @@ -167,9 +171,8 @@ impl<'de> Visitor<'de> for PyObjectDeserializer<'de> { // than wrapping the given object up and then unwrapping it to determine whether or // not it is a string while let Some((key_obj, value)) = access.next_entry_seed(self.clone(), self.clone())? { - let key: String = match key_obj.payload { - // FIXME - // PyObjectPayload::String { ref value } => value.clone(), + let key: String = match key_obj.payload::() { + Some(PyString { ref value }) => value.clone(), _ => unimplemented!("map keys must be strings"), }; self.vm.ctx.set_item(&dict, &key, value); From e2e3353441dcb83a4297d6eefec3a0d6c50e4fe9 Mon Sep 17 00:00:00 2001 From: Joey Hain Date: Fri, 1 Mar 2019 11:39:52 -0800 Subject: [PATCH 04/26] Add factory impl for boxed fn and remove extra constructor --- vm/src/pyobject.rs | 20 ++++++++------------ wasm/lib/src/vm_class.rs | 2 +- 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/vm/src/pyobject.rs b/vm/src/pyobject.rs index aed924b1a..bbde769b2 100644 --- a/vm/src/pyobject.rs +++ b/vm/src/pyobject.rs @@ -588,16 +588,6 @@ impl PyContext { ) } - pub fn new_rustfunc_from_box( - &self, - function: Box PyResult>, - ) -> PyObjectRef { - PyObject::new( - PyObjectPayload::RustFunction { function }, - self.builtin_function_or_method_type(), - ) - } - pub fn new_frame(&self, code: PyObjectRef, scope: PyObjectRef) -> PyObjectRef { PyObject::new( PyObjectPayload::Frame { @@ -1242,7 +1232,7 @@ tuple_from_py_func_args!(A, B, C); tuple_from_py_func_args!(A, B, C, D); tuple_from_py_func_args!(A, B, C, D, E); -pub type PyNativeFunc = Box PyResult>; +pub type PyNativeFunc = Box PyResult + 'static>; pub trait PyNativeFuncFactory { fn create(self) -> PyNativeFunc; @@ -1257,6 +1247,12 @@ where } } +impl PyNativeFuncFactory for PyNativeFunc { + fn create(self) -> PyNativeFunc { + self + } +} + macro_rules! py_native_func_factory_tuple { ($(($n:tt, $T:ident)),+) => { impl PyNativeFuncFactory<($($T,)+), R> for F @@ -1378,7 +1374,7 @@ pub enum PyObjectPayload { dict: RefCell, }, RustFunction { - function: Box PyResult>, + function: PyNativeFunc, }, Socket { socket: RefCell, diff --git a/wasm/lib/src/vm_class.rs b/wasm/lib/src/vm_class.rs index a61b36b92..31f9215dc 100644 --- a/wasm/lib/src/vm_class.rs +++ b/wasm/lib/src/vm_class.rs @@ -275,7 +275,7 @@ impl WASMVirtualMachine { .into()); }; vm.ctx - .set_attr(scope, "print", vm.ctx.new_rustfunc_from_box(print_fn)); + .set_attr(scope, "print", vm.ctx.new_rustfunc(print_fn)); Ok(()) }, )? From a70f251b6a0a93444b5ddcdd653065982f810662 Mon Sep 17 00:00:00 2001 From: Joey Hain Date: Sat, 2 Mar 2019 03:47:37 -0800 Subject: [PATCH 05/26] PyNativeFuncFactory => IntoPyNativeFunc --- vm/src/pyobject.rs | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/vm/src/pyobject.rs b/vm/src/pyobject.rs index bbde769b2..9cff28f79 100644 --- a/vm/src/pyobject.rs +++ b/vm/src/pyobject.rs @@ -576,13 +576,13 @@ impl PyContext { ) } - pub fn new_rustfunc(&self, factory: F) -> PyObjectRef + pub fn new_rustfunc(&self, f: F) -> PyObjectRef where - F: PyNativeFuncFactory, + F: IntoPyNativeFunc, { PyObject::new( PyObjectPayload::RustFunction { - function: factory.create(), + function: f.into_func(), }, self.builtin_function_or_method_type(), ) @@ -1234,35 +1234,35 @@ tuple_from_py_func_args!(A, B, C, D, E); pub type PyNativeFunc = Box PyResult + 'static>; -pub trait PyNativeFuncFactory { - fn create(self) -> PyNativeFunc; +pub trait IntoPyNativeFunc { + fn into_func(self) -> PyNativeFunc; } -impl PyNativeFuncFactory for F +impl IntoPyNativeFunc for F where F: Fn(&mut VirtualMachine, PyFuncArgs) -> PyResult + 'static, { - fn create(self) -> PyNativeFunc { + fn into_func(self) -> PyNativeFunc { Box::new(self) } } -impl PyNativeFuncFactory for PyNativeFunc { - fn create(self) -> PyNativeFunc { +impl IntoPyNativeFunc for PyNativeFunc { + fn into_func(self) -> PyNativeFunc { self } } -macro_rules! py_native_func_factory_tuple { +macro_rules! into_py_native_func_tuple { ($(($n:tt, $T:ident)),+) => { - impl PyNativeFuncFactory<($($T,)+), R> for F + impl IntoPyNativeFunc<($($T,)+), R> for F where F: Fn($($T,)+ &mut VirtualMachine) -> R + 'static, $($T: FromArgs,)+ ($($T,)+): FromArgs, R: IntoPyObject, { - fn create(self) -> PyNativeFunc { + fn into_func(self) -> PyNativeFunc { Box::new(move |vm, args| { let ($($n,)+) = args.bind::<($($T,)+)>(vm)?; @@ -1273,11 +1273,11 @@ macro_rules! py_native_func_factory_tuple { }; } -py_native_func_factory_tuple!((a, A)); -py_native_func_factory_tuple!((a, A), (b, B)); -py_native_func_factory_tuple!((a, A), (b, B), (c, C)); -py_native_func_factory_tuple!((a, A), (b, B), (c, C), (d, D)); -py_native_func_factory_tuple!((a, A), (b, B), (c, C), (d, D), (e, E)); +into_py_native_func_tuple!((a, A)); +into_py_native_func_tuple!((a, A), (b, B)); +into_py_native_func_tuple!((a, A), (b, B), (c, C)); +into_py_native_func_tuple!((a, A), (b, B), (c, C), (d, D)); +into_py_native_func_tuple!((a, A), (b, B), (c, C), (d, D), (e, E)); /// Rather than determining the type of a python object, this enum is more /// a holder for the rust payload of a python object. It is more a carrier From 4558dbf21047bbfb84f7c8d7647812c842b74dae Mon Sep 17 00:00:00 2001 From: Joey Hain Date: Sat, 2 Mar 2019 13:25:42 -0800 Subject: [PATCH 06/26] Fix build (rustfmt) --- vm/src/obj/objcode.rs | 5 +-- vm/src/obj/objint.rs | 6 +--- vm/src/stdlib/pystruct.rs | 66 +++++++-------------------------------- 3 files changed, 13 insertions(+), 64 deletions(-) diff --git a/vm/src/obj/objcode.rs b/vm/src/obj/objcode.rs index c9f244466..53ea55f4c 100644 --- a/vm/src/obj/objcode.rs +++ b/vm/src/obj/objcode.rs @@ -55,10 +55,7 @@ fn code_repr(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { Ok(vm.new_str(repr)) } -fn member_code_obj( - vm: &mut VirtualMachine, - args: PyFuncArgs, -) -> PyResult { +fn member_code_obj(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!( vm, args, diff --git a/vm/src/obj/objint.rs b/vm/src/obj/objint.rs index 8fb5bcdd1..a79dfb6dd 100644 --- a/vm/src/obj/objint.rs +++ b/vm/src/obj/objint.rs @@ -83,11 +83,7 @@ fn int_new(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { } // Casting function: -pub fn to_int( - vm: &mut VirtualMachine, - obj: &PyObjectRef, - base: u32, -) -> PyResult { +pub fn to_int(vm: &mut VirtualMachine, obj: &PyObjectRef, base: u32) -> PyResult { let val = if objtype::isinstance(obj, &vm.ctx.int_type()) { get_value(obj) } else if objtype::isinstance(obj, &vm.ctx.float_type()) { diff --git a/vm/src/stdlib/pystruct.rs b/vm/src/stdlib/pystruct.rs index 5599e61a4..c96bba736 100644 --- a/vm/src/stdlib/pystruct.rs +++ b/vm/src/stdlib/pystruct.rs @@ -46,31 +46,19 @@ fn get_int(vm: &mut VirtualMachine, arg: &PyObjectRef) -> PyResult { objint::to_int(vm, arg, 10) } -fn pack_i8( - vm: &mut VirtualMachine, - arg: &PyObjectRef, - data: &mut Write, -) -> PyResult<()> { +fn pack_i8(vm: &mut VirtualMachine, arg: &PyObjectRef, data: &mut Write) -> PyResult<()> { let v = get_int(vm, arg)?.to_i8().unwrap(); data.write_i8(v).unwrap(); Ok(()) } -fn pack_u8( - vm: &mut VirtualMachine, - arg: &PyObjectRef, - data: &mut Write, -) -> PyResult<()> { +fn pack_u8(vm: &mut VirtualMachine, arg: &PyObjectRef, data: &mut Write) -> PyResult<()> { let v = get_int(vm, arg)?.to_u8().unwrap(); data.write_u8(v).unwrap(); Ok(()) } -fn pack_bool( - vm: &mut VirtualMachine, - arg: &PyObjectRef, - data: &mut Write, -) -> PyResult<()> { +fn pack_bool(vm: &mut VirtualMachine, arg: &PyObjectRef, data: &mut Write) -> PyResult<()> { if objtype::isinstance(&arg, &vm.ctx.bool_type()) { let v = if objbool::get_value(arg) { 1 } else { 0 }; data.write_u8(v).unwrap(); @@ -80,71 +68,43 @@ fn pack_bool( } } -fn pack_i16( - vm: &mut VirtualMachine, - arg: &PyObjectRef, - data: &mut Write, -) -> PyResult<()> { +fn pack_i16(vm: &mut VirtualMachine, arg: &PyObjectRef, data: &mut Write) -> PyResult<()> { let v = get_int(vm, arg)?.to_i16().unwrap(); data.write_i16::(v).unwrap(); Ok(()) } -fn pack_u16( - vm: &mut VirtualMachine, - arg: &PyObjectRef, - data: &mut Write, -) -> PyResult<()> { +fn pack_u16(vm: &mut VirtualMachine, arg: &PyObjectRef, data: &mut Write) -> PyResult<()> { let v = get_int(vm, arg)?.to_u16().unwrap(); data.write_u16::(v).unwrap(); Ok(()) } -fn pack_i32( - vm: &mut VirtualMachine, - arg: &PyObjectRef, - data: &mut Write, -) -> PyResult<()> { +fn pack_i32(vm: &mut VirtualMachine, arg: &PyObjectRef, data: &mut Write) -> PyResult<()> { let v = get_int(vm, arg)?.to_i32().unwrap(); data.write_i32::(v).unwrap(); Ok(()) } -fn pack_u32( - vm: &mut VirtualMachine, - arg: &PyObjectRef, - data: &mut Write, -) -> PyResult<()> { +fn pack_u32(vm: &mut VirtualMachine, arg: &PyObjectRef, data: &mut Write) -> PyResult<()> { let v = get_int(vm, arg)?.to_u32().unwrap(); data.write_u32::(v).unwrap(); Ok(()) } -fn pack_i64( - vm: &mut VirtualMachine, - arg: &PyObjectRef, - data: &mut Write, -) -> PyResult<()> { +fn pack_i64(vm: &mut VirtualMachine, arg: &PyObjectRef, data: &mut Write) -> PyResult<()> { let v = get_int(vm, arg)?.to_i64().unwrap(); data.write_i64::(v).unwrap(); Ok(()) } -fn pack_u64( - vm: &mut VirtualMachine, - arg: &PyObjectRef, - data: &mut Write, -) -> PyResult<()> { +fn pack_u64(vm: &mut VirtualMachine, arg: &PyObjectRef, data: &mut Write) -> PyResult<()> { let v = get_int(vm, arg)?.to_u64().unwrap(); data.write_u64::(v).unwrap(); Ok(()) } -fn pack_f32( - vm: &mut VirtualMachine, - arg: &PyObjectRef, - data: &mut Write, -) -> PyResult<()> { +fn pack_f32(vm: &mut VirtualMachine, arg: &PyObjectRef, data: &mut Write) -> PyResult<()> { if objtype::isinstance(&arg, &vm.ctx.float_type()) { let v = objfloat::get_value(arg) as f32; data.write_f32::(v).unwrap(); @@ -154,11 +114,7 @@ fn pack_f32( } } -fn pack_f64( - vm: &mut VirtualMachine, - arg: &PyObjectRef, - data: &mut Write, -) -> PyResult<()> { +fn pack_f64(vm: &mut VirtualMachine, arg: &PyObjectRef, data: &mut Write) -> PyResult<()> { if objtype::isinstance(&arg, &vm.ctx.float_type()) { let v = objfloat::get_value(arg) as f64; data.write_f64::(v).unwrap(); From e7fb11621079b5f559c0673c9b8cf488bb214ff3 Mon Sep 17 00:00:00 2001 From: Joey Hain Date: Thu, 28 Feb 2019 15:52:26 -0800 Subject: [PATCH 07/26] Convert more objects to `Any` payload --- vm/src/obj/objbool.rs | 5 +++- vm/src/obj/objfloat.rs | 34 ++++++++++++++++++----- vm/src/obj/objiter.rs | 62 +++++++++++++++++++++--------------------- vm/src/obj/objrange.rs | 42 ++++++++++++++++------------ vm/src/pyobject.rs | 17 +++++------- 5 files changed, 94 insertions(+), 66 deletions(-) diff --git a/vm/src/obj/objbool.rs b/vm/src/obj/objbool.rs index 3e0798ef5..84b048e3a 100644 --- a/vm/src/obj/objbool.rs +++ b/vm/src/obj/objbool.rs @@ -1,3 +1,4 @@ +use super::objfloat::PyFloat; use super::objstr::PyString; use super::objtype; use crate::pyobject::{ @@ -16,9 +17,11 @@ pub fn boolval(vm: &mut VirtualMachine, obj: PyObjectRef) -> Result() { return Ok(!s.value.is_empty()); } + if let Some(value) = obj.payload::() { + return Ok(*value != PyFloat::from(0.0)); + } let result = match obj.payload { PyObjectPayload::Integer { ref value } => !value.is_zero(), - PyObjectPayload::Float { value } => value != 0.0, PyObjectPayload::Sequence { ref elements } => !elements.borrow().is_empty(), PyObjectPayload::Dict { ref elements } => !elements.borrow().is_empty(), PyObjectPayload::None { .. } => false, diff --git a/vm/src/obj/objfloat.rs b/vm/src/obj/objfloat.rs index b59e893e3..a2038bffb 100644 --- a/vm/src/obj/objfloat.rs +++ b/vm/src/obj/objfloat.rs @@ -3,12 +3,30 @@ use super::objint; use super::objstr; use super::objtype; use crate::pyobject::{ - PyContext, PyFuncArgs, PyObject, PyObjectPayload, PyObjectRef, PyResult, TypeProtocol, + PyContext, PyFuncArgs, PyObject, PyObjectPayload, PyObjectPayload2, PyObjectRef, PyResult, + TypeProtocol, }; use crate::vm::VirtualMachine; use num_bigint::ToBigInt; use num_traits::ToPrimitive; +#[derive(Debug, Copy, Clone, PartialEq)] +pub struct PyFloat { + value: f64, +} + +impl PyObjectPayload2 for PyFloat { + fn required_type(ctx: &PyContext) -> PyObjectRef { + ctx.float_type() + } +} + +impl From for PyFloat { + fn from(value: f64) -> Self { + PyFloat { value } + } +} + fn float_repr(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(float, Some(vm.ctx.float_type()))]); let v = get_value(float); @@ -50,16 +68,18 @@ fn float_new(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { let type_name = objtype::get_type_name(&arg.typ()); return Err(vm.new_type_error(format!("can't convert {} to float", type_name))); }; - Ok(PyObject::new(PyObjectPayload::Float { value }, cls.clone())) + + Ok(PyObject::new( + PyObjectPayload::AnyRustValue { + value: Box::new(PyFloat { value }), + }, + cls.clone(), + )) } // Retrieve inner float value: pub fn get_value(obj: &PyObjectRef) -> f64 { - if let PyObjectPayload::Float { value } = &obj.payload { - *value - } else { - panic!("Inner error getting float: {}", obj); - } + obj.payload::().unwrap().value } pub fn make_float(vm: &mut VirtualMachine, obj: &PyObjectRef) -> PyResult { diff --git a/vm/src/obj/objiter.rs b/vm/src/obj/objiter.rs index 2010da906..9985553fb 100644 --- a/vm/src/obj/objiter.rs +++ b/vm/src/obj/objiter.rs @@ -2,13 +2,14 @@ * Various types to support iteration. */ -use super::objbool; use crate::pyobject::{ PyContext, PyFuncArgs, PyObjectPayload, PyObjectRef, PyResult, TypeProtocol, }; use crate::vm::VirtualMachine; -// use super::objstr; -use super::objtype; // Required for arg_check! to use isinstance + +use super::objbool; +use super::objrange::PyRange; +use super::objtype; /* * This helper function is called at multiple places. First, it is called @@ -129,38 +130,37 @@ fn iter_next(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { iterated_obj: ref iterated_obj_ref, } = iter.payload { - match iterated_obj_ref.payload { - PyObjectPayload::Sequence { ref elements } => { - if position.get() < elements.borrow().len() { - let obj_ref = elements.borrow()[position.get()].clone(); - position.set(position.get() + 1); - Ok(obj_ref) - } else { - Err(new_stop_iteration(vm)) - } + if let Some(range) = iterated_obj_ref.payload::() { + if let Some(int) = range.get(position.get()) { + position.set(position.get() + 1); + Ok(vm.ctx.new_int(int)) + } else { + Err(new_stop_iteration(vm)) } - - PyObjectPayload::Range { ref range } => { - if let Some(int) = range.get(position.get()) { - position.set(position.get() + 1); - Ok(vm.ctx.new_int(int)) - } else { - Err(new_stop_iteration(vm)) + } else { + match iterated_obj_ref.payload { + PyObjectPayload::Sequence { ref elements } => { + if position.get() < elements.borrow().len() { + let obj_ref = elements.borrow()[position.get()].clone(); + position.set(position.get() + 1); + Ok(obj_ref) + } else { + Err(new_stop_iteration(vm)) + } } - } - - PyObjectPayload::Bytes { ref value } => { - if position.get() < value.borrow().len() { - let obj_ref = vm.ctx.new_int(value.borrow()[position.get()]); - position.set(position.get() + 1); - Ok(obj_ref) - } else { - Err(new_stop_iteration(vm)) + PyObjectPayload::Bytes { ref value } => { + if position.get() < value.borrow().len() { + let obj_ref = vm.ctx.new_int(value.borrow()[position.get()]); + position.set(position.get() + 1); + Ok(obj_ref) + } else { + Err(new_stop_iteration(vm)) + } } - } - _ => { - panic!("NOT IMPL"); + _ => { + panic!("NOT IMPL"); + } } } } else { diff --git a/vm/src/obj/objrange.rs b/vm/src/obj/objrange.rs index daa5aab52..41a4c6964 100644 --- a/vm/src/obj/objrange.rs +++ b/vm/src/obj/objrange.rs @@ -4,7 +4,8 @@ use std::ops::Mul; use super::objint; use super::objtype; use crate::pyobject::{ - PyContext, PyFuncArgs, PyObject, PyObjectPayload, PyObjectRef, PyResult, TypeProtocol, + PyContext, PyFuncArgs, PyObject, PyObjectPayload, PyObjectPayload2, PyObjectRef, PyResult, + TypeProtocol, }; use crate::vm::VirtualMachine; use num_bigint::{BigInt, Sign}; @@ -12,7 +13,7 @@ use num_integer::Integer; use num_traits::{One, Signed, ToPrimitive, Zero}; #[derive(Debug, Clone)] -pub struct RangeType { +pub struct PyRange { // Unfortunately Rust's built in range type doesn't support things like indexing // or ranges where start > end so we need to roll our own. pub start: BigInt, @@ -20,7 +21,13 @@ pub struct RangeType { pub step: BigInt, } -impl RangeType { +impl PyObjectPayload2 for PyRange { + fn required_type(ctx: &PyContext) -> PyObjectRef { + ctx.range_type() + } +} + +impl PyRange { #[inline] pub fn try_len(&self) -> Option { match self.step.sign() { @@ -116,12 +123,12 @@ impl RangeType { }; match self.step.sign() { - Sign::Plus => RangeType { + Sign::Plus => PyRange { start, end: &self.start - 1, step: -&self.step, }, - Sign::Minus => RangeType { + Sign::Minus => PyRange { start, end: &self.start + 1, step: -&self.step, @@ -139,12 +146,8 @@ impl RangeType { } } -pub fn get_value(obj: &PyObjectRef) -> RangeType { - if let PyObjectPayload::Range { range } = &obj.payload { - range.clone() - } else { - panic!("Inner error getting range {:?}", obj); - } +pub fn get_value(obj: &PyObjectRef) -> PyRange { + obj.payload::().unwrap().clone() } pub fn init(context: &PyContext) { @@ -223,8 +226,8 @@ fn range_new(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { Err(vm.new_value_error("range with 0 step size".to_string())) } else { Ok(PyObject::new( - PyObjectPayload::Range { - range: RangeType { start, end, step }, + PyObjectPayload::AnyRustValue { + value: Box::new(PyRange { start, end, step }), }, cls.clone(), )) @@ -251,7 +254,12 @@ fn range_reversed(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { Ok(PyObject::new( PyObjectPayload::Iterator { position: Cell::new(0), - iterated_obj: PyObject::new(PyObjectPayload::Range { range }, vm.ctx.range_type()), + iterated_obj: PyObject::new( + PyObjectPayload::AnyRustValue { + value: Box::new(range), + }, + vm.ctx.range_type(), + ), }, vm.ctx.iter_type(), )) @@ -316,12 +324,12 @@ fn range_getitem(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { }; Ok(PyObject::new( - PyObjectPayload::Range { - range: RangeType { + PyObjectPayload::AnyRustValue { + value: Box::new(PyRange { start: new_start, end: new_end, step: new_step, - }, + }), }, vm.ctx.range_type(), )) diff --git a/vm/src/pyobject.rs b/vm/src/pyobject.rs index c39aa71ae..bfb33871e 100644 --- a/vm/src/pyobject.rs +++ b/vm/src/pyobject.rs @@ -459,8 +459,13 @@ impl PyContext { ) } - pub fn new_float(&self, i: f64) -> PyObjectRef { - PyObject::new(PyObjectPayload::Float { value: i }, self.float_type()) + pub fn new_float(&self, value: f64) -> PyObjectRef { + PyObject::new( + PyObjectPayload::AnyRustValue { + value: Box::new(objfloat::PyFloat::from(value)), + }, + self.float_type(), + ) } pub fn new_complex(&self, i: Complex64) -> PyObjectRef { @@ -1245,9 +1250,6 @@ pub enum PyObjectPayload { Integer { value: BigInt, }, - Float { - value: f64, - }, Complex { value: Complex64, }, @@ -1284,9 +1286,6 @@ pub enum PyObjectPayload { stop: Option, step: Option, }, - Range { - range: objrange::RangeType, - }, MemoryView { obj: PyObjectRef, }, @@ -1340,7 +1339,6 @@ impl fmt::Debug for PyObjectPayload { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { PyObjectPayload::Integer { ref value } => write!(f, "int {}", value), - PyObjectPayload::Float { ref value } => write!(f, "float {}", value), PyObjectPayload::Complex { ref value } => write!(f, "complex {}", value), PyObjectPayload::Bytes { ref value } => write!(f, "bytes/bytearray {:?}", value), PyObjectPayload::MemoryView { ref obj } => write!(f, "bytes/bytearray {:?}", obj), @@ -1348,7 +1346,6 @@ impl fmt::Debug for PyObjectPayload { PyObjectPayload::Dict { .. } => write!(f, "dict"), PyObjectPayload::Set { .. } => write!(f, "set"), PyObjectPayload::WeakRef { .. } => write!(f, "weakref"), - PyObjectPayload::Range { .. } => write!(f, "range"), PyObjectPayload::Iterator { .. } => write!(f, "iterator"), PyObjectPayload::EnumerateIterator { .. } => write!(f, "enumerate"), PyObjectPayload::FilterIterator { .. } => write!(f, "filter"), From 0a68b7a3da67f478103ab1adb05f933ce1a269db Mon Sep 17 00:00:00 2001 From: Abdul Arfan Date: Sun, 3 Mar 2019 09:18:08 +0700 Subject: [PATCH 08/26] fix typo in README.md, will to with --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b060a29e9..7e7b0bd70 100644 --- a/README.md +++ b/README.md @@ -97,7 +97,7 @@ $ pipenv install $ pipenv run pytest -v ``` -There also are some unit tests, you can run those will cargo: +There also are some unit tests, you can run those with cargo: ```shell $ cargo test --all From 9271115db7f06afc3982bda571b9d8923f376f69 Mon Sep 17 00:00:00 2001 From: Windel Bouwman Date: Sun, 3 Mar 2019 14:25:00 +0100 Subject: [PATCH 09/26] Introduce py_class macro to define classes. (#589) * Introduce py_class macro to define classes. * rustfmt --- vm/src/dictdatatype.rs | 18 ++----- vm/src/macros.rs | 13 +++++ vm/src/stdlib/io.rs | 120 +++++++++++++++-------------------------- vm/src/stdlib/re.rs | 14 +++-- 4 files changed, 65 insertions(+), 100 deletions(-) diff --git a/vm/src/dictdatatype.rs b/vm/src/dictdatatype.rs index 2ba30c066..df6b3bb5a 100644 --- a/vm/src/dictdatatype.rs +++ b/vm/src/dictdatatype.rs @@ -66,11 +66,7 @@ impl Dict { } } - pub fn contains( - &self, - vm: &mut VirtualMachine, - key: &PyObjectRef, - ) -> PyResult { + pub fn contains(&self, vm: &mut VirtualMachine, key: &PyObjectRef) -> PyResult { if let LookupResult::Existing(_index) = self.lookup(vm, key)? { Ok(true) } else { @@ -93,11 +89,7 @@ impl Dict { } /// Delete a key - pub fn delete( - &mut self, - vm: &mut VirtualMachine, - key: &PyObjectRef, - ) -> PyResult<()> { + pub fn delete(&mut self, vm: &mut VirtualMachine, key: &PyObjectRef) -> PyResult<()> { if let LookupResult::Existing(index) = self.lookup(vm, key)? { self.entries[index] = None; self.size -= 1; @@ -126,11 +118,7 @@ impl Dict { } /// Lookup the index for the given key. - fn lookup( - &self, - vm: &mut VirtualMachine, - key: &PyObjectRef, - ) -> PyResult { + fn lookup(&self, vm: &mut VirtualMachine, key: &PyObjectRef) -> PyResult { let hash_value = calc_hash(vm, key)?; let perturb = hash_value; let mut hash_index: usize = hash_value; diff --git a/vm/src/macros.rs b/vm/src/macros.rs index 2c48286d3..f228b6a7f 100644 --- a/vm/src/macros.rs +++ b/vm/src/macros.rs @@ -124,3 +124,16 @@ macro_rules! py_module { } } } + +#[macro_export] +macro_rules! py_class { + ( $ctx:expr, $class_name:expr, $class_base:expr, { $($name:expr => $value:expr),* $(,)* }) => { + { + let py_class = $ctx.new_class($class_name, $class_base); + $( + $ctx.set_attr(&py_class, $name, $value); + )* + py_class + } + } +} diff --git a/vm/src/stdlib/io.rs b/vm/src/stdlib/io.rs index 077f907d8..b63b6c8e5 100644 --- a/vm/src/stdlib/io.rs +++ b/vm/src/stdlib/io.rs @@ -346,101 +346,67 @@ pub fn io_open(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { } pub fn mk_module(ctx: &PyContext) -> PyObjectRef { - let py_mod = ctx.new_module(&"io".to_string(), ctx.new_scope(None)); - - ctx.set_attr(&py_mod, "open", ctx.new_rustfunc(io_open)); - //IOBase the abstract base class of the IO Module let io_base = ctx.new_class("IOBase", ctx.object()); - ctx.set_attr(&py_mod, "IOBase", io_base.clone()); // IOBase Subclasses let raw_io_base = ctx.new_class("RawIOBase", ctx.object()); - ctx.set_attr(&py_mod, "RawIOBase", raw_io_base.clone()); - let buffered_io_base = { - let buffered_io_base = ctx.new_class("BufferedIOBase", io_base.clone()); - ctx.set_attr( - &buffered_io_base, - "__init__", - ctx.new_rustfunc(buffered_io_base_init), - ); - buffered_io_base - }; - ctx.set_attr(&py_mod, "BufferedIOBase", buffered_io_base.clone()); + let buffered_io_base = py_class!(ctx, "BufferedIOBase", io_base.clone(), { + "__init__" => ctx.new_rustfunc(buffered_io_base_init) + }); //TextIO Base has no public constructor - let text_io_base = { - let text_io_base = ctx.new_class("TextIOBase", io_base.clone()); - ctx.set_attr(&text_io_base, "read", ctx.new_rustfunc(text_io_base_read)); - text_io_base - }; - ctx.set_attr(&py_mod, "TextIOBase", text_io_base.clone()); + let text_io_base = py_class!(ctx, "TextIOBase", io_base.clone(), { + "read" => ctx.new_rustfunc(text_io_base_read) + }); // RawBaseIO Subclasses - let file_io = { - let file_io = ctx.new_class("FileIO", raw_io_base.clone()); - ctx.set_attr(&file_io, "__init__", ctx.new_rustfunc(file_io_init)); - ctx.set_attr(&file_io, "name", ctx.str_type()); - ctx.set_attr(&file_io, "read", ctx.new_rustfunc(file_io_read)); - ctx.set_attr(&file_io, "readinto", ctx.new_rustfunc(file_io_readinto)); - ctx.set_attr(&file_io, "write", ctx.new_rustfunc(file_io_write)); - file_io - }; - ctx.set_attr(&py_mod, "FileIO", file_io.clone()); + let file_io = py_class!(ctx, "FileIO", raw_io_base.clone(), { + "__init__" => ctx.new_rustfunc(file_io_init), + "name" => ctx.str_type(), + "read" => ctx.new_rustfunc(file_io_read), + "readinto" => ctx.new_rustfunc(file_io_readinto), + "write" => ctx.new_rustfunc(file_io_write) + }); // BufferedIOBase Subclasses - let buffered_reader = { - let buffered_reader = ctx.new_class("BufferedReader", buffered_io_base.clone()); - ctx.set_attr( - &buffered_reader, - "read", - ctx.new_rustfunc(buffered_reader_read), - ); - buffered_reader - }; - ctx.set_attr(&py_mod, "BufferedReader", buffered_reader.clone()); + let buffered_reader = py_class!(ctx, "BufferedReader", buffered_io_base.clone(), { + "read" => ctx.new_rustfunc(buffered_reader_read) + }); - let buffered_writer = { - let buffered_writer = ctx.new_class("BufferedWriter", buffered_io_base.clone()); - ctx.set_attr( - &buffered_writer, - "write", - ctx.new_rustfunc(buffered_writer_write), - ); - buffered_writer - }; - ctx.set_attr(&py_mod, "BufferedWriter", buffered_writer.clone()); + let buffered_writer = py_class!(ctx, "BufferedWriter", buffered_io_base.clone(), { + "write" => ctx.new_rustfunc(buffered_writer_write) + }); //TextIOBase Subclass - let text_io_wrapper = { - let text_io_wrapper = ctx.new_class("TextIOWrapper", text_io_base.clone()); - ctx.set_attr( - &text_io_wrapper, - "__init__", - ctx.new_rustfunc(text_io_wrapper_init), - ); - text_io_wrapper - }; - ctx.set_attr(&py_mod, "TextIOWrapper", text_io_wrapper.clone()); + let text_io_wrapper = py_class!(ctx, "TextIOWrapper", text_io_base.clone(), { + "__init__" => ctx.new_rustfunc(text_io_wrapper_init) + }); //StringIO: in-memory text - let string_io = { - let string_io = ctx.new_class("StringIO", text_io_base.clone()); - ctx.set_attr(&string_io, "__init__", ctx.new_rustfunc(string_io_init)); - ctx.set_attr(&string_io, "getvalue", ctx.new_rustfunc(string_io_getvalue)); - string_io - }; - ctx.set_attr(&py_mod, "StringIO", string_io); + let string_io = py_class!(ctx, "StringIO", text_io_base.clone(), { + "__init__" => ctx.new_rustfunc(string_io_init), + "getvalue" => ctx.new_rustfunc(string_io_getvalue) + }); //BytesIO: in-memory bytes - let bytes_io = { - let bytes_io = ctx.new_class("BytesIO", buffered_io_base.clone()); - ctx.set_attr(&bytes_io, "__init__", ctx.new_rustfunc(bytes_io_init)); - ctx.set_attr(&bytes_io, "getvalue", ctx.new_rustfunc(bytes_io_getvalue)); - bytes_io - }; - ctx.set_attr(&py_mod, "BytesIO", bytes_io); + let bytes_io = py_class!(ctx, "BytesIO", buffered_io_base.clone(), { + "__init__" => ctx.new_rustfunc(bytes_io_init), + "getvalue" => ctx.new_rustfunc(bytes_io_getvalue) + }); - py_mod + py_module!(ctx, "io", { + "open" => ctx.new_rustfunc(io_open), + "IOBase" => io_base.clone(), + "RawIOBase" => raw_io_base.clone(), + "BufferedIOBase" => buffered_io_base.clone(), + "TextIOBase" => text_io_base.clone(), + "FileIO" => file_io.clone(), + "BufferedReader" => buffered_reader.clone(), + "BufferedWriter" => buffered_writer.clone(), + "TextIOWrapper" => text_io_wrapper.clone(), + "StringIO" => string_io, + "BytesIO" => bytes_io, + }) } diff --git a/vm/src/stdlib/re.rs b/vm/src/stdlib/re.rs index 0c0cef934..b262ff76a 100644 --- a/vm/src/stdlib/re.rs +++ b/vm/src/stdlib/re.rs @@ -50,13 +50,11 @@ fn re_search(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { } pub fn mk_module(ctx: &PyContext) -> PyObjectRef { - let py_mod = ctx.new_module("re", ctx.new_scope(None)); + let match_type = py_class!(ctx, "Match", ctx.object(), {}); - let match_type = ctx.new_class("Match", ctx.object()); - ctx.set_attr(&py_mod, "Match", match_type); - - ctx.set_attr(&py_mod, "match", ctx.new_rustfunc(re_match)); - ctx.set_attr(&py_mod, "search", ctx.new_rustfunc(re_search)); - - py_mod + py_module!(ctx, "re", { + "Match" => match_type, + "match" => ctx.new_rustfunc(re_match), + "search" => ctx.new_rustfunc(re_search) + }) } From e5d1d11c3eaab4dd4114a8c7cda7c54a8417a61e Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Sat, 2 Mar 2019 13:46:45 +0200 Subject: [PATCH 10/26] Add UDP to socket --- tests/snippets/stdlib_socket.py | 39 +++++++++++++++------ vm/src/stdlib/socket.rs | 61 +++++++++++++++++++++++++-------- 2 files changed, 76 insertions(+), 24 deletions(-) diff --git a/tests/snippets/stdlib_socket.py b/tests/snippets/stdlib_socket.py index e0ea6d168..2a3c3a94e 100644 --- a/tests/snippets/stdlib_socket.py +++ b/tests/snippets/stdlib_socket.py @@ -1,6 +1,10 @@ import socket from testutils import assertRaises +MESSAGE_A = b'aaaa' +MESSAGE_B= b'bbbbb' + +# TCP listener = socket.socket(socket.AF_INET, socket.SOCK_STREAM) listener.bind(("127.0.0.1", 0)) @@ -10,16 +14,12 @@ connector = socket.socket(socket.AF_INET, socket.SOCK_STREAM) connector.connect(("127.0.0.1", listener.getsockname()[1])) connection = listener.accept()[0] -message_a = b'aaaa' -message_b = b'bbbbb' - -connector.send(message_a) -connection.send(message_b) -recv_a = connection.recv(len(message_a)) -recv_b = connector.recv(len(message_b)) -assert recv_a == message_a -assert recv_b == message_b - +connector.send(MESSAGE_A) +connection.send(MESSAGE_B) +recv_a = connection.recv(len(MESSAGE_A)) +recv_b = connector.recv(len(MESSAGE_B)) +assert recv_a == MESSAGE_A +assert recv_b == MESSAGE_B connection.close() connector.close() listener.close() @@ -35,3 +35,22 @@ with assertRaises(TypeError): s.bind((888, 8888)) s.close() + +# UDP +sock1 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) +sock1.bind(("127.0.0.1", 0)) + +sock2 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) +sock2.bind(("127.0.0.1", 0)) + +sock1.connect(("127.0.0.1", sock2.getsockname()[1])) +sock2.connect(("127.0.0.1", sock1.getsockname()[1])) + +sock1.send(MESSAGE_A) +sock2.send(MESSAGE_B) +recv_a = sock2.recv(len(MESSAGE_A)) +recv_b = sock1.recv(len(MESSAGE_B)) +assert recv_a == MESSAGE_A +assert recv_b == MESSAGE_B +sock1.close() +sock2.close() diff --git a/vm/src/stdlib/socket.rs b/vm/src/stdlib/socket.rs index 30a1bbdc5..e85b22d81 100644 --- a/vm/src/stdlib/socket.rs +++ b/vm/src/stdlib/socket.rs @@ -2,7 +2,7 @@ use std::cell::RefCell; use std::io; use std::io::Read; use std::io::Write; -use std::net::{SocketAddr, TcpListener, TcpStream}; +use std::net::{SocketAddr, TcpListener, TcpStream, UdpSocket}; use std::ops::DerefMut; use crate::obj::objbytes; @@ -53,7 +53,7 @@ impl SocketKind { enum Connection { TcpListener(TcpListener), TcpStream(TcpStream), - // UdpSocket(UdpSocket), + UdpSocket(UdpSocket), } impl Connection { @@ -67,6 +67,7 @@ impl Connection { fn local_addr(&self) -> io::Result { match self { Connection::TcpListener(con) => con.local_addr(), + Connection::UdpSocket(con) => con.local_addr(), _ => Err(io::Error::new(io::ErrorKind::Other, "oh no!")), } } @@ -76,6 +77,7 @@ impl Read for Connection { fn read(&mut self, buf: &mut [u8]) -> io::Result { match self { Connection::TcpStream(con) => con.read(buf), + Connection::UdpSocket(con) => con.recv(buf), _ => Err(io::Error::new(io::ErrorKind::Other, "oh no!")), } } @@ -85,6 +87,7 @@ impl Write for Connection { fn write(&mut self, buf: &[u8]) -> io::Result { match self { Connection::TcpStream(con) => con.write(buf), + Connection::UdpSocket(con) => con.send(buf), _ => Err(io::Error::new(io::ErrorKind::Other, "oh no!")), } } @@ -153,12 +156,27 @@ fn socket_connect(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { let mut socket = get_socket(zelf); - if let Ok(stream) = TcpStream::connect(address_string) { - socket.con = Some(Connection::TcpStream(stream)); - Ok(vm.get_none()) - } else { - // TODO: Socket error - Err(vm.new_type_error("socket failed".to_string())) + match socket.socket_kind { + SocketKind::Stream => { + if let Ok(stream) = TcpStream::connect(address_string) { + socket.con = Some(Connection::TcpStream(stream)); + Ok(vm.get_none()) + } else { + // TODO: Socket error + Err(vm.new_type_error("socket failed".to_string())) + } + } + SocketKind::Dgram => { + if let Some(Connection::UdpSocket(con)) = &socket.con { + match con.connect(address_string) { + Ok(_) => Ok(vm.get_none()), + // TODO: Socket error + Err(_) => Err(vm.new_type_error("socket failed".to_string())), + } + } else { + Err(vm.new_type_error("".to_string())) + } + } } } @@ -173,12 +191,25 @@ fn socket_bind(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { let mut socket = get_socket(zelf); - if let Ok(stream) = TcpListener::bind(address_string) { - socket.con = Some(Connection::TcpListener(stream)); - Ok(vm.get_none()) - } else { - // TODO: Socket error - Err(vm.new_type_error("socket failed".to_string())) + match socket.socket_kind { + SocketKind::Stream => { + if let Ok(stream) = TcpListener::bind(address_string) { + socket.con = Some(Connection::TcpListener(stream)); + Ok(vm.get_none()) + } else { + // TODO: Socket error + Err(vm.new_type_error("socket failed".to_string())) + } + } + SocketKind::Dgram => { + if let Ok(dgram) = UdpSocket::bind(address_string) { + socket.con = Some(Connection::UdpSocket(dgram)); + Ok(vm.get_none()) + } else { + // TODO: Socket error + Err(vm.new_type_error("socket failed".to_string())) + } + } } } @@ -325,6 +356,8 @@ pub fn mk_module(ctx: &PyContext) -> PyObjectRef { ctx.new_int(SocketKind::Stream as i32), ); + ctx.set_attr(&py_mod, "SOCK_DGRAM", ctx.new_int(SocketKind::Dgram as i32)); + let socket = { let socket = ctx.new_class("socket", ctx.object()); ctx.set_attr(&socket, "__new__", ctx.new_rustfunc(socket_new)); From 86b60faa61d80458e6304db5b6ad596da52f4183 Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Sat, 2 Mar 2019 17:12:13 +0200 Subject: [PATCH 11/26] Add socket.{sendto, recvfrom} --- tests/snippets/stdlib_socket.py | 6 ++- vm/src/stdlib/socket.rs | 87 +++++++++++++++++++++++++++++---- 2 files changed, 82 insertions(+), 11 deletions(-) diff --git a/tests/snippets/stdlib_socket.py b/tests/snippets/stdlib_socket.py index 2a3c3a94e..af29b451c 100644 --- a/tests/snippets/stdlib_socket.py +++ b/tests/snippets/stdlib_socket.py @@ -41,8 +41,12 @@ sock1 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock1.bind(("127.0.0.1", 0)) sock2 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) -sock2.bind(("127.0.0.1", 0)) +sock2.sendto(MESSAGE_A, sock1.getsockname()) +(recv_a, addr) = sock1.recvfrom(len(MESSAGE_A)) +assert recv_a == MESSAGE_A + +sock2.bind(("127.0.0.1", 0)) sock1.connect(("127.0.0.1", sock2.getsockname()[1])) sock2.connect(("127.0.0.1", sock1.getsockname()[1])) diff --git a/vm/src/stdlib/socket.rs b/vm/src/stdlib/socket.rs index e85b22d81..37dc8a6fa 100644 --- a/vm/src/stdlib/socket.rs +++ b/vm/src/stdlib/socket.rs @@ -71,6 +71,13 @@ impl Connection { _ => Err(io::Error::new(io::ErrorKind::Other, "oh no!")), } } + + fn recv_from(&self, buf: &mut [u8]) -> io::Result<(usize, SocketAddr)> { + match self { + Connection::UdpSocket(con) => con.recv_from(buf), + _ => Err(io::Error::new(io::ErrorKind::Other, "oh no!")), + } + } } impl Read for Connection { @@ -298,6 +305,34 @@ fn socket_recv(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { Ok(vm.ctx.new_bytes(buffer)) } +fn socket_recvfrom(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!( + vm, + args, + required = [(zelf, None), (bufsize, Some(vm.ctx.int_type()))] + ); + + let mut socket = get_socket(zelf); + + let mut buffer = vec![0u8; objint::get_value(bufsize).to_usize().unwrap()]; + let ret = match socket.con { + Some(ref mut v) => v.recv_from(&mut buffer), + None => return Err(vm.new_type_error("".to_string())), + }; + + let addr = match ret { + Ok((_size, addr)) => addr, + _ => return Err(vm.new_type_error("".to_string())), + }; + + let elements = RefCell::new(vec![vm.ctx.new_bytes(buffer), get_addr_tuple(vm, addr)?]); + + Ok(PyObject::new( + PyObjectPayload::Sequence { elements }, + vm.ctx.tuple_type(), + )) +} + fn socket_send(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!( vm, @@ -313,6 +348,34 @@ fn socket_send(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { Ok(vm.get_none()) } +fn socket_sendto(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!( + vm, + args, + required = [ + (zelf, None), + (bytes, Some(vm.ctx.bytes_type())), + (address, Some(vm.ctx.tuple_type())) + ] + ); + let address_string = get_address_string(vm, address)?; + + let socket = get_socket(zelf); + + match socket.socket_kind { + SocketKind::Dgram => { + // We can't do sendto without bind in std::net::UdpSocket + if let Ok(dgram) = UdpSocket::bind("0.0.0.0:0") { + if let Ok(_) = dgram.send_to(&objbytes::get_value(&bytes), address_string) { + return Ok(vm.get_none()); + } + } + Err(vm.new_type_error("socket failed".to_string())) + } + _ => Err(vm.new_not_implemented_error("".to_string())), + } +} + fn socket_close(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(zelf, None)]); @@ -331,20 +394,22 @@ fn socket_getsockname(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { }; match addr { - Ok(addr) => { - let port = vm.ctx.new_int(addr.port()); - let ip = vm.ctx.new_str(addr.ip().to_string()); - let elements = RefCell::new(vec![ip, port]); - - Ok(PyObject::new( - PyObjectPayload::Sequence { elements }, - vm.ctx.tuple_type(), - )) - } + Ok(addr) => get_addr_tuple(vm, addr), _ => Err(vm.new_type_error("".to_string())), } } +fn get_addr_tuple(vm: &mut VirtualMachine, addr: SocketAddr) -> PyResult { + let port = vm.ctx.new_int(addr.port()); + let ip = vm.ctx.new_str(addr.ip().to_string()); + let elements = RefCell::new(vec![ip, port]); + + Ok(PyObject::new( + PyObjectPayload::Sequence { elements }, + vm.ctx.tuple_type(), + )) +} + pub fn mk_module(ctx: &PyContext) -> PyObjectRef { let py_mod = ctx.new_module(&"socket".to_string(), ctx.new_scope(None)); @@ -369,6 +434,8 @@ pub fn mk_module(ctx: &PyContext) -> PyObjectRef { ctx.set_attr(&socket, "listen", ctx.new_rustfunc(socket_listen)); ctx.set_attr(&socket, "close", ctx.new_rustfunc(socket_close)); ctx.set_attr(&socket, "getsockname", ctx.new_rustfunc(socket_getsockname)); + ctx.set_attr(&socket, "sendto", ctx.new_rustfunc(socket_sendto)); + ctx.set_attr(&socket, "recvfrom", ctx.new_rustfunc(socket_recvfrom)); socket }; ctx.set_attr(&py_mod, "socket", socket.clone()); From 9b5ba068afc88a439999f884bb8e8f4ddbc9ca0f Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Sat, 2 Mar 2019 17:21:36 +0200 Subject: [PATCH 12/26] Return addr in accept --- tests/snippets/stdlib_socket.py | 3 ++- vm/src/stdlib/socket.rs | 8 ++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/tests/snippets/stdlib_socket.py b/tests/snippets/stdlib_socket.py index af29b451c..54ae180c3 100644 --- a/tests/snippets/stdlib_socket.py +++ b/tests/snippets/stdlib_socket.py @@ -12,7 +12,8 @@ listener.listen(1) connector = socket.socket(socket.AF_INET, socket.SOCK_STREAM) connector.connect(("127.0.0.1", listener.getsockname()[1])) -connection = listener.accept()[0] +(connection, addr) = listener.accept() +assert addr == connector.getsockname() connector.send(MESSAGE_A) connection.send(MESSAGE_B) diff --git a/vm/src/stdlib/socket.rs b/vm/src/stdlib/socket.rs index 37dc8a6fa..f4b88b85f 100644 --- a/vm/src/stdlib/socket.rs +++ b/vm/src/stdlib/socket.rs @@ -68,7 +68,7 @@ impl Connection { match self { Connection::TcpListener(con) => con.local_addr(), Connection::UdpSocket(con) => con.local_addr(), - _ => Err(io::Error::new(io::ErrorKind::Other, "oh no!")), + Connection::TcpStream(con) => con.local_addr(), } } @@ -263,8 +263,8 @@ fn socket_accept(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { None => return Err(vm.new_type_error("".to_string())), }; - let tcp_stream = match ret { - Ok((socket, _addr)) => socket, + let (tcp_stream, addr) = match ret { + Ok((socket, addr)) => (socket, addr), _ => return Err(vm.new_type_error("".to_string())), }; @@ -281,7 +281,7 @@ fn socket_accept(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { zelf.typ(), ); - let elements = RefCell::new(vec![sock_obj, vm.get_none()]); + let elements = RefCell::new(vec![sock_obj, get_addr_tuple(vm, addr)?]); Ok(PyObject::new( PyObjectPayload::Sequence { elements }, From f09f75ac8d5c1190aa6d7d89ecf652f786e62bc0 Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Sat, 2 Mar 2019 17:45:26 +0200 Subject: [PATCH 13/26] Do implicit bind in socket.sendto --- tests/snippets/stdlib_socket.py | 28 +++++++++++++++++++++------- vm/src/stdlib/socket.rs | 33 ++++++++++++++++++++++++++------- 2 files changed, 47 insertions(+), 14 deletions(-) diff --git a/tests/snippets/stdlib_socket.py b/tests/snippets/stdlib_socket.py index 54ae180c3..f984515ea 100644 --- a/tests/snippets/stdlib_socket.py +++ b/tests/snippets/stdlib_socket.py @@ -44,18 +44,32 @@ sock1.bind(("127.0.0.1", 0)) sock2 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock2.sendto(MESSAGE_A, sock1.getsockname()) -(recv_a, addr) = sock1.recvfrom(len(MESSAGE_A)) +(recv_a, addr1) = sock1.recvfrom(len(MESSAGE_A)) assert recv_a == MESSAGE_A -sock2.bind(("127.0.0.1", 0)) -sock1.connect(("127.0.0.1", sock2.getsockname()[1])) -sock2.connect(("127.0.0.1", sock1.getsockname()[1])) +sock2.sendto(MESSAGE_B, sock1.getsockname()) +(recv_b, addr2) = sock1.recvfrom(len(MESSAGE_B)) +assert recv_b == MESSAGE_B +assert addr1[0] == addr2[0] +assert addr1[1] == addr2[1] + +sock2.close() + +sock3 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) +sock3.bind(("127.0.0.1", 0)) +sock3.sendto(MESSAGE_A, sock1.getsockname()) +(recv_a, addr) = sock1.recvfrom(len(MESSAGE_A)) +assert recv_a == MESSAGE_A +assert addr == sock3.getsockname() + +sock1.connect(("127.0.0.1", sock3.getsockname()[1])) +sock3.connect(("127.0.0.1", sock1.getsockname()[1])) sock1.send(MESSAGE_A) -sock2.send(MESSAGE_B) -recv_a = sock2.recv(len(MESSAGE_A)) +sock3.send(MESSAGE_B) +recv_a = sock3.recv(len(MESSAGE_A)) recv_b = sock1.recv(len(MESSAGE_B)) assert recv_a == MESSAGE_A assert recv_b == MESSAGE_B sock1.close() -sock2.close() +sock3.close() diff --git a/vm/src/stdlib/socket.rs b/vm/src/stdlib/socket.rs index f4b88b85f..d0ff61b37 100644 --- a/vm/src/stdlib/socket.rs +++ b/vm/src/stdlib/socket.rs @@ -2,7 +2,7 @@ use std::cell::RefCell; use std::io; use std::io::Read; use std::io::Write; -use std::net::{SocketAddr, TcpListener, TcpStream, UdpSocket}; +use std::net::{SocketAddr, TcpListener, TcpStream, ToSocketAddrs, UdpSocket}; use std::ops::DerefMut; use crate::obj::objbytes; @@ -78,6 +78,13 @@ impl Connection { _ => Err(io::Error::new(io::ErrorKind::Other, "oh no!")), } } + + fn send_to(&self, buf: &[u8], addr: A) -> io::Result { + match self { + Connection::UdpSocket(con) => con.send_to(buf, addr), + _ => Err(io::Error::new(io::ErrorKind::Other, "oh no!")), + } + } } impl Read for Connection { @@ -360,17 +367,29 @@ fn socket_sendto(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { ); let address_string = get_address_string(vm, address)?; - let socket = get_socket(zelf); + let mut socket = get_socket(zelf); match socket.socket_kind { SocketKind::Dgram => { - // We can't do sendto without bind in std::net::UdpSocket - if let Ok(dgram) = UdpSocket::bind("0.0.0.0:0") { - if let Ok(_) = dgram.send_to(&objbytes::get_value(&bytes), address_string) { - return Ok(vm.get_none()); + match socket.con { + Some(ref mut v) => { + if let Ok(_) = v.send_to(&objbytes::get_value(&bytes), address_string) { + Ok(vm.get_none()) + } else { + Err(vm.new_type_error("socket failed".to_string())) + } + } + None => { + // Doing implicit bind + if let Ok(dgram) = UdpSocket::bind("0.0.0.0:0") { + if let Ok(_) = dgram.send_to(&objbytes::get_value(&bytes), address_string) { + socket.con = Some(Connection::UdpSocket(dgram)); + return Ok(vm.get_none()); + } + } + Err(vm.new_type_error("socket failed".to_string())) } } - Err(vm.new_type_error("socket failed".to_string())) } _ => Err(vm.new_not_implemented_error("".to_string())), } From 283b331323178e8a6c6f8f184d0a7e46d90b354a Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Sun, 3 Mar 2019 18:22:20 +0200 Subject: [PATCH 14/26] Use vm.ctx.new_tuple --- vm/src/stdlib/socket.rs | 20 +++++--------------- 1 file changed, 5 insertions(+), 15 deletions(-) diff --git a/vm/src/stdlib/socket.rs b/vm/src/stdlib/socket.rs index d0ff61b37..7626cc182 100644 --- a/vm/src/stdlib/socket.rs +++ b/vm/src/stdlib/socket.rs @@ -288,12 +288,9 @@ fn socket_accept(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { zelf.typ(), ); - let elements = RefCell::new(vec![sock_obj, get_addr_tuple(vm, addr)?]); + let addr_tuple = get_addr_tuple(vm, addr)?; - Ok(PyObject::new( - PyObjectPayload::Sequence { elements }, - vm.ctx.tuple_type(), - )) + Ok(vm.ctx.new_tuple(vec![sock_obj, addr_tuple])) } fn socket_recv(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { @@ -332,12 +329,9 @@ fn socket_recvfrom(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { _ => return Err(vm.new_type_error("".to_string())), }; - let elements = RefCell::new(vec![vm.ctx.new_bytes(buffer), get_addr_tuple(vm, addr)?]); + let addr_tuple = get_addr_tuple(vm, addr)?; - Ok(PyObject::new( - PyObjectPayload::Sequence { elements }, - vm.ctx.tuple_type(), - )) + Ok(vm.ctx.new_tuple(vec![vm.ctx.new_bytes(buffer), addr_tuple])) } fn socket_send(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { @@ -421,12 +415,8 @@ fn socket_getsockname(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { fn get_addr_tuple(vm: &mut VirtualMachine, addr: SocketAddr) -> PyResult { let port = vm.ctx.new_int(addr.port()); let ip = vm.ctx.new_str(addr.ip().to_string()); - let elements = RefCell::new(vec![ip, port]); - Ok(PyObject::new( - PyObjectPayload::Sequence { elements }, - vm.ctx.tuple_type(), - )) + Ok(vm.ctx.new_tuple(vec![ip, port])) } pub fn mk_module(ctx: &PyContext) -> PyObjectRef { From 4e822ed6cdf02a7826f733a5edf0c5e4f4f6765b Mon Sep 17 00:00:00 2001 From: Joey Hain Date: Sun, 3 Mar 2019 12:26:50 -0800 Subject: [PATCH 15/26] Improve error messages and add docs for new native function machinery --- vm/src/function.rs | 46 ++++++++++- vm/src/pyobject.rs | 192 +++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 222 insertions(+), 16 deletions(-) diff --git a/vm/src/function.rs b/vm/src/function.rs index 434f995bb..131fce129 100644 --- a/vm/src/function.rs +++ b/vm/src/function.rs @@ -2,17 +2,48 @@ use std::marker::PhantomData; use std::ops::Deref; use crate::obj::objtype; -use crate::pyobject::{PyObjectPayload2, PyObjectRef, PyResult, TryFromObject}; +use crate::pyobject::{ + IntoPyObject, PyContext, PyObject, PyObjectPayload, PyObjectPayload2, PyObjectRef, PyResult, + TryFromObject, TypeProtocol, +}; use crate::vm::VirtualMachine; // TODO: Move PyFuncArgs, FromArgs, etc. here +// TODO: `PyRef` probably actually belongs in the pyobject module. + +/// A reference to the payload of a built-in object. +/// +/// Note that a `PyRef` can only deref to a shared / immutable reference. +/// It is the payload type's responsibility to handle (possibly concurrent) +/// mutability with locks or concurrent data structures if required. +/// +/// A `PyRef` can be directly returned from a built-in function to handle +/// situations (such as when implementing in-place methods such as `__iadd__`) +/// where a reference to the same object must be returned. pub struct PyRef { // invariant: this obj must always have payload of type T obj: PyObjectRef, _payload: PhantomData, } +impl PyRef +where + T: PyObjectPayload2, +{ + pub fn new(ctx: &PyContext, payload: T) -> Self { + PyRef { + obj: PyObject::new( + PyObjectPayload::AnyRustValue { + value: Box::new(payload), + }, + T::required_type(ctx), + ), + _payload: PhantomData, + } + } +} + impl Deref for PyRef where T: PyObjectPayload2, @@ -35,7 +66,18 @@ where _payload: PhantomData, }) } else { - Err(vm.new_type_error("wrong type".to_string())) // TODO: better message + let expected_type = vm.to_pystr(&T::required_type(&vm.ctx))?; + let actual_type = vm.to_pystr(&obj.typ())?; + Err(vm.new_type_error(format!( + "Expected type {}, not {}", + expected_type, actual_type, + ))) } } } + +impl IntoPyObject for PyRef { + fn into_pyobject(self, _ctx: &PyContext) -> PyResult { + Ok(self.obj) + } +} diff --git a/vm/src/pyobject.rs b/vm/src/pyobject.rs index c39aa71ae..cb5bd7ed9 100644 --- a/vm/src/pyobject.rs +++ b/vm/src/pyobject.rs @@ -2,6 +2,7 @@ use std::cell::{Cell, RefCell}; use std::collections::HashMap; use std::fmt; use std::iter; +use std::ops::RangeInclusive; use std::rc::{Rc, Weak}; use crate::bytecode; @@ -937,6 +938,8 @@ impl PyFuncArgs { } } + /// Serializes these arguments into an iterator starting with the positional + /// arguments followed by keyword arguments. fn into_iter(self) -> impl Iterator { self.args.into_iter().map(PyArg::Positional).chain( self.kwargs @@ -945,30 +948,82 @@ impl PyFuncArgs { ) } + /// Binds these arguments to their respective values. + /// + /// If there is an insufficient number of arguments, there are leftover + /// arguments after performing the binding, or if an argument is not of + /// the expected type, a TypeError is raised. + /// + /// If the given `FromArgs` includes any conversions, exceptions raised + /// during the conversion will halt the binding and return the error. fn bind(self, vm: &mut VirtualMachine) -> PyResult { + let given_args = self.args.len(); let mut args = self.into_iter().peekable(); - let bound = T::from_args(vm, &mut args)?; + let bound = match T::from_args(vm, &mut args) { + Ok(args) => args, + Err(ArgumentError::TooFewArgs) => { + return Err(vm.new_type_error(format!( + "Expected at least {} arguments ({} given)", + T::arity().start(), + given_args, + ))); + } + Err(ArgumentError::Exception(ex)) => { + return Err(ex); + } + }; - if args.next().is_none() { - Ok(bound) - } else { - Err(vm.new_type_error("too many args".to_string())) // TODO: improve error message + match args.next() { + None => Ok(bound), + Some(PyArg::Positional(_)) => Err(vm.new_type_error(format!( + "Expected at most {} arguments ({} given)", + T::arity().end(), + given_args, + ))), + Some(PyArg::Keyword(name, _)) => { + Err(vm.new_type_error(format!("Unexpected keyword argument {}", name))) + } } } } +/// Implemented by any type that can be accepted as a parameter to a built-in +/// function. +/// pub trait FromArgs: Sized { - fn from_args(vm: &mut VirtualMachine, args: &mut iter::Peekable) -> PyResult + /// The range of positional arguments permitted by the function signature. + /// + /// Returns an empty range if not applicable. + fn arity() -> RangeInclusive { + 0..=0 + } + + /// Extracts this item from the next argument(s). + fn from_args( + vm: &mut VirtualMachine, + args: &mut iter::Peekable, + ) -> Result where I: Iterator; } -pub struct PyIterable { +/// An iterable Python object. +/// +/// `PyIterable` implements `FromArgs` so that a built-in function can accept +/// an object that is required to conform to the Python iterator protocol. +/// +/// PyIterable can optionally perform type checking and conversions on iterated +/// objects using a generic type parameter that implements `TryFromObject`. +pub struct PyIterable { method: PyObjectRef, _item: std::marker::PhantomData, } impl PyIterable { + /// Returns an iterator over this sequence of objects. + /// + /// This operation may fail if an exception is raised while invoking the + /// `__iter__` method of the iterable object. pub fn iter<'a>(&self, vm: &'a mut VirtualMachine) -> PyResult> { let iter_obj = vm.invoke( self.method.clone(), @@ -1037,13 +1092,24 @@ impl TryFromObject for PyObjectRef { } } -pub struct KwArgs(HashMap); +/// A map of keyword arguments to their values. +/// +/// A built-in function with a `KwArgs` parameter is analagous to a Python +/// function with `*kwargs`. All remaining keyword arguments are extracted +/// (and hence the function will permit an arbitrary number of them). +/// +/// `KwArgs` optionally accepts a generic type parameter to allow type checks +/// or conversions of each argument. +pub struct KwArgs(HashMap); impl FromArgs for KwArgs where T: TryFromObject, { - fn from_args(vm: &mut VirtualMachine, args: &mut iter::Peekable) -> PyResult + fn from_args( + vm: &mut VirtualMachine, + args: &mut iter::Peekable, + ) -> Result where I: Iterator, { @@ -1055,13 +1121,24 @@ where } } +/// A list of positional argument values. +/// +/// A built-in function with a `Args` parameter is analagous to a Python +/// function with `*args`. All remaining positional arguments are extracted +/// (and hence the function will permit an arbitrary number of them). +/// +/// `Args` optionally accepts a generic type parameter to allow type checks +/// or conversions of each argument. pub struct Args(Vec); impl FromArgs for Args where T: TryFromObject, { - fn from_args(vm: &mut VirtualMachine, args: &mut iter::Peekable) -> PyResult + fn from_args( + vm: &mut VirtualMachine, + args: &mut iter::Peekable, + ) -> Result where I: Iterator, { @@ -1077,14 +1154,21 @@ impl FromArgs for T where T: TryFromObject, { - fn from_args(vm: &mut VirtualMachine, args: &mut iter::Peekable) -> PyResult + fn arity() -> RangeInclusive { + 1..=1 + } + + fn from_args( + vm: &mut VirtualMachine, + args: &mut iter::Peekable, + ) -> Result where I: Iterator, { if let Some(PyArg::Positional(value)) = args.next() { Ok(T::try_from_object(vm, value)?) } else { - Err(vm.new_type_error("not enough args".to_string())) // TODO: improve error message + Err(ArgumentError::TooFewArgs) } } } @@ -1103,7 +1187,14 @@ impl FromArgs for OptArg where T: TryFromObject, { - fn from_args(vm: &mut VirtualMachine, args: &mut iter::Peekable) -> PyResult + fn arity() -> RangeInclusive { + 0..=1 + } + + fn from_args( + vm: &mut VirtualMachine, + args: &mut iter::Peekable, + ) -> Result where I: Iterator, { @@ -1125,10 +1216,31 @@ pub enum PyArg { Keyword(String, PyObjectRef), } +pub enum ArgumentError { + TooFewArgs, + Exception(PyObjectRef), +} + +impl From for ArgumentError { + fn from(ex: PyObjectRef) -> Self { + ArgumentError::Exception(ex) + } +} + +/// Implemented by any type that can be created from a Python object. +/// +/// Any type that implements `TryFromObject` is automatically `FromArgs`, and +/// so can be accepted as a argument to a built-in function. pub trait TryFromObject: Sized { + /// Attempt to convert a Python object to a value of this type. fn try_from_object(vm: &mut VirtualMachine, obj: PyObjectRef) -> PyResult; } +/// Implemented by any type that can be returned from a built-in Python function. +/// +/// `IntoPyObject` has a blanket implementation for any built-in object payload, +/// and should be implemented by many primitive Rust types, allowing a built-in +/// function to simply return a `bool` or a `usize` for example. pub trait IntoPyObject { fn into_pyobject(self, ctx: &PyContext) -> PyResult; } @@ -1148,12 +1260,22 @@ where } } +// This allows a built-in function to not return a value, mapping to +// Python's behavior of returning `None` in this situation. impl IntoPyObject for () { fn into_pyobject(self, ctx: &PyContext) -> PyResult { Ok(ctx.none()) } } +// TODO: Allow a built-in function to return an `Option`, i.e.: +// +// impl IntoPyObject for Option +// +// Option::None should map to a Python `None`. + +// Allows a built-in function to return any built-in object payload without +// explicitly implementing `IntoPyObject`. impl IntoPyObject for T where T: PyObjectPayload2 + Sized, @@ -1168,13 +1290,33 @@ where } } +// A tuple of types that each implement `FromArgs` represents a sequence of +// arguments that can be bound and passed to a built-in function. +// +// Technically, a tuple can contain tuples, which can contain tuples, and so on, +// so this actually represents a tree of values to be bound from arguments, but +// in practice this is only used for the top-level parameters. macro_rules! tuple_from_py_func_args { ($($T:ident),+) => { impl<$($T),+> FromArgs for ($($T,)+) where $($T: FromArgs),+ { - fn from_args(vm: &mut VirtualMachine, args: &mut iter::Peekable) -> PyResult + fn arity() -> RangeInclusive { + let mut min = 0; + let mut max = 0; + $( + let (start, end) = $T::arity().into_inner(); + min += start; + max += end; + )+ + min..=max + } + + fn from_args( + vm: &mut VirtualMachine, + args: &mut iter::Peekable + ) -> Result where I: Iterator { @@ -1184,14 +1326,32 @@ macro_rules! tuple_from_py_func_args { }; } +// Implement `FromArgs` for up to 5-tuples, allowing built-in functions to bind +// up to 5 top-level parameters (note that `Args`, `KwArgs`, nested tuples, etc. +// count as 1, so this should actually be more than enough). tuple_from_py_func_args!(A); tuple_from_py_func_args!(A, B); tuple_from_py_func_args!(A, B, C); tuple_from_py_func_args!(A, B, C, D); tuple_from_py_func_args!(A, B, C, D, E); +/// A built-in Python function. pub type PyNativeFunc = Box PyResult + 'static>; +/// Implemented by types that are or can generate built-in functions. +/// +/// For example, any function that: +/// +/// - Accepts a sequence of types that implement `FromArgs`, followed by a +/// `&mut VirtualMachine` +/// - Returns some type that implements `IntoPyObject` +/// +/// will generate a `PyNativeFunc` that performs the appropriate type and arity +/// checking, any requested conversions, and then if successful call the function +/// with the bound values. +/// +/// A bare `PyNativeFunc` also implements this trait, allowing the above to be +/// done manually, for rare situations that don't fit into this model. pub trait IntoPyNativeFunc { fn into_func(self) -> PyNativeFunc; } @@ -1211,6 +1371,10 @@ impl IntoPyNativeFunc for PyNativeFunc { } } +// This is the "magic" that allows rust functions of varying signatures to +// generate native python functions. +// +// Note that this could be done without a macro - it is simply to avoid repetition. macro_rules! into_py_native_func_tuple { ($(($n:tt, $T:ident)),+) => { impl IntoPyNativeFunc<($($T,)+), R> for F From 2d71f6de288cd8dce359f16499c2df56e0f03bb5 Mon Sep 17 00:00:00 2001 From: Joey Hain Date: Sun, 3 Mar 2019 20:01:07 -0800 Subject: [PATCH 16/26] bytes and bytearray --- vm/src/obj/objbytearray.rs | 49 ++++++++++++++++++++++++++++---------- vm/src/obj/objbytes.rs | 49 ++++++++++++++++++++++++-------------- vm/src/obj/objiter.rs | 28 ++++++++++++++-------- vm/src/pyobject.rs | 12 ++++------ vm/src/stdlib/io.rs | 20 ++++++++-------- 5 files changed, 100 insertions(+), 58 deletions(-) diff --git a/vm/src/obj/objbytearray.rs b/vm/src/obj/objbytearray.rs index 0cabf23fd..7e03d34d3 100644 --- a/vm/src/obj/objbytearray.rs +++ b/vm/src/obj/objbytearray.rs @@ -1,17 +1,47 @@ //! Implementation of the python bytearray object. use std::cell::RefCell; +use std::ops::{Deref, DerefMut}; -use crate::pyobject::{PyContext, PyFuncArgs, PyObject, PyObjectPayload, PyResult, TypeProtocol}; +use crate::pyobject::{ + PyContext, PyFuncArgs, PyObject, PyObjectPayload, PyObjectPayload2, PyObjectRef, PyResult, + TypeProtocol, +}; use super::objint; -use super::objbytes::get_mut_value; -use super::objbytes::get_value; use super::objtype; use crate::vm::VirtualMachine; use num_traits::ToPrimitive; +#[derive(Debug)] +pub struct PyByteArray { + // TODO: shouldn't be public + pub value: RefCell>, +} + +impl PyByteArray { + pub fn new(data: Vec) -> Self { + PyByteArray { + value: RefCell::new(data), + } + } +} + +impl PyObjectPayload2 for PyByteArray { + fn required_type(ctx: &PyContext) -> PyObjectRef { + ctx.bytearray_type() + } +} + +pub fn get_value<'a>(obj: &'a PyObjectRef) -> impl Deref> + 'a { + obj.payload::().unwrap().value.borrow() +} + +pub fn get_mut_value<'a>(obj: &'a PyObjectRef) -> impl DerefMut> + 'a { + obj.payload::().unwrap().value.borrow_mut() +} + // Binary data support /// Fill bytearray class methods dictionary. @@ -143,8 +173,8 @@ fn bytearray_new(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { vec![] }; Ok(PyObject::new( - PyObjectPayload::Bytes { - value: RefCell::new(value), + PyObjectPayload::AnyRustValue { + value: Box::new(PyByteArray::new(value)), }, cls.clone(), )) @@ -290,13 +320,8 @@ fn bytearray_repr(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { fn bytearray_clear(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(zelf, Some(vm.ctx.bytearray_type()))]); - match zelf.payload { - PyObjectPayload::Bytes { ref value } => { - value.borrow_mut().clear(); - Ok(vm.get_none()) - } - _ => panic!("Bytearray has incorrect payload."), - } + get_mut_value(zelf).clear(); + Ok(vm.get_none()) } fn bytearray_pop(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { diff --git a/vm/src/obj/objbytes.rs b/vm/src/obj/objbytes.rs index 6ab2f72af..d285b86e9 100644 --- a/vm/src/obj/objbytes.rs +++ b/vm/src/obj/objbytes.rs @@ -1,16 +1,41 @@ -use std::cell::{Cell, RefCell}; +use std::cell::Cell; use std::hash::{Hash, Hasher}; use std::ops::Deref; -use std::ops::DerefMut; use super::objint; use super::objtype; use crate::pyobject::{ - PyContext, PyFuncArgs, PyObject, PyObjectPayload, PyObjectRef, PyResult, TypeProtocol, + PyContext, PyFuncArgs, PyObject, PyObjectPayload, PyObjectPayload2, PyObjectRef, PyResult, + TypeProtocol, }; use crate::vm::VirtualMachine; use num_traits::ToPrimitive; +#[derive(Debug)] +pub struct PyBytes { + value: Vec, +} + +impl PyBytes { + pub fn new(data: Vec) -> Self { + PyBytes { value: data } + } +} + +impl Deref for PyBytes { + type Target = [u8]; + + fn deref(&self) -> &[u8] { + &self.value + } +} + +impl PyObjectPayload2 for PyBytes { + fn required_type(ctx: &PyContext) -> PyObjectRef { + ctx.bytes_type() + } +} + // Binary data support // Fill bytes class methods: @@ -71,8 +96,8 @@ fn bytes_new(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { }; Ok(PyObject::new( - PyObjectPayload::Bytes { - value: RefCell::new(value), + PyObjectPayload::AnyRustValue { + value: Box::new(PyBytes::new(value)), }, cls.clone(), )) @@ -170,19 +195,7 @@ fn bytes_hash(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { } pub fn get_value<'a>(obj: &'a PyObjectRef) -> impl Deref> + 'a { - if let PyObjectPayload::Bytes { ref value } = obj.payload { - value.borrow() - } else { - panic!("Inner error getting bytearray {:?}", obj); - } -} - -pub fn get_mut_value<'a>(obj: &'a PyObjectRef) -> impl DerefMut> + 'a { - if let PyObjectPayload::Bytes { ref value } = obj.payload { - value.borrow_mut() - } else { - panic!("Inner error getting bytearray {:?}", obj); - } + &obj.payload::().unwrap().value } fn bytes_repr(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { diff --git a/vm/src/obj/objiter.rs b/vm/src/obj/objiter.rs index 9985553fb..049ea8be3 100644 --- a/vm/src/obj/objiter.rs +++ b/vm/src/obj/objiter.rs @@ -8,6 +8,8 @@ use crate::pyobject::{ use crate::vm::VirtualMachine; use super::objbool; +use super::objbytearray::PyByteArray; +use super::objbytes::PyBytes; use super::objrange::PyRange; use super::objtype; @@ -137,6 +139,22 @@ fn iter_next(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { } else { Err(new_stop_iteration(vm)) } + } else if let Some(bytes) = iterated_obj_ref.payload::() { + if position.get() < bytes.len() { + let obj_ref = vm.ctx.new_int(bytes[position.get()]); + position.set(position.get() + 1); + Ok(obj_ref) + } else { + Err(new_stop_iteration(vm)) + } + } else if let Some(bytes) = iterated_obj_ref.payload::() { + if position.get() < bytes.value.borrow().len() { + let obj_ref = vm.ctx.new_int(bytes.value.borrow()[position.get()]); + position.set(position.get() + 1); + Ok(obj_ref) + } else { + Err(new_stop_iteration(vm)) + } } else { match iterated_obj_ref.payload { PyObjectPayload::Sequence { ref elements } => { @@ -148,16 +166,6 @@ fn iter_next(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { Err(new_stop_iteration(vm)) } } - PyObjectPayload::Bytes { ref value } => { - if position.get() < value.borrow().len() { - let obj_ref = vm.ctx.new_int(value.borrow()[position.get()]); - position.set(position.get() + 1); - Ok(obj_ref) - } else { - Err(new_stop_iteration(vm)) - } - } - _ => { panic!("NOT IMPL"); } diff --git a/vm/src/pyobject.rs b/vm/src/pyobject.rs index bfb33871e..038eb1d0f 100644 --- a/vm/src/pyobject.rs +++ b/vm/src/pyobject.rs @@ -483,8 +483,8 @@ impl PyContext { pub fn new_bytes(&self, data: Vec) -> PyObjectRef { PyObject::new( - PyObjectPayload::Bytes { - value: RefCell::new(data), + PyObjectPayload::AnyRustValue { + value: Box::new(objbytes::PyBytes::new(data)), }, self.bytes_type(), ) @@ -492,8 +492,8 @@ impl PyContext { pub fn new_bytearray(&self, data: Vec) -> PyObjectRef { PyObject::new( - PyObjectPayload::Bytes { - value: RefCell::new(data), + PyObjectPayload::AnyRustValue { + value: Box::new(objbytearray::PyByteArray::new(data)), }, self.bytearray_type(), ) @@ -1253,9 +1253,6 @@ pub enum PyObjectPayload { Complex { value: Complex64, }, - Bytes { - value: RefCell>, - }, Sequence { elements: RefCell>, }, @@ -1340,7 +1337,6 @@ impl fmt::Debug for PyObjectPayload { match self { PyObjectPayload::Integer { ref value } => write!(f, "int {}", value), PyObjectPayload::Complex { ref value } => write!(f, "complex {}", value), - PyObjectPayload::Bytes { ref value } => write!(f, "bytes/bytearray {:?}", value), PyObjectPayload::MemoryView { ref obj } => write!(f, "bytes/bytearray {:?}", obj), PyObjectPayload::Sequence { .. } => write!(f, "list or tuple"), PyObjectPayload::Dict { .. } => write!(f, "dict"), diff --git a/vm/src/stdlib/io.rs b/vm/src/stdlib/io.rs index 077f907d8..95f6a10a9 100644 --- a/vm/src/stdlib/io.rs +++ b/vm/src/stdlib/io.rs @@ -15,13 +15,13 @@ use num_traits::ToPrimitive; //custom imports use super::os; +use crate::obj::objbytearray::PyByteArray; use crate::obj::objbytes; use crate::obj::objint; use crate::obj::objstr; use crate::pyobject::{ - AttributeProtocol, BufferProtocol, PyContext, PyFuncArgs, PyObjectPayload, PyObjectRef, - PyResult, TypeProtocol, + AttributeProtocol, BufferProtocol, PyContext, PyFuncArgs, PyObjectRef, PyResult, TypeProtocol, }; use crate::import; @@ -86,8 +86,8 @@ fn buffered_reader_read(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { .map_err(|_| vm.new_value_error("IO Error".to_string()))?; //Copy bytes from the buffer vector into the results vector - if let PyObjectPayload::Bytes { ref value } = buffer.payload { - result.extend(value.borrow().iter().cloned()); + if let Some(bytes) = buffer.payload::() { + result.extend_from_slice(&bytes.value.borrow()); }; let len = vm.get_method(buffer.clone(), &"__len__".to_string()); @@ -169,10 +169,10 @@ fn file_io_readinto(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { let handle = os::rust_file(raw_fd); let mut f = handle.take(length); - if let PyObjectPayload::Bytes { ref value } = obj.payload { + if let Some(bytes) = obj.payload::() { //TODO: Implement for MemoryView - let mut value_mut = value.borrow_mut(); + let mut value_mut = bytes.value.borrow_mut(); value_mut.clear(); match f.read_to_end(&mut value_mut) { Ok(_) => {} @@ -200,9 +200,9 @@ fn file_io_write(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { //to support windows - i.e. raw file_handles let mut handle = os::rust_file(raw_fd); - match obj.payload { - PyObjectPayload::Bytes { ref value } => { - let value_mut = value.borrow(); + match obj.payload::() { + Some(bytes) => { + let value_mut = bytes.value.borrow(); match handle.write(&value_mut[..]) { Ok(len) => { //reset raw fd on the FileIO object @@ -215,7 +215,7 @@ fn file_io_write(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { Err(_) => Err(vm.new_value_error("Error Writing Bytes to Handle".to_string())), } } - _ => Err(vm.new_value_error("Expected Bytes Object".to_string())), + None => Err(vm.new_value_error("Expected Bytes Object".to_string())), } } From b1a070acc2468a9a66cb69c026e1d80f85e9ca5d Mon Sep 17 00:00:00 2001 From: ben Date: Mon, 4 Mar 2019 19:55:58 +1300 Subject: [PATCH 17/26] Only use real isinstance/issubclass for builtins and move the real versions to the vm. --- vm/src/builtins.rs | 6 +++--- vm/src/macros.rs | 2 +- vm/src/obj/objsuper.rs | 4 +--- vm/src/obj/objtype.rs | 30 ------------------------------ vm/src/pyobject.rs | 10 ++-------- vm/src/vm.rs | 20 ++++++++++++++++++++ 6 files changed, 27 insertions(+), 45 deletions(-) diff --git a/vm/src/builtins.rs b/vm/src/builtins.rs index 0e48315e9..e7a3bbb44 100644 --- a/vm/src/builtins.rs +++ b/vm/src/builtins.rs @@ -347,7 +347,7 @@ fn builtin_isinstance(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { required = [(obj, None), (typ, Some(vm.get_type()))] ); - let isinstance = objtype::real_isinstance(vm, obj, typ)?; + let isinstance = vm.isinstance(obj, typ)?; Ok(vm.new_bool(isinstance)) } @@ -358,7 +358,7 @@ fn builtin_issubclass(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { required = [(subclass, Some(vm.get_type())), (cls, Some(vm.get_type()))] ); - let issubclass = objtype::real_issubclass(vm, subclass, cls)?; + let issubclass = vm.issubclass(subclass, cls)?; Ok(vm.context().new_bool(issubclass)) } @@ -814,7 +814,7 @@ pub fn builtin_build_class_(vm: &mut VirtualMachine, mut args: PyFuncArgs) -> Py let mut metaclass = args.get_kwarg("metaclass", vm.get_type()); for base in bases.clone() { - if objtype::real_issubclass(vm, &base.typ(), &metaclass)? { + if objtype::issubclass(&base.typ(), &metaclass) { metaclass = base.typ(); } else if !objtype::issubclass(&metaclass, &base.typ()) { return Err(vm.new_type_error("metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases".to_string())); diff --git a/vm/src/macros.rs b/vm/src/macros.rs index f228b6a7f..f91cd7c61 100644 --- a/vm/src/macros.rs +++ b/vm/src/macros.rs @@ -19,7 +19,7 @@ macro_rules! type_check { if let Some(expected_type) = $arg_type { let arg = &$args.args[$arg_count]; - if !$crate::obj::objtype::real_isinstance($vm, arg, &expected_type)? { + if !$crate::obj::objtype::isinstance(arg, &expected_type) { let arg_typ = arg.typ(); let expected_type_name = $vm.to_pystr(&expected_type)?; let actual_type = $vm.to_pystr(&arg_typ)?; diff --git a/vm/src/obj/objsuper.rs b/vm/src/obj/objsuper.rs index b3d218430..79c516d4c 100644 --- a/vm/src/obj/objsuper.rs +++ b/vm/src/obj/objsuper.rs @@ -72,9 +72,7 @@ fn super_init(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { }; // Check obj type: - if !(objtype::real_isinstance(vm, &py_obj, &py_type)? - || objtype::real_issubclass(vm, &py_obj, &py_type)?) - { + if !(objtype::isinstance(&py_obj, &py_type) || objtype::issubclass(&py_obj, &py_type)) { return Err(vm.new_type_error( "super(type, obj): obj must be an instance or subtype of type".to_string(), )); diff --git a/vm/src/obj/objtype.rs b/vm/src/obj/objtype.rs index 785ca1b87..c800b7f52 100644 --- a/vm/src/obj/objtype.rs +++ b/vm/src/obj/objtype.rs @@ -1,4 +1,3 @@ -use super::objbool; use super::objdict; use super::objstr; use super::objtype; // Required for arg_check! to use isinstance @@ -9,7 +8,6 @@ use crate::pyobject::{ use crate::vm::VirtualMachine; use std::cell::RefCell; use std::collections::HashMap; -use std::rc::Rc; /* * The magical type type @@ -108,23 +106,6 @@ pub fn isinstance(obj: &PyObjectRef, cls: &PyObjectRef) -> bool { mro.into_iter().any(|c| c.is(&cls)) } -/// Determines if `obj` is an instance of `cls`, either directly, indirectly or virtually via the -/// __instancecheck__ magic method. -pub fn real_isinstance( - vm: &mut VirtualMachine, - obj: &PyObjectRef, - cls: &PyObjectRef, -) -> PyResult { - // cpython first does an exact check on the type, although documentation doesn't state that - // https://github.com/python/cpython/blob/a24107b04c1277e3c1105f98aff5bfa3a98b33a0/Objects/abstract.c#L2408 - if Rc::ptr_eq(&obj.typ(), cls) { - Ok(true) - } else { - let ret = vm.call_method(cls, "__instancecheck__", vec![obj.clone()])?; - objbool::boolval(vm, ret) - } -} - fn type_instance_check(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!( vm, @@ -142,17 +123,6 @@ pub fn issubclass(subclass: &PyObjectRef, cls: &PyObjectRef) -> bool { mro.into_iter().any(|c| c.is(&cls)) } -/// Determines if `subclass` is a subclass of `cls`, either directly, indirectly or virtually via -/// the __subclasscheck__ magic method. -pub fn real_issubclass( - vm: &mut VirtualMachine, - subclass: &PyObjectRef, - cls: &PyObjectRef, -) -> PyResult { - let ret = vm.call_method(cls, "__subclasscheck__", vec![subclass.clone()])?; - objbool::boolval(vm, ret) -} - fn type_subclass_check(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!( vm, diff --git a/vm/src/pyobject.rs b/vm/src/pyobject.rs index c39aa71ae..e3900627f 100644 --- a/vm/src/pyobject.rs +++ b/vm/src/pyobject.rs @@ -922,7 +922,7 @@ impl PyFuncArgs { ) -> Result, PyObjectRef> { match self.get_optional_kwarg(key) { Some(kwarg) => { - if objtype::real_isinstance(vm, &kwarg, &ty)? { + if objtype::isinstance(&kwarg, &ty) { Ok(Some(kwarg)) } else { let expected_ty_name = vm.to_pystr(&ty)?; @@ -1003,13 +1003,7 @@ where Ok(value) => Some(T::try_from_object(self.vm, value)), Err(err) => { let stop_ex = self.vm.ctx.exceptions.stop_iteration.clone(); - let stop = match objtype::real_isinstance(self.vm, &err, &stop_ex) { - Ok(stop) => stop, - Err(e) => { - return Some(Err(e)); - } - }; - if stop { + if objtype::isinstance(&err, &stop_ex) { None } else { Some(Err(err)) diff --git a/vm/src/vm.rs b/vm/src/vm.rs index 9d91abf9e..c4b6c14ce 100644 --- a/vm/src/vm.rs +++ b/vm/src/vm.rs @@ -242,6 +242,26 @@ impl VirtualMachine { self.call_method(obj, "__repr__", vec![]) } + /// Determines if `obj` is an instance of `cls`, either directly, indirectly or virtually via + /// the __instancecheck__ magic method. + pub fn isinstance(&mut self, obj: &PyObjectRef, cls: &PyObjectRef) -> PyResult { + // cpython first does an exact check on the type, although documentation doesn't state that + // https://github.com/python/cpython/blob/a24107b04c1277e3c1105f98aff5bfa3a98b33a0/Objects/abstract.c#L2408 + if Rc::ptr_eq(&obj.typ(), cls) { + Ok(true) + } else { + let ret = self.call_method(cls, "__instancecheck__", vec![obj.clone()])?; + objbool::boolval(self, ret) + } + } + + /// Determines if `subclass` is a subclass of `cls`, either directly, indirectly or virtually + /// via the __subclasscheck__ magic method. + pub fn issubclass(&mut self, subclass: &PyObjectRef, cls: &PyObjectRef) -> PyResult { + let ret = self.call_method(cls, "__subclasscheck__", vec![subclass.clone()])?; + objbool::boolval(self, ret) + } + pub fn call_get_descriptor(&mut self, attr: PyObjectRef, obj: PyObjectRef) -> PyResult { let attr_class = attr.typ(); if let Some(descriptor) = attr_class.get_attr("__get__") { From c5789a03a32d76e83cc5da0a9ed6e434997ff277 Mon Sep 17 00:00:00 2001 From: Adam Kelly Date: Sun, 3 Mar 2019 14:06:36 +0000 Subject: [PATCH 18/26] Proper construction of scope for exec/eval. --- tests/snippets/test_exec.py | 12 ++++++++++++ vm/src/builtins.rs | 35 ++++++++++++++++++----------------- vm/src/frame.rs | 6 ++++++ vm/src/vm.rs | 8 +++++++- 4 files changed, 43 insertions(+), 18 deletions(-) create mode 100644 tests/snippets/test_exec.py diff --git a/tests/snippets/test_exec.py b/tests/snippets/test_exec.py new file mode 100644 index 000000000..c610af565 --- /dev/null +++ b/tests/snippets/test_exec.py @@ -0,0 +1,12 @@ +exec("def square(x):\n return x * x\n") +assert 16 == square(4) + +d = {} +exec("def square(x):\n return x * x\n", {}, d) +assert 16 == d['square'](4) + +exec("assert 2 == x", {}, {'x': 2}) +exec("assert 2 == x", {'x': 2}, {}) +exec("assert 4 == x", {'x': 2}, {'x': 4}) + +exec("assert max(1, 2) == 2", {}, {}) diff --git a/vm/src/builtins.rs b/vm/src/builtins.rs index e7a3bbb44..3b8ea0f2a 100644 --- a/vm/src/builtins.rs +++ b/vm/src/builtins.rs @@ -18,7 +18,6 @@ use crate::frame::{Scope, ScopeRef}; use crate::pyobject::{ AttributeProtocol, IdProtocol, PyContext, PyFuncArgs, PyObjectRef, PyResult, TypeProtocol, }; -use std::rc::Rc; #[cfg(not(target_arch = "wasm32"))] use crate::stdlib::io::io_open; @@ -192,7 +191,7 @@ fn builtin_eval(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { args, required = [(source, None)], optional = [ - (_globals, Some(vm.ctx.dict_type())), + (globals, Some(vm.ctx.dict_type())), (locals, Some(vm.ctx.dict_type())) ] ); @@ -215,7 +214,7 @@ fn builtin_eval(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { return Err(vm.new_type_error("code argument must be str or code object".to_string())); }; - let scope = make_scope(vm, locals); + let scope = make_scope(vm, globals, locals); // Run the source: vm.run_code_obj(code_obj.clone(), scope) @@ -229,7 +228,7 @@ fn builtin_exec(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { args, required = [(source, None)], optional = [ - (_globals, Some(vm.ctx.dict_type())), + (globals, Some(vm.ctx.dict_type())), (locals, Some(vm.ctx.dict_type())) ] ); @@ -252,26 +251,28 @@ fn builtin_exec(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { return Err(vm.new_type_error("source argument must be str or code object".to_string())); }; - let scope = make_scope(vm, locals); + let scope = make_scope(vm, globals, locals); // Run the code: vm.run_code_obj(code_obj, scope) } -fn make_scope(vm: &mut VirtualMachine, locals: Option<&PyObjectRef>) -> ScopeRef { - // handle optional global and locals - let locals = if let Some(locals) = locals { - locals.clone() - } else { - vm.new_dict() +fn make_scope( + vm: &mut VirtualMachine, + globals: Option<&PyObjectRef>, + locals: Option<&PyObjectRef>, +) -> ScopeRef { + let current_scope = vm.current_scope(); + let parent = match globals { + Some(dict) => Some(Scope::new(dict.clone(), Some(vm.get_builtin_scope()))), + None => current_scope.parent.clone(), + }; + let locals = match locals { + Some(dict) => dict.clone(), + None => current_scope.locals.clone(), }; - // TODO: handle optional globals - // Construct new scope: - Rc::new(Scope { - locals, - parent: None, - }) + Scope::new(locals, parent) } fn builtin_format(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { diff --git a/vm/src/frame.rs b/vm/src/frame.rs index ec0c09923..c76552b76 100644 --- a/vm/src/frame.rs +++ b/vm/src/frame.rs @@ -35,6 +35,12 @@ pub struct Scope { } pub type ScopeRef = Rc; +impl Scope { + pub fn new(locals: PyObjectRef, parent: Option) -> ScopeRef { + Rc::new(Scope { locals, parent }) + } +} + #[derive(Clone, Debug)] struct Block { /// The type of block. diff --git a/vm/src/vm.rs b/vm/src/vm.rs index c4b6c14ce..60a260479 100644 --- a/vm/src/vm.rs +++ b/vm/src/vm.rs @@ -90,6 +90,12 @@ impl VirtualMachine { result } + pub fn current_scope(&self) -> &ScopeRef { + let current_frame = &self.frames[self.frames.len() - 1]; + let frame = objframe::get_value(current_frame); + &frame.scope + } + /// Create a new python string object. pub fn new_str(&self, s: String) -> PyObjectRef { self.ctx.new_str(s) @@ -218,7 +224,7 @@ impl VirtualMachine { &self.ctx } - pub fn get_builtin_scope(&mut self) -> ScopeRef { + pub fn get_builtin_scope(&self) -> ScopeRef { let a2 = &*self.builtins; match a2.payload { PyObjectPayload::Module { ref scope, .. } => scope.clone(), From 4dd03047122d35ec35a40660b45f6c6a5bb0ec7b Mon Sep 17 00:00:00 2001 From: Adam Kelly Date: Sun, 3 Mar 2019 16:33:45 +0000 Subject: [PATCH 19/26] Add check_but_allow_none macro for optional argument checking. --- vm/src/macros.rs | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/vm/src/macros.rs b/vm/src/macros.rs index f91cd7c61..167a305a1 100644 --- a/vm/src/macros.rs +++ b/vm/src/macros.rs @@ -35,6 +35,34 @@ macro_rules! type_check { }; } +#[macro_export] +macro_rules! check_but_allow_none { + ( $vm: ident, $arg: ident, $expected_type: expr ) => { + let $arg = match $arg { + Some(arg) => { + if arg.is(&$vm.get_none()) { + None + } else { + if $crate::obj::objtype::real_isinstance($vm, arg, &$expected_type)? { + Some(arg) + } else { + let arg_typ = arg.typ(); + let actual_type = $vm.to_pystr(&arg_typ)?; + let expected_type_name = $vm.to_pystr(&$expected_type)?; + return Err($vm.new_type_error(format!( + "{} must be a {}, not {}", + stringify!($arg), + expected_type_name, + actual_type + ))); + } + } + } + None => None, + }; + }; +} + #[macro_export] macro_rules! arg_check { ( $vm: ident, $args:ident ) => { From 6548c365cb7a9556dc2161b827b72fae8be9e57c Mon Sep 17 00:00:00 2001 From: Adam Kelly Date: Sun, 3 Mar 2019 16:45:48 +0000 Subject: [PATCH 20/26] Use check_but_allow_none for exec/eval. --- tests/snippets/test_exec.py | 9 +++++++++ vm/src/builtins.rs | 14 ++++++-------- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/tests/snippets/test_exec.py b/tests/snippets/test_exec.py index c610af565..852ef6143 100644 --- a/tests/snippets/test_exec.py +++ b/tests/snippets/test_exec.py @@ -10,3 +10,12 @@ exec("assert 2 == x", {'x': 2}, {}) exec("assert 4 == x", {'x': 2}, {'x': 4}) exec("assert max(1, 2) == 2", {}, {}) + +exec("max(1, 5, square(5)) == 25", None) + +try: + exec("", 1) +except TypeError: + pass +else: + raise TypeError("exec should fail unless globals is a dict or None") diff --git a/vm/src/builtins.rs b/vm/src/builtins.rs index 3b8ea0f2a..71d6112c5 100644 --- a/vm/src/builtins.rs +++ b/vm/src/builtins.rs @@ -190,12 +190,11 @@ fn builtin_eval(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { vm, args, required = [(source, None)], - optional = [ - (globals, Some(vm.ctx.dict_type())), - (locals, Some(vm.ctx.dict_type())) - ] + optional = [(globals, None), (locals, Some(vm.ctx.dict_type()))] ); + check_but_allow_none!(vm, globals, vm.ctx.dict_type()); + // Determine code object: let code_obj = if objtype::isinstance(source, &vm.ctx.code_type()) { source.clone() @@ -227,12 +226,11 @@ fn builtin_exec(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { vm, args, required = [(source, None)], - optional = [ - (globals, Some(vm.ctx.dict_type())), - (locals, Some(vm.ctx.dict_type())) - ] + optional = [(globals, None), (locals, Some(vm.ctx.dict_type()))] ); + check_but_allow_none!(vm, globals, vm.ctx.dict_type()); + // Determine code object: let code_obj = if objtype::isinstance(source, &vm.ctx.str_type()) { let mode = compile::Mode::Exec; From 0d4887abe6d6fe77ffdaae9dc669cf865778adfe Mon Sep 17 00:00:00 2001 From: Adam Kelly Date: Mon, 4 Mar 2019 14:22:48 +0000 Subject: [PATCH 21/26] Fix missing asserts and add (disabled) tests for things that don't work yet. --- tests/snippets/test_exec.py | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/tests/snippets/test_exec.py b/tests/snippets/test_exec.py index 852ef6143..37ba33ff1 100644 --- a/tests/snippets/test_exec.py +++ b/tests/snippets/test_exec.py @@ -11,7 +11,28 @@ exec("assert 4 == x", {'x': 2}, {'x': 4}) exec("assert max(1, 2) == 2", {}, {}) -exec("max(1, 5, square(5)) == 25", None) +exec("assert max(1, 5, square(5)) == 25", None) + +# +# These doesn't work yet: +# +# Local environment shouldn't replace global environment: +# +# exec("assert max(1, 5, square(5)) == 25", None, {}) +# +# Closures aren't available if local scope is replaced: +# +# def g(): +# seven = "seven" +# def f(): +# try: +# exec("seven", None, {}) +# except NameError: +# pass +# else: +# raise NameError("seven shouldn't be in scope") +# f() +# g() try: exec("", 1) From c4953ee9ec51d0ec4fb0bfca0b1b2a1a99c8a339 Mon Sep 17 00:00:00 2001 From: Adam Kelly Date: Mon, 4 Mar 2019 14:29:44 +0000 Subject: [PATCH 22/26] Remove new macro and inline check into make_scope. --- vm/src/builtins.rs | 34 ++++++++++++++++++++++++++-------- vm/src/macros.rs | 28 ---------------------------- 2 files changed, 26 insertions(+), 36 deletions(-) diff --git a/vm/src/builtins.rs b/vm/src/builtins.rs index 71d6112c5..b146fcf91 100644 --- a/vm/src/builtins.rs +++ b/vm/src/builtins.rs @@ -193,7 +193,7 @@ fn builtin_eval(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { optional = [(globals, None), (locals, Some(vm.ctx.dict_type()))] ); - check_but_allow_none!(vm, globals, vm.ctx.dict_type()); + let scope = make_scope(vm, globals, locals)?; // Determine code object: let code_obj = if objtype::isinstance(source, &vm.ctx.code_type()) { @@ -213,8 +213,6 @@ fn builtin_eval(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { return Err(vm.new_type_error("code argument must be str or code object".to_string())); }; - let scope = make_scope(vm, globals, locals); - // Run the source: vm.run_code_obj(code_obj.clone(), scope) } @@ -229,7 +227,7 @@ fn builtin_exec(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { optional = [(globals, None), (locals, Some(vm.ctx.dict_type()))] ); - check_but_allow_none!(vm, globals, vm.ctx.dict_type()); + let scope = make_scope(vm, globals, locals)?; // Determine code object: let code_obj = if objtype::isinstance(source, &vm.ctx.str_type()) { @@ -249,8 +247,6 @@ fn builtin_exec(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { return Err(vm.new_type_error("source argument must be str or code object".to_string())); }; - let scope = make_scope(vm, globals, locals); - // Run the code: vm.run_code_obj(code_obj, scope) } @@ -259,7 +255,29 @@ fn make_scope( vm: &mut VirtualMachine, globals: Option<&PyObjectRef>, locals: Option<&PyObjectRef>, -) -> ScopeRef { +) -> PyResult { + let dict_type = vm.ctx.dict_type(); + let globals = match globals { + Some(arg) => { + if arg.is(&vm.get_none()) { + None + } else { + if vm.isinstance(arg, &dict_type)? { + Some(arg) + } else { + let arg_typ = arg.typ(); + let actual_type = vm.to_pystr(&arg_typ)?; + let expected_type_name = vm.to_pystr(&dict_type)?; + return Err(vm.new_type_error(format!( + "globals must be a {}, not {}", + expected_type_name, actual_type + ))); + } + } + } + None => None, + }; + let current_scope = vm.current_scope(); let parent = match globals { Some(dict) => Some(Scope::new(dict.clone(), Some(vm.get_builtin_scope()))), @@ -270,7 +288,7 @@ fn make_scope( None => current_scope.locals.clone(), }; - Scope::new(locals, parent) + Ok(Scope::new(locals, parent)) } fn builtin_format(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { diff --git a/vm/src/macros.rs b/vm/src/macros.rs index 167a305a1..f91cd7c61 100644 --- a/vm/src/macros.rs +++ b/vm/src/macros.rs @@ -35,34 +35,6 @@ macro_rules! type_check { }; } -#[macro_export] -macro_rules! check_but_allow_none { - ( $vm: ident, $arg: ident, $expected_type: expr ) => { - let $arg = match $arg { - Some(arg) => { - if arg.is(&$vm.get_none()) { - None - } else { - if $crate::obj::objtype::real_isinstance($vm, arg, &$expected_type)? { - Some(arg) - } else { - let arg_typ = arg.typ(); - let actual_type = $vm.to_pystr(&arg_typ)?; - let expected_type_name = $vm.to_pystr(&$expected_type)?; - return Err($vm.new_type_error(format!( - "{} must be a {}, not {}", - stringify!($arg), - expected_type_name, - actual_type - ))); - } - } - } - None => None, - }; - }; -} - #[macro_export] macro_rules! arg_check { ( $vm: ident, $args:ident ) => { From d2061004a3fc391d2868945c62f200f7468bcb15 Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Mon, 4 Mar 2019 18:27:33 +0200 Subject: [PATCH 23/26] Use py_class macro --- vm/src/stdlib/socket.rs | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/vm/src/stdlib/socket.rs b/vm/src/stdlib/socket.rs index 7626cc182..50cab5481 100644 --- a/vm/src/stdlib/socket.rs +++ b/vm/src/stdlib/socket.rs @@ -432,21 +432,19 @@ pub fn mk_module(ctx: &PyContext) -> PyObjectRef { ctx.set_attr(&py_mod, "SOCK_DGRAM", ctx.new_int(SocketKind::Dgram as i32)); - let socket = { - let socket = ctx.new_class("socket", ctx.object()); - ctx.set_attr(&socket, "__new__", ctx.new_rustfunc(socket_new)); - ctx.set_attr(&socket, "connect", ctx.new_rustfunc(socket_connect)); - ctx.set_attr(&socket, "recv", ctx.new_rustfunc(socket_recv)); - ctx.set_attr(&socket, "send", ctx.new_rustfunc(socket_send)); - ctx.set_attr(&socket, "bind", ctx.new_rustfunc(socket_bind)); - ctx.set_attr(&socket, "accept", ctx.new_rustfunc(socket_accept)); - ctx.set_attr(&socket, "listen", ctx.new_rustfunc(socket_listen)); - ctx.set_attr(&socket, "close", ctx.new_rustfunc(socket_close)); - ctx.set_attr(&socket, "getsockname", ctx.new_rustfunc(socket_getsockname)); - ctx.set_attr(&socket, "sendto", ctx.new_rustfunc(socket_sendto)); - ctx.set_attr(&socket, "recvfrom", ctx.new_rustfunc(socket_recvfrom)); - socket - }; + let socket = py_class!(ctx, "socket", ctx.object(), { + "__new__" => ctx.new_rustfunc(socket_new), + "connect" => ctx.new_rustfunc(socket_connect), + "recv" => ctx.new_rustfunc(socket_recv), + "send" => ctx.new_rustfunc(socket_send), + "bind" => ctx.new_rustfunc(socket_bind), + "accept" => ctx.new_rustfunc(socket_accept), + "listen" => ctx.new_rustfunc(socket_listen), + "close" => ctx.new_rustfunc(socket_close), + "getsockname" => ctx.new_rustfunc(socket_getsockname), + "sendto" => ctx.new_rustfunc(socket_sendto), + "recvfrom" => ctx.new_rustfunc(socket_recvfrom), + }); ctx.set_attr(&py_mod, "socket", socket.clone()); py_mod From 933c8dc792c016206c9797d736f8c181eb9310df Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Mon, 4 Mar 2019 18:30:37 +0200 Subject: [PATCH 24/26] Use py_module macro --- vm/src/stdlib/socket.rs | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/vm/src/stdlib/socket.rs b/vm/src/stdlib/socket.rs index 50cab5481..4afe7a372 100644 --- a/vm/src/stdlib/socket.rs +++ b/vm/src/stdlib/socket.rs @@ -420,18 +420,6 @@ fn get_addr_tuple(vm: &mut VirtualMachine, addr: SocketAddr) -> PyResult { } pub fn mk_module(ctx: &PyContext) -> PyObjectRef { - let py_mod = ctx.new_module(&"socket".to_string(), ctx.new_scope(None)); - - ctx.set_attr(&py_mod, "AF_INET", ctx.new_int(AddressFamily::Inet as i32)); - - ctx.set_attr( - &py_mod, - "SOCK_STREAM", - ctx.new_int(SocketKind::Stream as i32), - ); - - ctx.set_attr(&py_mod, "SOCK_DGRAM", ctx.new_int(SocketKind::Dgram as i32)); - let socket = py_class!(ctx, "socket", ctx.object(), { "__new__" => ctx.new_rustfunc(socket_new), "connect" => ctx.new_rustfunc(socket_connect), @@ -445,7 +433,11 @@ pub fn mk_module(ctx: &PyContext) -> PyObjectRef { "sendto" => ctx.new_rustfunc(socket_sendto), "recvfrom" => ctx.new_rustfunc(socket_recvfrom), }); - ctx.set_attr(&py_mod, "socket", socket.clone()); - py_mod + py_module!(ctx, "socket", { + "AF_INET" => ctx.new_int(AddressFamily::Inet as i32), + "SOCK_STREAM" => ctx.new_int(SocketKind::Stream as i32), + "SOCK_DGRAM" => ctx.new_int(SocketKind::Dgram as i32), + "socket" => socket.clone(), + }) } From f820aeb1ea46c46a0f302edfc14fbece2cfb540c Mon Sep 17 00:00:00 2001 From: Joey Hain Date: Mon, 4 Mar 2019 08:46:40 -0800 Subject: [PATCH 25/26] Convert complex payload --- vm/src/obj/objcomplex.rs | 30 +++++++++++++++++++++++------- vm/src/pyobject.rs | 28 +++++++++++++++------------- 2 files changed, 38 insertions(+), 20 deletions(-) diff --git a/vm/src/obj/objcomplex.rs b/vm/src/obj/objcomplex.rs index c6be5e5f8..66205559c 100644 --- a/vm/src/obj/objcomplex.rs +++ b/vm/src/obj/objcomplex.rs @@ -2,12 +2,30 @@ use super::objfloat; use super::objint; use super::objtype; use crate::pyobject::{ - PyContext, PyFuncArgs, PyObject, PyObjectPayload, PyObjectRef, PyResult, TypeProtocol, + PyContext, PyFuncArgs, PyObject, PyObjectPayload, PyObjectPayload2, PyObjectRef, PyResult, + TypeProtocol, }; use crate::vm::VirtualMachine; use num_complex::Complex64; use num_traits::ToPrimitive; +#[derive(Debug, Copy, Clone, PartialEq)] +pub struct PyComplex { + value: Complex64, +} + +impl PyObjectPayload2 for PyComplex { + fn required_type(ctx: &PyContext) -> PyObjectRef { + ctx.complex_type() + } +} + +impl From for PyComplex { + fn from(value: Complex64) -> Self { + PyComplex { value } + } +} + pub fn init(context: &PyContext) { let complex_type = &context.complex_type; @@ -45,11 +63,7 @@ pub fn init(context: &PyContext) { } pub fn get_value(obj: &PyObjectRef) -> Complex64 { - if let PyObjectPayload::Complex { value } = &obj.payload { - *value - } else { - panic!("Inner error getting complex"); - } + obj.payload::().unwrap().value } fn complex_new(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { @@ -77,7 +91,9 @@ fn complex_new(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { let value = Complex64::new(real, imag); Ok(PyObject::new( - PyObjectPayload::Complex { value }, + PyObjectPayload::AnyRustValue { + value: Box::new(PyComplex { value }), + }, cls.clone(), )) } diff --git a/vm/src/pyobject.rs b/vm/src/pyobject.rs index 2e07e30f6..e581b1fae 100644 --- a/vm/src/pyobject.rs +++ b/vm/src/pyobject.rs @@ -5,6 +5,11 @@ use std::iter; use std::ops::RangeInclusive; use std::rc::{Rc, Weak}; +use num_bigint::BigInt; +use num_bigint::ToBigInt; +use num_complex::Complex64; +use num_traits::{One, Zero}; + use crate::bytecode; use crate::exceptions; use crate::frame::{Frame, Scope, ScopeRef}; @@ -12,11 +17,11 @@ use crate::obj::objbool; use crate::obj::objbytearray; use crate::obj::objbytes; use crate::obj::objcode; -use crate::obj::objcomplex; +use crate::obj::objcomplex::{self, PyComplex}; use crate::obj::objdict; use crate::obj::objenumerate; use crate::obj::objfilter; -use crate::obj::objfloat; +use crate::obj::objfloat::{self, PyFloat}; use crate::obj::objframe; use crate::obj::objfunction; use crate::obj::objgenerator; @@ -37,10 +42,6 @@ use crate::obj::objtuple; use crate::obj::objtype; use crate::obj::objzip; use crate::vm::VirtualMachine; -use num_bigint::BigInt; -use num_bigint::ToBigInt; -use num_complex::Complex64; -use num_traits::{One, Zero}; /* Python objects and references. @@ -463,14 +464,19 @@ impl PyContext { pub fn new_float(&self, value: f64) -> PyObjectRef { PyObject::new( PyObjectPayload::AnyRustValue { - value: Box::new(objfloat::PyFloat::from(value)), + value: Box::new(PyFloat::from(value)), }, self.float_type(), ) } - pub fn new_complex(&self, i: Complex64) -> PyObjectRef { - PyObject::new(PyObjectPayload::Complex { value: i }, self.complex_type()) + pub fn new_complex(&self, value: Complex64) -> PyObjectRef { + PyObject::new( + PyObjectPayload::AnyRustValue { + value: Box::new(PyComplex::from(value)), + }, + self.complex_type(), + ) } pub fn new_str(&self, s: String) -> PyObjectRef { @@ -1408,9 +1414,6 @@ pub enum PyObjectPayload { Integer { value: BigInt, }, - Complex { - value: Complex64, - }, Sequence { elements: RefCell>, }, @@ -1494,7 +1497,6 @@ impl fmt::Debug for PyObjectPayload { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { PyObjectPayload::Integer { ref value } => write!(f, "int {}", value), - PyObjectPayload::Complex { ref value } => write!(f, "complex {}", value), PyObjectPayload::MemoryView { ref obj } => write!(f, "bytes/bytearray {:?}", obj), PyObjectPayload::Sequence { .. } => write!(f, "list or tuple"), PyObjectPayload::Dict { .. } => write!(f, "dict"), From a26401707f501c604cd25fb0c55bb4645f6abf96 Mon Sep 17 00:00:00 2001 From: Windel Bouwman Date: Mon, 4 Mar 2019 21:25:35 +0100 Subject: [PATCH 26/26] Minor lexer simplifications. --- parser/src/lexer.rs | 102 ++++++++++++++++++-------------------------- 1 file changed, 42 insertions(+), 60 deletions(-) diff --git a/parser/src/lexer.rs b/parser/src/lexer.rs index 0c3d88780..e49ef5148 100644 --- a/parser/src/lexer.rs +++ b/parser/src/lexer.rs @@ -719,16 +719,13 @@ where Some('+') => { let tok_start = self.get_pos(); self.next_char(); - match self.chr0 { - Some('=') => { - self.next_char(); - let tok_end = self.get_pos(); - return Some(Ok((tok_start, Tok::PlusEqual, tok_end))); - } - _ => { - let tok_end = self.get_pos(); - return Some(Ok((tok_start, Tok::Plus, tok_end))); - } + if let Some('=') = self.chr0 { + self.next_char(); + let tok_end = self.get_pos(); + return Some(Ok((tok_start, Tok::PlusEqual, tok_end))); + } else { + let tok_end = self.get_pos(); + return Some(Ok((tok_start, Tok::Plus, tok_end))); } } Some('*') => { @@ -792,61 +789,49 @@ where Some('%') => { let tok_start = self.get_pos(); self.next_char(); - match self.chr0 { - Some('=') => { - self.next_char(); - let tok_end = self.get_pos(); - return Some(Ok((tok_start, Tok::PercentEqual, tok_end))); - } - _ => { - let tok_end = self.get_pos(); - return Some(Ok((tok_start, Tok::Percent, tok_end))); - } + if let Some('=') = self.chr0 { + self.next_char(); + let tok_end = self.get_pos(); + return Some(Ok((tok_start, Tok::PercentEqual, tok_end))); + } else { + let tok_end = self.get_pos(); + return Some(Ok((tok_start, Tok::Percent, tok_end))); } } Some('|') => { let tok_start = self.get_pos(); self.next_char(); - match self.chr0 { - Some('=') => { - self.next_char(); - let tok_end = self.get_pos(); - return Some(Ok((tok_start, Tok::VbarEqual, tok_end))); - } - _ => { - let tok_end = self.get_pos(); - return Some(Ok((tok_start, Tok::Vbar, tok_end))); - } + if let Some('=') = self.chr0 { + self.next_char(); + let tok_end = self.get_pos(); + return Some(Ok((tok_start, Tok::VbarEqual, tok_end))); + } else { + let tok_end = self.get_pos(); + return Some(Ok((tok_start, Tok::Vbar, tok_end))); } } Some('^') => { let tok_start = self.get_pos(); self.next_char(); - match self.chr0 { - Some('=') => { - self.next_char(); - let tok_end = self.get_pos(); - return Some(Ok((tok_start, Tok::CircumflexEqual, tok_end))); - } - _ => { - let tok_end = self.get_pos(); - return Some(Ok((tok_start, Tok::CircumFlex, tok_end))); - } + if let Some('=') = self.chr0 { + self.next_char(); + let tok_end = self.get_pos(); + return Some(Ok((tok_start, Tok::CircumflexEqual, tok_end))); + } else { + let tok_end = self.get_pos(); + return Some(Ok((tok_start, Tok::CircumFlex, tok_end))); } } Some('&') => { let tok_start = self.get_pos(); self.next_char(); - match self.chr0 { - Some('=') => { - self.next_char(); - let tok_end = self.get_pos(); - return Some(Ok((tok_start, Tok::AmperEqual, tok_end))); - } - _ => { - let tok_end = self.get_pos(); - return Some(Ok((tok_start, Tok::Amper, tok_end))); - } + if let Some('=') = self.chr0 { + self.next_char(); + let tok_end = self.get_pos(); + return Some(Ok((tok_start, Tok::AmperEqual, tok_end))); + } else { + let tok_end = self.get_pos(); + return Some(Ok((tok_start, Tok::Amper, tok_end))); } } Some('-') => { @@ -872,16 +857,13 @@ where Some('@') => { let tok_start = self.get_pos(); self.next_char(); - match self.chr0 { - Some('=') => { - self.next_char(); - let tok_end = self.get_pos(); - return Some(Ok((tok_start, Tok::AtEqual, tok_end))); - } - _ => { - let tok_end = self.get_pos(); - return Some(Ok((tok_start, Tok::At, tok_end))); - } + if let Some('=') = self.chr0 { + self.next_char(); + let tok_end = self.get_pos(); + return Some(Ok((tok_start, Tok::AtEqual, tok_end))); + } else { + let tok_end = self.get_pos(); + return Some(Ok((tok_start, Tok::At, tok_end))); } } Some('!') => {