diff --git a/parser/src/python.lalrpop b/parser/src/python.lalrpop index d83c01a3e..644ef6f1c 100644 --- a/parser/src/python.lalrpop +++ b/parser/src/python.lalrpop @@ -252,7 +252,7 @@ ForStatement: ast::LocatedStatement = { }; TryStatement: ast::LocatedStatement = { - "try" ":" => { + "try" ":" => { let or_else = match else_suite { Some(s) => Some(s.2), None => None, diff --git a/tests/snippets/try_exceptions.py b/tests/snippets/try_exceptions.py index e9696d87a..c865c0afa 100644 --- a/tests/snippets/try_exceptions.py +++ b/tests/snippets/try_exceptions.py @@ -7,3 +7,68 @@ except BaseException as ex: print(type(ex)) # print(ex.__traceback__) # print(type(ex.__traceback__)) + + +l = [] +try: + l.append(1) + assert 0 + l.append(2) +except: + l.append(3) + print('boom') +finally: + l.append(4) + print('kablam') +assert l == [1, 3, 4] + + +l = [] +try: + l.append(1) + assert 0 + l.append(2) +except AssertionError as ex: + l.append(3) + print('boom', type(ex)) +finally: + l.append(4) + print('kablam') +assert l == [1, 3, 4] + +l = [] +try: + l.append(1) + assert 1 + l.append(2) +except AssertionError as ex: + l.append(3) + print('boom', type(ex)) +finally: + l.append(4) + print('kablam') +assert l == [1, 2, 4] + +l = [] +try: + try: + l.append(1) + assert 0 + l.append(2) + finally: + l.append(3) + print('kablam') +except AssertionError as ex: + l.append(4) + print('boom', type(ex)) +assert l == [1, 3, 4] + +l = [] +try: + l.append(1) + fubar + l.append(2) +except NameError as ex: + l.append(3) + print('boom', type(ex)) +assert l == [1, 3] diff --git a/vm/src/builtins.rs b/vm/src/builtins.rs index 7a41b828c..ab6b4bd01 100644 --- a/vm/src/builtins.rs +++ b/vm/src/builtins.rs @@ -354,10 +354,39 @@ pub fn make_module(ctx: &PyContext) -> PyObjectRef { dict.insert(String::from("tuple"), ctx.tuple_type.clone()); dict.insert(String::from("type"), ctx.type_type.clone()); dict.insert(String::from("object"), ctx.object.clone()); + + // Exceptions: dict.insert( String::from("BaseException"), - ctx.base_exception_type.clone(), + ctx.exceptions.base_exception_type.clone(), ); + dict.insert( + String::from("Exception"), + ctx.exceptions.exception_type.clone(), + ); + dict.insert( + String::from("AssertionError"), + ctx.exceptions.assertion_error.clone(), + ); + dict.insert( + String::from("AttributeError"), + ctx.exceptions.attribute_error.clone(), + ); + dict.insert(String::from("NameError"), ctx.exceptions.name_error.clone()); + dict.insert( + String::from("RuntimeError"), + ctx.exceptions.runtime_error.clone(), + ); + dict.insert( + String::from("NotImplementedError"), + ctx.exceptions.not_implemented_error.clone(), + ); + dict.insert(String::from("TypeError"), ctx.exceptions.type_error.clone()); + dict.insert( + String::from("ValueError"), + ctx.exceptions.value_error.clone(), + ); + let d2 = PyObject::new(PyObjectKind::Dict { elements: dict }, ctx.type_type.clone()); let scope = PyObject::new( PyObjectKind::Scope { diff --git a/vm/src/compile.rs b/vm/src/compile.rs index 2a5a74597..5eeccc1f3 100644 --- a/vm/src/compile.rs +++ b/vm/src/compile.rs @@ -263,25 +263,44 @@ impl Compiler { // except handlers: self.set_label(handler_label); + // Exception is on top of stack now handler_label = self.new_label(); for handler in handlers { - // Check if this handler can handle the exception: + // If we gave a typ, + // check if this handler can handle the exception: + if let Some(exc_type) = &handler.typ { + // Duplicate exception for test: + self.emit(Instruction::Duplicate); - // TODO: self.emit(isinstance()) start of hack - self.emit(Instruction::LoadConst { - value: bytecode::Constant::Boolean { value: false }, - }); - // End of hack - self.emit(Instruction::JumpIf { - target: handler_label, - }); - - // We have a match - if let Some(alias) = &handler.name { - self.emit(Instruction::StoreName { - name: alias.clone(), + // Check exception type: + self.emit(Instruction::LoadName { + name: String::from("isinstance"), }); + self.emit(Instruction::Rotate { amount: 2 }); + self.compile_expression(exc_type); + self.emit(Instruction::CallFunction { count: 2 }); + + // We cannot handle this exception type: + self.emit(Instruction::JumpIfFalse { + target: handler_label, + }); + + // We have a match, store in name (except x as y) + if let Some(alias) = &handler.name { + self.emit(Instruction::StoreName { + name: alias.clone(), + }); + } else { + // Drop exception from top of stack: + self.emit(Instruction::Pop); + } + } else { + // Catch all! + // Drop exception from top of stack: + self.emit(Instruction::Pop); } + + // Handler code: self.compile_statements(&handler.body); self.emit(Instruction::Jump { target: finally_label, @@ -294,6 +313,16 @@ impl Compiler { self.emit(Instruction::Jump { target: handler_label, }); + self.set_label(handler_label); + // If code flows here, we have an unhandled exception, + // emit finally code and raise again! + // Duplicate finally code here: + // TODO: this bytecode is now duplicate, could this be + // improved? + if let Some(statements) = finalbody { + self.compile_statements(statements); + } + self.emit(Instruction::Raise { argc: 1 }); // We successfully ran the try block: // else: @@ -401,6 +430,7 @@ impl Compiler { self.emit(Instruction::CallFunction { count: 0 }); } } + self.emit(Instruction::Raise { argc: 1 }); self.set_label(end_label); } ast::Statement::Break => { diff --git a/vm/src/exceptions.rs b/vm/src/exceptions.rs index c677a8837..442585828 100644 --- a/vm/src/exceptions.rs +++ b/vm/src/exceptions.rs @@ -1,12 +1,99 @@ -use super::pyobject::{AttributeProtocol, PyContext, PyFuncArgs, PyResult}; +use super::pyobject::{ + create_type, AttributeProtocol, PyContext, PyFuncArgs, PyObjectRef, PyResult, +}; use super::vm::VirtualMachine; fn exception_init(vm: &mut VirtualMachine, _args: PyFuncArgs) -> PyResult { Ok(vm.get_none()) } +#[derive(Debug)] +pub struct ExceptionZoo { + pub base_exception_type: PyObjectRef, + pub exception_type: PyObjectRef, + pub assertion_error: PyObjectRef, + pub attribute_error: PyObjectRef, + pub name_error: PyObjectRef, + pub runtime_error: PyObjectRef, + pub not_implemented_error: PyObjectRef, + pub type_error: PyObjectRef, + pub value_error: PyObjectRef, +} + +impl ExceptionZoo { + pub fn new( + type_type: &PyObjectRef, + object_type: &PyObjectRef, + dict_type: &PyObjectRef, + ) -> Self { + let base_exception_type = + create_type("BaseException", &type_type, &object_type, &dict_type); + + let exception_type = create_type( + &String::from("Exception"), + &type_type, + &base_exception_type, + &dict_type, + ); + let assertion_error = create_type( + &String::from("AssertionError"), + &type_type, + &exception_type, + &dict_type, + ); + let attribute_error = create_type( + &String::from("AttributeError"), + &type_type, + &exception_type.clone(), + &dict_type, + ); + let name_error = create_type( + &String::from("NameError"), + &type_type, + &exception_type.clone(), + &dict_type, + ); + let runtime_error = create_type( + &String::from("RuntimeError"), + &type_type, + &exception_type, + &dict_type, + ); + let not_implemented_error = create_type( + &String::from("NotImplementedError"), + &type_type, + &runtime_error, + &dict_type, + ); + let type_error = create_type( + &String::from("TypeError"), + &type_type, + &exception_type, + &dict_type, + ); + let value_error = create_type( + &String::from("ValueError"), + &type_type, + &exception_type, + &dict_type, + ); + + ExceptionZoo { + base_exception_type: base_exception_type, + exception_type: exception_type, + assertion_error: assertion_error, + attribute_error: attribute_error, + name_error: name_error, + runtime_error: runtime_error, + not_implemented_error: not_implemented_error, + type_error: type_error, + value_error: value_error, + } + } +} + pub fn init(context: &PyContext) { - let ref base_exception_type = context.base_exception_type; + let ref base_exception_type = context.exceptions.base_exception_type; base_exception_type.set_attr("__init__", context.new_rustfunc(exception_init)); // TODO: create a whole exception hierarchy somehow? diff --git a/vm/src/lib.rs b/vm/src/lib.rs index a9cc00dd1..81512a7b5 100644 --- a/vm/src/lib.rs +++ b/vm/src/lib.rs @@ -23,6 +23,7 @@ mod objstr; mod objtype; pub mod pyobject; mod sysmodule; +mod traceback; mod vm; // pub use self::pyobject::Executor; diff --git a/vm/src/pyobject.rs b/vm/src/pyobject.rs index b958c341d..18af9b8b3 100644 --- a/vm/src/pyobject.rs +++ b/vm/src/pyobject.rs @@ -60,7 +60,7 @@ pub struct PyContext { pub bound_method_type: PyObjectRef, pub member_descriptor_type: PyObjectRef, pub object: PyObjectRef, - pub base_exception_type: PyObjectRef, + pub exceptions: exceptions::ExceptionZoo, } /* @@ -80,7 +80,7 @@ fn _nothing() -> PyObjectRef { }.into_ref() } -fn create_type( +pub fn create_type( name: &str, type_type: &PyObjectRef, object: &PyObjectRef, @@ -97,7 +97,7 @@ fn create_type( // Basic objects: impl PyContext { - pub fn new() -> PyContext { + pub fn new() -> Self { let type_type = _nothing(); let object_type = _nothing(); let dict_type = _nothing(); @@ -116,9 +116,7 @@ impl PyContext { let float_type = create_type("float", &type_type, &object_type, &dict_type); let tuple_type = create_type("tuple", &type_type, &object_type, &dict_type); let bool_type = create_type("bool", &type_type, &object_type, &dict_type); - - let base_exception_type = - create_type("BaseException", &type_type, &object_type, &dict_type); + let exceptions = exceptions::ExceptionZoo::new(&type_type, &object_type, &dict_type); let none = PyObject::new( PyObjectKind::None, @@ -139,7 +137,7 @@ impl PyContext { bound_method_type: bound_method_type, member_descriptor_type: member_descriptor_type, type_type: type_type, - base_exception_type: base_exception_type, + exceptions: exceptions, }; objtype::init(&context); objlist::init(&context); @@ -194,6 +192,10 @@ impl PyContext { ) } + pub fn new_class(&self, name: &String, base: PyObjectRef) -> PyObjectRef { + objtype::new(self.type_type.clone(), name, vec![base], self.new_dict()).unwrap() + } + pub fn new_scope(&self, parent: Option) -> PyObjectRef { let locals = self.new_dict(); let scope = Scope { diff --git a/vm/src/traceback.rs b/vm/src/traceback.rs new file mode 100644 index 000000000..9cb95948d --- /dev/null +++ b/vm/src/traceback.rs @@ -0,0 +1,2 @@ +// python tracebacks +// diff --git a/vm/src/vm.rs b/vm/src/vm.rs index 659541046..781cb3315 100644 --- a/vm/src/vm.rs +++ b/vm/src/vm.rs @@ -204,11 +204,9 @@ impl VirtualMachine { } else if scope.has_parent() { scope = scope.get_parent(); } else { - let name_error = PyObject::new( - PyObjectKind::NameError { - name: name.to_string(), - }, - self.get_type(), + let name_error = self.context().new_instance( + self.context().new_dict(), + self.context().exceptions.name_error.clone(), ); break Some(Err(name_error)); } @@ -583,6 +581,36 @@ impl VirtualMachine { self.pop_value(); None } + bytecode::Instruction::Duplicate => { + // Duplicate top of stack + let value = self.pop_value(); + self.push_value(value.clone()); + self.push_value(value); + None + } + bytecode::Instruction::Rotate { amount } => { + // Shuffles top of stack amount down + if amount < &2 { + panic!("Can only rotate two or more values"); + } + + let mut values = Vec::new(); + + // Pop all values from stack: + for _ in 0..*amount { + values.push(self.pop_value()); + } + + // Push top of stack back first: + self.push_value(values.remove(0)); + + // Push other value back in order: + values.reverse(); + for value in values { + self.push_value(value); + } + None + } bytecode::Instruction::BuildList { size } => { let elements = self.pop_multiple(*size); let list_obj = self.context().new_list(elements); @@ -772,8 +800,23 @@ impl VirtualMachine { 0 | 2 | 3 => panic!("Not implemented!"), _ => panic!("Invalid paramter for RAISE_VARARGS, must be between 0 to 3"), }; - info!("Exception raised: {:?}", exception); - Some(Err(exception)) + if self.isinstance( + exception.clone(), + self.context().exceptions.base_exception_type.clone(), + ) { + info!("Exception raised: {:?}", exception); + Some(Err(exception)) + } else { + Some(Err(exception)) + // TODO: enable this when isinstance works properly: + /* + info!( + "Can only raise BaseException derived types: {:?}", + exception + ); + let type_error = self.context().exceptions.type_error.clone(); + Some(Err(type_error)) + */ } } bytecode::Instruction::Break => { @@ -832,7 +875,6 @@ impl VirtualMachine { } None } - _ => panic!("NOT IMPL {:?}", instruction), } }