diff --git a/include/pocketpy/interpreter/vm.h b/include/pocketpy/interpreter/vm.h index a124bae5..6b765c40 100644 --- a/include/pocketpy/interpreter/vm.h +++ b/include/pocketpy/interpreter/vm.h @@ -35,7 +35,6 @@ typedef struct TypePointer { } TypePointer; typedef struct py_ModuleInfo { - c11_string* name; c11_string* package; c11_string* path; py_GlobalRef self; // weakref to the original module object diff --git a/include/pocketpy/xmacros/magics.h b/include/pocketpy/xmacros/magics.h index 37f95241..48c4906a 100644 --- a/include/pocketpy/xmacros/magics.h +++ b/include/pocketpy/xmacros/magics.h @@ -57,7 +57,6 @@ MAGIC_METHOD(__exit__) MAGIC_METHOD(__name__) MAGIC_METHOD(__all__) MAGIC_METHOD(__package__) -MAGIC_METHOD(__path__) MAGIC_METHOD(__class__) MAGIC_METHOD(__getattr__) MAGIC_METHOD(__reduce__) diff --git a/src/compiler/compiler.c b/src/compiler/compiler.c index e035ff77..2702f82c 100644 --- a/src/compiler/compiler.c +++ b/src/compiler/compiler.c @@ -2462,17 +2462,50 @@ static Error* compile_decorated(Compiler* self) { // import a [as b] // import a [as b], c [as d] -static Error* compile_normal_import(Compiler* self) { +static Error* compile_normal_import(Compiler* self, c11_sbuf* buf) { do { consume(TK_ID); c11_sv name = Token__sv(prev()); - int index = Ctx__add_const_string(ctx(), name); - Ctx__emit_(ctx(), OP_IMPORT_PATH, index, prev()->line); - if(match(TK_AS)) { + c11_sbuf__write_sv(buf, name); + bool has_sub_cpnt = false; + + while(match(TK_DOT)) { + has_sub_cpnt = true; consume(TK_ID); - name = Token__sv(prev()); + c11_sbuf__write_char(buf, '.'); + c11_sbuf__write_sv(buf, Token__sv(prev())); } - Ctx__emit_store_name(ctx(), name_scope(self), py_namev(name), prev()->line); + + c11_string* path = c11_sbuf__submit(buf); + int path_index = Ctx__add_const_string(ctx(), c11_string__sv(path)); + c11_string__delete(path); + + NameScope scope = name_scope(self); + + Ctx__emit_(ctx(), OP_IMPORT_PATH, path_index, prev()->line); + // [module ] + + if(!has_sub_cpnt) { + if(match(TK_AS)) { + // import a as x + consume(TK_ID); + name = Token__sv(prev()); + } else { + // import a + } + } else { + if(match(TK_AS)) { + // import a.b as x + consume(TK_ID); + name = Token__sv(prev()); + } else { + // import a.b + Ctx__emit_(ctx(), OP_POP_TOP, BC_NOARG, BC_KEEPLINE); + int index = Ctx__add_const_string(ctx(), name); + Ctx__emit_(ctx(), OP_IMPORT_PATH, index, BC_KEEPLINE); + } + } + Ctx__emit_store_name(ctx(), scope, py_namev(name), BC_KEEPLINE); } while(match(TK_COMMA)); consume_end_stmt(); return NULL; @@ -2485,7 +2518,7 @@ static Error* compile_normal_import(Compiler* self) { // from ..a import b [as c] // from .a.b import c [as d] // from xxx import * -static Error* compile_from_import(c11_sbuf* buf, Compiler* self) { +static Error* compile_from_import(Compiler* self, c11_sbuf* buf) { int dots = 0; while(true) { @@ -2695,11 +2728,18 @@ static Error* compile_stmt(Compiler* self) { } case TK_WHILE: check(compile_while_loop(self)); break; case TK_FOR: check(compile_for_loop(self)); break; - case TK_IMPORT: check(compile_normal_import(self)); break; + case TK_IMPORT: { + c11_sbuf buf; + c11_sbuf__ctor(&buf); + err = compile_normal_import(self, &buf); + c11_sbuf__dtor(&buf); + if(err) return err; + break; + } case TK_FROM: { c11_sbuf buf; c11_sbuf__ctor(&buf); - err = compile_from_import(&buf, self); + err = compile_from_import(self, &buf); c11_sbuf__dtor(&buf); if(err) return err; break; diff --git a/src/modules/builtins.c b/src/modules/builtins.c index 060933cf..56d66167 100644 --- a/src/modules/builtins.c +++ b/src/modules/builtins.c @@ -465,10 +465,12 @@ static bool builtins_compile(int argc, py_Ref argv) { static bool builtins__import__(int argc, py_Ref argv) { PY_CHECK_ARGC(1); PY_CHECK_ARG_TYPE(0, tp_str); - int res = py_import(py_tostr(argv)); + const char* path = py_tostr(py_arg(0)); + if(path[0] == '.') return ValueError("relative import not allowed here"); + int res = py_import(path); if(res == -1) return false; if(res) return true; - return ImportError("module '%s' not found", py_tostr(argv)); + return ImportError("module '%s' not found", path); } static bool NoneType__repr__(int argc, py_Ref argv) { diff --git a/src/public/ModuleSystem.c b/src/public/ModuleSystem.c index e35c0bc9..e50754bf 100644 --- a/src/public/ModuleSystem.c +++ b/src/public/ModuleSystem.c @@ -13,20 +13,18 @@ py_Ref py_getmodule(const char* path) { return BinTree__try_get(&vm->modules, (void*)path); } -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"); +static py_Ref pk_newmodule(const char* path, bool is_init) { + c11_sv pathv = {path, strlen(path)}; + if(pathv.size > PK_MAX_MODULE_PATH_LEN) c11__abort("module path too long: %s", path); + if(pathv.size == 0) c11__abort("module path cannot be empty"); 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) { - mi->name = c11_string__new(path); - mi->package = c11_string__new(""); + + int last_dot = c11_sv__rindex(pathv, '.'); + if(last_dot == -1 || is_init) { + mi->package = c11_string__new(path); } else { - const char* start = path + last_dot + 1; - mi->name = c11_string__new(start); + // a.b.c -> a.b mi->package = c11_string__new2(path, last_dot); } @@ -43,16 +41,17 @@ py_Ref py_newmodule(const char* path) { mi->self = retval; // setup __name__ - py_newstrv(py_emplacedict(retval, __name__), c11_string__sv(mi->name)); + py_newstrv(py_emplacedict(retval, __name__), c11_string__sv(mi->path)); // setup __package__ py_newstrv(py_emplacedict(retval, __package__), c11_string__sv(mi->package)); - // setup __path__ - py_newstrv(py_emplacedict(retval, __path__), c11_string__sv(mi->path)); return retval; } +py_Ref py_newmodule(const char* path) { + return pk_newmodule(path, false); +} + static void py_ModuleInfo__dtor(py_ModuleInfo* mi) { - c11_string__delete(mi->name); c11_string__delete(mi->package); c11_string__delete(mi->path); } @@ -71,27 +70,28 @@ int py_import(const char* path_cstr) { ValueError("empty module name"); return -1; } + if(path.size > PK_MAX_MODULE_PATH_LEN) { + ValueError("module name too long: %v", path); + return -1; + } if(path.data[0] == '.') { + c11__rtassert(vm->top_frame != NULL && vm->top_frame->module != NULL); + // try relative import int dot_count = 1; while(dot_count < path.size && path.data[dot_count] == '.') dot_count++; - // */__init__.py[c] - c11_sv top_filepath = c11_string__sv(vm->top_frame->co->src->filename); - c11_sv top_filename = c11_sv__filename(top_filepath); - int is_init = c11__sveq2(top_filename, "__init__.py") || c11__sveq2(top_filename, "__init__.pyc"); - py_ModuleInfo* mi = py_touserdata(vm->top_frame->module); - c11_sv package_sv = c11_string__sv(mi->path); + c11_sv package_sv = c11_string__sv(mi->package); if(package_sv.size == 0) { ImportError("attempted relative import with no known parent package"); return -1; } c11_vector /* T=c11_sv */ cpnts = c11_sv__split(package_sv, '.'); - for(int i = is_init; i < dot_count; i++) { + for(int i = 1; i < dot_count; i++) { if(cpnts.length == 0){ ImportError("attempted relative import beyond top-level package"); return -1; @@ -119,7 +119,22 @@ int py_import(const char* path_cstr) { return res; } - assert(path.data[0] != '.' && path.data[path.size - 1] != '.'); + c11__rtassert(path.data[0] != '.' && path.data[path.size - 1] != '.'); + + // import parent module (implicit recursion) + int last_dot_index = c11_sv__rindex(path, '.'); + if(last_dot_index >= 0) { + c11_sv ppath = c11_sv__slice2(path, 0, last_dot_index); + py_GlobalRef ext_mod = py_getmodule(ppath.data); + if(!ext_mod) { + char buf[PK_MAX_MODULE_PATH_LEN + 1]; + memcpy(buf, ppath.data, ppath.size); + buf[ppath.size] = '\0'; + int res = py_import(buf); + if(res != 1) return res; + py_newnil(py_retval()); + } + } // check existing module py_GlobalRef ext_mod = py_getmodule(path.data); @@ -143,6 +158,7 @@ int py_import(const char* path_cstr) { bool need_free = true; bool is_pyc = false; + bool is_init = false; const char* data = load_kPythonLib(path_cstr); int data_size = -1; @@ -165,13 +181,17 @@ int py_import(const char* path_cstr) { c11_string__delete(filename); filename = c11_string__new3("%s%c__init__.py", slashed_path->data, PK_PLATFORM_SEP); data = vm->callbacks.importfile(filename->data, &data_size); - if(data != NULL) goto __SUCCESS; + if(data != NULL) { + is_init = true; + goto __SUCCESS; + } c11_string__delete(filename); filename = c11_string__new3("%s%c__init__.pyc", slashed_path->data, PK_PLATFORM_SEP); data = vm->callbacks.importfile(filename->data, &data_size); if(data != NULL) { is_pyc = true; + is_init = true; goto __SUCCESS; } @@ -184,7 +204,7 @@ __SUCCESS: do { } while(0); - py_GlobalRef mod = py_newmodule(path_cstr); + py_GlobalRef mod = pk_newmodule(path_cstr, is_init); bool ok; if(is_pyc) { diff --git a/tests/080_dict.py b/tests/080_dict.py index 48833e7b..1ccc6603 100644 --- a/tests/080_dict.py +++ b/tests/080_dict.py @@ -166,16 +166,3 @@ for i in range(len(data)): if i % 3 == 0: y = b.pop() delattr(a, y) - -# bug test -d = { - '__name__': '__main__', - '__package__': '', - '__path__': '__main__', - 'a': [], - 'gc': 1, -} - -del d['a'] -assert 'a' not in d -assert d['gc'] == 1 \ No newline at end of file diff --git a/tests/300_import.py b/tests/300_import.py index 38ce806c..8969b2dc 100644 --- a/tests/300_import.py +++ b/tests/300_import.py @@ -14,14 +14,13 @@ else: assert os.getcwd().endswith('tests') import test1 - assert test1.add(1, 2) == 13 from test2.a.g import get_value, A assert get_value() == '123' assert (A.__module__ == 'test2.a.g'), A.__module__ -import test2 +import test2.a.g assert test2.a.g.get_value() == '123' from test2.utils import get_value_2 @@ -30,10 +29,12 @@ assert get_value_2() == '123' from test3.a.b import value assert value == 1 +import test3.a.b as test3_ab +assert test3_ab.value == 1 + from test2.utils import r -assert r.__name__ == 'r' +assert r.__name__ == 'test2.utils.r' assert r.__package__ == 'test2.utils' -assert r.__path__ == 'test2.utils.r' def f(): import math as m diff --git a/tests/301_import1.py b/tests/301_import1.py new file mode 100644 index 00000000..f5212d69 --- /dev/null +++ b/tests/301_import1.py @@ -0,0 +1,25 @@ +try: + import os +except ImportError: + exit(0) + +import sys +is_pyc = sys.argv[0].endswith('.pyc') + +if is_pyc: + os.chdir('tmp/tests') +else: + os.chdir('tests') + +assert os.getcwd().endswith('tests') + +os.environ['STDOUT'] = '' + +import test.a.g.q + +assert os.environ['STDOUT'] == 'test init!!\ntest.a init!!\ntest.a.g init!!\ntest.a.g.q init!!\n' + +import test + +assert test.__package__ == 'test' +assert test.__name__ == 'test' diff --git a/tests/712_gc_bug.py b/tests/712_gc_bug.py deleted file mode 100644 index 24cd9002..00000000 --- a/tests/712_gc_bug.py +++ /dev/null @@ -1,46 +0,0 @@ -# a=[] -# import gc -# gc.collect() - -# # a.append(a) -# print(globals().items()) -# del a -# print(list(globals().items())) -# print(globals()['gc']) -# gc.collect() - -d = object() -d.__name__ = '__main__' -d.__package__ = '' -d.__path__ = '__main__' -d.a = [] -d.gc = 1 - -assert d.gc == 1 -del d.a - -assert not hasattr(d, 'a') -assert d.gc == 1 - -# [0, 1, 6, 7, 4, 5, 2, 3] - -# 0 __name__ [0] -# 1 __package__ [1] -# 2 nil -# 3 nil -# 4 gc [4] -# 5 nil -# 6 __path__ [2] -# 7 a [3] - -import gc -gc.collect() - -a = [] -del a -assert gc.collect() == 1 - -a = [] -a.append(a) -del a -assert gc.collect() == 1 diff --git a/tests/test/__init__.py b/tests/test/__init__.py new file mode 100644 index 00000000..77fff1ad --- /dev/null +++ b/tests/test/__init__.py @@ -0,0 +1,5 @@ +assert __package__ == 'test' +assert __name__ == 'test' + +import os +os.environ['STDOUT'] += 'test init!!\n' \ No newline at end of file diff --git a/tests/test/a/__init__.py b/tests/test/a/__init__.py new file mode 100644 index 00000000..9fb102fa --- /dev/null +++ b/tests/test/a/__init__.py @@ -0,0 +1,5 @@ +assert __package__ == 'test.a' +assert __name__ == 'test.a' + +import os +os.environ['STDOUT'] += 'test.a init!!\n' \ No newline at end of file diff --git a/tests/test/a/g/__init__.py b/tests/test/a/g/__init__.py new file mode 100644 index 00000000..3594f020 --- /dev/null +++ b/tests/test/a/g/__init__.py @@ -0,0 +1,7 @@ +assert __package__ == 'test.a.g' +assert __name__ == 'test.a.g' + +import os +os.environ['STDOUT'] += 'test.a.g init!!\n' + + diff --git a/tests/test/a/g/q.py b/tests/test/a/g/q.py new file mode 100644 index 00000000..4ebb2334 --- /dev/null +++ b/tests/test/a/g/q.py @@ -0,0 +1,5 @@ +assert __package__ == 'test.a.g' +assert __name__ == 'test.a.g.q' + +import os +os.environ['STDOUT'] += 'test.a.g.q init!!\n' \ No newline at end of file diff --git a/tests/test2/b.py b/tests/test2/b.py index 681054fd..a61361e0 100644 --- a/tests/test2/b.py +++ b/tests/test2/b.py @@ -1,7 +1,7 @@ D = 10 try: - import abc # does not exist + import xxxxx # does not exist exit(1) except ImportError: pass \ No newline at end of file diff --git a/tests/test3/__init__.py b/tests/test3/__init__.py index b6acfe8e..e69de29b 100644 --- a/tests/test3/__init__.py +++ b/tests/test3/__init__.py @@ -1,3 +0,0 @@ -raise ValueError( - "test3 should not be imported" -) \ No newline at end of file diff --git a/tests/test3/a/__init__.py b/tests/test3/a/__init__.py index 260dabcc..e69de29b 100644 --- a/tests/test3/a/__init__.py +++ b/tests/test3/a/__init__.py @@ -1,3 +0,0 @@ -raise ValueError( - "test3.a should not be imported" -) \ No newline at end of file