Skip to content

Commit 93cd4f8

Browse files
gh-106: Fix ASMODULE extension operator registration in imported modules.
1 parent 3bacf1f commit 93cd4f8

File tree

5 files changed

+157
-54
lines changed

5 files changed

+157
-54
lines changed

src/builtins.c

Lines changed: 139 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
// Forward declarations for interpreter functions we need
3333
Value eval_expr(Interpreter* interp, Expr* expr, Env* env);
3434
int value_truthiness(Value v);
35+
static int module_export_bindings(Interpreter* interp, Env* caller_env, Env* mod_env, const char* alias, int line, int col, const char* fail_msg);
3536

3637
// Helper macros
3738
#define RUNTIME_ERROR(interp, msg, line, col) \
@@ -5211,22 +5212,9 @@ static Value builtin_import_path(Interpreter* interp, Value* args, int argc, Exp
52115212
free(found_path);
52125213
free(canonical_path);
52135214

5214-
// Expose module symbols into caller env under alias prefix: alias.name -> value
5215-
size_t alias_len = strlen(alias);
5216-
for (size_t i = 0; i < mod_env->count; i++) {
5217-
EnvEntry* e = &mod_env->entries[i];
5218-
if (!e->initialized) continue;
5219-
if (e->name && e->name[0] == '_' && e->name[1] == '_') continue;
5220-
size_t qlen = alias_len + 1 + strlen(e->name) + 1;
5221-
char* qualified = malloc(qlen);
5222-
if (!qualified) { if (alias_dup) free(alias_dup); RUNTIME_ERROR(interp, "Out of memory", line, col); }
5223-
snprintf(qualified, qlen, "%s.%s", alias, e->name);
5224-
if (!env_assign(env, qualified, e->value, e->decl_type, true)) {
5225-
free(qualified);
5226-
if (alias_dup) free(alias_dup);
5227-
RUNTIME_ERROR(interp, "IMPORT_PATH failed to assign qualified name", line, col);
5228-
}
5229-
free(qualified);
5215+
if (module_export_bindings(interp, env, mod_env, alias, line, col, "IMPORT_PATH failed to assign qualified name") != 0) {
5216+
if (alias_dup) free(alias_dup);
5217+
return value_null();
52305218
}
52315219

52325220
// Ensure the module name itself exists in caller env
@@ -7178,13 +7166,146 @@ static Value builtin_extend(Interpreter* interp, Value* args, int argc, Expr** a
71787166
// Expose the extension namespace symbol in the current module environment.
71797167
if (loaded_name && loaded_name[0] != '\0') {
71807168
(void)env_assign(env, loaded_name, value_str(""), TYPE_STR, true);
7169+
7170+
EnvEntry* namespaces_entry = env_get_entry(env, "__EXTEND_NAMES__");
7171+
const char* existing_namespaces = (namespaces_entry && namespaces_entry->initialized && namespaces_entry->value.type == VAL_STR && namespaces_entry->value.as.s)
7172+
? namespaces_entry->value.as.s
7173+
: "";
7174+
size_t loaded_len = strlen(loaded_name);
7175+
int already_present = 0;
7176+
const char* cursor = existing_namespaces;
7177+
while (cursor && *cursor != '\0') {
7178+
while (*cursor == '|') cursor++;
7179+
if (*cursor == '\0') break;
7180+
const char* start = cursor;
7181+
while (*cursor != '\0' && *cursor != '|') cursor++;
7182+
size_t token_len = (size_t)(cursor - start);
7183+
if (token_len == loaded_len && strncmp(start, loaded_name, loaded_len) == 0) {
7184+
already_present = 1;
7185+
break;
7186+
}
7187+
}
7188+
if (!already_present) {
7189+
size_t existing_len = strlen(existing_namespaces);
7190+
size_t combined_len = existing_len + loaded_len + 2;
7191+
char* combined = malloc(combined_len + 1);
7192+
if (!combined) {
7193+
if (interp->error) free(interp->error);
7194+
interp->error = strdup("Out of memory");
7195+
interp->error_line = line;
7196+
interp->error_col = col;
7197+
free(loaded_name);
7198+
return value_null();
7199+
}
7200+
if (existing_len > 0) {
7201+
memcpy(combined, existing_namespaces, existing_len);
7202+
combined[existing_len] = '|';
7203+
memcpy(combined + existing_len + 1, loaded_name, loaded_len);
7204+
combined[existing_len + loaded_len + 1] = '|';
7205+
combined[existing_len + loaded_len + 2] = '\0';
7206+
} else {
7207+
combined[0] = '|';
7208+
memcpy(combined + 1, loaded_name, loaded_len);
7209+
combined[loaded_len + 1] = '|';
7210+
combined[loaded_len + 2] = '\0';
7211+
}
7212+
(void)env_assign(env, "__EXTEND_NAMES__", value_str(combined), TYPE_STR, true);
7213+
free(combined);
7214+
}
71817215
}
71827216

71837217
free(loaded_name);
71847218
free(ext_err);
71857219
return value_bool(false);
71867220
}
71877221

7222+
static int module_export_bindings(Interpreter* interp, Env* caller_env, Env* mod_env, const char* alias, int line, int col, const char* fail_msg) {
7223+
if (!interp || !caller_env || !mod_env || !alias || alias[0] == '\0') return -1;
7224+
7225+
size_t alias_len = strlen(alias);
7226+
7227+
for (size_t i = 0; i < mod_env->count; i++) {
7228+
EnvEntry* e = &mod_env->entries[i];
7229+
if (!e->initialized) continue;
7230+
if (e->name && e->name[0] == '_' && e->name[1] == '_') continue;
7231+
size_t qlen = alias_len + 1 + strlen(e->name) + 1;
7232+
char* qualified = malloc(qlen);
7233+
if (!qualified) return -1;
7234+
snprintf(qualified, qlen, "%s.%s", alias, e->name);
7235+
if (!env_assign(caller_env, qualified, e->value, e->decl_type, true)) {
7236+
free(qualified);
7237+
if (interp->error) free(interp->error);
7238+
interp->error = strdup(fail_msg);
7239+
interp->error_line = line;
7240+
interp->error_col = col;
7241+
return -1;
7242+
}
7243+
free(qualified);
7244+
}
7245+
7246+
EnvEntry* namespaces_entry = env_get_entry(mod_env, "__EXTEND_NAMES__");
7247+
if (namespaces_entry && namespaces_entry->initialized && namespaces_entry->value.type == VAL_STR && namespaces_entry->value.as.s && namespaces_entry->value.as.s[0] != '\0') {
7248+
const char* namespaces = namespaces_entry->value.as.s;
7249+
const char* cursor = namespaces;
7250+
while (cursor && *cursor != '\0') {
7251+
while (*cursor == '|') cursor++;
7252+
if (*cursor == '\0') break;
7253+
7254+
const char* start = cursor;
7255+
while (*cursor != '\0' && *cursor != '|') cursor++;
7256+
size_t ns_len = (size_t)(cursor - start);
7257+
if (ns_len == 0) continue;
7258+
7259+
char* ns = malloc(ns_len + 1);
7260+
if (!ns) return -1;
7261+
memcpy(ns, start, ns_len);
7262+
ns[ns_len] = '\0';
7263+
7264+
size_t ns_qual_len = alias_len + 1 + ns_len + 1;
7265+
char* ns_qualified = malloc(ns_qual_len);
7266+
if (!ns_qualified) { free(ns); return -1; }
7267+
snprintf(ns_qualified, ns_qual_len, "%s.%s", alias, ns);
7268+
if (!env_get_entry(caller_env, ns_qualified)) {
7269+
if (!env_assign(caller_env, ns_qualified, value_str(""), TYPE_STR, true)) {
7270+
free(ns_qualified);
7271+
free(ns);
7272+
if (interp->error) free(interp->error);
7273+
interp->error = strdup(fail_msg);
7274+
interp->error_line = line;
7275+
interp->error_col = col;
7276+
return -1;
7277+
}
7278+
}
7279+
7280+
for (size_t j = 0; j < mod_env->count; j++) {
7281+
EnvEntry* e = &mod_env->entries[j];
7282+
if (!e->initialized) continue;
7283+
if (e->name && e->name[0] == '_' && e->name[1] == '_') continue;
7284+
size_t qlen = ns_qual_len + 1 + strlen(e->name) + 1;
7285+
char* qualified = malloc(qlen);
7286+
if (!qualified) { free(ns_qualified); free(ns); return -1; }
7287+
snprintf(qualified, qlen, "%s.%s", ns_qualified, e->name);
7288+
if (!env_assign(caller_env, qualified, e->value, e->decl_type, true)) {
7289+
free(qualified);
7290+
free(ns_qualified);
7291+
free(ns);
7292+
if (interp->error) free(interp->error);
7293+
interp->error = strdup(fail_msg);
7294+
interp->error_line = line;
7295+
interp->error_col = col;
7296+
return -1;
7297+
}
7298+
free(qualified);
7299+
}
7300+
7301+
free(ns_qualified);
7302+
free(ns);
7303+
}
7304+
}
7305+
7306+
return 0;
7307+
}
7308+
71887309
// Stubs for operations requiring TNS/MAP/THD
71897310
static Value builtin_import(Interpreter* interp, Value* args, int argc, Expr** arg_nodes, Env* env, int line, int col) {
71907311
(void)args; (void)argc;
@@ -7432,22 +7553,8 @@ static Value builtin_import(Interpreter* interp, Value* args, int argc, Expr** a
74327553
free(found_path);
74337554
free(canonical_path);
74347555

7435-
// Expose module symbols into caller env under alias prefix: alias.name -> value
7436-
size_t alias_len = strlen(alias);
7437-
7438-
for (size_t i = 0; i < mod_env->count; i++) {
7439-
EnvEntry* e = &mod_env->entries[i];
7440-
if (!e->initialized) continue;
7441-
if (e->name && e->name[0] == '_' && e->name[1] == '_') continue; // skip magic
7442-
size_t qlen = alias_len + 1 + strlen(e->name) + 1;
7443-
char* qualified = malloc(qlen);
7444-
if (!qualified) { RUNTIME_ERROR(interp, "Out of memory", line, col); }
7445-
snprintf(qualified, qlen, "%s.%s", alias, e->name);
7446-
if (!env_assign(env, qualified, e->value, e->decl_type, true)) {
7447-
free(qualified);
7448-
RUNTIME_ERROR(interp, "IMPORT failed to assign qualified name", line, col);
7449-
}
7450-
free(qualified);
7556+
if (module_export_bindings(interp, env, mod_env, alias, line, col, "IMPORT failed to assign qualified name") != 0) {
7557+
return value_null();
74517558
}
74527559

74537560
// Ensure the module name itself exists in caller env (avoid undefined identifier errors)

src/extensions.c

Lines changed: 5 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -323,14 +323,9 @@ static char* resolve_extension_spec_path(const char* spec, const char* base_dir)
323323
return resolved;
324324
}
325325

326-
static char* exposure_key_for(const char* scope_name, const char* ext_name) {
327-
const char* scope = (scope_name && scope_name[0] != '\0') ? scope_name : "";
326+
static char* exposure_key_for(const char* ext_name) {
328327
const char* ext = (ext_name && ext_name[0] != '\0') ? ext_name : "extension";
329-
size_t n = strlen(scope) + 1 + strlen(ext) + 1;
330-
char* out = malloc(n);
331-
if (!out) return NULL;
332-
snprintf(out, n, "%s|%s", scope, ext);
333-
return out;
328+
return strdup(ext);
334329
}
335330

336331
static int exposure_exists(LoadedExtension* le, const char* key) {
@@ -371,27 +366,14 @@ static int ctx_register_operator(const char* name, prefix_operator_fn fn, int as
371366
char* final_name = NULL;
372367
if ((asmodule & PREFIX_EXTENSION_ASMODULE) != 0 && g_loading_extension_name && g_loading_extension_name[0] != '\0') {
373368
const char* ext_name = g_loading_extension_name;
374-
const char* scope_name = (g_loading_scope_name && g_loading_scope_name[0] != '\0') ? g_loading_scope_name : NULL;
375-
376-
int collapse_scope = 0;
377-
if (scope_name && strcmp(scope_name, ext_name) == 0) {
378-
collapse_scope = 1;
379-
}
380369

381370
size_t a = strlen(ext_name);
382-
size_t s = (scope_name && !collapse_scope) ? strlen(scope_name) : 0;
383371
size_t b = strlen(name);
384372
size_t total = a + 1 + b + 1;
385-
if (s > 0) total += s + 1;
386373
final_name = malloc(total);
387374
if (!final_name) return -1;
388375

389376
size_t p = 0;
390-
if (s > 0) {
391-
memcpy(final_name + p, scope_name, s);
392-
p += s;
393-
final_name[p++] = '.';
394-
}
395377
memcpy(final_name + p, ext_name, a);
396378
p += a;
397379
final_name[p++] = '.';
@@ -475,7 +457,9 @@ static int extension_register_exposure(LoadedExtension* le,
475457
return -1;
476458
}
477459

478-
char* key = exposure_key_for(scope_name, ext_name);
460+
(void)scope_name;
461+
462+
char* key = exposure_key_for(ext_name);
479463
if (!key) {
480464
set_error(error_out, "Out of memory");
481465
return -1;

src/extensions.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ int extensions_load_library(const char* path, const char* base_dir, char** error
1515

1616
// Load an extension by logical name/specifier used by EXTEND.
1717
// spec excludes platform filename extension and may use package semantics via "..".
18-
// scope_name controls module-qualified operator exposure for PREFIX_EXTENSION_ASMODULE.
18+
// scope_name is retained for loader bookkeeping; PREFIX_EXTENSION_ASMODULE exposure
19+
// uses the extension name only.
1920
// If loaded_name_out is non-NULL, it receives the extension namespace name (heap-allocated).
2021
// Returns 0 on success, -1 on failure. On failure, *error_out is heap-allocated.
2122
int extensions_load_named(const char* spec,

tests/ext_import.pre

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
EXTEND(EXTENSION: test_ext)
2+
3+
FUNC INT: CALL_PING(){
4+
RETURN(test_ext.PING())
5+
}

tests/test2.pre

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1391,4 +1391,10 @@ ASSERT(EQ(test_ext.GET_COUNTER(), 0d0))
13911391

13921392
PRINT("Extensions: PASS\n")
13931393

1394+
PRINT("Testing imported EXTEND module...")
1395+
IMPORT(ext_import)
1396+
ASSERT(EQ(ext_import.CALL_PING(), 0d0))
1397+
ASSERT(EQ(ext_import.test_ext.CALL_PING(), 0d0))
1398+
PRINT("Imported EXTEND module: PASS\n")
1399+
13941400
PRINT("=== Built-in tests passed. ===")

0 commit comments

Comments
 (0)