diff --git a/python/builtins.py b/python/builtins.py index 2da2fc9a..91e82eea 100644 --- a/python/builtins.py +++ b/python/builtins.py @@ -161,6 +161,8 @@ def list::pop(self, i=-1): return res def list::__eq__(self, other): + if type(self) is not type(other): + return False if len(self) != len(other): return False for i in range(len(self)): @@ -188,8 +190,24 @@ tuple.__contains__ = list.__contains__ class property: - def __init__(self, fget): + def __init__(self, fget, fset=None): self.fget = fget + self.fset = fset def __get__(self, obj): - return self.fget(obj) \ No newline at end of file + return self.fget(obj) + + def __set__(self, obj, value): + if self.fset is None: + raise AttributeError("readonly property") + self.fset(obj, value) + +class staticmethod: + def __init__(self, f): + self.f = f + + def __get__(self, obj): + return self.f + +def type::__repr__(self): + return "" \ No newline at end of file diff --git a/src/common.h b/src/common.h index ba989292..06ad1327 100644 --- a/src/common.h +++ b/src/common.h @@ -29,8 +29,8 @@ #include #include -#define PK_VERSION "0.9.4" -#define PK_EXTRA_CHECK 0 +#define PK_VERSION "0.9.5" +#define PK_EXTRA_CHECK 1 #if (defined(__ANDROID__) && __ANDROID_API__ <= 22) || defined(__EMSCRIPTEN__) #define PK_ENABLE_FILEIO 0 diff --git a/src/compiler.h b/src/compiler.h index a90f1b0e..3312d5be 100644 --- a/src/compiler.h +++ b/src/compiler.h @@ -996,14 +996,18 @@ private: } else if(match(TK("pass"))){ consume_end_stmt(); } else { + int begin = co()->codes.size(); EXPR_ANY(); + int end = co()->codes.size(); consume_end_stmt(); // If last op is not an assignment, pop the result. uint8_t last_op = co()->codes.back().op; if( last_op!=OP_STORE_NAME && last_op!=OP_STORE_REF && last_op!=OP_INPLACE_BINARY_OP && last_op!=OP_INPLACE_BITWISE_OP && last_op!=OP_STORE_ALL_NAMES && last_op!=OP_STORE_CLASS_ATTR){ - if(last_op == OP_BUILD_TUPLE_REF) co()->codes.back().op = OP_BUILD_TUPLE; + for(int i=begin; icodes[i].op==OP_BUILD_TUPLE_REF) co()->codes[i].op = OP_BUILD_TUPLE; + } if(mode()==REPL_MODE && name_scope() == NAME_GLOBAL) emit(OP_PRINT_EXPR, -1, true); emit(OP_POP_TOP, -1, true); } diff --git a/src/memory.h b/src/memory.h index 2f3ff1bc..2e446528 100644 --- a/src/memory.h +++ b/src/memory.h @@ -69,7 +69,7 @@ public: T* get() const { return _t(); } int use_count() const { - if(is_tagged()) return 1; + if(is_tagged()) return 0; return counter ? *counter : 0; } diff --git a/src/obj.h b/src/obj.h index 3454612c..8e99ef9f 100644 --- a/src/obj.h +++ b/src/obj.h @@ -122,7 +122,7 @@ struct Py_ : PyObject { }; #define OBJ_GET(T, obj) (((Py_*)((obj).get()))->_value) -#define OBJ_NAME(obj) OBJ_GET(Str, (obj)->attr(__name__)) +#define OBJ_NAME(obj) OBJ_GET(Str, vm->getattr(obj, __name__)) const int kTpIntIndex = 2; const int kTpFloatIndex = 3; diff --git a/src/pocketpy.h b/src/pocketpy.h index 2efe3941..21b2d7f0 100644 --- a/src/pocketpy.h +++ b/src/pocketpy.h @@ -65,7 +65,9 @@ void init_builtins(VM* _vm) { _vm->bind_builtin_func<0>("super", [](VM* vm, Args& args) { const PyVar* self = vm->top_frame()->f_locals().try_get(m_self); if(self == nullptr) vm->TypeError("super() can only be called in a class"); - return vm->new_object(vm->tp_super, *self); + // base should be CURRENT_CLASS_BASE + Type base = vm->_all_types[(*self)->type.index].base; + return vm->new_object(vm->tp_super, Super(*self, base)); }); _vm->bind_builtin_func<1>("id", [](VM* vm, Args& args) { @@ -164,8 +166,6 @@ void init_builtins(VM* _vm) { _vm->bind_method<1>("object", "__ne__", CPP_LAMBDA(VAR(args[0] != args[1]))); _vm->bind_static_method<1>("type", "__new__", CPP_LAMBDA(vm->_t(args[0]))); - _vm->bind_method<0>("type", "__repr__", CPP_LAMBDA(VAR("attr(__name__)) + "'>"))); - _vm->bind_static_method<-1>("range", "__new__", [](VM* vm, Args& args) { Range r; switch (args.size()) { @@ -490,7 +490,7 @@ void init_builtins(VM* _vm) { /************ PyTuple ************/ _vm->bind_static_method<1>("tuple", "__new__", [](VM* vm, Args& args) { List list = CAST(List, vm->asList(args[0])); - return VAR(std::move(list)); + return VAR(Tuple::from_list(std::move(list))); }); _vm->bind_method<0>("tuple", "__iter__", [](VM* vm, Args& args) { @@ -505,7 +505,7 @@ void init_builtins(VM* _vm) { s.normalize(self.size()); List new_list; for(size_t i = s.start; i < s.stop; i++) new_list.push_back(self[i]); - return VAR(std::move(new_list)); + return VAR(Tuple::from_list(std::move(new_list))); } int index = CAST(int, args[1]); @@ -745,11 +745,10 @@ void VM::post_init(){ add_module_io(this); add_module_os(this); add_module_c(this); - _lazy_modules["functools"] = kPythonLibs["functools"]; - _lazy_modules["collections"] = kPythonLibs["collections"]; - _lazy_modules["heapq"] = kPythonLibs["heapq"]; - _lazy_modules["bisect"] = kPythonLibs["bisect"]; - _lazy_modules["this"] = kPythonLibs["this"]; + + for(const char* name: {"this", "functools", "collections", "heapq", "bisect"}){ + _lazy_modules[name] = kPythonLibs[name]; + } CodeObject_ code = compile(kPythonLibs["builtins"], "", EXEC_MODE); this->_exec(code, this->builtins); @@ -757,6 +756,17 @@ void VM::post_init(){ this->_exec(code, this->builtins); code = compile(kPythonLibs["set"], "", EXEC_MODE); this->_exec(code, this->builtins); + + // property is defined in builtins.py so we need to add it after builtins is loaded + _t(tp_object)->attr().set(__class__, property(CPP_LAMBDA(vm->_t(args[0])))); + _t(tp_type)->attr().set(__base__, property([](VM* vm, Args& args){ + const PyTypeInfo& info = vm->_all_types[OBJ_GET(Type, args[0]).index]; + return info.base.index == -1 ? vm->None : vm->_all_types[info.base.index].obj; + })); + _t(tp_type)->attr().set(__name__, property([](VM* vm, Args& args){ + const PyTypeInfo& info = vm->_all_types[OBJ_GET(Type, args[0]).index]; + return VAR(info.name); + })); } } // namespace pkpy diff --git a/src/vm.h b/src/vm.h index 7da9e490..d86aaf2c 100644 --- a/src/vm.h +++ b/src/vm.h @@ -33,18 +33,23 @@ public: PyVar next(); }; +struct PyTypeInfo{ + PyVar obj; + Type base; + Str name; +}; + class VM { VM* vm; // self reference for simplify code public: std::stack< std::unique_ptr > callstack; PyVar _py_op_call; PyVar _py_op_yield; - std::vector _all_types; + std::vector _all_types; PyVar run_frame(Frame* frame); - NameDict _types; - NameDict _modules; // loaded modules + NameDict _modules; // loaded modules std::map _lazy_modules; // lazy loaded modules PyVar None, True, False, Ellipsis; @@ -98,13 +103,22 @@ public: return call(_t(tp_list), one_arg(iterable)); } + PyVar* find_name_in_mro(PyObject* cls, StrName name){ + PyVar* val; + do{ + val = cls->attr().try_get(name); + if(val != nullptr) return val; + Type cls_t = static_cast*>(cls)->_value; + Type base = _all_types[cls_t.index].base; + if(base.index == -1) break; + cls = _all_types[base.index].obj.get(); + }while(true); + return nullptr; + } + PyVar fast_call(StrName name, Args&& args){ - PyObject* cls = _t(args[0]).get(); - while(cls != None.get()) { - PyVar* val = cls->attr().try_get(name); - if(val != nullptr) return call(*val, std::move(args)); - cls = cls->attr(__base__).get(); - } + PyVar* val = find_name_in_mro(_t(args[0]).get(), name); + if(val != nullptr) return call(*val, std::move(args)); AttributeError(args[0], name); return nullptr; } @@ -160,11 +174,26 @@ public: return _exec(); } - Type _new_type_object(StrName name, Type base=0) { + PyVar property(NativeFuncRaw fget){ + PyVar p = builtins->attr("property"); + PyVar method = new_object(tp_native_function, NativeFunc(fget, 1, false)); + return call(p, one_arg(method)); + } + + PyVar new_type_object(PyVar mod, StrName name, Type base){ PyVar obj = make_sp>(tp_type, _all_types.size()); - setattr(obj, __base__, _t(base)); - _types.set(name, obj); - _all_types.push_back(obj); + PyTypeInfo info{ + .obj = obj, + .base = base, + .name = (mod!=nullptr && mod!=builtins) ? Str(OBJ_NAME(mod)+"."+name.str()): name.str() + }; + if(mod != nullptr) mod->attr().set(name, obj); + _all_types.push_back(info); + return obj; + } + + Type _new_type_object(StrName name, Type base=0) { + PyVar obj = new_type_object(nullptr, name, base); return OBJ_GET(Type, obj); } @@ -192,14 +221,23 @@ public: return make_sp>>(type, std::move(_value)); } - template - void bind_func(Str typeName, Str funcName, NativeFuncRaw fn) { - bind_func(_types[typeName], funcName, fn); + PyVar _find_type(const Str& type){ + PyVar* obj = builtins->attr().try_get(type); + if(!obj){ + for(auto& t: _all_types) if(t.name == type) return t.obj; + throw std::runtime_error("type not found: " + type); + } + return *obj; } template - void bind_method(Str typeName, Str funcName, NativeFuncRaw fn) { - bind_method(_types[typeName], funcName, fn); + void bind_func(Str type, Str name, NativeFuncRaw fn) { + bind_func(_find_type(type), name, fn); + } + + template + void bind_method(Str type, Str name, NativeFuncRaw fn) { + bind_method(_find_type(type), name, fn); } template @@ -208,13 +246,13 @@ public: } template - void _bind_methods(std::vector typeNames, Str funcName, NativeFuncRaw fn) { - for(auto& typeName : typeNames) bind_method(typeName, funcName, fn); + void _bind_methods(std::vector types, Str name, NativeFuncRaw fn) { + for(auto& type: types) bind_method(type, name, fn); } template - void bind_builtin_func(Str funcName, NativeFuncRaw fn) { - bind_func(builtins, funcName, fn); + void bind_builtin_func(Str name, NativeFuncRaw fn) { + bind_func(builtins, name, fn); } int normalized_index(int index, int size){ @@ -276,13 +314,13 @@ public: } inline PyVar& _t(Type t){ - return _all_types[t.index]; + return _all_types[t.index].obj; } inline PyVar& _t(const PyVar& obj){ if(is_int(obj)) return _t(tp_int); if(is_float(obj)) return _t(tp_float); - return _all_types[OBJ_GET(Type, _t(obj->type)).index]; + return _all_types[OBJ_GET(Type, _t(obj->type)).index].obj; } ~VM() { @@ -292,6 +330,14 @@ public: } } + inline PyVarOrNull getattr(const PyVar& obj, StrName name, bool throw_err=true, bool class_only=false){ + return getattr(&obj, name, throw_err, class_only); + } + template + inline void setattr(PyVar& obj, StrName name, T&& value){ + setattr(&obj, name, std::forward(value)); + } + CodeObject_ compile(Str source, Str filename, CompileMode mode); void post_init(); PyVar num_negated(const PyVar& obj); @@ -299,15 +345,14 @@ public: const PyVar& asBool(const PyVar& obj); i64 hash(const PyVar& obj); PyVar asRepr(const PyVar& obj); - PyVar new_type_object(PyVar mod, StrName name, PyVar base); PyVar new_module(StrName name); Str disassemble(CodeObject_ co); void init_builtin_types(); PyVar call(const PyVar& _callable, Args args, const Args& kwargs, bool opCall); void unpack_args(Args& args); - PyVarOrNull getattr(const PyVar& obj, StrName name, bool throw_err=true, bool class_only=false); + PyVarOrNull getattr(const PyVar* obj, StrName name, bool throw_err=true, bool class_only=false); template - void setattr(PyVar& obj, StrName name, T&& value); + void setattr(PyVar* obj, StrName name, T&& value); template void bind_method(PyVar obj, Str funcName, NativeFuncRaw fn); template @@ -542,21 +587,9 @@ PyVar VM::asRepr(const PyVar& obj){ return call(obj, __repr__); } -PyVar VM::new_type_object(PyVar mod, StrName name, PyVar base){ - if(!is_type(base, tp_type)) UNREACHABLE(); - PyVar obj = make_sp>(tp_type, _all_types.size()); - setattr(obj, __base__, base); - Str fullName = name.str(); - if(mod != builtins) fullName = OBJ_NAME(mod) + "." + name.str(); - setattr(obj, __name__, VAR(fullName)); - setattr(mod, name, obj); - _all_types.push_back(obj); - return obj; -} - PyVar VM::new_module(StrName name) { PyVar obj = new_object(tp_module, DummyModule()); - setattr(obj, __name__, VAR(name.str())); + obj->attr().set(__name__, VAR(name.str())); _modules.set(name, obj); return obj; } @@ -631,15 +664,13 @@ Str VM::disassemble(CodeObject_ co){ } void VM::init_builtin_types(){ - PyVar _tp_object = make_sp>(1, 0); - PyVar _tp_type = make_sp>(1, 1); - _all_types.push_back(_tp_object); - _all_types.push_back(_tp_type); + // Py_(Type type, T&& val) + PyVar _tp_object = make_sp>(Type(1), Type(0)); + PyVar _tp_type = make_sp>(Type(1), Type(1)); + _all_types.push_back({.obj = _tp_object, .base = -1, .name = "object"}); + _all_types.push_back({.obj = _tp_type, .base = 0, .name = "type"}); tp_object = 0; tp_type = 1; - _types.set("object", _tp_object); - _types.set("type", _tp_type); - tp_int = _new_type_object("int"); tp_float = _new_type_object("float"); if(tp_int.index != kTpIntIndex || tp_float.index != kTpFloatIndex) UNREACHABLE(); @@ -665,25 +696,27 @@ void VM::init_builtin_types(){ this->Ellipsis = new_object(_new_type_object("ellipsis"), DUMMY_VAL); this->True = new_object(tp_bool, true); this->False = new_object(tp_bool, false); - this->builtins = new_module("builtins"); - this->_main = new_module("__main__"); this->_py_op_call = new_object(_new_type_object("_py_op_call"), DUMMY_VAL); this->_py_op_yield = new_object(_new_type_object("_py_op_yield"), DUMMY_VAL); - - setattr(_t(tp_type), __base__, _t(tp_object)); - setattr(_t(tp_object), __base__, None); + this->builtins = new_module("builtins"); + this->_main = new_module("__main__"); - for(auto [k, v]: _types.items()){ - setattr(v, __name__, VAR(k.str())); - } - - std::vector pb_types = {"type", "object", "bool", "int", "float", "str", "list", "tuple", "range"}; - for (auto& name : pb_types) { - setattr(builtins, name, _types[name]); - } + // setup public types + builtins->attr().set("type", _t(tp_type)); + builtins->attr().set("object", _t(tp_object)); + builtins->attr().set("bool", _t(tp_bool)); + builtins->attr().set("int", _t(tp_int)); + builtins->attr().set("float", _t(tp_float)); + builtins->attr().set("str", _t(tp_str)); + builtins->attr().set("list", _t(tp_list)); + builtins->attr().set("tuple", _t(tp_tuple)); + builtins->attr().set("range", _t(tp_range)); post_init(); - for(auto [k, v]: _types.items()) v->attr()._try_perfect_rehash(); + for(int i=0; i<_all_types.size(); i++){ + auto& t = _all_types[i]; + t.obj->attr()._try_perfect_rehash(); + } for(auto [k, v]: _modules.items()) v->attr()._try_perfect_rehash(); } @@ -785,77 +818,75 @@ void VM::unpack_args(Args& args){ args = Args::from_list(std::move(unpacked)); } -PyVarOrNull VM::getattr(const PyVar& obj, StrName name, bool throw_err, bool class_only) { - PyVar* val; - PyObject* cls; +using Super = std::pair; - if(is_type(obj, tp_super)){ - const PyVar* root = &obj; - int depth = 1; - while(true){ - root = &OBJ_GET(PyVar, *root); - if(!is_type(*root, tp_super)) break; - depth++; - } - cls = _t(*root).get(); - for(int i=0; iattr(__base__).get(); - - if(!class_only){ - val = (*root)->attr().try_get(name); - if(val != nullptr) return *val; - } - }else{ - if(!class_only && !obj.is_tagged() && obj->is_attr_valid()){ - val = obj->attr().try_get(name); - if(val != nullptr) return *val; - } - cls = _t(obj).get(); +// https://docs.python.org/3/howto/descriptor.html#invocation-from-an-instance +PyVarOrNull VM::getattr(const PyVar* obj, StrName name, bool throw_err, bool class_only){ + PyObject* objtype = _t(*obj).get(); + if(is_type(*obj, tp_super)){ + const Super& super = OBJ_GET(Super, *obj); + obj = &super.first; + objtype = _t(super.second).get(); } - - while(cls != None.get()) { - val = cls->attr().try_get(name); - if(val != nullptr){ - PyVarOrNull descriptor = getattr(*val, __get__, false, true); - if(descriptor != nullptr) return call(descriptor, one_arg(obj)); - if(is_type(*val, tp_function) || is_type(*val, tp_native_function)){ - return VAR(BoundMethod(obj, *val)); - }else{ - return *val; - } - }else{ - // this operation is expensive!!! - const Str& s = name.str(); - if(s.empty() || s[0] != '_'){ - PyVar* interceptor = cls->attr().try_get(__getattr__); - if(interceptor != nullptr){ - return call(*interceptor, two_args(obj, VAR(s))); - } - } - } - cls = cls->attr(__base__).get(); + PyVar* cls_var = find_name_in_mro(objtype, name); + if(cls_var != nullptr){ + // handle descriptor + PyVar* descr_get = _t(*cls_var)->attr().try_get(__get__); + if(descr_get != nullptr) return call(*descr_get, two_args(*cls_var, *obj)); } - if(throw_err) AttributeError(obj, name); + // handle instance __dict__ + if(!class_only && !(*obj).is_tagged() && (*obj)->is_attr_valid()){ + PyVar* val = (*obj)->attr().try_get(name); + if(val != nullptr) return *val; + } + if(cls_var != nullptr){ + // bound method is non-data descriptor + if(is_type(*cls_var, tp_function) || is_type(*cls_var, tp_native_function)){ + return VAR(BoundMethod(*obj, *cls_var)); + } + return *cls_var; + } + if(throw_err) AttributeError(*obj, name); return nullptr; } template -void VM::setattr(PyVar& obj, StrName name, T&& value) { - if(obj.is_tagged()) TypeError("cannot set attribute"); - PyObject* p = obj.get(); - while(p->type == tp_super) p = static_cast(p->value())->get(); - if(!p->is_attr_valid()) TypeError("cannot set attribute"); - p->attr().set(name, std::forward(value)); +void VM::setattr(PyVar* obj, StrName name, T&& value){ + static_assert(std::is_same_v, PyVar>); + PyObject* objtype = _t(*obj).get(); + if(is_type(*obj, tp_super)){ + Super& super = OBJ_GET(Super, *obj); + obj = &super.first; + objtype = _t(super.second).get(); + } + PyVar* cls_var = find_name_in_mro(objtype, name); + if(cls_var != nullptr){ + // handle descriptor + const PyVar& cls_var_t = _t(*cls_var); + if(cls_var_t->attr().contains(__get__)){ + PyVar* descr_set = cls_var_t->attr().try_get(__set__); + if(descr_set != nullptr){ + call(*descr_set, three_args(*cls_var, *obj, std::forward(value))); + }else{ + TypeError("readonly attribute: " + name.str().escape(true)); + } + return; + } + } + // handle instance __dict__ + if((*obj).is_tagged() || !(*obj)->is_attr_valid()) TypeError("cannot set attribute"); + (*obj)->attr().set(name, std::forward(value)); } template -void VM::bind_method(PyVar obj, Str funcName, NativeFuncRaw fn) { +void VM::bind_method(PyVar obj, Str name, NativeFuncRaw fn) { check_type(obj, tp_type); - setattr(obj, funcName, VAR(NativeFunc(fn, ARGC, true))); + obj->attr().set(name, VAR(NativeFunc(fn, ARGC, true))); } template -void VM::bind_func(PyVar obj, Str funcName, NativeFuncRaw fn) { - setattr(obj, funcName, VAR(NativeFunc(fn, ARGC, false))); +void VM::bind_func(PyVar obj, Str name, NativeFuncRaw fn) { + obj->attr().set(name, VAR(NativeFunc(fn, ARGC, false))); } void VM::_error(Exception e){ diff --git a/tests/40_class.py b/tests/40_class.py index aade430b..751cc42d 100644 --- a/tests/40_class.py +++ b/tests/40_class.py @@ -13,7 +13,7 @@ a = A(1, 2) assert a.add() == 3 assert a.sub() == -1 -assert a.__base__ is object +assert A.__base__ is object class B(A): def __init__(self, a, b, c):