From 402230684f9e37c868ba27f6e1b74945acf73da9 Mon Sep 17 00:00:00 2001 From: Adam Kelly Date: Sun, 5 Aug 2018 19:18:57 +0100 Subject: [PATCH 1/6] Add support for parsing classes to ast. --- parser/src/ast.rs | 1 + parser/src/parser.rs | 18 ++++++++++++++++++ parser/src/python.lalrpop | 4 +++- 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/parser/src/ast.rs b/parser/src/ast.rs index b4757271e..591f0fa7b 100644 --- a/parser/src/ast.rs +++ b/parser/src/ast.rs @@ -72,6 +72,7 @@ pub enum Statement { }, ClassDef { name: String, + body: Vec, // TODO: docstring: String, }, FunctionDef { diff --git a/parser/src/parser.rs b/parser/src/parser.rs index 68e45717e..39dc0919a 100644 --- a/parser/src/parser.rs +++ b/parser/src/parser.rs @@ -190,4 +190,22 @@ mod tests { }) ) } + + #[test] + fn test_parse_class() { + let source = String::from("class Foo:\n def __init__(self):\n pass\n"); + assert_eq!( + parse_statement(&source), + Ok(ast::Statement::ClassDef { + name: String::from("Foo"), + body: vec![ + ast::Statement::FunctionDef { + name: String::from("__init__"), + args: vec![String::from("self")], + body: vec![ast::Statement::Pass], + } + ], + }) + ) + } } diff --git a/parser/src/python.lalrpop b/parser/src/python.lalrpop index fd4d7bdc5..f1602bdf1 100644 --- a/parser/src/python.lalrpop +++ b/parser/src/python.lalrpop @@ -178,7 +178,9 @@ TypedArgsList: Vec = { }; ClassDef: ast::Statement = { - "class" <_a:("(" ")")?> ":" <_s:Suite> => ast::Statement::ClassDef { name: n }, + "class" <_a:("(" ")")?> ":" => ast::Statement::ClassDef { + name: n, + body: s}, }; Test: ast::Expression = { From ccfbe6c4fffe8e7da3f4a8104bf2ff2a4188a8c6 Mon Sep 17 00:00:00 2001 From: Adam Kelly Date: Tue, 7 Aug 2018 21:28:15 +0100 Subject: [PATCH 2/6] Ability to define classes. --- vm/src/builtins.rs | 12 +++++++++++ vm/src/bytecode.rs | 2 ++ vm/src/compile.rs | 37 ++++++++++++++++++++++++++++++++-- vm/src/objint.rs | 2 +- vm/src/objtype.rs | 5 +---- vm/src/pyobject.rs | 16 ++++++++++----- vm/src/vm.rs | 50 +++++++++++++++++++++++++++++++++++++++++----- 7 files changed, 107 insertions(+), 17 deletions(-) diff --git a/vm/src/builtins.rs b/vm/src/builtins.rs index d509d3d68..3d6a6b44e 100644 --- a/vm/src/builtins.rs +++ b/vm/src/builtins.rs @@ -269,3 +269,15 @@ pub fn make_module(ctx: &PyContext) -> PyObjectRef { obj } +pub fn builtin_build_class_(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { + let function = args.args[0].clone(); + let a1 = &*args.args[1].borrow(); + let name = match &a1.kind { + PyObjectKind::String { value: name } => name, + _ => panic!("Class name must be a string."), + }; + + let new_dict = vm.new_dict(); + &vm.invoke(function, PyFuncArgs { args: vec![ new_dict.clone() ] }); + Ok(vm.new_class(name.to_string(), new_dict)) +} diff --git a/vm/src/bytecode.rs b/vm/src/bytecode.rs index 906e29e26..96056b699 100644 --- a/vm/src/bytecode.rs +++ b/vm/src/bytecode.rs @@ -64,6 +64,8 @@ pub enum Instruction { BuildMap { size: usize }, BuildSlice { size: usize }, PrintExpr, + LoadBuildClass, + StoreLocals, } #[derive(Debug, Clone)] diff --git a/vm/src/compile.rs b/vm/src/compile.rs index 9162f9fab..faa3cacf0 100644 --- a/vm/src/compile.rs +++ b/vm/src/compile.rs @@ -229,8 +229,41 @@ impl Compiler { name: name.to_string(), }); } - ast::Statement::ClassDef { name } => { - // TODO? + ast::Statement::ClassDef { name, body } => { + self.emit(Instruction::LoadBuildClass); + self.code_object_stack.push( + CodeObject::new(vec![String::from("__locals__")])); + self.emit(Instruction::LoadName { + name: String::from("__locals__")}); + self.emit(Instruction::StoreLocals); + self.compile_statements(body); + self.emit(Instruction::LoadConst { + value: bytecode::Constant::None, + }); + self.emit(Instruction::ReturnValue); + + let code = self.code_object_stack.pop().unwrap(); + self.emit(Instruction::LoadConst { + value: bytecode::Constant::Code { code: code }, + }); + self.emit(Instruction::LoadConst { + value: bytecode::Constant::String { + value: name.clone(), + }, + }); + // Turn code object into function object: + self.emit(Instruction::MakeFunction); + + self.emit(Instruction::LoadConst { + value: bytecode::Constant::String { + value: name.clone(), + }, + }); + self.emit(Instruction::CallFunction { count: 2 }); + + self.emit(Instruction::StoreName { + name: name.to_string(), + }); } ast::Statement::Assert { test, msg } => { // TODO: if some flag, ignore all assert statements! diff --git a/vm/src/objint.rs b/vm/src/objint.rs index d15f0fe65..c818ad74e 100644 --- a/vm/src/objint.rs +++ b/vm/src/objint.rs @@ -26,7 +26,7 @@ pub fn create_type(type_type: PyObjectRef) -> PyObjectRef { let typ = PyObject::new( PyObjectKind::Class { name: "int".to_string(), - // dict: PyObject::new(PyObjectKind::Dict { elements: dict }, type_type.clone() ), + dict: PyObject::new(PyObjectKind::Dict { elements: dict }, type_type.clone() ), }, type_type.clone(), ); diff --git a/vm/src/objtype.rs b/vm/src/objtype.rs index bf4792e6c..07d624ba1 100644 --- a/vm/src/objtype.rs +++ b/vm/src/objtype.rs @@ -9,10 +9,7 @@ use super::pyobject::{PyObject, PyObjectKind, PyObjectRef}; pub fn create_type() -> PyObjectRef { let typ = PyObject::default().into_ref(); - (*typ.borrow_mut()).kind = PyObjectKind::Class { - name: "type".to_string(), - // dict: dict, - }; + (*typ.borrow_mut()).kind = PyObjectKind::Type; (*typ.borrow_mut()).typ = Some(typ.clone()); // typ.borrow_mut().dict.insert("__str__".to_string(), PyObject::new(PyObjectKind::RustFunction { function: str })); typ diff --git a/vm/src/pyobject.rs b/vm/src/pyobject.rs index 199a4b988..5f6fa081e 100644 --- a/vm/src/pyobject.rs +++ b/vm/src/pyobject.rs @@ -141,8 +141,11 @@ impl PyContext { ) } - pub fn new_class(&self, name: String) -> PyObjectRef { - PyObject::new(PyObjectKind::Class { name: name }, self.type_type.clone()) + pub fn new_class(&self, name: String, namespace: PyObjectRef) -> PyObjectRef { + PyObject::new(PyObjectKind::Class { + name: name, + dict: namespace.clone() + }, self.type_type.clone()) } /* TODO: something like this? @@ -312,11 +315,12 @@ pub enum PyObjectKind { None, Class { name: String, - // dict: PyObjectRef, + dict: PyObjectRef, }, RustFunction { function: RustPyFunc, }, + Type } impl fmt::Debug for PyObjectKind { @@ -337,8 +341,9 @@ impl fmt::Debug for PyObjectKind { &PyObjectKind::Module { name: _, dict: _ } => write!(f, "module"), &PyObjectKind::Scope { scope: _ } => write!(f, "scope"), &PyObjectKind::None => write!(f, "None"), - &PyObjectKind::Class { name: _ } => write!(f, "class"), + &PyObjectKind::Class { name: _, dict: _ } => write!(f, "class"), &PyObjectKind::RustFunction { function: _ } => write!(f, "rust function"), + &PyObjectKind::Type => write!(f, "type"), } } } @@ -396,7 +401,8 @@ impl PyObject { .join(", ") ), PyObjectKind::None => String::from("None"), - PyObjectKind::Class { ref name } => format!("", name), + PyObjectKind::Class { ref name, dict: ref _dict } => + format!("", name), PyObjectKind::Code { code: _ } => format!(""), PyObjectKind::Function { code: _, scope: _ } => format!(""), PyObjectKind::RustFunction { function: _ } => format!(""), diff --git a/vm/src/vm.rs b/vm/src/vm.rs index f55a5f08b..5ac73c3c6 100644 --- a/vm/src/vm.rs +++ b/vm/src/vm.rs @@ -14,11 +14,19 @@ use super::frame::{Block, Frame, copy_code}; use super::import::import; use super::objlist; use super::objstr; +use super::objtype; use super::pyobject::{ - DictProtocol, IdProtocol, ParentProtocol, PyContext, PyFuncArgs, PyObject, PyObjectKind, - PyObjectRef, PyResult, + DictProtocol, + IdProtocol, + ParentProtocol, + PyContext, + PyFuncArgs, + PyObject, + PyObjectKind, + PyObjectRef, + PyResult, + Scope, }; - use super::sysmodule; // use objects::objects; @@ -57,10 +65,19 @@ impl VirtualMachine { self.ctx.new_dict() } + pub fn new_class(&self, name: String, namespace: PyObjectRef) -> PyObjectRef { + self.ctx.new_class(name, namespace) + } + pub fn new_exception(&self, msg: String) -> PyObjectRef { self.new_str(msg) } + pub fn new_scope(&mut self) -> PyObjectRef { + let parent_scope = self.current_frame().locals.clone(); + self.ctx.new_scope(Some(parent_scope)) + } + pub fn get_none(&self) -> PyObjectRef { // TODO self.ctx.new_bool(false) @@ -464,7 +481,7 @@ impl VirtualMachine { Ok(obj) } - fn invoke(&mut self, func_ref: PyObjectRef, args: PyFuncArgs) -> PyResult { + pub fn invoke(&mut self, func_ref: PyObjectRef, args: PyFuncArgs) -> PyResult { let f = func_ref.borrow(); match f.kind { @@ -478,7 +495,8 @@ impl VirtualMachine { let frame = Frame::new(code.clone(), scope); self.run_frame(frame) } - PyObjectKind::Class { name: _ } => self.new_instance(func_ref.clone(), args), + PyObjectKind::Class { name: _, dict: _ } => + self.new_instance(func_ref.clone(), args), ref kind => { unimplemented!("invoke unimplemented for: {:?}", kind); } @@ -770,6 +788,28 @@ impl VirtualMachine { } None } + bytecode::Instruction::LoadBuildClass => { + let rustfunc = PyObject::new( + PyObjectKind::RustFunction { + function: builtins::builtin_build_class_ + }, + objtype::create_type() + ); + self.push_value(rustfunc); + None + } + bytecode::Instruction::StoreLocals => { + let locals = self.pop_value(); + let ref mut frame = self.current_frame(); + match frame.locals.borrow_mut().kind { + PyObjectKind::Scope { ref mut scope } => { + scope.locals = locals; + } + _ => + panic!("We really expect our scope to be a scope!") + } + None + }, _ => panic!("NOT IMPL {:?}", instruction), } } From 13313e44e81f83aad48e7efbbb9cf788d8511386 Mon Sep 17 00:00:00 2001 From: Adam Kelly Date: Wed, 8 Aug 2018 07:41:38 +0100 Subject: [PATCH 3/6] Add support creating instances of objects. --- vm/src/pyobject.rs | 21 +++++++++++++++++++++ vm/src/vm.rs | 14 ++++++++------ 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/vm/src/pyobject.rs b/vm/src/pyobject.rs index 5f6fa081e..d46a5d79c 100644 --- a/vm/src/pyobject.rs +++ b/vm/src/pyobject.rs @@ -208,6 +208,21 @@ impl ParentProtocol for PyObjectRef { } } +pub trait AttributeProtocol { + fn get_attr(&self, attr_name: &String) -> PyObjectRef; +} + +impl AttributeProtocol for PyObjectRef { + fn get_attr(&self, attr_name: &String) -> PyObjectRef { + match self.borrow().kind { + PyObjectKind::Module { name: _, ref dict } => dict.get_item(attr_name), + PyObjectKind::Class { name: _, ref dict } => dict.get_item(attr_name), + PyObjectKind::Instance { ref dict } => dict.get_item(attr_name), + ref kind => unimplemented!("load_attr unimplemented for: {:?}", kind), + } + } +} + pub trait DictProtocol { fn contains_key(&self, k: &String) -> bool; fn get_item(&self, k: &String) -> PyObjectRef; @@ -317,6 +332,9 @@ pub enum PyObjectKind { name: String, dict: PyObjectRef, }, + Instance { + dict: PyObjectRef + }, RustFunction { function: RustPyFunc, }, @@ -342,6 +360,7 @@ impl fmt::Debug for PyObjectKind { &PyObjectKind::Scope { scope: _ } => write!(f, "scope"), &PyObjectKind::None => write!(f, "None"), &PyObjectKind::Class { name: _, dict: _ } => write!(f, "class"), + &PyObjectKind::Instance { dict: _ } => write!(f, "instance"), &PyObjectKind::RustFunction { function: _ } => write!(f, "rust function"), &PyObjectKind::Type => write!(f, "type"), } @@ -403,6 +422,8 @@ impl PyObject { PyObjectKind::None => String::from("None"), PyObjectKind::Class { ref name, dict: ref _dict } => format!("", name), + PyObjectKind::Instance { dict: _ } => + format!(""), PyObjectKind::Code { code: _ } => format!(""), PyObjectKind::Function { code: _, scope: _ } => format!(""), PyObjectKind::RustFunction { function: _ } => format!(""), diff --git a/vm/src/vm.rs b/vm/src/vm.rs index 5ac73c3c6..4ef7151c2 100644 --- a/vm/src/vm.rs +++ b/vm/src/vm.rs @@ -16,6 +16,7 @@ use super::objlist; use super::objstr; use super::objtype; use super::pyobject::{ + AttributeProtocol, DictProtocol, IdProtocol, ParentProtocol, @@ -477,7 +478,12 @@ impl VirtualMachine { fn new_instance(&mut self, type_ref: PyObjectRef, args: PyFuncArgs) -> PyResult { // more or less __new__ operator - let obj = PyObject::new(PyObjectKind::None, type_ref.clone()); + let dict = self.new_dict(); + let obj = PyObject::new(PyObjectKind::Instance{dict: dict}, type_ref.clone()); + let init = type_ref.get_attr(&String::from("__init__")); + let mut self_args = PyFuncArgs { args: args.args }; + self_args.args.insert(0, obj.clone()); + self.invoke(init, self_args); Ok(obj) } @@ -516,11 +522,7 @@ impl VirtualMachine { fn load_attr(&mut self, attr_name: &String) -> Option { let parent = self.pop_value(); - // Lookup name in obj - let obj = match parent.borrow().kind { - PyObjectKind::Module { name: _, ref dict } => dict.get_item(attr_name), - ref kind => unimplemented!("load_attr unimplemented for: {:?}", kind), - }; + let obj = parent.get_attr(attr_name); self.push_value(obj); None } From 9df841b38b86cb02ab3c0848def228bf7e073858 Mon Sep 17 00:00:00 2001 From: Adam Kelly Date: Wed, 8 Aug 2018 07:58:16 +0100 Subject: [PATCH 4/6] Set and retrieve attributes on instances. --- vm/src/bytecode.rs | 1 + vm/src/compile.rs | 8 +++++++- vm/src/pyobject.rs | 9 +++++++++ vm/src/vm.rs | 10 +++++++++- 4 files changed, 26 insertions(+), 2 deletions(-) diff --git a/vm/src/bytecode.rs b/vm/src/bytecode.rs index 96056b699..0d9486d0a 100644 --- a/vm/src/bytecode.rs +++ b/vm/src/bytecode.rs @@ -38,6 +38,7 @@ pub enum Instruction { LoadName { name: String }, StoreName { name: String }, StoreSubscript, + StoreAttr { name: String }, LoadConst { value: Constant }, UnaryOperation { op: UnaryOperator }, BinaryOperation { op: BinaryOperator }, diff --git a/vm/src/compile.rs b/vm/src/compile.rs index faa3cacf0..285687b62 100644 --- a/vm/src/compile.rs +++ b/vm/src/compile.rs @@ -351,8 +351,14 @@ impl Compiler { self.compile_expression(b); self.emit(Instruction::StoreSubscript); } + ast::Expression::Attribute { value, name } => { + self.compile_expression(value); + self.emit(Instruction::StoreAttr { + name: name.to_string() + }); + } _ => { - panic!("WTF"); + panic!("WTF: {:?}", target); } } } diff --git a/vm/src/pyobject.rs b/vm/src/pyobject.rs index d46a5d79c..903c4d63e 100644 --- a/vm/src/pyobject.rs +++ b/vm/src/pyobject.rs @@ -210,6 +210,7 @@ impl ParentProtocol for PyObjectRef { pub trait AttributeProtocol { fn get_attr(&self, attr_name: &String) -> PyObjectRef; + fn set_attr(&self, attr_name: &String, value: PyObjectRef); } impl AttributeProtocol for PyObjectRef { @@ -221,6 +222,14 @@ impl AttributeProtocol for PyObjectRef { ref kind => unimplemented!("load_attr unimplemented for: {:?}", kind), } } + + fn set_attr(&self, attr_name: &String, value: PyObjectRef) { + match self.borrow_mut().kind { + PyObjectKind::Instance { ref mut dict } => + dict.set_item(attr_name, value), + ref kind => unimplemented!("load_attr unimplemented for: {:?}", kind), + }; + } } pub trait DictProtocol { diff --git a/vm/src/vm.rs b/vm/src/vm.rs index 4ef7151c2..fd4b9e100 100644 --- a/vm/src/vm.rs +++ b/vm/src/vm.rs @@ -483,7 +483,7 @@ impl VirtualMachine { let init = type_ref.get_attr(&String::from("__init__")); let mut self_args = PyFuncArgs { args: args.args }; self_args.args.insert(0, obj.clone()); - self.invoke(init, self_args); + self.invoke(init, self_args).unwrap(); Ok(obj) } @@ -527,6 +527,13 @@ impl VirtualMachine { None } + fn store_attr(&mut self, attr_name: &String) -> Option { + let parent = self.pop_value(); + let value = self.pop_value(); + parent.set_attr(attr_name, value); + None + } + // Execute a single instruction: fn execute_instruction(&mut self) -> Option { let instruction = self.current_frame().fetch_instruction(); @@ -629,6 +636,7 @@ impl VirtualMachine { } bytecode::Instruction::BinaryOperation { ref op } => self.execute_binop(op), bytecode::Instruction::LoadAttr { ref name } => self.load_attr(name), + bytecode::Instruction::StoreAttr { ref name } => self.store_attr(name), bytecode::Instruction::UnaryOperation { ref op } => self.execute_unop(op), bytecode::Instruction::CompareOperation { ref op } => self.execute_compare(op), bytecode::Instruction::ReturnValue => { From aa1207adcf3c863674d8e0fb2e5325f5c3c2e00d Mon Sep 17 00:00:00 2001 From: Adam Kelly Date: Wed, 8 Aug 2018 08:17:26 +0100 Subject: [PATCH 5/6] Snippet to test out class definition. --- tests/snippets/class.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 tests/snippets/class.py diff --git a/tests/snippets/class.py b/tests/snippets/class.py new file mode 100644 index 000000000..439edcd8f --- /dev/null +++ b/tests/snippets/class.py @@ -0,0 +1,14 @@ +class Foo: + print("Defining class") + def __init__(self): + print("initing: ", self) + self.x = 5 + + y = 7 + +print("Done defining: ", Foo) +print("Init: ", Foo.__init__) +print("y = ", Foo.y) +foo = Foo() +print("Done initting: ", foo) +print("Foo's x: ", foo.x) From 339189aff43af934cddbde694779c18e798fcd77 Mon Sep 17 00:00:00 2001 From: Adam Kelly Date: Thu, 9 Aug 2018 08:58:36 +0100 Subject: [PATCH 6/6] Remove special Type type. Type is just a class. --- vm/src/objtype.rs | 13 +++++++++---- vm/src/pyobject.rs | 2 -- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/vm/src/objtype.rs b/vm/src/objtype.rs index 07d624ba1..5193f1f18 100644 --- a/vm/src/objtype.rs +++ b/vm/src/objtype.rs @@ -1,5 +1,4 @@ -// use std::rc::Rc; -// use std::cell::RefCell; +use std::collections::HashMap; /* * The magical type type @@ -9,8 +8,14 @@ use super::pyobject::{PyObject, PyObjectKind, PyObjectRef}; pub fn create_type() -> PyObjectRef { let typ = PyObject::default().into_ref(); - (*typ.borrow_mut()).kind = PyObjectKind::Type; + let dict = PyObject::new( + PyObjectKind::Dict { + elements: HashMap::new(), + }, typ.clone()); + (*typ.borrow_mut()).kind = PyObjectKind::Class { + name: String::from("type"), + dict: dict, + }; (*typ.borrow_mut()).typ = Some(typ.clone()); - // typ.borrow_mut().dict.insert("__str__".to_string(), PyObject::new(PyObjectKind::RustFunction { function: str })); typ } diff --git a/vm/src/pyobject.rs b/vm/src/pyobject.rs index 903c4d63e..76d442d8c 100644 --- a/vm/src/pyobject.rs +++ b/vm/src/pyobject.rs @@ -347,7 +347,6 @@ pub enum PyObjectKind { RustFunction { function: RustPyFunc, }, - Type } impl fmt::Debug for PyObjectKind { @@ -371,7 +370,6 @@ impl fmt::Debug for PyObjectKind { &PyObjectKind::Class { name: _, dict: _ } => write!(f, "class"), &PyObjectKind::Instance { dict: _ } => write!(f, "instance"), &PyObjectKind::RustFunction { function: _ } => write!(f, "rust function"), - &PyObjectKind::Type => write!(f, "type"), } } }