diff --git a/ports/sh/mpconfigport.h b/ports/sh/mpconfigport.h index bbbf55deb..6dd1401e3 100644 --- a/ports/sh/mpconfigport.h +++ b/ports/sh/mpconfigport.h @@ -22,6 +22,10 @@ extern const struct _mp_print_t mp_debug_print; #define MICROPY_DEBUG_PRINTER (&mp_debug_print) #endif +/* Custom option to use relative imports. For instance when working at the fs + root, 'import b' in '/folder/a.py' will import 'folder/b.py' not '/b.py'. */ +#define MICROPY_RELATIVE_FILE_IMPORTS (1) + /* General feature set selection Other options: BASIC_FEATURES, EXTRA_FEATURES, FULL_FEATURES, EVERYTHING */ #define MICROPY_CONFIG_ROM_LEVEL (MICROPY_CONFIG_ROM_LEVEL_CORE_FEATURES) diff --git a/py/builtinimport.c b/py/builtinimport.c index a578d4ad2..a7c1abef7 100644 --- a/py/builtinimport.c +++ b/py/builtinimport.c @@ -444,6 +444,7 @@ STATIC mp_obj_t process_import_at_level(qstr full_mod_name, qstr level_mod_name, } else { // No-op. Nothing to load. // mp_warning("%s is imported as namespace package", vstr_str(&path)); + DEBUG_printf("%s is imported as namespace package\n", vstr_str(path)); } // Remove /__init__.py suffix. path->len = orig_path_len; @@ -522,6 +523,23 @@ mp_obj_t mp_builtin___import___default(size_t n_args, const mp_obj_t *args) { mp_obj_t top_module_obj = MP_OBJ_NULL; mp_obj_t outer_module_obj = MP_OBJ_NULL; + #if MICROPY_RELATIVE_FILE_IMPORTS + size_t is_len; + mp_obj_t *is_items; + mp_obj_list_get(MP_STATE_VM(mp_import_stack), &is_len, &is_items); + if(is_len > 0) { + // Start next to last import + char const *prev = qstr_str(MP_OBJ_QSTR_VALUE(is_items[is_len - 1])); + char const *slash = strchr(prev, '/'); + int prev_len = (slash ? slash : prev) - prev; + vstr_add_strn(&path, prev, prev_len); + outer_module_obj = mp_obj_new_module(MP_QSTR___blankmodule); + mp_store_attr(outer_module_obj, MP_QSTR___path__, mp_obj_new_str(vstr_str(&path), vstr_len(&path))); + DEBUG_printf("last import was '%s'\n", prev); + DEBUG_printf("starting in '%.*s'\n", (int)vstr_len(&path), vstr_str(&path)); + } + #endif + // Search for the end of each component. size_t current_component_start = 0; for (size_t i = 1; i <= module_name_len; i++) { diff --git a/py/mpstate.h b/py/mpstate.h index ba47c7648..789e21c43 100644 --- a/py/mpstate.h +++ b/py/mpstate.h @@ -176,6 +176,11 @@ typedef struct _mp_state_vm_t { mp_obj_dict_t *mp_module_builtins_override_dict; #endif + // list (stack) of nested imports for relative import resolution + #if MICROPY_RELATIVE_FILE_IMPORTS + mp_obj_t mp_import_stack; + #endif + // Include any root pointers registered with MP_REGISTER_ROOT_POINTER(). #ifndef NO_QSTR // Only include root pointer definitions when not doing qstr extraction, because diff --git a/py/obj.h b/py/obj.h index 8aa5b0a8e..bc7d0b122 100644 --- a/py/obj.h +++ b/py/obj.h @@ -1116,6 +1116,7 @@ mp_int_t mp_obj_tuple_hash(mp_obj_t self_in); // list mp_obj_t mp_obj_list_append(mp_obj_t self_in, mp_obj_t arg); mp_obj_t mp_obj_list_remove(mp_obj_t self_in, mp_obj_t value); +mp_obj_t mp_obj_list_pop(mp_obj_t self_in, mp_obj_t index); void mp_obj_list_get(mp_obj_t self_in, size_t *len, mp_obj_t **items); void mp_obj_list_set_len(mp_obj_t self_in, size_t len); void mp_obj_list_store(mp_obj_t self_in, mp_obj_t index, mp_obj_t value); diff --git a/py/objlist.c b/py/objlist.c index 18da91ba3..d7365d2d6 100644 --- a/py/objlist.c +++ b/py/objlist.c @@ -290,6 +290,12 @@ STATIC mp_obj_t list_pop(size_t n_args, const mp_obj_t *args) { return ret; } +mp_obj_t mp_obj_list_pop(mp_obj_t self_in, mp_obj_t index) +{ + mp_obj_t args[] = {self_in, index}; + return list_pop(2, args); +} + STATIC void mp_quicksort(mp_obj_t *head, mp_obj_t *tail, mp_obj_t key_fn, mp_obj_t binop_less_result) { MP_STACK_CHECK(); while (head < tail) { diff --git a/py/runtime.c b/py/runtime.c index 23fae6041..c1a49210c 100644 --- a/py/runtime.c +++ b/py/runtime.c @@ -119,6 +119,10 @@ void mp_init(void) { MP_STATE_VM(mp_module_builtins_override_dict) = NULL; #endif + #if MICROPY_RELATIVE_FILE_IMPORTS + MP_STATE_VM(mp_import_stack) = mp_obj_new_list(0, NULL); + #endif + #if MICROPY_PERSISTENT_CODE_TRACK_RELOC_CODE MP_STATE_VM(track_reloc_code_list) = MP_OBJ_NULL; #endif @@ -1585,13 +1589,28 @@ mp_obj_t mp_parse_compile_execute(mp_lexer_t *lex, mp_parse_input_kind_t parse_i mp_globals_set(globals); mp_locals_set(locals); + #if MICROPY_RELATIVE_FILE_IMPORTS + char const *volatile name = qstr_str(lex->source_name); + (void)name; + if (parse_input_kind == MP_PARSE_FILE_INPUT) { + mp_obj_list_append(MP_STATE_VM(mp_import_stack), MP_OBJ_NEW_QSTR(lex->source_name)); + #if DEBUG_PRINT + DEBUG_printf("pushing import: ", name); + mp_obj_print_helper(MICROPY_DEBUG_PRINTER, MP_STATE_VM(mp_import_stack), PRINT_REPR); + DEBUG_printf("\n"); + #endif + } + #endif + nlr_buf_t nlr; + bool volatile failed = false; + mp_obj_t volatile ret; + if (nlr_push(&nlr) == 0) { qstr source_name = lex->source_name; mp_parse_tree_t parse_tree = mp_parse(lex, parse_input_kind); mp_obj_t module_fun = mp_compile(&parse_tree, source_name, parse_input_kind == MP_PARSE_SINGLE_INPUT); - mp_obj_t ret; if (MICROPY_PY_BUILTINS_COMPILE && globals == NULL) { // for compile only, return value is the module function ret = module_fun; @@ -1604,13 +1623,27 @@ mp_obj_t mp_parse_compile_execute(mp_lexer_t *lex, mp_parse_input_kind_t parse_i nlr_pop(); mp_globals_set(old_globals); mp_locals_set(old_locals); - return ret; } else { // exception; restore context and re-raise same exception mp_globals_set(old_globals); mp_locals_set(old_locals); - nlr_jump(nlr.ret_val); + failed = true; } + + #if MICROPY_RELATIVE_FILE_IMPORTS + if (parse_input_kind == MP_PARSE_FILE_INPUT) { + mp_obj_list_pop(MP_STATE_VM(mp_import_stack), MP_OBJ_NEW_SMALL_INT(-1)); + #if DEBUG_PRINT + DEBUG_printf("popping import: ", name); + mp_obj_print_helper(MICROPY_DEBUG_PRINTER, MP_STATE_VM(mp_import_stack), PRINT_REPR); + DEBUG_printf("\n"); + #endif + } + #endif + + if(failed) + nlr_jump(nlr.ret_val); + return ret; } #endif // MICROPY_ENABLE_COMPILER