Przeglądaj źródła

impl `@property`

Update codeobject.h
blueloveTH 3 lat temu
rodzic
commit
3721f48f8b
12 zmienionych plików z 86 dodań i 54 usunięć
  1. 7 0
      src/builtins.h
  2. 14 11
      src/ceval.h
  3. 1 0
      src/codeobject.h
  4. 1 2
      src/common.h
  5. 23 18
      src/compiler.h
  6. 1 1
      src/obj.h
  7. 4 1
      src/opcodes.h
  8. 1 0
      src/pocketpy.h
  9. 1 0
      src/str.h
  10. 5 19
      src/vm.h
  11. 16 1
      tests/_class.py
  12. 12 1
      tests/_decorator.py

+ 7 - 0
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"(

+ 14 - 11
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: {

+ 1 - 0
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;
     }

+ 1 - 2
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
+#define PK_EXTRA_CHECK

+ 23 - 18
src/compiler.h

@@ -19,7 +19,6 @@ enum StringType { NORMAL_STRING, RAW_STRING, F_STRING };
 class Compiler {
     std::unique_ptr<Parser> parser;
     std::stack<CodeObject_> 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:

+ 1 - 1
src/obj.h

@@ -109,7 +109,7 @@ struct Py_ : PyObject {
             _attr = new pkpy::NameDict(16, kTypeAttrLoadFactor);
         }else if constexpr(std::is_same_v<T, DummyInstance>){
             _attr = new pkpy::NameDict(4, kInstAttrLoadFactor);
-        }else if constexpr(std::is_same_v<T, pkpy::Function> || std::is_same_v<T, pkpy::NativeFunc> || std::is_same_v<T, DummyProperty>){
+        }else if constexpr(std::is_same_v<T, pkpy::Function> || std::is_same_v<T, pkpy::NativeFunc>){
             _attr = new pkpy::NameDict(4, kInstAttrLoadFactor);
         }else{
             _attr = nullptr;

+ 4 - 1
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

+ 1 - 0
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;
     }

+ 1 - 0
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");

+ 5 - 19
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<PyVar*>(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<T>(value)));
-            return;
-        }
-
         if(!p->is_attr_valid()) TypeError("cannot set attribute");
         p->attr().set(name, std::forward<T>(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<int ARGC>
     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<typename P>
     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");

+ 16 - 1
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)
+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

+ 12 - 1
tests/_decorator.py

@@ -15,4 +15,15 @@ def fib(n):
         return n
     return fib(n-1) + fib(n-2)
 
-assert fib(32) == 2178309
+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