From 3721f48f8b57be1e481e5cd128c068efe886a003 Mon Sep 17 00:00:00 2001 From: blueloveTH Date: Mon, 27 Feb 2023 00:15:30 +0800 Subject: [PATCH] impl `@property` Update codeobject.h --- src/builtins.h | 7 +++++++ src/ceval.h | 25 ++++++++++++++----------- src/codeobject.h | 1 + src/common.h | 3 +-- src/compiler.h | 41 +++++++++++++++++++++++------------------ src/obj.h | 2 +- src/opcodes.h | 5 ++++- src/pocketpy.h | 1 + src/str.h | 1 + src/vm.h | 24 +++++------------------- tests/_class.py | 17 ++++++++++++++++- tests/_decorator.py | 13 ++++++++++++- 12 files changed, 86 insertions(+), 54 deletions(-) diff --git a/src/builtins.h b/src/builtins.h index fd5cd4ec..ac875eec 100644 --- a/src/builtins.h +++ b/src/builtins.h @@ -384,6 +384,13 @@ class set: def __iter__(self): return self._a.keys() + +class property: + def __init__(self, fget): + self.fget = fget + + def __get__(self, obj): + return self.fget(obj) )"; const char* kRandomCode = R"( diff --git a/src/ceval.h b/src/ceval.h index 30f141ba..b89b740e 100644 --- a/src/ceval.h +++ b/src/ceval.h @@ -85,25 +85,28 @@ PyVar VM::run_frame(Frame* frame){ pkpy::List& list = PyList_AS_C(frame->top_1()); list.push_back(std::move(obj)); } continue; - case OP_BUILD_CLASS: { - const Str& clsName = frame->co->names[byte.arg].first.str(); + case OP_BEGIN_CLASS: { + auto& name = frame->co->names[byte.arg]; PyVar clsBase = frame->pop_value(this); if(clsBase == None) clsBase = _t(tp_object); check_type(clsBase, tp_type); - PyVar cls = new_type_object(frame->_module, clsName, clsBase); - while(true){ - PyVar fn = frame->pop_value(this); - if(fn == None) break; - const pkpy::Function& f = PyFunction_AS_C(fn); - setattr(cls, f.name, fn); - } + PyVar cls = new_type_object(frame->_module, name.first, clsBase); + frame->push(cls); + } continue; + case OP_END_CLASS: { + PyVar cls = frame->pop(); cls->attr()._try_perfect_rehash(); + }; continue; + case OP_STORE_CLASS_ATTR: { + auto& name = frame->co->names[byte.arg]; + PyVar obj = frame->pop_value(this); + PyVar& cls = frame->top(); + cls->attr().set(name.first, std::move(obj)); } continue; case OP_RETURN_VALUE: return frame->pop_value(this); case OP_PRINT_EXPR: { const PyVar expr = frame->top_value(this); - if(expr == None) continue; - *_stdout << PyStr_AS_C(asRepr(expr)) << '\n'; + if(expr != None) *_stdout << PyStr_AS_C(asRepr(expr)) << '\n'; } continue; case OP_POP_TOP: frame->_pop(); continue; case OP_BINARY_OP: { diff --git a/src/codeobject.h b/src/codeobject.h index a754cf05..6ed6837a 100644 --- a/src/codeobject.h +++ b/src/codeobject.h @@ -94,6 +94,7 @@ struct CodeObject { /************************************************/ int _curr_block_i = 0; int _rvalue = 0; + bool _is_compiling_class = false; bool _is_curr_block_loop() const { return blocks[_curr_block_i].type == FOR_LOOP || blocks[_curr_block_i].type == WHILE_LOOP; } diff --git a/src/common.h b/src/common.h index d6a79d44..feebc043 100644 --- a/src/common.h +++ b/src/common.h @@ -45,7 +45,6 @@ typedef double f64; struct Dummy { }; struct DummyInstance { }; -struct DummyProperty { }; struct DummyModule { }; #define DUMMY_VAL Dummy() @@ -79,4 +78,4 @@ const float kInstAttrLoadFactor = 0.67; const float kTypeAttrLoadFactor = 0.5; // do extra check for debug -// #define PK_EXTRA_CHECK \ No newline at end of file +#define PK_EXTRA_CHECK \ No newline at end of file diff --git a/src/compiler.h b/src/compiler.h index 16a91d25..050ec334 100644 --- a/src/compiler.h +++ b/src/compiler.h @@ -19,7 +19,6 @@ enum StringType { NORMAL_STRING, RAW_STRING, F_STRING }; class Compiler { std::unique_ptr parser; std::stack codes; - bool is_compiling_class = false; int lexing_count = 0; bool used = false; VM* vm; @@ -334,7 +333,7 @@ private: consumed = true; } if (repl_throw && peek() == TK("@eof")){ - throw NeedMoreLines(is_compiling_class); + throw NeedMoreLines(co()->_is_compiling_class); } return consumed; } @@ -409,13 +408,19 @@ private: if(op == TK("=")) { // a = (expr) EXPR_TUPLE(); if(lhs!=-1 && co()->codes[lhs].op == OP_LOAD_NAME_REF){ - emit(OP_STORE_NAME, co()->codes[lhs].arg); + if(co()->_is_compiling_class){ + emit(OP_STORE_CLASS_ATTR, co()->codes[lhs].arg); + }else{ + emit(OP_STORE_NAME, co()->codes[lhs].arg); + } co()->codes[lhs].op = OP_NO_OP; co()->codes[lhs].arg = -1; }else{ + if(co()->_is_compiling_class) SyntaxError(); emit(OP_STORE_REF); } }else{ // a += (expr) -> a = a + (expr) + if(co()->_is_compiling_class) SyntaxError(); EXPR(); switch (op) { case TK("+="): emit(OP_INPLACE_BINARY_OP, 0); break; @@ -778,7 +783,7 @@ __LISTCOMP: lex_token(); TokenIndex op = parser->prev.type; if (op == TK("=")){ - if(meet_assign_token) SyntaxError("invalid syntax"); + if(meet_assign_token) SyntaxError(); meet_assign_token = true; } GrammarFn infix = rules[op].infix; @@ -977,7 +982,9 @@ __LISTCOMP: 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){ + 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; if(mode()==REPL_MODE && name_scope() == NAME_GLOBAL) emit(OP_PRINT_EXPR, -1, true); emit(OP_POP_TOP, -1, true); @@ -993,13 +1000,13 @@ __LISTCOMP: super_cls_name_idx = co()->add_name(parser->prev.str(), NAME_GLOBAL); consume(TK(")")); } - emit(OP_LOAD_NONE); - is_compiling_class = true; - compile_block_body(&Compiler::compile_function); - is_compiling_class = false; if(super_cls_name_idx == -1) emit(OP_LOAD_NONE); - else emit(OP_LOAD_NAME_REF, super_cls_name_idx); - emit(OP_BUILD_CLASS, cls_name_idx); + else emit(OP_LOAD_NAME, super_cls_name_idx); + emit(OP_BEGIN_CLASS, cls_name_idx); + co()->_is_compiling_class = true; + compile_block_body(); + co()->_is_compiling_class = false; + emit(OP_END_CLASS); } void _compile_f_args(pkpy::Function& func, bool enable_type_hints){ @@ -1044,15 +1051,11 @@ __LISTCOMP: void compile_function(){ bool has_decorator = !co()->codes.empty() && co()->codes.back().op == OP_SETUP_DECORATOR; - if(is_compiling_class){ - if(match(TK("pass"))) return; - consume(TK("def")); - } pkpy::Function func; StrName obj_name; consume(TK("@id")); func.name = parser->prev.str(); - if(!is_compiling_class && match(TK("::"))){ + if(!co()->_is_compiling_class && match(TK("::"))){ consume(TK("@id")); obj_name = func.name; func.name = parser->prev.str(); @@ -1070,7 +1073,7 @@ __LISTCOMP: this->codes.pop(); emit(OP_LOAD_FUNCTION, co()->add_const(vm->PyFunction(func))); if(name_scope() == NAME_LOCAL) emit(OP_SETUP_CLOSURE); - if(!is_compiling_class){ + if(!co()->_is_compiling_class){ if(obj_name.empty()){ if(has_decorator) emit(OP_CALL, 1); emit(OP_STORE_NAME, co()->add_name(func.name, name_scope())); @@ -1083,7 +1086,8 @@ __LISTCOMP: emit(OP_STORE_REF); } }else{ - if(has_decorator) SyntaxError("decorator is not supported here"); + if(has_decorator) emit(OP_CALL, 1); + emit(OP_STORE_CLASS_ATTR, co()->add_name(func.name, name_scope())); } } @@ -1117,6 +1121,7 @@ __LISTCOMP: throw e; } void SyntaxError(Str msg){ throw_err("SyntaxError", msg); } + void SyntaxError(){ throw_err("SyntaxError", "invalid syntax"); } void IndentationError(Str msg){ throw_err("IndentationError", msg); } public: diff --git a/src/obj.h b/src/obj.h index 4014b9a2..735b5238 100644 --- a/src/obj.h +++ b/src/obj.h @@ -109,7 +109,7 @@ struct Py_ : PyObject { _attr = new pkpy::NameDict(16, kTypeAttrLoadFactor); }else if constexpr(std::is_same_v){ _attr = new pkpy::NameDict(4, kInstAttrLoadFactor); - }else if constexpr(std::is_same_v || std::is_same_v || std::is_same_v){ + }else if constexpr(std::is_same_v || std::is_same_v){ _attr = new pkpy::NameDict(4, kInstAttrLoadFactor); }else{ _attr = nullptr; diff --git a/src/opcodes.h b/src/opcodes.h index 2c80e3d7..28ff7eb6 100644 --- a/src/opcodes.h +++ b/src/opcodes.h @@ -24,7 +24,6 @@ OPCODE(BUILD_LIST) OPCODE(BUILD_MAP) OPCODE(BUILD_SET) OPCODE(BUILD_SLICE) -OPCODE(BUILD_CLASS) OPCODE(BUILD_TUPLE) OPCODE(BUILD_TUPLE_REF) OPCODE(BUILD_STRING) @@ -87,4 +86,8 @@ OPCODE(SETUP_CLOSURE) OPCODE(SETUP_DECORATOR) OPCODE(STORE_ALL_NAMES) +OPCODE(BEGIN_CLASS) +OPCODE(END_CLASS) +OPCODE(STORE_CLASS_ATTR) + #endif \ No newline at end of file diff --git a/src/pocketpy.h b/src/pocketpy.h index e12e9cfd..3aa73dfc 100644 --- a/src/pocketpy.h +++ b/src/pocketpy.h @@ -10,6 +10,7 @@ CodeObject_ VM::compile(Str source, Str filename, CompileMode mode) { try{ return compiler.compile(); }catch(pkpy::Exception& e){ + // std::cout << e.summary() << std::endl; _error(e); return nullptr; } diff --git a/src/str.h b/src/str.h index f1751d64..7ac9882c 100644 --- a/src/str.h +++ b/src/str.h @@ -191,6 +191,7 @@ const StrName __init__ = StrName::get("__init__"); const StrName __json__ = StrName::get("__json__"); const StrName __name__ = StrName::get("__name__"); const StrName __len__ = StrName::get("__len__"); +const StrName __get__ = StrName::get("__get__"); const StrName m_eval = StrName::get("eval"); const StrName m_self = StrName::get("self"); diff --git a/src/vm.h b/src/vm.h index a9d590bf..db5894fb 100644 --- a/src/vm.h +++ b/src/vm.h @@ -361,7 +361,10 @@ public: while(cls != None.get()) { val = cls->attr().try_get(name); if(val != nullptr){ - if(is_type(*val, tp_property)) return call((*val)->attr("__get__"), pkpy::one_arg(obj)); + PyVarOrNull descriptor = getattr(*val, __get__, false); + if(descriptor != nullptr){ + return call(descriptor, pkpy::one_arg(obj)); + } if(is_type(*val, tp_function) || is_type(*val, tp_native_function)){ return PyBoundMethod({obj, *val}); }else{ @@ -379,26 +382,10 @@ public: if(obj.is_tagged()) TypeError("cannot set attribute"); PyObject* p = obj.get(); while(p->type == tp_super) p = static_cast(p->value())->get(); - - // handle property - PyVar* prop = _t(obj)->attr().try_get(name); - if(prop != nullptr && is_type(*prop, tp_property)){ - call((*prop)->attr("__set__"), pkpy::two_args(obj, std::forward(value))); - return; - } - if(!p->is_attr_valid()) TypeError("cannot set attribute"); p->attr().set(name, std::forward(value)); } - void bind_property(PyVar obj, Str field, NativeFuncRaw getter, NativeFuncRaw setter){ - check_type(obj, tp_type); - PyVar prop = new_object(tp_property, DummyProperty()); - prop->attr().set("__get__", PyNativeFunc(pkpy::NativeFunc(getter, 0, true))); - prop->attr().set("__set__", PyNativeFunc(pkpy::NativeFunc(setter, 1, true))); - setattr(obj, field, prop); - } - template void bind_method(PyVar obj, Str funcName, NativeFuncRaw fn) { check_type(obj, tp_type); @@ -537,7 +524,7 @@ public: Type tp_list, tp_tuple; Type tp_function, tp_native_function, tp_native_iterator, tp_bound_method; Type tp_slice, tp_range, tp_module, tp_ref; - Type tp_super, tp_exception, tp_star_wrapper, tp_property; + Type tp_super, tp_exception, tp_star_wrapper; template inline PyVarRef PyRef(P&& value) { @@ -654,7 +641,6 @@ public: tp_module = _new_type_object("module"); tp_ref = _new_type_object("_ref"); tp_star_wrapper = _new_type_object("_star_wrapper"); - tp_property = _new_type_object("property"); tp_function = _new_type_object("function"); tp_native_function = _new_type_object("native_function"); diff --git a/tests/_class.py b/tests/_class.py index d16833fc..aade430b 100644 --- a/tests/_class.py +++ b/tests/_class.py @@ -75,4 +75,19 @@ assert isinstance(d, C) assert isinstance(d, B) assert isinstance(d, A) assert isinstance(object, object) -assert isinstance(type, object) \ No newline at end of file +assert isinstance(type, object) + +class A: + a = 1 + b = 2 + +assert A.a == 1 +assert A.b == 2 + +class B(A): + b = 3 + c = 4 + +# assert B.a == 1 ...bug here +assert B.b == 3 +assert B.c == 4 diff --git a/tests/_decorator.py b/tests/_decorator.py index 29c7a659..6de0d06b 100644 --- a/tests/_decorator.py +++ b/tests/_decorator.py @@ -15,4 +15,15 @@ def fib(n): return n return fib(n-1) + fib(n-2) -assert fib(32) == 2178309 \ No newline at end of file +assert fib(32) == 2178309 + +class A: + def __init__(self, x): + self._x = x + + @property + def x(self): + return self._x + +a = A(1) +assert a.x == 1 \ No newline at end of file