Skip to content

Commit d3abe3d

Browse files
gh-21: Add module aliasing.
1 parent c99aafe commit d3abe3d

File tree

3 files changed

+21
-7
lines changed

3 files changed

+21
-7
lines changed

docs/SPECIFICATION.html

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -826,8 +826,6 @@
826826

827827
Package imports: Prefix supports package namespaces using `..` as the package separator. The canonical form is `package..subpackage..module`. When `IMPORT(pkg)` is used the interpreter will prefer a package layout and attempt to load `pkg/init.pre` from the referring source's directory (falling back to the interpreter `lib/` directory). If a directory named `pkg` exists but contains no `init.pre` the import raises a clear error. When `IMPORT(pkg..mod)` is used the interpreter resolves to `pkg/mod.pre` (or the corresponding file under `lib/`). When a package directory and a same-named module file both exist in the same search location, the package takes precedence. Items within the package are still imported using their full dotted names (for example, `pkg..mod.FOO`).
828828

829-
- `IMPORT_PATH(STR: path)` - Loads a Prefix module from the absolute filesystem `path` provided as a `STR`. The argument must be an absolute path to a `.pre` source file. The imported module is treated the same as `IMPORT` with respect to parsing, execution isolation, function/module caching, and companion extension loading (companion `<module>.prex` alongside the resolved file and a built-in `ext/<module>.py` are both checked and loaded if present). The module's basename (filename without extension) is used as the module identifier for qualifying exported bindings.
830-
831829
The first argument must be an identifier naming a module; the interpreter first looks for a file named `<name>.pre` in the same directory as the referring source file. When the referring source is provided via the `-source` string literal mode, the primary search directory is the process's current working directory. If the module file is not found there, the interpreter will additionally attempt to load the file from a `lib` subdirectory located alongside the interpreter implementation (that is, `<interpreter_dir>/lib/<name>.pre`, where `<interpreter_dir>` is the directory containing the interpreter script or executable).
832830

833831
The imported file is parsed and executed in its own isolated top-level environment on the first import during a given interpreter invocation: top-level assignments and function definitions in the imported module do not directly mutate the caller's environment during execution. During that execution unqualified identifiers (for example, `x` or `helper`) refer to names in the module's own top-level namespace. Qualified identifiers (for example, `other.FOO`) refer only to the dotted names that the module itself has created or imported; those qualified bindings are scoped to the module's namespace.
@@ -838,6 +836,9 @@
838836

839837
When a module is imported, the interpreter also attempts to load any associated runtime extensions so their operators are immediately available to the importer. The interpreter first looks for a companion pointer file named `<module>.prex` next to the resolved module file (or in the interpreter `lib/` fallback) and loads any extensions listed there. If no pointer file is present (or as an additional fallback), the interpreter will also check its built-in `ext/` directory for a single-file extension named `<module>.py` and load it if present. Operators registered by such extensions are attached to the running interpreter at import time and become callable (typically under the module-qualified names they register).
840838

839+
840+
- `IMPORT_PATH(STR: path)` - Loads a Prefix module from the absolute filesystem `path` provided as a `STR`. The argument must be an absolute path to a `.pre` source file. The imported module is treated the same as `IMPORT` with respect to parsing, execution isolation, function/module caching, and companion extension loading (companion `<module>.prex` alongside the resolved file and a built-in `ext/<module>.py` are both checked and loaded if present). The module's basename (filename without extension) is used as the module identifier for qualifying exported bindings.
841+
841842
- `EXPORT(SYMBOL: symbol, MODULE: module):INT` - Adds the caller's binding for the identifier `symbol` into the namespace of the imported module named by the identifier `module`. The first argument must be an identifier (not a string literal); its current value in the caller's environment is copied into the imported module's namespace and becomes available as the qualified name `module.symbol` in the caller's environment. The second argument must be an identifier naming a previously-imported module; if the module has not been imported yet, the interpreter raises a runtime error (rewrite: EXPORT). `EXPORT` returns `INT` 0 on success.
842843
843844
### 12.16 File operations:

src/builtins.c

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4251,6 +4251,11 @@ static Value builtin_import_path(Interpreter* interp, Value* args, int argc, Exp
42514251
if (found_path && strcmp(found_path, cache_key) != 0) {
42524252
(void)module_register_alias(interp, found_path, mod_env);
42534253
}
4254+
// Register provided alias name in module registry so callers can
4255+
// refer to the module by that identifier (EXPORT relies on this).
4256+
if (alias && strcmp(alias, cache_key) != 0) {
4257+
(void)module_register_alias(interp, alias, mod_env);
4258+
}
42544259

42554260
// If not already loaded, execute module source once.
42564261
EnvEntry* marker = env_get_entry(mod_env, "__MODULE_LOADED__");
@@ -4332,10 +4337,14 @@ static Value builtin_import_path(Interpreter* interp, Value* args, int argc, Exp
43324337
}
43334338
if (!belongs) continue;
43344339

4335-
size_t qlen = alias_len + 1 + strlen(fe->name) + 1;
4340+
const char* fname = fe->name ? fe->name : "";
4341+
const char* unq = strchr(fname, '.');
4342+
if (unq) unq = unq + 1; else unq = fname;
4343+
4344+
size_t qlen = alias_len + 1 + strlen(unq) + 1;
43364345
char* qualified = malloc(qlen);
43374346
if (!qualified) { RUNTIME_ERROR(interp, "Out of memory", line, col); }
4338-
snprintf(qualified, qlen, "%s.%s", alias, fe->name);
4347+
snprintf(qualified, qlen, "%s.%s", alias, unq);
43394348
Value fv = value_func(fe->func);
43404349

43414350
// Try to assign qualified symbol into caller env
@@ -6208,10 +6217,14 @@ static Value builtin_import(Interpreter* interp, Value* args, int argc, Expr** a
62086217
}
62096218
if (!belongs) continue;
62106219

6211-
size_t qlen = alias_len + 1 + strlen(fe->name) + 1;
6220+
const char* fname = fe->name ? fe->name : "";
6221+
const char* unq = strchr(fname, '.');
6222+
if (unq) unq = unq + 1; else unq = fname;
6223+
6224+
size_t qlen = alias_len + 1 + strlen(unq) + 1;
62126225
char* qualified = malloc(qlen);
62136226
if (!qualified) { RUNTIME_ERROR(interp, "Out of memory", line, col); }
6214-
snprintf(qualified, qlen, "%s.%s", alias, fe->name);
6227+
snprintf(qualified, qlen, "%s.%s", alias, unq);
62156228
Value fv = value_func(fe->func);
62166229
if (!env_assign(env, qualified, fv, TYPE_FUNC, true)) {
62176230
value_free(fv);

src/interpreter.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -568,7 +568,7 @@ Value eval_expr(Interpreter* interp, Expr* expr, Env* env) {
568568
for (int i = 0; i < pos_argc; i++) {
569569
arg_nodes[i] = expr->as.call.args.items[i];
570570
if (((strcmp(func_name, "DEL") == 0 || strcmp(func_name, "EXIST") == 0 || strcmp(func_name, "IMPORT") == 0 || strcmp(func_name, "ASSIGN") == 0) && i == 0)
571-
|| (strcmp(func_name, "IMPORT_PATH") == 0 && i == 1)) {
571+
|| ((strcmp(func_name, "IMPORT") == 0 || strcmp(func_name, "IMPORT_PATH") == 0) && i == 1)) {
572572
// leave as null placeholder
573573
continue;
574574
}

0 commit comments

Comments
 (0)