diff --git a/include/pocketpy/interpreter/vm.h b/include/pocketpy/interpreter/vm.h index ce822c85..c8bf1797 100644 --- a/include/pocketpy/interpreter/vm.h +++ b/include/pocketpy/interpreter/vm.h @@ -34,6 +34,13 @@ typedef struct TypePointer { py_Dtor dtor; } TypePointer; +typedef struct py_ModuleInfo { + c11_string* name; + c11_string* package; + c11_string* path; +} py_ModuleInfo; + + typedef struct VM { py_Frame* top_frame; @@ -139,6 +146,7 @@ py_Type pk_nativefunc__register(); py_Type pk_boundmethod__register(); py_Type pk_range__register(); py_Type pk_range_iterator__register(); +py_Type pk_module__register(); py_Type pk_BaseException__register(); py_Type pk_Exception__register(); py_Type pk_StopIteration__register(); diff --git a/include/pocketpy/pocketpy.h b/include/pocketpy/pocketpy.h index 79abaa2e..3ce3938f 100644 --- a/include/pocketpy/pocketpy.h +++ b/include/pocketpy/pocketpy.h @@ -406,6 +406,8 @@ PK_API py_ItemRef py_emplacedict(py_Ref self, py_Name name); /// NOTE: Be careful if `f` modifies the object's `__dict__`. PK_API bool py_applydict(py_Ref self, bool (*f)(py_Name name, py_Ref val, void* ctx), void* ctx) PY_RAISE; +/// Clear the object's `__dict__`. This function is dangerous. +PK_API void py_cleardict(py_Ref self); /// Get the i-th slot of the object. /// The object must have slots and `i` must be in valid range. diff --git a/src/interpreter/vm.c b/src/interpreter/vm.c index 648a8fa4..488d76da 100644 --- a/src/interpreter/vm.c +++ b/src/interpreter/vm.c @@ -50,7 +50,7 @@ void VM__ctor(VM* self) { const static BinTreeConfig modules_config = { .f_cmp = BinTree__cmp_cstr, - .need_free_key = true, + .need_free_key = false, }; BinTree__ctor(&self->modules, c11_strdup(""), py_NIL(), &modules_config); c11_vector__ctor(&self->types, sizeof(TypePointer)); @@ -114,7 +114,7 @@ void VM__ctor(VM* self) { validate(tp_slice, pk_slice__register()); validate(tp_range, pk_range__register()); validate(tp_range_iterator, pk_range_iterator__register()); - validate(tp_module, pk_newtype("module", tp_object, NULL, NULL, false, true)); + validate(tp_module, pk_module__register()); validate(tp_function, pk_function__register()); validate(tp_nativefunc, pk_nativefunc__register()); diff --git a/src/interpreter/vmx.c b/src/interpreter/vmx.c index e9b25876..4334981b 100644 --- a/src/interpreter/vmx.c +++ b/src/interpreter/vmx.c @@ -37,8 +37,8 @@ void pk_print_stack(VM* self, py_Frame* frame, Bytecode byte) { break; } case tp_module: { - py_Ref path = py_getdict(p, __path__); - pk_sprintf(&buf, "", py_tosv(path)); + py_ModuleInfo* mi = py_touserdata(p); + pk_sprintf(&buf, "", c11_string__sv(mi->path)); break; } default: { diff --git a/src/modules/pickle.c b/src/modules/pickle.c index 1c8f92b2..497e0fb1 100644 --- a/src/modules/pickle.c +++ b/src/modules/pickle.c @@ -66,10 +66,10 @@ static void c11_sbuf__write_type_path(c11_sbuf* path_buf, py_Type type) { c11_sbuf__write_cstr(path_buf, py_name2str(ti->name)); return; } - const char* mod_path = py_tostr(py_getdict(ti->module, __path__)); - c11_sbuf__write_cstr(path_buf, mod_path); + py_ModuleInfo* mi = py_touserdata(ti->module); + c11_sbuf__write_sv(path_buf, c11_string__sv(mi->path)); c11_sbuf__write_char(path_buf, '.'); - c11_sbuf__write_cstr(path_buf, py_name2str(ti->name)); + c11_sbuf__write_sv(path_buf, py_name2sv(ti->name)); } static void pkl__emit_op(PickleObject* buf, PickleOp op) { diff --git a/src/public/modules.c b/src/public/modules.c index 7915a68f..e8148dd3 100644 --- a/src/public/modules.c +++ b/src/public/modules.c @@ -22,49 +22,67 @@ py_Ref py_getglobal(py_Name name) { return py_getdict(pk_current_vm->main, name) void py_setglobal(py_Name name, py_Ref val) { py_setdict(pk_current_vm->main, name, val); } -py_Ref py_newmodule(const char* path) { - ManagedHeap* heap = &pk_current_vm->heap; +static void py_ModuleInfo__dtor(py_ModuleInfo* mi) { + c11_string__delete(mi->name); + c11_string__delete(mi->package); + c11_string__delete(mi->path); +} +static bool module__name__(int argc, py_Ref argv) { + PY_CHECK_ARGC(1); + py_ModuleInfo* mi = py_touserdata(argv); + py_newstrv(py_retval(), c11_string__sv(mi->name)); + return true; +} + +static bool module__package__(int argc, py_Ref argv) { + PY_CHECK_ARGC(1); + py_ModuleInfo* mi = py_touserdata(argv); + py_newstrv(py_retval(), c11_string__sv(mi->package)); + return true; +} + +static bool module__path__(int argc, py_Ref argv) { + PY_CHECK_ARGC(1); + py_ModuleInfo* mi = py_touserdata(argv); + py_newstrv(py_retval(), c11_string__sv(mi->path)); + return true; +} + +py_Type pk_module__register() { + py_Type type = pk_newtype("module", tp_object, NULL, (py_Dtor)py_ModuleInfo__dtor, false, true); + py_bindproperty(type, "__name__", module__name__, NULL); + py_bindproperty(type, "__package__", module__package__, NULL); + py_bindproperty(type, "__path__", module__path__, NULL); + return type; +} + +py_Ref py_newmodule(const char* path) { int path_len = strlen(path); if(path_len > PK_MAX_MODULE_PATH_LEN) c11__abort("module path too long: %s", path); if(path_len == 0) c11__abort("module path cannot be empty"); - py_Ref r0 = py_pushtmp(); - py_Ref r1 = py_pushtmp(); - - *r0 = (py_TValue){ - .type = tp_module, - .is_ptr = true, - ._obj = ManagedHeap__new(heap, tp_module, -1, 0), - }; + py_ModuleInfo* mi = py_newobject(py_retval(), tp_module, -1, sizeof(py_ModuleInfo)); int last_dot = c11_sv__rindex((c11_sv){path, path_len}, '.'); if(last_dot == -1) { - py_newstr(r1, path); - py_setdict(r0, __name__, r1); - py_newstr(r1, ""); - py_setdict(r0, __package__, r1); + mi->name = c11_string__new(path); + mi->package = c11_string__new(""); } else { const char* start = path + last_dot + 1; - py_newstr(r1, start); - py_setdict(r0, __name__, r1); - py_newstrv(r1, (c11_sv){path, last_dot}); - py_setdict(r0, __package__, r1); + mi->name = c11_string__new(start); + mi->package = c11_string__new2(path, last_dot); } - py_newstr(r1, path); - py_setdict(r0, __path__, r1); + mi->path = c11_string__new(path); + path = mi->path->data; // we do not allow override in order to avoid memory leak // it is because Module objects are not garbage collected bool exists = BinTree__contains(&pk_current_vm->modules, (void*)path); if(exists) c11__abort("module '%s' already exists", path); - // convert to a weak (const char*) - path = py_tostr(py_getdict(r0, __path__)); - BinTree__set(&pk_current_vm->modules, c11_strdup(path), r0); - - py_shrink(2); + BinTree__set(&pk_current_vm->modules, (void*)path, py_retval()); return py_getmodule(path); } @@ -84,8 +102,8 @@ int py_import(const char* path_cstr) { c11_sv top_filename = c11_string__sv(vm->top_frame->co->src->filename); int is_init = c11_sv__endswith(top_filename, (c11_sv){"__init__.py", 11}); - py_Ref package = py_getdict(vm->top_frame->module, __path__); - c11_sv package_sv = py_tosv(package); + py_ModuleInfo* mi = py_touserdata(vm->top_frame->module); + c11_sv package_sv = c11_string__sv(mi->path); if(package_sv.size == 0) { return ImportError("attempted relative import with no known parent package"); } @@ -165,7 +183,8 @@ __SUCCESS: bool py_importlib_reload(py_GlobalRef module) { VM* vm = pk_current_vm; - c11_sv path = py_tosv(py_getdict(module, __path__)); + py_ModuleInfo* mi = py_touserdata(module); + c11_sv path = c11_string__sv(mi->path); c11_string* slashed_path = c11_sv__replace(path, '.', PK_PLATFORM_SEP); c11_string* filename = c11_string__new3("%s.py", slashed_path->data); char* data = vm->callbacks.importfile(filename->data); @@ -176,6 +195,7 @@ bool py_importlib_reload(py_GlobalRef module) { } c11_string__delete(slashed_path); if(data == NULL) return ImportError("module '%v' not found", path); + py_cleardict(module); bool ok = py_exec(data, filename->data, RELOAD_MODE, module); c11_string__delete(filename); PK_FREE(data); diff --git a/src/public/py_object.c b/src/public/py_object.c index 6ed83760..02b877a1 100644 --- a/src/public/py_object.c +++ b/src/public/py_object.c @@ -104,8 +104,8 @@ static bool type__module__(int argc, py_Ref argv) { if(py_isnil(ti->module)) { py_newnone(py_retval()); } else { - py_Ref path = py_getdict(ti->module, __path__); - py_assign(py_retval(), path); + py_ModuleInfo* mi = py_touserdata(ti->module); + py_newstrv(py_retval(), c11_string__sv(mi->path)); } return true; } diff --git a/src/public/py_ops.c b/src/public/py_ops.c index a6a60214..d384092c 100644 --- a/src/public/py_ops.c +++ b/src/public/py_ops.c @@ -166,10 +166,10 @@ bool py_getattr(py_Ref self, py_Name name) { } if(self->type == tp_module) { - py_Ref path = py_getdict(self, __path__); + py_ModuleInfo* mi = py_touserdata(self); c11_sbuf buf; c11_sbuf__ctor(&buf); - pk_sprintf(&buf, "%v.%n", py_tosv(path), name); + pk_sprintf(&buf, "%v.%n", c11_string__sv(mi->path), name); c11_string* new_path = c11_sbuf__submit(&buf); int res = py_import(new_path->data); c11_string__delete(new_path); diff --git a/src/public/stack_ops.c b/src/public/stack_ops.c index abf8a5b3..1c65000b 100644 --- a/src/public/stack_ops.c +++ b/src/public/stack_ops.c @@ -35,6 +35,12 @@ bool py_applydict(py_Ref self, bool (*f)(py_Name, py_Ref, void*), void* ctx) { return true; } +void py_cleardict(py_Ref self) { + assert(self && self->is_ptr); + NameDict* dict = PyObject__dict(self->_obj); + NameDict__clear(dict); +} + bool py_deldict(py_Ref self, py_Name name) { assert(self && self->is_ptr); return NameDict__del(PyObject__dict(self->_obj), name); diff --git a/tests/30_import.py b/tests/30_import.py index 5a40d1ab..e33bc5b1 100644 --- a/tests/30_import.py +++ b/tests/30_import.py @@ -23,6 +23,11 @@ assert get_value_2() == '123' from test3.a.b import value assert value == 1 +from test2.utils import r +assert r.__name__ == 'r' +assert r.__package__ == 'test2.utils' +assert r.__path__ == 'test2.utils.r' + def f(): import math as m assert m.pi > 3