diff --git a/parser/src/lexer.rs b/parser/src/lexer.rs index dbde9b56b..da063fffc 100644 --- a/parser/src/lexer.rs +++ b/parser/src/lexer.rs @@ -197,6 +197,9 @@ where // Skip backslash and newline self.shift(); self.shift(); + // Idea: insert trailing newline here: + // } else if self.chr0 != Some('\n') && self.chr1.is_none() { + // self.chr1 = Some('\n'); } else { break; } diff --git a/tests/tobefixed/builtin_max.py b/tests/snippets/builtin_max.py similarity index 100% rename from tests/tobefixed/builtin_max.py rename to tests/snippets/builtin_max.py diff --git a/tests/snippets/builtins.py b/tests/snippets/builtins.py index 5033cb633..bbf116abc 100644 --- a/tests/snippets/builtins.py +++ b/tests/snippets/builtins.py @@ -17,3 +17,7 @@ assert list(zip(['a', 'b', 'c'], range(3), [9, 8, 7, 99])) == [('a', 0, 9), ('b' assert list(filter(lambda x: ((x % 2) == 0), [0, 1, 2])) == [0, 2] +assert 3 == eval('1+2') + +code = compile('5+3', 'x.py', 'eval') +assert eval(code) == 8 diff --git a/vm/src/builtins.rs b/vm/src/builtins.rs index aeedff400..845647033 100644 --- a/vm/src/builtins.rs +++ b/vm/src/builtins.rs @@ -132,6 +132,8 @@ fn builtin_compile(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { ] ); let source = objstr::get_value(source); + // TODO: fix this newline bug: + let source = format!("{}\n", source); let mode = { let mode = objstr::get_value(mode); @@ -206,19 +208,36 @@ fn builtin_eval(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!( vm, args, - required = [ - (source, None), // TODO: Use more specific type + required = [(source, None)], + optional = [ (_globals, Some(vm.ctx.dict_type())), (locals, Some(vm.ctx.dict_type())) ] ); - // TODO: handle optional global and locals - let code_obj = source; // if source.borrow().kind + // Determine code object: + let code_obj = if objtype::isinstance(source, &vm.ctx.code_type()) { + source.clone() + } else if objtype::isinstance(source, &vm.ctx.str_type()) { + let mode = compile::Mode::Eval; + let source = objstr::get_value(source); + // TODO: fix this newline bug: + let source = format!("{}\n", source); + compile::compile(vm, &source, mode, None)? + } else { + return Err(vm.new_type_error("code argument must be str or code object".to_string())); + }; + let locals = if let Some(locals) = locals { + locals.clone() + } else { + vm.new_dict() + }; + + // TODO: handle optional globals // Construct new scope: let scope_inner = Scope { - locals: locals.clone(), + locals: locals, parent: None, }; let scope = PyObject { @@ -235,26 +254,38 @@ fn builtin_exec(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!( vm, args, - required = [ - (source, None), + required = [(source, None)], + optional = [ (_globals, Some(vm.ctx.dict_type())), (locals, Some(vm.ctx.dict_type())) ] ); - // TODO: handle optional global and locals // Determine code object: let code_obj = if objtype::isinstance(source, &vm.ctx.str_type()) { let mode = compile::Mode::Exec; let source = objstr::get_value(source); + // TODO: fix this newline bug: + let source = format!("{}\n", source); compile::compile(vm, &source, mode, None)? - } else { + } else if objtype::isinstance(source, &vm.ctx.code_type()) { source.clone() + } else { + return Err(vm.new_type_error("source argument must be str or code object".to_string())); }; + // handle optional global and locals + let locals = if let Some(locals) = locals { + locals.clone() + } else { + vm.new_dict() + }; + + // TODO: use globals + // Construct new scope: let scope_inner = Scope { - locals: locals.clone(), + locals: locals, parent: None, }; let scope = PyObject { @@ -416,15 +447,56 @@ fn builtin_map(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { } fn builtin_max(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(x, None), (y, None)]); + // arg_check!(vm, args, required = [(x, None), (y, None)]); - let order = vm.call_method(x, "__gt__", vec![y.clone()])?; - - if objbool::get_value(&order) { - Ok(x.clone()) + let candidates = if args.args.len() > 1 { + args.args.clone() + } else if args.args.len() == 1 { + vm.extract_elements(&args.args[0])? } else { - Ok(y.clone()) + // zero arguments means type error: + return Err(vm.new_type_error("Expected 1 or more arguments".to_string())); + }; + + if candidates.len() == 0 { + let default = args.get_optional_kwarg("default"); + if default.is_none() { + return Err(vm.new_value_error("max() arg is an empty sequence".to_string())); + } else { + return Ok(default.unwrap()); + } } + + let key_func = args.get_optional_kwarg("key"); + + // Start with first assumption: + let mut candidates_iter = candidates.into_iter(); + let mut x = candidates_iter.next().unwrap(); + // TODO: this key function looks pretty duplicate. Maybe we can create + // a local function? + let mut x_key = if let Some(f) = &key_func { + let args = PyFuncArgs::new(vec![x.clone()], vec![]); + vm.invoke(f.clone(), args)? + } else { + x.clone() + }; + + for y in candidates_iter { + let y_key = if let Some(f) = &key_func { + let args = PyFuncArgs::new(vec![y.clone()], vec![]); + vm.invoke(f.clone(), args)? + } else { + y.clone() + }; + let order = vm.call_method(&x_key, "__gt__", vec![y_key.clone()])?; + + if !objbool::get_value(&order) { + x = y.clone(); + x_key = y_key; + } + } + + Ok(x) } // builtin_memoryview diff --git a/vm/src/obj/objcode.rs b/vm/src/obj/objcode.rs index 1776c5a3b..53b73d09c 100644 --- a/vm/src/obj/objcode.rs +++ b/vm/src/obj/objcode.rs @@ -11,6 +11,7 @@ use super::objtype; pub fn init(context: &PyContext) { let ref code_type = context.code_type; + code_type.set_attr("__new__", context.new_rustfunc(code_new)); code_type.set_attr("__repr__", context.new_rustfunc(code_repr)); } @@ -24,6 +25,11 @@ pub fn copy_code(code_obj: &PyObjectRef) -> bytecode::CodeObject { } } +fn code_new(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!(vm, args, required = [(_cls, None)]); + Err(vm.new_type_error(format!("Cannot directly create code object"))) +} + fn code_repr(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(o, Some(vm.ctx.code_type()))]); diff --git a/vm/src/obj/objdict.rs b/vm/src/obj/objdict.rs index 0ca4baea9..80440e9d7 100644 --- a/vm/src/obj/objdict.rs +++ b/vm/src/obj/objdict.rs @@ -109,6 +109,27 @@ fn dict_delitem(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { } } +/// When iterating over a dictionary, we iterate over the keys of it. +fn dict_iter(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!(vm, args, required = [(dict, Some(vm.ctx.dict_type()))]); + + let keys = get_elements(dict) + .keys() + .map(|k| vm.ctx.new_str(k.to_string())) + .collect(); + let key_list = vm.ctx.new_list(keys); + + let iter_obj = PyObject::new( + PyObjectKind::Iterator { + position: 0, + iterated_obj: key_list, + }, + vm.ctx.iter_type(), + ); + + Ok(iter_obj) +} + fn dict_setitem(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!( vm, @@ -165,6 +186,7 @@ pub fn init(context: &PyContext) { dict_type.set_attr("__contains__", context.new_rustfunc(dict_contains)); dict_type.set_attr("__delitem__", context.new_rustfunc(dict_delitem)); dict_type.set_attr("__getitem__", context.new_rustfunc(dict_getitem)); + dict_type.set_attr("__iter__", context.new_rustfunc(dict_iter)); dict_type.set_attr("__new__", context.new_rustfunc(dict_new)); dict_type.set_attr("__repr__", context.new_rustfunc(dict_repr)); dict_type.set_attr("__setitem__", context.new_rustfunc(dict_setitem)); diff --git a/vm/src/obj/objframe.rs b/vm/src/obj/objframe.rs index 04c309613..511888b31 100644 --- a/vm/src/obj/objframe.rs +++ b/vm/src/obj/objframe.rs @@ -10,9 +10,15 @@ use super::objtype; pub fn init(context: &PyContext) { let ref frame_type = context.frame_type; + frame_type.set_attr("__new__", context.new_rustfunc(frame_new)); frame_type.set_attr("__repr__", context.new_rustfunc(frame_repr)); } +fn frame_new(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!(vm, args, required = [(_cls, None)]); + Err(vm.new_type_error(format!("Cannot directly create frame object"))) +} + fn frame_repr(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(_frame, Some(vm.ctx.frame_type()))]); let repr = format!(""); diff --git a/vm/src/obj/objiter.rs b/vm/src/obj/objiter.rs index 1e22a26e7..796d42282 100644 --- a/vm/src/obj/objiter.rs +++ b/vm/src/obj/objiter.rs @@ -3,8 +3,7 @@ */ use super::super::pyobject::{ - AttributeProtocol, PyContext, PyFuncArgs, PyObject, PyObjectKind, PyObjectRef, PyResult, - TypeProtocol, + AttributeProtocol, PyContext, PyFuncArgs, PyObjectKind, PyObjectRef, PyResult, TypeProtocol, }; use super::super::vm::VirtualMachine; use super::objbool; @@ -17,30 +16,10 @@ use super::objtype; // Required for arg_check! to use isinstance * function 'iter' is called. */ pub fn get_iter(vm: &mut VirtualMachine, iter_target: &PyObjectRef) -> PyResult { - // Check what we are going to iterate over: - let iterated_obj = if objtype::isinstance(iter_target, &vm.ctx.iter_type()) { - // If object is already an iterator, return that one. - return Ok(iter_target.clone()); - } else if objtype::isinstance(iter_target, &vm.ctx.list_type()) { - iter_target.clone() - // } else if hasattr(iter_target, "__iter__") { - } else { - return vm.call_method(iter_target, "__iter__", vec![]); - // let type_str = objstr::get_value(&vm.to_str(iter_target.typ()).unwrap()); - // let type_error = vm.new_type_error(format!("Cannot iterate over {}", type_str)); - // return Err(type_error); - }; - - let iter_obj = PyObject::new( - PyObjectKind::Iterator { - position: 0, - iterated_obj: iterated_obj, - }, - vm.ctx.iter_type(), - ); - - // We are all good here: - Ok(iter_obj) + vm.call_method(iter_target, "__iter__", vec![]) + // let type_str = objstr::get_value(&vm.to_str(iter_target.typ()).unwrap()); + // let type_error = vm.new_type_error(format!("Cannot iterate over {}", type_str)); + // return Err(type_error); } /* diff --git a/vm/src/obj/objlist.rs b/vm/src/obj/objlist.rs index 16f665fd3..6f1fd0908 100644 --- a/vm/src/obj/objlist.rs +++ b/vm/src/obj/objlist.rs @@ -183,6 +183,21 @@ fn list_getitem(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { get_item(vm, list, &get_elements(list), needle.clone()) } +fn list_iter(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!(vm, args, required = [(list, Some(vm.ctx.list_type()))]); + + let iter_obj = PyObject::new( + PyObjectKind::Iterator { + position: 0, + iterated_obj: list.clone(), + }, + vm.ctx.iter_type(), + ); + + // We are all good here: + Ok(iter_obj) +} + fn list_setitem(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!( vm, @@ -199,6 +214,7 @@ pub fn init(context: &PyContext) { list_type.set_attr("__contains__", context.new_rustfunc(list_contains)); list_type.set_attr("__eq__", context.new_rustfunc(list_eq)); list_type.set_attr("__getitem__", context.new_rustfunc(list_getitem)); + list_type.set_attr("__iter__", context.new_rustfunc(list_iter)); list_type.set_attr("__setitem__", context.new_rustfunc(list_setitem)); list_type.set_attr("__len__", context.new_rustfunc(list_len)); list_type.set_attr("__new__", context.new_rustfunc(list_new)); diff --git a/vm/src/obj/objstr.rs b/vm/src/obj/objstr.rs index b31648b3f..7a6c7e780 100644 --- a/vm/src/obj/objstr.rs +++ b/vm/src/obj/objstr.rs @@ -14,6 +14,7 @@ pub fn init(context: &PyContext) { str_type.set_attr("__eq__", context.new_rustfunc(str_eq)); str_type.set_attr("__contains__", context.new_rustfunc(str_contains)); str_type.set_attr("__getitem__", context.new_rustfunc(str_getitem)); + str_type.set_attr("__gt__", context.new_rustfunc(str_gt)); str_type.set_attr("__len__", context.new_rustfunc(str_len)); str_type.set_attr("__mul__", context.new_rustfunc(str_mul)); str_type.set_attr("__new__", context.new_rustfunc(str_new)); @@ -53,6 +54,21 @@ fn str_eq(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { Ok(vm.ctx.new_bool(result)) } +fn str_gt(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!( + vm, + args, + required = [ + (zelf, Some(vm.ctx.str_type())), + (other, Some(vm.ctx.str_type())) + ] + ); + let zelf = get_value(zelf); + let other = get_value(other); + let result = zelf > other; + Ok(vm.ctx.new_bool(result)) +} + fn str_str(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(s, Some(vm.ctx.str_type()))]); Ok(s.clone()) diff --git a/vm/src/obj/objtuple.rs b/vm/src/obj/objtuple.rs index 1d1a8bff9..9a7b64395 100644 --- a/vm/src/obj/objtuple.rs +++ b/vm/src/obj/objtuple.rs @@ -48,6 +48,20 @@ fn tuple_hash(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { Ok(vm.ctx.new_int(x.to_bigint().unwrap())) } +fn tuple_iter(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!(vm, args, required = [(tuple, Some(vm.ctx.tuple_type()))]); + + let iter_obj = PyObject::new( + PyObjectKind::Iterator { + position: 0, + iterated_obj: tuple.clone(), + }, + vm.ctx.iter_type(), + ); + + Ok(iter_obj) +} + fn tuple_len(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(zelf, Some(vm.ctx.tuple_type()))]); let elements = get_elements(zelf); @@ -132,6 +146,7 @@ pub fn init(context: &PyContext) { tuple_type.set_attr("__contains__", context.new_rustfunc(tuple_contains)); tuple_type.set_attr("__getitem__", context.new_rustfunc(tuple_getitem)); tuple_type.set_attr("__hash__", context.new_rustfunc(tuple_hash)); + tuple_type.set_attr("__iter__", context.new_rustfunc(tuple_iter)); tuple_type.set_attr("__len__", context.new_rustfunc(tuple_len)); tuple_type.set_attr("__new__", context.new_rustfunc(tuple_new)); tuple_type.set_attr("__repr__", context.new_rustfunc(tuple_repr)); diff --git a/vm/src/pyobject.rs b/vm/src/pyobject.rs index b65757714..bfdd4d668 100644 --- a/vm/src/pyobject.rs +++ b/vm/src/pyobject.rs @@ -728,6 +728,15 @@ impl PyFuncArgs { } default.clone() } + + pub fn get_optional_kwarg(&self, key: &str) -> Option { + for (arg_name, arg_value) in self.kwargs.iter() { + if arg_name == key { + return Some(arg_value.clone()); + } + } + None + } } type RustPyFunc = fn(vm: &mut VirtualMachine, PyFuncArgs) -> PyResult; diff --git a/vm/src/vm.rs b/vm/src/vm.rs index 41946a2de..9435db199 100644 --- a/vm/src/vm.rs +++ b/vm/src/vm.rs @@ -35,6 +35,7 @@ pub struct VirtualMachine { pub sys_module: PyObjectRef, pub stdlib_inits: HashMap, pub ctx: PyContext, + pub current_frame: Option, } impl VirtualMachine { @@ -51,6 +52,7 @@ impl VirtualMachine { sys_module: sysmod, stdlib_inits, ctx: ctx, + current_frame: None, } }